diff --git a/.github/dependabot.yml b/.github/dependabot.yml index fe2414bdf30..227b6249f1c 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -14,6 +14,12 @@ updates: patterns: - "org.dhatim:fastexcel" - "org.dhatim:fastexcel-reader" + bouncycastle: + patterns: + - "org.bouncycastle:*" + pax-logging: + patterns: + - "org.ops4j.pax.logging:*" - package-ecosystem: npm directory: "/ui" @@ -27,6 +33,7 @@ updates: - "@angular/*" - "@angular-devkit/*" - "@angular-eslint/*" + - "@schematics/angular" capacitor: patterns: - "@capacitor/*" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b03b5287e16..f27aae06ef8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -41,7 +41,7 @@ jobs: run: ./gradlew jacocoTestReport - name: Upload coverage reports to Codecov - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v5 with: flags: java token: ${{ secrets.CODECOV_TOKEN }} @@ -76,7 +76,7 @@ jobs: npm run test -- --code-coverage --no-watch --no-progress --browsers=ChromeHeadlessCI - name: Upload coverage reports to Codecov - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v5 with: flags: ui directory: ./ui/ diff --git a/.gradle-wrapper/gradle-wrapper.properties b/.gradle-wrapper/gradle-wrapper.properties index 0aaefbcaf0f..cea7a793a84 100644 --- a/.gradle-wrapper/gradle-wrapper.properties +++ b/.gradle-wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/.woodpecker/ui-build.yml b/.woodpecker/ui-build.yml deleted file mode 100644 index 224fc19ad14..00000000000 --- a/.woodpecker/ui-build.yml +++ /dev/null @@ -1,80 +0,0 @@ -variables: - - &sftp-settings - server: ${CACHE_SERVER} - username: user - password: pass - ignore_branch: true - port: 2222 - path: /cache - mount: - - cache - - - &rsync-settings - user: fenecon-docs - hosts: - - ${ARTIFACT_SERVER} - port: 22 - key: - from_secret: ssh_key_intranet - args: '-v' - - - &main-build - - branch: [main, develop] - - evaluate: 'CI_COMMIT_MESSAGE contains "[APP]"' - - path: - include: ['.woodpecker/ui-build.yml'] - on_empty: false - -when: - event: - - push - -matrix: - THEME: - - fenecon - - heckert - -clone: - git: - when: *main-build - image: woodpeckerci/plugin-git - -steps: - restore-cache: - when: *main-build - image: appleboy/drone-sftp-cache - settings: - restore: true - <<: *sftp-settings - - prepare-environment: - when: *main-build - image: openems-bash - commands: - - export CACHE=$CI_WORKSPACE/cache - - mkdir -p $CI_WORKSPACE/cache build/target - - source tools/common.sh - - common_initialize_environment - - common_build_snapshot_version - - common_save_environment $CI_WORKSPACE/.openems-env - depends_on: [restore-cache] - - build-android-app: - when: *main-build - image: openems-android:20.32 - environment: - - THEME=${THEME} - commands: - - source $CI_WORKSPACE/.openems-env - - source tools/common.sh - - common_build_android_app - depends_on: [prepare-environment] - - refresh-dev-android: - when: *main-build - image: woodpeckerci/rsync:latest - settings: - <<: *rsync-settings - source: $CI_WORKSPACE/ui/android/target/ - target: /var/opt/develop/fems-artifacts/html/${CI_COMMIT_BRANCH} - depends_on: [build-android-app] diff --git a/README.md b/README.md index 519047007e3..b1ad40deb8a 100644 --- a/README.md +++ b/README.md @@ -80,14 +80,14 @@ If you use OpenEMS in your scientific research, please use our Zenodo Digital Ob * OpenEMS Edge * OpenEMS Backend -Copyright (C) 2016-2022 OpenEMS Association e.V. +Copyright (C) 2016-2025 OpenEMS Association e.V. This product includes software developed at FENECON GmbH: you can redistribute it and/or modify it under the terms of the [Eclipse Public License version 2.0](LICENSE-EPL-2.0). * OpenEMS UI -Copyright (C) 2016-2022 OpenEMS Association e.V. +Copyright (C) 2016-2025 OpenEMS Association e.V. This product includes software developed at FENECON GmbH: you can redistribute it and/or modify it under the terms of the [GNU Affero General Public License version 3](LICENSE-AGPL-3.0). diff --git a/build.gradle b/build.gradle index 82249fb14f2..ad224209fd9 100644 --- a/build.gradle +++ b/build.gradle @@ -68,7 +68,7 @@ subprojects { } checkstyle { - toolVersion = '10.11.0' + toolVersion = '10.18.2' configFile = file("${rootDir}/cnf/checkstyle.xml") maxWarnings = 0 ignoreFailures = false diff --git a/cnf/build.bnd b/cnf/build.bnd index 00cba399ba6..9dd618398b5 100644 --- a/cnf/build.bnd +++ b/cnf/build.bnd @@ -40,7 +40,8 @@ buildpath: \ org.osgi.service.metatype;version='1.4.1',\ org.osgi.service.metatype.annotations;version='1.4.1',\ org.osgi.util.promise;version='1.2.0',\ - com.google.guava;version='33.3.1.jre',\ + com.google.guava;version='33.4.0.jre',\ + com.google.guava.failureaccess;version='1.0.2',\ com.google.gson;version='2.11.0',\ testpath: \ @@ -74,5 +75,5 @@ testpath: \ Edge_Timedata;member=${filter;${p};io\.openems\.edge\.timedata\..*},\ Edge_TimeOfUseTariff;member=${filter;${p};io\.openems\.edge\.timeofusetariff\..*},\ -javac.source: 17 -javac.target: 17 +javac.source: 21 +javac.target: 21 diff --git a/cnf/checkstyle.xml b/cnf/checkstyle.xml index ad22318becf..6ed4ad0febb 100644 --- a/cnf/checkstyle.xml +++ b/cnf/checkstyle.xml @@ -23,8 +23,8 @@ - + @@ -47,9 +47,9 @@ + - @@ -107,8 +107,8 @@ - + @@ -144,9 +144,9 @@ + - @@ -179,12 +179,10 @@ - - - + - + @@ -206,15 +204,15 @@ - + - + diff --git a/cnf/pom.xml b/cnf/pom.xml index 83b8c69ddd9..191ff3f5c56 100644 --- a/cnf/pom.xml +++ b/cnf/pom.xml @@ -11,7 +11,7 @@ biz.aQute.bnd biz.aQute.bnd.gradle - 7.0.0 + 7.1.0 @@ -38,7 +38,7 @@ com.google.guava guava - 33.3.1-jre + 33.4.0-jre com.google.guava @@ -97,7 +97,7 @@ com.zaxxer HikariCP - 6.0.0 + 6.2.1 @@ -105,7 +105,7 @@ de.bytefish pgbulkinsert - 8.1.4 + 8.1.5 @@ -130,7 +130,7 @@ fr.turri aXMLRPC - 1.14.0 + 1.16.0 @@ -139,17 +139,45 @@ 1.5 + + + + io.helins + linux-common + 0.1.4 + + + + + io.helins + linux-i2c + 1.0.2 + + + + + io.helins + linux-io + 0.0.4 + + + + + io.helins + linux-errno + 1.0.2 + io.reactivex.rxjava3 rxjava - 3.1.9 + 3.1.10 io.jenetics jenetics - 7.2.0 + 8.1.0 @@ -162,7 +190,7 @@ net.java.dev.jna jna - 5.15.0 + 5.16.0 @@ -253,9 +281,17 @@ + + org.bouncycastle + bcpkix-jdk18on + 1.79 + + + + org.bouncycastle - bcpkix-jdk15on - 1.70 + bcprov-jdk18on + 1.79 org.dhatim @@ -301,18 +337,18 @@ org.jetbrains.kotlin kotlin-osgi-bundle - 2.0.20 + 2.1.0 org.jetbrains.kotlinx kotlinx-coroutines-core-jvm - 1.9.0 + 1.10.1 org.jsoup jsoup - 1.18.1 + 1.18.3 org.osgi @@ -367,12 +403,12 @@ org.ops4j.pax.logging pax-logging-api - 2.2.1 + 2.2.7 org.ops4j.pax.logging pax-logging-log4j2 - 2.2.1 + 2.2.7 org.osgi @@ -414,4 +450,4 @@ 3.9 - + \ No newline at end of file diff --git a/codecov.yml b/codecov.yml index 49e61532bad..3c67c72647e 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,3 +1,8 @@ +codecov: + require_ci_to_pass: true + notify: + wait_for_ci: true + coverage: round: up precision: 2 @@ -5,7 +10,10 @@ coverage: project: default: target: auto - threshold: 10% + threshold: 1% + patch: + default: + target: 75% comment: layout: "condensed_header, diff" @@ -42,4 +50,4 @@ component_management: - component_id: openems_ui name: "OpenEMS UI" paths: - - ui/** \ No newline at end of file + - ui/** diff --git a/doc/modules/ROOT/assets/images/deploy-docker-backend-check-version.png b/doc/modules/ROOT/assets/images/deploy-docker-backend-check-version.png new file mode 100644 index 00000000000..351a863a197 Binary files /dev/null and b/doc/modules/ROOT/assets/images/deploy-docker-backend-check-version.png differ diff --git a/doc/modules/ROOT/assets/images/deploy-docker-backend.png b/doc/modules/ROOT/assets/images/deploy-docker-backend.png new file mode 100644 index 00000000000..64a1ee887a1 Binary files /dev/null and b/doc/modules/ROOT/assets/images/deploy-docker-backend.png differ diff --git a/doc/modules/ROOT/assets/images/deploy-docker-edge-check-version.png b/doc/modules/ROOT/assets/images/deploy-docker-edge-check-version.png new file mode 100644 index 00000000000..c2bddc8d89e Binary files /dev/null and b/doc/modules/ROOT/assets/images/deploy-docker-edge-check-version.png differ diff --git a/doc/modules/ROOT/assets/images/deploy-docker-edge.png b/doc/modules/ROOT/assets/images/deploy-docker-edge.png new file mode 100644 index 00000000000..22d46700d49 Binary files /dev/null and b/doc/modules/ROOT/assets/images/deploy-docker-edge.png differ diff --git a/doc/modules/ROOT/assets/images/deploy-docker-ssh.png b/doc/modules/ROOT/assets/images/deploy-docker-ssh.png new file mode 100644 index 00000000000..291a103d2d2 Binary files /dev/null and b/doc/modules/ROOT/assets/images/deploy-docker-ssh.png differ diff --git a/doc/modules/ROOT/pages/backend/deploy.adoc b/doc/modules/ROOT/pages/backend/deploy.adoc index b7c19ea5c83..b29e8775ce2 100644 --- a/doc/modules/ROOT/pages/backend/deploy.adoc +++ b/doc/modules/ROOT/pages/backend/deploy.adoc @@ -8,25 +8,35 @@ :icons: font :imagesdir: ../../assets/images +== Debian Linux + This chapter explains how OpenEMS Backend can be deployed on a Debian Linux server. Similar techniques will work for other operating systems as well. -== Prepare operating system environment +=== Prepare operating system environment NOTE: It is recommended to run every service on a server with limited permissions. This example runs OpenEMS Backend with user "root" which is a bad idea for a production server! -=== Create an application directory +==== Check JAVA version + +Ensure that a JRE version 21 or later is installed. We recommend using `temurin-21-jre` + +For detailed installation instructions, visit https://adoptium.net/de/installation/linux/[Adoptium Installation Guide]. + +NOTE: If you are using an *ARM32* device, download https://openems.io/download/temurin-21-jre-armhf_21.0.6+2.deb[temurin-21-jre-armhf_21.0.6+2.deb] directly from OpenEMS. + +==== Create an application directory Create the directory */opt/openems-backend*. This is going to be the place, where we put the JAR file. Execute `mkdir /opt/openems-backend`. -=== Create a config directory +==== Create a config directory Create the directory */opt/openems-backend/config.d*. This is going to be the place, where all the bundle configurations are held. Execute `mkdir /opt/openems-backend/config.d`. -=== Create a systemd service definition +==== Create a systemd service definition The systemd 'Service Manager' manages system processes in a Debian Linux. We will create a systemd service definition file, so that systemd takes care of managing (starting/restarting/...) the OpenEMS Backend service. @@ -78,3 +88,82 @@ To update the OpenEMS JAR file at the target device, it is required to copy the Execute `systemctl restart openems-backend --no-block; journalctl -lfu openems-backend` + The command restarts the service (_systemctl restart openems-backend_) while not waiting for the OpenEMS startup notification (_--no-block_). Then it directly prints the OpenEMS system log (_journalctl -lfu openems-backend_). + +== Docker + +This chapter explains how OpenEMS Backend can be deployed using our official https://hub.docker.com/r/openems/backend[Docker image]. + +=== Prepare system + +==== Connect to the server + +image::deploy-docker-ssh.png[SSH into device] + +==== Check docker installation + +image::deploy-docker-backend-check-version.png[Check docker installation] + +__if not already installed, follow <>__ + +==== Setup docker + +To setup docker follow the instuctions from https://docs.docker.com/engine/install/[docs.docker.com]. + +=== Create a Docker compose + +Paste content into a `docker-compose.yml` +---- +services: + openems_backend: + image: openems/backend:latest + container_name: openems_backend + hostname: openems_backend + restart: unless-stopped + volumes: + - openems-backend-conf:/var/opt/openems/config:rw + - openems-backend-data:/var/opt/openems/data:rw + ports: + - 8079:8079 # Apache-Felix + - 8081:8081 # Edge-Websocket + - 8082:8082 # UI-Websocket + + openems-ui: + image: openems/ui-backend:latest + container_name: openems_ui + hostname: openems_ui + restart: unless-stopped + volumes: + - openems-ui-conf:/etc/nginx:rw + - openems-ui-log:/var/log/nginx:rw + environment: + - UI_WEBSOCKET=ws://:8082 # Change to your actual hostname or ip + ports: + - 80:80 + - 443:443 + +volumes: + openems-backend-conf: + openems-backend-data: + openems-ui-conf: + openems-ui-log: +---- + +=== Run compose file + +To start the previously created `docker-compose.yml` run the command: +---- +docker compose up -d +---- + +=== Check logs + +To check if the container is up and running, check `docker ps`: + +image::deploy-docker-backend.png[docker ps] + +or read its logs with: +---- +docker logs openems_backend +---- + +NOTE: If you want to run the backand with an InfluxDB instance as well, see: https://github.com/OpenEMS/openems/tree/develop/tools/docker/backend. \ No newline at end of file diff --git a/doc/modules/ROOT/pages/contribute/coding-guidelines.adoc b/doc/modules/ROOT/pages/contribute/coding-guidelines.adoc index 065e9838276..ef73cf51635 100644 --- a/doc/modules/ROOT/pages/contribute/coding-guidelines.adoc +++ b/doc/modules/ROOT/pages/contribute/coding-guidelines.adoc @@ -25,7 +25,7 @@ * Use precise naming for methods/functions * Use narrow scopes for variables; avoid class variables * Split code in Interface, Implementation(..Impl) and Config files -* Use modern Java 17 syntax (e.g. 'var' keyword, streams, etc.) +* Use modern Java 21 syntax (e.g. 'var' keyword, streams, etc.) * Add readme.adoc to a new bundle * Review data in bnd.bnd file * Format all files via Eclipse Autoformat, organize Imports, apply Checkstyle suggestions (see below) diff --git a/doc/modules/ROOT/pages/edge/deploy.adoc b/doc/modules/ROOT/pages/edge/deploy.adoc index 58059b150a3..3a9e28dcb2f 100644 --- a/doc/modules/ROOT/pages/edge/deploy.adoc +++ b/doc/modules/ROOT/pages/edge/deploy.adoc @@ -8,6 +8,8 @@ :icons: font :imagesdir: ../../assets/images +== Debian Linux + This chapter explains how OpenEMS can be deployed on a Debian Linux Internet-of-Things Gateway. Similar techniques will work for other operating systems as well. This guide covers a simple, manual approach. For productive systems it is required to automate deployment to IoT devices. Good approaches include a Debian package repository that provides *.deb-files and third-party tools like http://www.eclipse.org/hawkbit/[Eclipse Hawkbit]. This is out-of-scope for this small guide. @@ -19,7 +21,7 @@ Prerequisites: * Setup an SSH client to connect to the Linux console, e.g. http://www.9bis.net/kitty/[KiTTY] * Setup an SCP client to copy the JAR file via SSH, e.g. https://winscp.net/eng/docs/lang:de[WinSCP] -== Connect via SSH and SCP +=== Connect via SSH and SCP . Connect via SSH using KiTTY .. Open KiTTY and connect to the target device. @@ -56,6 +58,14 @@ image::deploy-winscp.png[Start WinSCP from KiTTY] === Prepare operating system environment +==== Check JAVA version + +Ensure that a JRE version 21 or later is installed. We recommend using `temurin-21-jre` + +For detailed installation instructions, visit https://adoptium.net/de/installation/linux/[Adoptium Installation Guide]. + +NOTE: If you are using an *ARM32* device, download https://openems.io/download/temurin-21-jre-armhf_21.0.6+2.deb[temurin-21-jre-armhf_21.0.6+2.deb] directly from OpenEMS. + ==== Create an application directory Create the directory */usr/lib/openems*. This is going to be the place, where we put the JAR file. @@ -132,3 +142,74 @@ The command restarts the service (_systemctl restart openems_) while not waiting + .OpenEMS Edge start-up image::deploy-openems-start.png[OpenEMS Edge start-up] + + +== Docker + +This chapter explains how OpenEMS can be deployed using our official https://hub.docker.com/r/openems/edge[Docker image]. + +Prerequisites: + +* A amd64 or arm64 device running Linux. You need the IP address and SSH access. +* A working docker environment. To setup follow instruction from https://docs.docker.com/engine/install/[docs.docker.com]. + +=== Prepare system + +==== Connect to the device + +image::deploy-docker-ssh.png[SSH into device] + +==== Check docker installation + +image::deploy-docker-edge-check-version.png[Check docker installation] + +__if not already installed, follow <>__ + +==== Setup docker + +To setup docker follow the instuctions from https://docs.docker.com/engine/install/[docs.docker.com]. + +=== Start Container + +==== Create a Docker compose + +Paste content into a `docker-compose.yml` +---- +services: + openems-edge: + image: openems/edge:latest + container_name: openems_edge + hostname: openems_edge + restart: unless-stopped + volumes: + - openems-edge-conf:/var/opt/openems/config:rw + - openems-edge-data:/var/opt/openems/data:rw + ports: + - 8080:8080 # Apache-Felix + +volumes: + openems-edge-conf: + openems-edge-data: +---- + +==== Run compose file + +To start the previously created `docker-compose.yml` run the command: +---- +docker compose up -d +---- + +==== Check logs + +To check if the container is up and running, check `docker ps`: + +image::deploy-docker-edge.png[docker ps] + +or read its logs with: +---- +docker logs openems_edge +---- + +--- + +NOTE: If you want to start a UI instance as well, see: https://github.com/OpenEMS/openems/tree/develop/tools/docker/edge. \ No newline at end of file diff --git a/doc/modules/ROOT/pages/gettingstarted.adoc b/doc/modules/ROOT/pages/gettingstarted.adoc index 6ecd5effb4d..87441ec6fc2 100644 --- a/doc/modules/ROOT/pages/gettingstarted.adoc +++ b/doc/modules/ROOT/pages/gettingstarted.adoc @@ -41,7 +41,7 @@ NOTE: OpenEMS uses the **git** version control system via the popular GitHub pla NOTE: Eclipse IDE is the recommended development environment for newcomers to OpenEMS. If you are more familiar with IntelliJ IDEA, feel free to use it. Follow xref:intellij.adoc[this guide]. . Prepare Eclipse IDE -.. Download Java Development Kit (JDK) 17 and install it. We recommend the https://adoptium.net/de/temurin/releases/?version=17[OpenJDK Temurin builds by the Adoptium project] +.. Download Java Development Kit (JDK) 21 and install it. We recommend the https://adoptium.net/de/temurin/releases/?version=21[OpenJDK Temurin builds by the Adoptium project] .. Download https://www.eclipse.org/downloads/[Eclipse for Java icon:external-link[]], install and start it .. On first start you will get asked to create a workspace. Select your source code directory (`C:\Users\your.user\git\openems` in our example) and press btn:[Launch]. @@ -53,15 +53,15 @@ image::eclipse-workspace.png[Creating a workspace in Eclipse IDE] + Menu: btn:[Help] → btn:[Eclipse Marketplace...] → btn:[Find:] → enter btn:[Bndtools] → press btn:[Install] -.. Configure Eclipse IDE to use JDK 17. +.. Configure Eclipse IDE to use JDK 21. + - In the Menu select btn:[Windows] → btn:[Preferences] - Select btn:[Java] - btn:[Installed JREs] in the navigation tree - Press the btn:[Add...] button - Keep btn:[Standard VM] selected and press btn:[Next >] -- Press the btn:[Directory...] button and select the folder of the installed JDK (e.g. `C:\Program Files\Eclipse Adoptium\jdk-17.0.7.7-hotspot`) +- Press the btn:[Directory...] button and select the folder of the installed JDK (e.g. `C:\Program Files\Eclipse Adoptium\jdk-21.0.3.9-hotspot`) - Press the btn:[Finish] button -- Back in the Preferences window, tick the newly added JDK 17 and press btn:[Apply and Close] +- Back in the Preferences window, tick the newly added JDK 21 and press btn:[Apply and Close] + .Creating a workspace in Eclipse IDE image::eclipse-select-jdk.png[Set the Java Development Kit in Eclipse IDE] diff --git a/doc/modules/ROOT/pages/ui/implementing-a-widget/components/chart.adoc b/doc/modules/ROOT/pages/ui/implementing-a-widget/components/chart.adoc index 283db853759..c13f06a39d2 100644 --- a/doc/modules/ROOT/pages/ui/implementing-a-widget/components/chart.adoc +++ b/doc/modules/ROOT/pages/ui/implementing-a-widget/components/chart.adoc @@ -8,7 +8,7 @@ Charts are mainly used in the History-View and should be acting like the `modal` Creating or updating charts has been very difficult, but if you use the recommended and new way of creating them, its much easier and can be done fast. Furthermore they are unittestable now. -If we take a look at a link:src\app\edge\history\common\autarchy\chart\chart.ts[Working Example], we will see, that the chart directory includes not only the chart.ts but also the corresponding .spec-file. If you are not familiar with angulars unit testing, check it out link:https://angular.io/guide/testing#test-file-name-and-location[here]. +If we take a look at a link:https://github.com/OpenEMS/openems/blob/develop/ui/src/app/edge/history/common/autarchy/chart/chart.ts[Working Example], we will see, that the chart directory includes not only the chart.ts but also the corresponding .spec-file. If you are not familiar with angulars unit testing, check it out link:https://angular.io/guide/testing#test-file-name-and-location[here]. NOTE: It is recommended to have the `component.ts` and `component.spec.ts` files in the same folder. @@ -55,4 +55,4 @@ The `output` is then defining the dataset, with the `nameSuffix` callback having The second one is passed with the output callback, and has to be used for the converter callback-function. -Additionally we define the formatting of the data in the tooltip and the yAxes. \ No newline at end of file +Additionally we define the formatting of the data in the tooltip and the yAxes. diff --git a/gradle.properties b/gradle.properties index ac682e37020..0c9eee2fd06 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,7 @@ java_source=21 -java_target=17 +java_target=21 -bnd_version=7.0.0 +bnd_version=7.1.0 bnd_snapshots=https://bndtools.jfrog.io/bndtools/libs-snapshot-local bnd_releases=https://bndtools.jfrog.io/bndtools/libs-release-local diff --git a/io.openems.backend.alerting/.classpath b/io.openems.backend.alerting/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.backend.alerting/.classpath +++ b/io.openems.backend.alerting/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.backend.alerting/src/io/openems/backend/alerting/scheduler/MessageScheduler.java b/io.openems.backend.alerting/src/io/openems/backend/alerting/scheduler/MessageScheduler.java index 7585adf772c..51a760bcc7c 100644 --- a/io.openems.backend.alerting/src/io/openems/backend/alerting/scheduler/MessageScheduler.java +++ b/io.openems.backend.alerting/src/io/openems/backend/alerting/scheduler/MessageScheduler.java @@ -12,6 +12,7 @@ /** * Schedules one or more {@link Message} for type {@link T} to a specific time. + * *

* After the specified time is reached, the scheduler sends the Messages to * their {@link Handler} and removes them from itself. diff --git a/io.openems.backend.alerting/src/io/openems/backend/alerting/scheduler/TimedExecutor.java b/io.openems.backend.alerting/src/io/openems/backend/alerting/scheduler/TimedExecutor.java index debb78c6cbf..ba255e80876 100644 --- a/io.openems.backend.alerting/src/io/openems/backend/alerting/scheduler/TimedExecutor.java +++ b/io.openems.backend.alerting/src/io/openems/backend/alerting/scheduler/TimedExecutor.java @@ -16,7 +16,7 @@ public TimedTask(ZonedDateTime executeAt, Consumer task) { @Override public int compareTo(TimedTask other) { - if (other == null || other.executeAt == null) { + if (other == null || other.executeAt == null) { return 1; } return this.executeAt.compareTo(other.executeAt); diff --git a/io.openems.backend.alerting/test/io/openems/backend/alerting/Dummy.java b/io.openems.backend.alerting/test/io/openems/backend/alerting/Dummy.java index 84d406508e8..66feef1c4ab 100644 --- a/io.openems.backend.alerting/test/io/openems/backend/alerting/Dummy.java +++ b/io.openems.backend.alerting/test/io/openems/backend/alerting/Dummy.java @@ -224,14 +224,14 @@ public void leap(long amount) { * Try to advance the Clock to a specific amount of minutes after * initialization. If the given point is ahead, the time will leap by the * missing amount. If the given point is behind, nothing will happen. + * *

* A return value >=0 means, the clock has advanced the given amount in minutes * with this call. - *

+ * *

* A return value <0 means, the clock has already advanced the given amount * above. - *

* * @param point to advance to * @return difference diff --git a/io.openems.backend.alerting/test/io/openems/backend/alerting/MessageTest.java b/io.openems.backend.alerting/test/io/openems/backend/alerting/MessageTest.java index d1e2296f3c2..132858e15ab 100644 --- a/io.openems.backend.alerting/test/io/openems/backend/alerting/MessageTest.java +++ b/io.openems.backend.alerting/test/io/openems/backend/alerting/MessageTest.java @@ -35,7 +35,7 @@ public void testMessage() { assertTrue("msg10 should be greater than msg11", msg10.compareTo(msg11) > 0); assertTrue("msg10 should be lower than msg20", msg10.compareTo(msg20) < 0); - + assertTrue("msg10 should be greater than null", msg10.compareTo(null) > 0); } diff --git a/io.openems.backend.application/.classpath b/io.openems.backend.application/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.backend.application/.classpath +++ b/io.openems.backend.application/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.backend.application/BackendApp.bndrun b/io.openems.backend.application/BackendApp.bndrun index e210938f4fd..2f9caaea57f 100644 --- a/io.openems.backend.application/BackendApp.bndrun +++ b/io.openems.backend.application/BackendApp.bndrun @@ -1,5 +1,5 @@ -runfw: org.apache.felix.framework;version='[7.0.5,7.0.5]' --runee: JavaSE-17 +-runee: JavaSE-21 -runprovidedcapabilities: ${native_capability} -resolve.effective: active @@ -35,6 +35,7 @@ bnd.identity;id='org.ops4j.pax.logging.pax-logging-log4j2',\ bnd.identity;id='org.osgi.service.jdbc',\ bnd.identity;id='org.apache.felix.http.jetty',\ + bnd.identity;id='org.apache.felix.http.servlet-api',\ bnd.identity;id='org.apache.felix.webconsole',\ bnd.identity;id='org.apache.felix.webconsole.plugins.ds',\ bnd.identity;id='org.apache.felix.inventory',\ @@ -62,10 +63,10 @@ Java-WebSocket;version='[1.5.4,1.5.5)',\ com.fasterxml.aalto-xml;version='[1.3.3,1.3.4)',\ com.google.gson;version='[2.11.0,2.11.1)',\ - com.google.guava;version='[33.3.1,33.3.2)',\ + com.google.guava;version='[33.4.0,33.4.1)',\ com.google.guava.failureaccess;version='[1.0.2,1.0.3)',\ com.squareup.okio;version='[3.9.1,3.9.2)',\ - com.zaxxer.HikariCP;version='[6.0.0,6.0.1)',\ + com.zaxxer.HikariCP;version='[6.2.1,6.2.2)',\ io.openems.backend.alerting;version=snapshot,\ io.openems.backend.application;version=snapshot,\ io.openems.backend.b2brest;version=snapshot,\ @@ -100,7 +101,7 @@ io.openems.wrapper.retrofit-converter-gson;version=snapshot,\ io.openems.wrapper.retrofit-converter-scalars;version=snapshot,\ io.openems.wrapper.retrofit2;version=snapshot,\ - io.reactivex.rxjava3.rxjava;version='[3.1.9,3.1.10)',\ + io.reactivex.rxjava3.rxjava;version='[3.1.10,3.1.11)',\ org.apache.commons.commons-codec;version='[1.17.1,1.17.2)',\ org.apache.commons.commons-compress;version='[1.27.1,1.27.2)',\ org.apache.commons.commons-csv;version='[1.11.0,1.11.1)',\ @@ -115,10 +116,10 @@ org.apache.felix.scr;version='[2.2.12,2.2.13)',\ org.apache.felix.webconsole;version='[5.0.8,5.0.9)',\ org.apache.felix.webconsole.plugins.ds;version='[2.3.0,2.3.1)',\ - org.jetbrains.kotlin.osgi-bundle;version='[2.0.20,2.0.21)',\ + org.jetbrains.kotlin.osgi-bundle;version='[2.1.0,2.1.1)',\ org.jsr-305;version='[3.0.2,3.0.3)',\ - org.ops4j.pax.logging.pax-logging-api;version='[2.2.1,2.2.2)',\ - org.ops4j.pax.logging.pax-logging-log4j2;version='[2.2.1,2.2.2)',\ + org.ops4j.pax.logging.pax-logging-api;version='[2.2.7,2.2.8)',\ + org.ops4j.pax.logging.pax-logging-log4j2;version='[2.2.7,2.2.8)',\ org.osgi.service.component;version='[1.5.1,1.5.2)',\ org.osgi.service.jdbc;version='[1.1.0,1.1.1)',\ org.osgi.util.function;version='[1.2.0,1.2.1)',\ diff --git a/io.openems.backend.application/test/io/openems/backend/edge/application/TestClient.java b/io.openems.backend.application/test/io/openems/backend/edge/application/TestClient.java index c6030384134..12c4a97ac8a 100644 --- a/io.openems.backend.application/test/io/openems/backend/edge/application/TestClient.java +++ b/io.openems.backend.application/test/io/openems/backend/edge/application/TestClient.java @@ -92,12 +92,7 @@ public void setOnNotification(OnNotification onNotification) { @Override protected WsData createWsData(WebSocket ws) { - return new WsData(ws) { - @Override - public String toString() { - return "TestClient.WsData []"; - } - }; + return new WsData(ws); } @Override diff --git a/io.openems.backend.b2brest/.classpath b/io.openems.backend.b2brest/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.backend.b2brest/.classpath +++ b/io.openems.backend.b2brest/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.backend.b2bwebsocket/.classpath b/io.openems.backend.b2bwebsocket/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.backend.b2bwebsocket/.classpath +++ b/io.openems.backend.b2bwebsocket/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.backend.b2bwebsocket/src/io/openems/backend/b2bwebsocket/WsData.java b/io.openems.backend.b2bwebsocket/src/io/openems/backend/b2bwebsocket/WsData.java index 24298d8c0f2..9276a41a2c1 100644 --- a/io.openems.backend.b2bwebsocket/src/io/openems/backend/b2bwebsocket/WsData.java +++ b/io.openems.backend.b2bwebsocket/src/io/openems/backend/b2bwebsocket/WsData.java @@ -65,7 +65,11 @@ public SubscribedEdgesChannelsWorker getSubscribedChannelsWorker() { } @Override - public String toString() { - return "B2bWebsocket.WsData [user=" + this.user.getNow(null) + "]"; + public String toLogString() { + var user = this.user.getNow(null); + var userId = user == null // + ? "UNDEFINED" // + : user.getId(); + return "B2bWebsocket.WsData [user=" + userId + "]"; } } diff --git a/io.openems.backend.b2bwebsocket/test/io/openems/backend/b2bwebsocket/TestClient.java b/io.openems.backend.b2bwebsocket/test/io/openems/backend/b2bwebsocket/TestClient.java index 3f313d2c93b..1e04b149df7 100644 --- a/io.openems.backend.b2bwebsocket/test/io/openems/backend/b2bwebsocket/TestClient.java +++ b/io.openems.backend.b2bwebsocket/test/io/openems/backend/b2bwebsocket/TestClient.java @@ -93,12 +93,7 @@ public void setOnNotification(OnNotification onNotification) { @Override protected WsData createWsData(WebSocket ws) { - return new WsData(ws) { - @Override - public String toString() { - return "TestClient.WsData []"; - } - }; + return new WsData(ws); } @Override diff --git a/io.openems.backend.b2bwebsocket/test/io/openems/backend/b2bwebsocket/WsDataTest.java b/io.openems.backend.b2bwebsocket/test/io/openems/backend/b2bwebsocket/WsDataTest.java new file mode 100644 index 00000000000..eeedb52b567 --- /dev/null +++ b/io.openems.backend.b2bwebsocket/test/io/openems/backend/b2bwebsocket/WsDataTest.java @@ -0,0 +1,35 @@ +package io.openems.backend.b2bwebsocket; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThrows; + +import java.util.Optional; + +import org.junit.Test; + +import io.openems.backend.common.metadata.User; +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; + +public class WsDataTest { + + @Test + public void test() throws OpenemsNamedException { + var sut = new WsData(null, null); + assertEquals("B2bWebsocket.WsData [user=UNDEFINED]", sut.toLogString()); + assertEquals(Optional.empty(), sut.getUserOpt()); + assertThrows(OpenemsNamedException.class, () -> sut.getUserWithTimeout(1, MILLISECONDS)); + assertEquals(null, sut.getUser().getNow(null)); + + var user = new User("foo", null, null, null, null, false, null); + sut.setUser(user); + assertEquals("B2bWebsocket.WsData [user=foo]", sut.toLogString()); + assertEquals(Optional.of(user), sut.getUserOpt()); + assertEquals(user, sut.getUserWithTimeout(1, MILLISECONDS)); + assertNotNull(sut.getSubscribedChannelsWorker()); + + sut.dispose(); + } + +} diff --git a/io.openems.backend.common/.classpath b/io.openems.backend.common/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.backend.common/.classpath +++ b/io.openems.backend.common/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.backend.common/src/io/openems/backend/common/jsonrpc/SimulationEngine.java b/io.openems.backend.common/src/io/openems/backend/common/jsonrpc/SimulationEngine.java index 975f1d00ea6..16447638f6e 100644 --- a/io.openems.backend.common/src/io/openems/backend/common/jsonrpc/SimulationEngine.java +++ b/io.openems.backend.common/src/io/openems/backend/common/jsonrpc/SimulationEngine.java @@ -5,6 +5,7 @@ import io.openems.backend.common.jsonrpc.request.SimulationRequest; import io.openems.backend.common.metadata.User; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.jsonrpc.base.JsonrpcRequest; import io.openems.common.jsonrpc.base.JsonrpcResponseSuccess; public interface SimulationEngine { diff --git a/io.openems.backend.core/.classpath b/io.openems.backend.core/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.backend.core/.classpath +++ b/io.openems.backend.core/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.backend.core/src/io/openems/backend/core/jsonrpcrequesthandler/EdgeRpcRequestHandler.java b/io.openems.backend.core/src/io/openems/backend/core/jsonrpcrequesthandler/EdgeRpcRequestHandler.java index c98e855c17d..5c4e6ef0109 100644 --- a/io.openems.backend.core/src/io/openems/backend/core/jsonrpcrequesthandler/EdgeRpcRequestHandler.java +++ b/io.openems.backend.core/src/io/openems/backend/core/jsonrpcrequesthandler/EdgeRpcRequestHandler.java @@ -5,8 +5,12 @@ import static io.openems.common.utils.JsonUtils.getAsString; import static java.util.Collections.emptyMap; +import java.io.IOException; +import java.time.temporal.ChronoUnit; +import java.util.Collections; import java.util.Map; import java.util.Optional; +import java.util.TreeSet; import java.util.UUID; import java.util.concurrent.CompletableFuture; @@ -15,6 +19,7 @@ import io.openems.backend.common.metadata.User; import io.openems.common.exceptions.OpenemsError; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.exceptions.OpenemsException; import io.openems.common.jsonrpc.base.JsonrpcResponseSuccess; import io.openems.common.jsonrpc.request.AppCenterRequest; import io.openems.common.jsonrpc.request.ComponentJsonApiRequest; @@ -29,7 +34,13 @@ import io.openems.common.jsonrpc.response.QueryHistoricTimeseriesDataResponse; import io.openems.common.jsonrpc.response.QueryHistoricTimeseriesEnergyPerPeriodResponse; import io.openems.common.jsonrpc.response.QueryHistoricTimeseriesEnergyResponse; +import io.openems.common.jsonrpc.response.QueryHistoricTimeseriesExportXlsxResponse; +import io.openems.common.session.Language; import io.openems.common.session.Role; +import io.openems.common.timedata.Resolution; +import io.openems.common.timedata.XlsxExportDetailData.XlsxExportDataEntry.HistoricTimedataSaveType; +import io.openems.common.timedata.XlsxExportUtil; +import io.openems.common.types.ChannelAddress; public class EdgeRpcRequestHandler { @@ -265,8 +276,38 @@ private CompletableFuture handleQueryHistoricEnergyPerPe */ private CompletableFuture handleQueryHistoricTimeseriesExportXlxsRequest(String edgeId, User user, QueryHistoricTimeseriesExportXlxsRequest request) throws OpenemsNamedException { - return CompletableFuture.completedFuture(this.parent.timedataManager - .handleQueryHistoricTimeseriesExportXlxsRequest(edgeId, request, user.getLanguage())); + return CompletableFuture.completedFuture( + this.handleQueryHistoricTimeseriesExportXlxsRequest(edgeId, request, user.getLanguage())); + } + + private QueryHistoricTimeseriesExportXlsxResponse handleQueryHistoricTimeseriesExportXlxsRequest(String edgeId, + QueryHistoricTimeseriesExportXlxsRequest request, Language language) throws OpenemsNamedException { + final var powerChannels = new TreeSet(QueryHistoricTimeseriesExportXlsxResponse.POWER_CHANNELS); + final var energyChannels = new TreeSet( + QueryHistoricTimeseriesExportXlsxResponse.ENERGY_CHANNELS); + + final var edge = this.parent.metadata.edge().getEdgeConfig(edgeId); + + final var detailData = XlsxExportUtil.getDetailData(edge); + final var channelsByType = detailData.getChannelsBySaveType(); + powerChannels.addAll(channelsByType.getOrDefault(HistoricTimedataSaveType.POWER, Collections.emptyList())); + energyChannels.addAll(channelsByType.getOrDefault(HistoricTimedataSaveType.ENERGY, Collections.emptyList())); + + var powerData = this.parent.timedataManager.queryHistoricData(edgeId, request.getFromDate(), + request.getToDate(), powerChannels, new Resolution(15, ChronoUnit.MINUTES)); + + var energyData = this.parent.timedataManager.queryHistoricEnergy(edgeId, request.getFromDate(), + request.getToDate(), energyChannels); + if (powerData == null || energyData == null) { + return null; + } + try { + return new QueryHistoricTimeseriesExportXlsxResponse(request.getId(), edgeId, request.getFromDate(), + request.getToDate(), powerData, energyData, language, detailData); + + } catch (IOException e) { + throw new OpenemsException("QueryHistoricTimeseriesExportXlxsRequest failed: " + e.getMessage()); + } } /** diff --git a/io.openems.backend.edgewebsocket/.classpath b/io.openems.backend.edgewebsocket/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.backend.edgewebsocket/.classpath +++ b/io.openems.backend.edgewebsocket/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.backend.edgewebsocket/src/io/openems/backend/edgewebsocket/WsData.java b/io.openems.backend.edgewebsocket/src/io/openems/backend/edgewebsocket/WsData.java index 180eaca7c5a..6ff6bab290d 100644 --- a/io.openems.backend.edgewebsocket/src/io/openems/backend/edgewebsocket/WsData.java +++ b/io.openems.backend.edgewebsocket/src/io/openems/backend/edgewebsocket/WsData.java @@ -76,10 +76,11 @@ public synchronized Optional getEdgeId() { } @Override - public String toString() { - return "EdgeWebsocket.WsData [" // - + "edgeId=" + this.edgeId.orElse("UNKNOWN") // - + "]"; + protected String toLogString() { + return new StringBuilder("EdgeWebsocket.WsData [edgeId=") // + .append(this.edgeId.orElse("UNKNOWN")) // + .append("]") // + .toString(); } } diff --git a/io.openems.backend.edgewebsocket/test/io/openems/backend/edgewebsocket/WsDataTest.java b/io.openems.backend.edgewebsocket/test/io/openems/backend/edgewebsocket/WsDataTest.java new file mode 100644 index 00000000000..707168f40e3 --- /dev/null +++ b/io.openems.backend.edgewebsocket/test/io/openems/backend/edgewebsocket/WsDataTest.java @@ -0,0 +1,37 @@ +package io.openems.backend.edgewebsocket; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +import java.util.Optional; + +import org.junit.Test; + +import com.google.gson.JsonObject; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.exceptions.OpenemsException; +import io.openems.common.jsonrpc.base.GenericJsonrpcNotification; +import io.openems.common.jsonrpc.base.JsonrpcMessage; + +public class WsDataTest { + + private static final String EDGE_ID = "edge0"; + private static final JsonrpcMessage JMSG = new GenericJsonrpcNotification("foo", new JsonObject()); + + @Test + public void test() throws OpenemsException { + var sut = new WsData(null); + assertEquals("EdgeWebsocket.WsData [edgeId=UNKNOWN]", sut.toLogString()); + assertThrows(OpenemsNamedException.class, () -> sut.assertEdgeId(JMSG)); + assertThrows(OpenemsNamedException.class, () -> sut.assertEdgeIdWithTimeout(JMSG, 1, MILLISECONDS)); + + sut.setEdgeId(EDGE_ID); + assertEquals("EdgeWebsocket.WsData [edgeId=edge0]", sut.toLogString()); + sut.assertEdgeId(null); + sut.assertEdgeIdWithTimeout(JMSG, 1, MILLISECONDS); + assertEquals(Optional.of(EDGE_ID), sut.getEdgeId()); + } + +} diff --git a/io.openems.backend.metadata.dummy/.classpath b/io.openems.backend.metadata.dummy/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.backend.metadata.dummy/.classpath +++ b/io.openems.backend.metadata.dummy/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.backend.metadata.file/.classpath b/io.openems.backend.metadata.file/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.backend.metadata.file/.classpath +++ b/io.openems.backend.metadata.file/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.backend.metadata.odoo/.classpath b/io.openems.backend.metadata.odoo/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.backend.metadata.odoo/.classpath +++ b/io.openems.backend.metadata.odoo/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/odoo/OdooUtils.java b/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/odoo/OdooUtils.java index 638d3c09408..fd92b4cb783 100644 --- a/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/odoo/OdooUtils.java +++ b/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/odoo/OdooUtils.java @@ -6,7 +6,7 @@ import java.io.OutputStreamWriter; import java.net.HttpURLConnection; import java.net.MalformedURLException; -import java.net.URL; +import java.net.URI; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; @@ -101,7 +101,9 @@ public static SuccessResponseAndHeaders sendJsonrpcRequest(String url, String co HttpURLConnection connection = null; try { // Open connection to Odoo - connection = (HttpURLConnection) new URL(url).openConnection(); + connection = (HttpURLConnection) URI.create(url) // + .toURL() // + .openConnection(); // connection.setConnectTimeout(5000);// 5 secs connection.setReadTimeout(timeout);// 5 secs connection.setRequestProperty("Accept-Charset", "US-ASCII"); @@ -276,8 +278,9 @@ private static Object executeKw(Credentials creds, String model, String action, private static Object executeKw(Credentials creds, String model, String action, Object[] arg, Map kw) throws MalformedURLException, XMLRPCException { var params = new Object[] { creds.getDatabase(), creds.getUid(), creds.getPassword(), model, action, arg, kw }; - var client = new XMLRPCClient(new URL(String.format("%s/xmlrpc/2/object", creds.getUrl())), - XMLRPCClient.FLAGS_NIL); + var uri = URI.create(String.format("%s/xmlrpc/2/object", creds.getUrl())); + var client = new XMLRPCClient(uri.toURL(), XMLRPCClient.FLAGS_NIL); + client.setTimeout(60 /* seconds */); return client.call("execute_kw", params); } @@ -557,9 +560,9 @@ protected static byte[] getOdooReport(Credentials credentials, String report, in HttpURLConnection connection = null; try { - connection = (HttpURLConnection) new URL( - credentials.getUrl() + "/report/pdf/" + report + "/" + id + "?session_id=" + session) - .openConnection(); + connection = (HttpURLConnection) URI + .create(credentials.getUrl() + "/report/pdf/" + report + "/" + id + "?session_id=" + session) + .toURL().openConnection(); connection.setConnectTimeout(5000); connection.setReadTimeout(5000); connection.setRequestMethod("GET"); diff --git a/io.openems.backend.timedata.aggregatedinflux/.classpath b/io.openems.backend.timedata.aggregatedinflux/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.backend.timedata.aggregatedinflux/.classpath +++ b/io.openems.backend.timedata.aggregatedinflux/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.backend.timedata.dummy/.classpath b/io.openems.backend.timedata.dummy/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.backend.timedata.dummy/.classpath +++ b/io.openems.backend.timedata.dummy/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.backend.timedata.influx/.classpath b/io.openems.backend.timedata.influx/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.backend.timedata.influx/.classpath +++ b/io.openems.backend.timedata.influx/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.backend.timedata.timescaledb/.classpath b/io.openems.backend.timedata.timescaledb/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.backend.timedata.timescaledb/.classpath +++ b/io.openems.backend.timedata.timescaledb/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.backend.uiwebsocket/.classpath b/io.openems.backend.uiwebsocket/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.backend.uiwebsocket/.classpath +++ b/io.openems.backend.uiwebsocket/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.backend.uiwebsocket/src/io/openems/backend/uiwebsocket/impl/WsData.java b/io.openems.backend.uiwebsocket/src/io/openems/backend/uiwebsocket/impl/WsData.java index c60424b0d3d..b2bfdca34ef 100644 --- a/io.openems.backend.uiwebsocket/src/io/openems/backend/uiwebsocket/impl/WsData.java +++ b/io.openems.backend.uiwebsocket/src/io/openems/backend/uiwebsocket/impl/WsData.java @@ -154,14 +154,15 @@ public String assertToken() throws OpenemsNamedException { } @Override - public String toString() { - String tokenString; - if (this.token.isPresent()) { - tokenString = this.token.get().toString(); - } else { - tokenString = "UNKNOWN"; - } - return "UiWebsocket.WsData [userId=" + this.userId.orElse("UNKNOWN") + ", token=" + tokenString + "]"; + protected String toLogString() { + return new StringBuilder("UiWebsocket.WsData [userId=") // + .append(this.userId.orElse("UNKNOWN")) // + .append(", token=") // + .append(this.token.isPresent() // + ? this.token.get().toString() // + : "UNKNOWN") // + .append("]") // + .toString(); } /** diff --git a/io.openems.backend.uiwebsocket/test/io/openems/backend/uiwebsocket/impl/WsDataTest.java b/io.openems.backend.uiwebsocket/test/io/openems/backend/uiwebsocket/impl/WsDataTest.java new file mode 100644 index 00000000000..8832a3c6dfd --- /dev/null +++ b/io.openems.backend.uiwebsocket/test/io/openems/backend/uiwebsocket/impl/WsDataTest.java @@ -0,0 +1,35 @@ +package io.openems.backend.uiwebsocket.impl; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +import java.util.Optional; + +import org.junit.Test; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; + +public class WsDataTest { + + private static final String USER_ID = "user0"; + private static final String TOKEN = "token"; + + @Test + public void test() throws OpenemsNamedException { + var sut = new WsData(null); + assertEquals(Optional.empty(), sut.getUser(null)); + assertThrows(OpenemsNamedException.class, () -> sut.assertToken()); + assertEquals("UiWebsocket.WsData [userId=UNKNOWN, token=UNKNOWN]", sut.toLogString()); + + sut.setUserId(USER_ID); + sut.setToken(TOKEN); + + assertEquals(Optional.of(USER_ID), sut.getUserId()); + assertEquals(Optional.of(TOKEN), sut.getToken()); + assertEquals(TOKEN, sut.assertToken()); + assertEquals("UiWebsocket.WsData [userId=user0, token=token]", sut.toLogString()); + + sut.logout(); + } + +} diff --git a/io.openems.common/.classpath b/io.openems.common/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.common/.classpath +++ b/io.openems.common/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.common/resources/templates/controller/$testSrcDir$/$basePackageDir$/MyControllerTest.java b/io.openems.common/resources/templates/controller/$testSrcDir$/$basePackageDir$/MyControllerTest.java index 2205624e13f..41b5f380e43 100644 --- a/io.openems.common/resources/templates/controller/$testSrcDir$/$basePackageDir$/MyControllerTest.java +++ b/io.openems.common/resources/templates/controller/$testSrcDir$/$basePackageDir$/MyControllerTest.java @@ -7,15 +7,14 @@ public class MyControllerTest { - private static final String CTRL_ID = "ctrl0"; - @Test public void test() throws Exception { new ControllerTest(new MyControllerImpl()) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .build()) - .next(new TestCase()); + .setId("ctrl0") // + .build()) // + .next(new TestCase()) // + .deactivate(); } } diff --git a/io.openems.common/resources/templates/device-modbus/$testSrcDir$/$basePackageDir$/MyModbusDeviceTest.java b/io.openems.common/resources/templates/device-modbus/$testSrcDir$/$basePackageDir$/MyModbusDeviceTest.java index c9961ad3a77..eb60ad3df76 100644 --- a/io.openems.common/resources/templates/device-modbus/$testSrcDir$/$basePackageDir$/MyModbusDeviceTest.java +++ b/io.openems.common/resources/templates/device-modbus/$testSrcDir$/$basePackageDir$/MyModbusDeviceTest.java @@ -9,19 +9,17 @@ public class MyModbusDeviceTest { - private static final String COMPONENT_ID = "component0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new MyModbusDeviceImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // - .setModbusId(MODBUS_ID) // - .build()) - .next(new TestCase()); + .setId("component0") // + .setModbusId("modbus0") // + .build()) // + .next(new TestCase()) // + .deactivate(); } } diff --git a/io.openems.common/resources/templates/device/$testSrcDir$/$basePackageDir$/MyDeviceTest.java b/io.openems.common/resources/templates/device/$testSrcDir$/$basePackageDir$/MyDeviceTest.java index d5cb74df7c9..f02eed1d7f9 100644 --- a/io.openems.common/resources/templates/device/$testSrcDir$/$basePackageDir$/MyDeviceTest.java +++ b/io.openems.common/resources/templates/device/$testSrcDir$/$basePackageDir$/MyDeviceTest.java @@ -7,15 +7,14 @@ public class MyDeviceTest { - private static final String COMPONENT_ID = "component0"; - @Test public void test() throws Exception { new ComponentTest(new MyDeviceImpl()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // - .build()) - .next(new TestCase()); + .setId("component0") // + .build()) // + .next(new TestCase()) // + .deactivate(); } } diff --git a/io.openems.common/src/io/openems/common/OpenemsConstants.java b/io.openems.common/src/io/openems/common/OpenemsConstants.java index b27257da16a..3d6116e121b 100644 --- a/io.openems.common/src/io/openems/common/OpenemsConstants.java +++ b/io.openems.common/src/io/openems/common/OpenemsConstants.java @@ -14,7 +14,7 @@ public class OpenemsConstants { *

* This is the year of the release. */ - public static final short VERSION_MAJOR = 2024; + public static final short VERSION_MAJOR = 2025; /** * The minor version of OpenEMS. @@ -22,7 +22,7 @@ public class OpenemsConstants { *

* This is the month of the release. */ - public static final short VERSION_MINOR = 11; + public static final short VERSION_MINOR = 2; /** * The patch version of OpenEMS. diff --git a/io.openems.common/src/io/openems/common/channel/Unit.java b/io.openems.common/src/io/openems/common/channel/Unit.java index 466ea4fbef6..bc8b2aedb0f 100644 --- a/io.openems.common/src/io/openems/common/channel/Unit.java +++ b/io.openems.common/src/io/openems/common/channel/Unit.java @@ -38,6 +38,11 @@ public enum Unit { */ THOUSANDTH("‰"), + /** + * Ten Thousandth [‰], 0-10000. + */ + TENTHOUSANDTH("0.1‰"), + /** * On or Off. */ @@ -91,6 +96,11 @@ public enum Unit { */ VOLT("V"), + /** + * Unit of Voltage [dV]. + */ + DEZIVOLT("dV", VOLT, -1), + /** * Unit of Voltage [mV]. */ @@ -110,6 +120,11 @@ public enum Unit { */ AMPERE("A"), + /** + * Unit of Current [dA]. + */ + DEZIAMPERE("dA", AMPERE, -1), + /** * Unit of Current [mA]. */ @@ -284,7 +299,12 @@ public enum Unit { /** * Unit of Pressure [bar]. */ - BAR("bar"); + BAR("bar"), + + /** + * Unit of Pressure [mbar]. + */ + MILLIBAR("mbar", BAR, -3); public final String symbol; public final Unit baseUnit; @@ -363,7 +383,8 @@ public String format(Object value, OpenemsType type) { MILLIWATT, WATT_HOURS, OHM, KILOOHM, SECONDS, AMPERE_HOURS, HOUR, CUMULATED_SECONDS, KILOAMPERE_HOURS, KILOVOLT_AMPERE, KILOVOLT_AMPERE_REACTIVE, KILOVOLT_AMPERE_REACTIVE_HOURS, KILOWATT_HOURS, MICROOHM, MILLIAMPERE_HOURS, MILLIOHM, MILLISECONDS, MINUTE, THOUSANDTH, VOLT_AMPERE_HOURS, - VOLT_AMPERE_REACTIVE_HOURS, WATT_HOURS_BY_WATT_PEAK, CUMULATED_WATT_HOURS, BAR -> // + VOLT_AMPERE_REACTIVE_HOURS, WATT_HOURS_BY_WATT_PEAK, CUMULATED_WATT_HOURS, BAR, MILLIBAR, TENTHOUSANDTH, + DEZIAMPERE, DEZIVOLT -> // value + " " + this.symbol; case ON_OFF -> // diff --git a/io.openems.common/src/io/openems/common/jscalendar/JSCalendar.java b/io.openems.common/src/io/openems/common/jscalendar/JSCalendar.java new file mode 100644 index 00000000000..fcf4e8f06ec --- /dev/null +++ b/io.openems.common/src/io/openems/common/jscalendar/JSCalendar.java @@ -0,0 +1,390 @@ +package io.openems.common.jscalendar; + +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.collect.ImmutableSortedSet.toImmutableSortedSet; +import static io.openems.common.jscalendar.JSCalendar.RecurrenceFrequency.WEEKLY; +import static io.openems.common.utils.JsonUtils.getAsEnum; +import static io.openems.common.utils.JsonUtils.getAsJsonArray; +import static io.openems.common.utils.JsonUtils.getAsJsonObject; +import static io.openems.common.utils.JsonUtils.getAsLocalDateTime; +import static io.openems.common.utils.JsonUtils.getAsString; +import static io.openems.common.utils.JsonUtils.getAsUUID; +import static io.openems.common.utils.JsonUtils.getAsZonedDateTime; +import static io.openems.common.utils.JsonUtils.stream; +import static io.openems.common.utils.JsonUtils.toJsonArray; +import static java.time.DayOfWeek.FRIDAY; +import static java.time.DayOfWeek.MONDAY; +import static java.time.DayOfWeek.SATURDAY; +import static java.time.DayOfWeek.SUNDAY; +import static java.time.DayOfWeek.THURSDAY; +import static java.time.DayOfWeek.TUESDAY; +import static java.time.DayOfWeek.WEDNESDAY; +import static java.time.format.DateTimeFormatter.ISO_INSTANT; +import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME; +import static java.time.temporal.ChronoField.NANO_OF_DAY; +import static java.time.temporal.TemporalAdjusters.nextOrSame; +import static java.util.Arrays.stream; +import static java.util.UUID.randomUUID; + +import java.time.DayOfWeek; +import java.time.LocalDateTime; +import java.time.ZonedDateTime; +import java.util.NoSuchElementException; +import java.util.UUID; +import java.util.function.Consumer; +import java.util.function.Function; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSortedSet; +import com.google.common.collect.Ordering; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.exceptions.OpenemsException; +import io.openems.common.function.ThrowingFunction; +import io.openems.common.utils.JsonUtils; + +/** + * Implementation of RFC 8984 "JSCalendar: A JSON Representation of Calendar + * Data". + * + *

+ * See https://www.rfc-editor.org/rfc/rfc8984.html + */ +// CHECKSTYLE:OFF +public class JSCalendar { + // CHECKSTYLE:ON + + public static record Task(UUID uid, ZonedDateTime updated, LocalDateTime start, + ImmutableList recurrenceRules, PAYLOAD payload) { + + /** + * Parse a List of {@link Task}s from a {@link JsonArray}. + * + * @param the type of the Payload + * @param json the {@link JsonArray} + * @param payloadParser a parser for a Payload + * @return the List of {@link Task}s + * @throws OpenemsNamedException on error + */ + public static ImmutableList> fromJson(JsonArray json, + ThrowingFunction payloadParser) + throws OpenemsNamedException { + return stream(json) // + .map(j -> { + try { + return fromJson(JsonUtils.getAsJsonObject(j), payloadParser); + } catch (OpenemsNamedException e) { + e.printStackTrace(); + throw new NoSuchElementException(e.getMessage()); + } + }) // + .collect(toImmutableList()); + } + + /** + * Parse a {@link Task} from a {@link JsonObject}. + * + * @param the type of the Payload + * @param json the {@link JsonObject} + * @param payloadParser a parser for a Payload + * @return the {@link Task} + * @throws OpenemsNamedException on error + */ + public static Task fromJson(JsonObject json, Function payloadParser) + throws OpenemsNamedException { + return fromJson(json, new ThrowingFunction() { + + @Override + public PAYLOAD apply(JsonObject json) throws OpenemsNamedException { + return payloadParser.apply(json); + } + }); + } + + /** + * Parse a {@link Task} from a {@link JsonObject}. + * + * @param the type of the Payload + * @param json the {@link JsonObject} + * @param payloadParser a parser for a Payload + * @return the {@link Task} + * @throws OpenemsNamedException on error + */ + public static Task fromJson(JsonObject json, + ThrowingFunction payloadParser) + throws OpenemsNamedException { + var type = getAsString(json, "@type"); + if (!type.equalsIgnoreCase("Task")) { + throw new OpenemsException("This is not a 'Task': " + type); + } + try { + var uid = getAsUUID(json, "uid"); + var updated = getAsZonedDateTime(json, "updated"); + var start = getAsLocalDateTime(json, "start"); + var recurrenceRules = stream(getAsJsonArray(json, "recurrenceRules")) // + .map(r -> { + try { + return RecurrenceRule.fromJson(r); + } catch (OpenemsNamedException e) { + e.printStackTrace(); + throw new NoSuchElementException(e.getMessage()); + } + }) // + .collect(toImmutableList()); + var payload = payloadParser.apply(getAsJsonObject(json, "payload")); + return new Task(uid, updated, start, recurrenceRules, payload); + + } catch (NoSuchElementException e) { + throw new OpenemsException("NoSuchElementException: " + e.getMessage()); + } + } + + public static class Builder { + private final UUID uid; + private final ZonedDateTime updated; + + private LocalDateTime start = null; + private ImmutableList.Builder recurrenceRules = ImmutableList.builder(); + private PAYLOAD payload = null; + + protected Builder() { + this(randomUUID(), ZonedDateTime.now()); + } + + protected Builder(UUID uid, ZonedDateTime updated) { + this.uid = uid; + this.updated = updated; + } + + public Builder setStart(LocalDateTime start) { + this.start = start; + return this; + } + + protected Builder setStart(String start) { + this.setStart(LocalDateTime.parse(start)); + return this; + } + + /** + * Adds a {@link RecurrenceRule}. + * + * @param recurrenceRule the {@link RecurrenceRule} + * @return myself + */ + public Builder addRecurrenceRule(RecurrenceRule recurrenceRule) { + this.recurrenceRules.add(recurrenceRule); + return this; + } + + /** + * Adds a {@link RecurrenceRule}. + * + * @param consumer a RecurrenceRule Builder + * @return myself + */ + public Builder addRecurrenceRule(Consumer consumer) { + var builder = RecurrenceRule.create(); + consumer.accept(builder); + this.recurrenceRules.add(builder.build()); + return this; + } + + public Builder setPayload(PAYLOAD payload) { + this.payload = payload; + return this; + } + + public Task build() { + return new Task(this.uid, this.updated, this.start, this.recurrenceRules.build(), + this.payload); + } + } + + /** + * Create a {@link CalendarEvent} {@link Builder}. + * + * @param the type of the Payload + * @return a {@link Builder} + */ + public static Builder create() { + return new Builder(); + } + + /** + * Convert to {@link JsonObject}. + * + * @param payloadConverter a converter for a Payload + * @return a {@link JsonObject} + */ + public JsonObject toJson(Function payloadConverter) { + var j = JsonUtils.buildJsonObject() // + .addProperty("@type", "Task") // + .addProperty("uid", this.uid.toString()) // + .addProperty("updated", this.updated.format(ISO_INSTANT)); + if (this.start != null) { + j.addProperty("start", this.start.format(ISO_LOCAL_DATE_TIME)); + } + if (!this.recurrenceRules.isEmpty()) { + j.add("recurrenceRules", this.recurrenceRules.stream() // + .map(RecurrenceRule::toJson) // + .collect(toJsonArray())); + } + if (this.payload != null) { + j.add("payload", payloadConverter.apply(this.payload)); + } + return j.build(); + } + + /** + * Gets the next occurence of the {@link Task} at or after a date. + * + * @param from the from timestamp + * @return a {@link ZonedDateTime} + */ + public ZonedDateTime getNextOccurence(ZonedDateTime from) { + var start = this.start.atZone(from.getZone()); + return this.recurrenceRules.stream() // + .map(rr -> rr.getNextOccurence(from.isBefore(start) ? start : from, start)) // + .min((o1, o2) -> o1.toInstant().compareTo(o2.toInstant())) // + .orElse(null); + } + } + + public enum RecurrenceFrequency { + // SECONDLY("secondly"), + // MINUTELY("minutely"), + // HOURLY("hourly"), + DAILY("daily"), // + WEEKLY("weekly"), // + MONTHLY("monthly"), // + YEARLY("yearly"); + + public final String name; + + private RecurrenceFrequency(String name) { + this.name = name; + } + } + + public record RecurrenceRule(RecurrenceFrequency frequency, ImmutableSortedSet byDay) { + + /** + * Parse a {@link RecurrenceRule} from a {@link JsonObject}. + * + * @param json the {@link JsonObject} + * @return the {@link RecurrenceRule} + * @throws OpenemsNamedException on error + */ + public static RecurrenceRule fromJson(JsonElement json) throws OpenemsNamedException, NoSuchElementException { + var frequency = getAsEnum(RecurrenceFrequency.class, json, "frequency"); + var byDay = stream(getAsJsonArray(json, "byDay")) // + .map(j -> switch (JsonUtils.getAsOptionalString(j).orElseThrow()) { + case "mo" -> MONDAY; + case "tu" -> TUESDAY; + case "we" -> WEDNESDAY; + case "th" -> THURSDAY; + case "fr" -> FRIDAY; + case "sa" -> SATURDAY; + case "su" -> SUNDAY; + default -> throw new NoSuchElementException(""); + }) // + .collect(toImmutableSortedSet(Ordering.natural())); + return new RecurrenceRule(frequency, byDay); + } + + public static class Builder { + private RecurrenceFrequency frequency; + private ImmutableSortedSet.Builder byDay = ImmutableSortedSet.naturalOrder(); + + public Builder() { + } + + public Builder setFrequency(RecurrenceFrequency frequency) { + this.frequency = frequency; + return this; + } + + /** + * Adds a `byDay`rule. + * + * @param byDay the {@link DayOfWeek}s + * @return myself + */ + public Builder addByDay(DayOfWeek... byDay) { + stream(byDay).forEach(this.byDay::add); + return this; + } + + public RecurrenceRule build() { + return new RecurrenceRule(this.frequency, this.byDay.build()); + } + } + + /** + * Create a {@link RecurrenceRule} {@link Builder}. + * + * @return a {@link Builder} + */ + public static Builder create() { + return new Builder(); + } + + /** + * Gets the next occurence of the {@link RecurrenceRule} at or after a date. + * + * @param from the from date + * @param start the start timestamp of the {@link Task} + * @return a {@link ZonedDateTime} + */ + public ZonedDateTime getNextOccurence(ZonedDateTime from, ZonedDateTime start) { + if (this.frequency == WEEKLY) { + if (!this.byDay.isEmpty()) { + var startTime = start.toLocalTime(); + var nextByDay = this.byDay.ceiling(from.toLocalTime().isAfter(startTime) // + ? from.getDayOfWeek().plus(1) // next day + : from.getDayOfWeek()); // same day + if (nextByDay == null) { + nextByDay = this.byDay.first(); + } + return from // + .with(nextOrSame(nextByDay)) // + .with(NANO_OF_DAY, startTime.toNanoOfDay()); + } + } + return null; + } + + /** + * Convert to {@link JsonObject}. + * + * @return a {@link JsonObject} + */ + public JsonObject toJson() { + var j = JsonUtils.buildJsonObject(); + if (this.frequency != null) { + j.addProperty("frequency", this.frequency.name); + } + if (!this.byDay.isEmpty()) { + j.add("byDay", this.byDay.stream() // + .map(d -> switch (d) { + case MONDAY -> "mo"; + case TUESDAY -> "tu"; + case WEDNESDAY -> "we"; + case THURSDAY -> "th"; + case FRIDAY -> "fr"; + case SATURDAY -> "sa"; + case SUNDAY -> "su"; + }) // + .map(JsonUtils::toJson) // + .collect(toJsonArray())); + } + return j.build(); + } + } + +} diff --git a/io.openems.common/src/io/openems/common/jscalendar/package-info.java b/io.openems.common/src/io/openems/common/jscalendar/package-info.java new file mode 100644 index 00000000000..0d681ebdb59 --- /dev/null +++ b/io.openems.common/src/io/openems/common/jscalendar/package-info.java @@ -0,0 +1,11 @@ +/** + * Implementation of RFC 8984 "JSCalendar: A JSON Representation of Calendar + * Data". + * + *

+ * See https://www.rfc-editor.org/rfc/rfc8984.html + */ +@org.osgi.annotation.versioning.Version("1.0.0") +@org.osgi.annotation.bundle.Export +package io.openems.common.jscalendar; diff --git a/io.openems.common/src/io/openems/common/jsonrpc/response/QueryHistoricTimeseriesExportXlsxResponse.java b/io.openems.common/src/io/openems/common/jsonrpc/response/QueryHistoricTimeseriesExportXlsxResponse.java index 6158477f132..5e9fba1f529 100644 --- a/io.openems.common/src/io/openems/common/jsonrpc/response/QueryHistoricTimeseriesExportXlsxResponse.java +++ b/io.openems.common/src/io/openems/common/jsonrpc/response/QueryHistoricTimeseriesExportXlsxResponse.java @@ -6,15 +6,19 @@ import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; import java.util.HashSet; +import java.util.List; import java.util.Locale; import java.util.Map.Entry; import java.util.ResourceBundle; import java.util.Set; import java.util.SortedMap; import java.util.UUID; +import java.util.function.BiFunction; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.dhatim.fastexcel.BorderSide; +import org.dhatim.fastexcel.BorderStyle; import org.dhatim.fastexcel.Workbook; import org.dhatim.fastexcel.Worksheet; @@ -22,14 +26,16 @@ import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.session.Language; +import io.openems.common.timedata.XlsxExportDetailData; +import io.openems.common.timedata.XlsxExportDetailData.XlsxExportCategory; +import io.openems.common.timedata.XlsxExportDetailData.XlsxExportDataEntry; +import io.openems.common.timedata.XlsxWorksheetWrapper; import io.openems.common.types.ChannelAddress; import io.openems.common.utils.JsonUtils; /** * Represents a JSON-RPC Response for 'queryHistoricTimeseriesExportXlxs'. * - *

- * *

  * {
  *   "jsonrpc": "2.0",
@@ -60,6 +66,11 @@ protected static class Channel {
 		public static final ChannelAddress ESS_SOC = new ChannelAddress("_sum", "EssSoc");
 	}
 
+	private static final String BLUE = "44B3E1";
+	private static final String LIGHT_GREY = "BFBFBF";
+	private static final String DARK_GREY = "D9D9D9";
+	private static final String FONT_NAME = "Calibri";
+
 	/**
 	 * All Power Channels, i.e. Channels that are exported per channel and
 	 * timestamp.
@@ -91,21 +102,24 @@ protected static class Channel {
 	 * While constructing, the actual Excel file is generated as payload of the
 	 * JSON-RPC Response.
 	 *
-	 * @param id             the JSON-RPC ID
-	 * @param edgeId         the Edge-ID
-	 * @param fromDate       the start date of the export
-	 * @param toDate         the end date of the export
-	 * @param historicData   the power data per channel and timestamp
-	 * @param historicEnergy the energy data, one value per channel
-	 * @param language       the {@link Language}
+	 * @param id               the JSON-RPC ID
+	 * @param edgeId           the Edge-ID
+	 * @param fromDate         the start date of the export
+	 * @param toDate           the end date of the export
+	 * @param historicData     the power data per channel and timestamp
+	 * @param historicEnergy   the energy data, one value per channel
+	 * @param language         the {@link Language}
+	 * @param detailComponents the components for the detail view
 	 * @throws IOException           on error
 	 * @throws OpenemsNamedException on error
 	 */
 	public QueryHistoricTimeseriesExportXlsxResponse(UUID id, String edgeId, ZonedDateTime fromDate,
 			ZonedDateTime toDate, SortedMap> historicData,
-			SortedMap historicEnergy, Language language)
-			throws IOException, OpenemsNamedException {
-		super(id, XlsxUtils.generatePayload(edgeId, fromDate, toDate, historicData, historicEnergy, language));
+			SortedMap historicEnergy, Language language,
+			XlsxExportDetailData detailComponents) throws IOException, OpenemsNamedException {
+
+		super(id, XlsxUtils.generatePayload(edgeId, fromDate, toDate, historicData, historicEnergy, language,
+				detailComponents));
 	}
 
 	protected static class XlsxUtils {
@@ -118,20 +132,21 @@ protected static class XlsxUtils {
 		 * Generates the Payload for a
 		 * {@link QueryHistoricTimeseriesExportXlsxResponse}.
 		 *
-		 * @param edgeId     the Edge-Id
-		 * @param fromDate   the start date of the export
-		 * @param toDate     the end date of the export
-		 * @param powerData  the power data per channel and timestamp
-		 * @param energyData the energy data, one value per channel
-		 * @param language   the {@link Language}
+		 * @param edgeId           the Edge-Id
+		 * @param fromDate         the start date of the export
+		 * @param toDate           the end date of the export
+		 * @param powerData        the power data per channel and timestamp
+		 * @param energyData       the energy data, one value per channel
+		 * @param language         the {@link Language}
+		 * @param detailComponents the components for the detail view
 		 * @return the Excel file as byte-array.
 		 * @throws IOException           on error
 		 * @throws OpenemsNamedException on error
 		 */
 		private static byte[] generatePayload(String edgeId, ZonedDateTime fromDate, ZonedDateTime toDate,
 				SortedMap> powerData,
-				SortedMap energyData, Language language)
-				throws IOException, OpenemsNamedException {
+				SortedMap energyData, Language language,
+				XlsxExportDetailData detailComponents) throws IOException, OpenemsNamedException {
 			byte[] payload = {};
 			try (//
 					var os = new ByteArrayOutputStream();
@@ -146,8 +161,54 @@ private static byte[] generatePayload(String edgeId, ZonedDateTime fromDate, Zon
 
 				XlsxUtils.addBasicInfo(ws, edgeId, fromDate, toDate, translationBundle);
 				XlsxUtils.addEnergyData(ws, energyData, translationBundle);
-				XlsxUtils.addPowerData(ws, powerData, translationBundle);
+				var rowCount = XlsxUtils.addPowerData(ws, powerData, translationBundle);
+
+				final var worksheetWrapper = new XlsxWorksheetWrapper(ws);
+
+				// Box for Total Overview
+				worksheetWrapper.setForRange(9, 0, 9, 7, t -> t.style().borderStyle(BorderSide.TOP, BorderStyle.THIN));
+				worksheetWrapper.setForRange(9, 0, rowCount, 0,
+						t -> t.style().borderStyle(BorderSide.LEFT, BorderStyle.THIN));
+				worksheetWrapper.setForRange(rowCount, 0, rowCount, 7,
+						t -> t.style().borderStyle(BorderSide.BOTTOM, BorderStyle.THIN));
+				worksheetWrapper.setForRange(9, 7, rowCount, 7,
+						t -> t.style().borderStyle(BorderSide.RIGHT, BorderStyle.THIN));
+
+				if (detailComponents.data().values().stream().anyMatch(de -> !de.isEmpty())) { //
+					var rightestColumns = XlsxUtils.addDetailData(ws, powerData, detailComponents, translationBundle,
+							energyData);
+
+					final var colProd = rightestColumns.get(0) - 1;
+					final var colCons = rightestColumns.get(1) - 1;
+					final var colTou = rightestColumns.get(2) - 1;
+
+					// Set Box for detail Timerange data
+					worksheetWrapper.setForRange(9, 10, 9, colTou,
+							t -> t.style().borderStyle(BorderSide.TOP, BorderStyle.THIN));
+					worksheetWrapper.setForRange(9, 10, rowCount, 10,
+							t -> t.style().borderStyle(BorderSide.LEFT, BorderStyle.THIN));
+					worksheetWrapper.setForRange(rowCount, 10, rowCount, colTou,
+							t -> t.style().borderStyle(BorderSide.BOTTOM, BorderStyle.THIN));
+					worksheetWrapper.setForRange(9, colTou, rowCount, colTou,
+							t -> t.style().borderStyle(BorderSide.RIGHT, BorderStyle.THIN));
+
+					// Set Separators between prod, cons and tou
+					worksheetWrapper.setForRange(9, colProd, rowCount, colProd,
+							t -> t.style().borderStyle(BorderSide.RIGHT, BorderStyle.THIN));
+					worksheetWrapper.setForRange(9, colCons, rowCount, colCons,
+							t -> t.style().borderStyle(BorderSide.RIGHT, BorderStyle.THIN));
+
+					// Set "blue" Separator between detailed Overview and total Overview
+					worksheetWrapper.setForRange(0, 8, rowCount, 8, t -> t.style()//
+							.borderStyle(BorderSide.LEFT, BorderStyle.MEDIUM)//
+							.borderStyle(BorderSide.RIGHT, BorderStyle.MEDIUM)//
+							.fillColor(BLUE));
+					worksheetWrapper.getCellWrapper(0, 8).style().borderStyle(BorderSide.TOP, BorderStyle.MEDIUM);
+					worksheetWrapper.getCellWrapper(rowCount, 8).style().borderStyle(BorderSide.BOTTOM,
+							BorderStyle.MEDIUM);
+				}
 
+				worksheetWrapper.setAll();
 				wb.finish();
 				os.flush();
 				payload = os.toByteArray();
@@ -199,29 +260,35 @@ protected static void addBasicInfo(Worksheet ws, String edgeId, ZonedDateTime fr
 		 */
 		protected static void addEnergyData(Worksheet ws, SortedMap data,
 				ResourceBundle translationBundle) throws OpenemsNamedException {
+			ws.range(4, 1, 4, 6).merge();
+			ws.value(4, 1, translationBundle.getString("totalOverview"));
+			ws.range(4, 1, 4, 6).style().fontName(FONT_NAME).fontSize(12).horizontalAlignment("center")
+					.verticalAlignment("center").bold().fillColor(BLUE).set();
+			ws.range(5, 1, 5, 6).style().bold().fillColor(LIGHT_GREY).set();
+			ws.range(6, 1, 6, 6).style().fillColor(DARK_GREY).set();
 			// Grid buy energy
-			XlsxUtils.addStringValueBold(ws, 4, 1, translationBundle.getString("gridBuy") + " [kWh]");
-			XlsxUtils.addKwhValueIfnotNull(ws, 5, 1, data.get(Channel.GRID_BUY_ACTIVE_ENERGY), translationBundle);
+			XlsxUtils.addStringValueBold(ws, 5, 1, translationBundle.getString("gridBuy") + " [kWh]");
+			XlsxUtils.addKwhValueIfnotNull(ws, 6, 1, data.get(Channel.GRID_BUY_ACTIVE_ENERGY), translationBundle);
 
 			// Grid sell energy
-			XlsxUtils.addStringValueBold(ws, 4, 2, translationBundle.getString("gridFeedIn") + " [kWh]");
-			XlsxUtils.addKwhValueIfnotNull(ws, 5, 2, data.get(Channel.GRID_SELL_ACTIVE_ENERGY), translationBundle);
+			XlsxUtils.addStringValueBold(ws, 5, 2, translationBundle.getString("gridFeedIn") + " [kWh]");
+			XlsxUtils.addKwhValueIfnotNull(ws, 6, 2, data.get(Channel.GRID_SELL_ACTIVE_ENERGY), translationBundle);
 
 			// Production energy
-			XlsxUtils.addStringValueBold(ws, 4, 3, translationBundle.getString("production") + " [kWh]");
-			XlsxUtils.addKwhValueIfnotNull(ws, 5, 3, data.get(Channel.PRODUCTION_ACTIVE_ENERGY), translationBundle);
+			XlsxUtils.addStringValueBold(ws, 5, 3, translationBundle.getString("production") + " [kWh]");
+			XlsxUtils.addKwhValueIfnotNull(ws, 6, 3, data.get(Channel.PRODUCTION_ACTIVE_ENERGY), translationBundle);
 
 			// Charge energy
-			XlsxUtils.addStringValueBold(ws, 4, 4, translationBundle.getString("storageCharging") + " [kWh]");
-			XlsxUtils.addKwhValueIfnotNull(ws, 5, 4, data.get(Channel.ESS_DC_CHARGE_ENERGY), translationBundle);
+			XlsxUtils.addStringValueBold(ws, 5, 4, translationBundle.getString("storageCharging") + " [kWh]");
+			XlsxUtils.addKwhValueIfnotNull(ws, 6, 4, data.get(Channel.ESS_DC_CHARGE_ENERGY), translationBundle);
 
 			// Charge energy
-			XlsxUtils.addStringValueBold(ws, 4, 5, translationBundle.getString("storageDischarging") + " [kWh]");
-			XlsxUtils.addKwhValueIfnotNull(ws, 5, 5, data.get(Channel.ESS_DC_DISCHARGE_ENERGY), translationBundle);
+			XlsxUtils.addStringValueBold(ws, 5, 5, translationBundle.getString("storageDischarging") + " [kWh]");
+			XlsxUtils.addKwhValueIfnotNull(ws, 6, 5, data.get(Channel.ESS_DC_DISCHARGE_ENERGY), translationBundle);
 
 			// Consumption energy
-			XlsxUtils.addStringValueBold(ws, 4, 6, translationBundle.getString("consumption") + " [kWh]");
-			XlsxUtils.addKwhValueIfnotNull(ws, 5, 6, data.get(Channel.CONSUMPTION_ACTIVE_ENERGY), translationBundle);
+			XlsxUtils.addStringValueBold(ws, 5, 6, translationBundle.getString("consumption") + " [kWh]");
+			XlsxUtils.addKwhValueIfnotNull(ws, 6, 6, data.get(Channel.CONSUMPTION_ACTIVE_ENERGY), translationBundle);
 		}
 
 		/**
@@ -230,22 +297,24 @@ protected static void addEnergyData(Worksheet ws, SortedMap> data, ResourceBundle translationBundle)
 				throws OpenemsNamedException {
 			// Adding the headers
-			XlsxUtils.addStringValueBold(ws, 7, 0, translationBundle.getString("date/time"));
-			XlsxUtils.addStringValueBold(ws, 7, 1, translationBundle.getString("gridBuy") + " [W]");
-			XlsxUtils.addStringValueBold(ws, 7, 2, translationBundle.getString("gridFeedIn") + " [W]");
-			XlsxUtils.addStringValueBold(ws, 7, 3, translationBundle.getString("production") + " [W]");
-			XlsxUtils.addStringValueBold(ws, 7, 4, translationBundle.getString("storageCharging") + " [W]");
-			XlsxUtils.addStringValueBold(ws, 7, 5, translationBundle.getString("storageDischarging") + " [W]");
-			XlsxUtils.addStringValueBold(ws, 7, 6, translationBundle.getString("consumption") + " [W]");
-			XlsxUtils.addStringValueBold(ws, 7, 7, translationBundle.getString("stateOfCharge") + " [%]");
-
-			var rowCount = 8;
+			XlsxUtils.addStringValueBold(ws, 9, 0, translationBundle.getString("date/time"));
+			XlsxUtils.addStringValueBold(ws, 9, 1, translationBundle.getString("gridBuy") + " [W]");
+			XlsxUtils.addStringValueBold(ws, 9, 2, translationBundle.getString("gridFeedIn") + " [W]");
+			XlsxUtils.addStringValueBold(ws, 9, 3, translationBundle.getString("production") + " [W]");
+			XlsxUtils.addStringValueBold(ws, 9, 4, translationBundle.getString("storageCharging") + " [W]");
+			XlsxUtils.addStringValueBold(ws, 9, 5, translationBundle.getString("storageDischarging") + " [W]");
+			XlsxUtils.addStringValueBold(ws, 9, 6, translationBundle.getString("consumption") + " [W]");
+			XlsxUtils.addStringValueBold(ws, 9, 7, translationBundle.getString("stateOfCharge") + " [%]");
+			XlsxUtils.addStringValueBold(ws, 8, 1, translationBundle.getString("generalData"));
+
+			var rowCount = 10;
 
 			for (Entry> row : data.entrySet()) {
 				var values = row.getValue();
@@ -267,12 +336,17 @@ protected static void addPowerData(Worksheet ws,
 						// Grid sell power
 						XlsxUtils.addFloatValue(ws, rowCount, 2, gridActivePower / -1);
 					}
+				} else {
+					XlsxUtils.addStringValue(ws, rowCount, 1, "-");
+					XlsxUtils.addStringValue(ws, rowCount, 2, "-");
 				}
 
 				// Production power
 				if (XlsxUtils.isNotNull(values.get(Channel.PRODUCTION_ACTIVE_POWER))) {
 					XlsxUtils.addFloatValue(ws, rowCount, 3,
 							JsonUtils.getAsFloat(values.get(Channel.PRODUCTION_ACTIVE_POWER)));
+				} else {
+					XlsxUtils.addStringValue(ws, rowCount, 3, "-");
 				}
 
 				if (XlsxUtils.isNotNull(values.get(Channel.ESS_DISCHARGE_POWER))) {
@@ -284,19 +358,120 @@ protected static void addPowerData(Worksheet ws,
 						XlsxUtils.addFloatValue(ws, rowCount, 4, essDischargePower / -1);
 						XlsxUtils.addFloatValue(ws, rowCount, 5, 0);
 					}
+				} else {
+					XlsxUtils.addStringValue(ws, rowCount, 4, "-");
+					XlsxUtils.addStringValue(ws, rowCount, 5, "-");
 				}
 				// Consumption power
 				if (XlsxUtils.isNotNull(values.get(Channel.CONSUMPTION_ACTIVE_POWER))) {
 					XlsxUtils.addFloatValue(ws, rowCount, 6,
 							JsonUtils.getAsFloat(values.get(Channel.CONSUMPTION_ACTIVE_POWER)));
+				} else {
+					XlsxUtils.addStringValue(ws, rowCount, 6, "-");
 				}
 
 				// State of charge
 				if (XlsxUtils.isNotNull(values.get(Channel.ESS_SOC))) {
 					XlsxUtils.addFloatValue(ws, rowCount, 7, JsonUtils.getAsFloat(values.get(Channel.ESS_SOC)));
+				} else {
+					XlsxUtils.addStringValue(ws, rowCount, 7, "-");
 				}
 				rowCount++;
 			}
+			rowCount--;
+			return rowCount;
+		}
+
+		protected static List addDetailData(Worksheet ws,
+				SortedMap> data,
+				XlsxExportDetailData detailComponents, ResourceBundle translationBundle,
+				SortedMap energyData) throws OpenemsNamedException {
+			ws.width(8, 4);
+			ws.width(9, 4);
+			ws.width(10, 25);
+			ws.width(11, 25);
+			ws.width(12, 25);
+			ws.width(13, 25);
+			ws.width(14, 25);
+			ws.width(15, 25);
+
+			ws.range(4, 10, 4, 15).merge();
+			ws.range(4, 10, 4, 15).style().fontName(FONT_NAME).fontSize(12).bold().fillColor(BLUE).set();
+			ws.value(4, 10, translationBundle.getString("detailData"));
+			ws.range(5, 10, 5, 15).merge();
+			ws.range(5, 10, 5, 15).style().fillColor(LIGHT_GREY).set();
+			ws.value(5, 10, translationBundle.getString("detailHint"));
+			var rightestColumn1 = addProductionData(ws, data, detailComponents, translationBundle);
+			var rightestColumn2 = addConsumptionData(ws, data, detailComponents, rightestColumn1, translationBundle);
+			var rightestColumn3 = addTimeOfUseTariffData(ws, data, detailComponents, rightestColumn2,
+					translationBundle);
+			ws.width(rightestColumn3, 35);
+			return List.of(rightestColumn1, rightestColumn2, rightestColumn3);
+		}
+
+		protected static int addProductionData(Worksheet ws,
+				SortedMap> data,
+				XlsxExportDetailData detailComponents, ResourceBundle translationBundle) throws OpenemsNamedException {
+			return XlsxUtils.addGenericData(ws, data, detailComponents, 10, translationBundle,
+					XlsxExportCategory.PRODUCTION, "production", (t, d) -> t.alias() + " [W]", 1);
+		}
+
+		protected static int addConsumptionData(Worksheet ws,
+				SortedMap> data,
+				XlsxExportDetailData detailComponents, int righestColumn, ResourceBundle translationBundle)
+				throws OpenemsNamedException {
+			return XlsxUtils.addGenericData(ws, data, detailComponents, righestColumn, translationBundle,
+					XlsxExportCategory.CONSUMPTION, "consumption", (t, d) -> t.alias() + " [W]", 1);
+		}
+
+		protected static int addTimeOfUseTariffData(Worksheet ws,
+				SortedMap> data,
+				XlsxExportDetailData detailComponents, int righestColumn, ResourceBundle translationBundle)
+				throws OpenemsNamedException {
+			final var unit = "[" + detailComponents.currency().getUnderPart() + "/kWh]";
+			return XlsxUtils.addGenericData(ws, data, detailComponents, righestColumn, translationBundle,
+					XlsxExportCategory.TIME_OF_USE_TARIFF, "timeOfUse", (t, d) -> t.alias() + " " + unit,
+					1000f / detailComponents.currency().getRatio());
+		}
+
+		protected static int addGenericData(//
+				Worksheet ws, //
+				SortedMap> data, //
+				XlsxExportDetailData detailComponents, //
+				int righestColumn, //
+				ResourceBundle translationBundle, //
+				XlsxExportDetailData.XlsxExportCategory category, //
+				String translationKey, //
+				BiFunction aliasBuilder, //
+				float ratio) //
+				throws OpenemsNamedException {
+
+			final var righestColumnOld = righestColumn;
+
+			for (var item : detailComponents.data().get(category)) {
+				ws.value(8, righestColumnOld, translationBundle.getString(translationKey));
+				ws.style(8, righestColumnOld).bold().set();
+
+				ws.value(9, righestColumn, aliasBuilder.apply(item, detailComponents));
+				ws.style(9, righestColumn).bold().set();
+
+				var rowCount = 10;
+				for (final var row : data.entrySet()) {
+					final var values = row.getValue();
+					final var channelAddress = item.channel();
+
+					if (XlsxUtils.isNotNull(values.get(channelAddress))) {
+						XlsxUtils.addFloatValueNotRounded(ws, rowCount, righestColumn,
+								JsonUtils.getAsFloat(values.get(channelAddress)) / ratio);
+					} else {
+						XlsxUtils.addStringValue(ws, rowCount, righestColumn, "-");
+					}
+					rowCount++;
+				}
+				righestColumn++;
+			}
+
+			return righestColumn;
 		}
 
 		/**
@@ -385,6 +560,19 @@ protected static void addFloatValue(Worksheet ws, int row, int col, float value)
 			ws.value(row, col, Math.round(value));
 		}
 
+		/**
+		 * Helper method to add the value to the excel sheet. The float value is
+		 * mathematically rounded.
+		 *
+		 * @param ws    the {@link Worksheet}
+		 * @param row   row number
+		 * @param col   column number
+		 * @param value actual value in the sheet
+		 */
+		protected static void addFloatValueNotRounded(Worksheet ws, int row, int col, float value) {
+			ws.value(row, col, value);
+		}
+
 		/**
 		 * Simple helper method to check for null values.
 		 *
diff --git a/io.openems.common/src/io/openems/common/jsonrpc/response/translation_de.properties b/io.openems.common/src/io/openems/common/jsonrpc/response/translation_de.properties
index 1ac1c7f1860..4fdcc640291 100644
--- a/io.openems.common/src/io/openems/common/jsonrpc/response/translation_de.properties
+++ b/io.openems.common/src/io/openems/common/jsonrpc/response/translation_de.properties
@@ -8,4 +8,9 @@ storageDischarging = Speicher Entladung
 consumption = Verbrauch
 stateOfCharge = Ladezustand
 date/time = Datum / Uhrzeit
-notAvailable = nicht vorhanden
\ No newline at end of file
+notAvailable = nicht vorhanden
+detailData = Detaillierte Auswertung der Erzeuger, Verbraucher und Apps
+detailHint = * Bitte beachten Sie, dass diese Werte bereits in den Leistungsdaten in Ihrer Gesamtbersicht enthalten sind.
+timeOfUse = Dynamischer Stromtarif
+totalOverview = Gesamtbersicht
+generalData = Allgemeine Daten
\ No newline at end of file
diff --git a/io.openems.common/src/io/openems/common/jsonrpc/response/translation_en.properties b/io.openems.common/src/io/openems/common/jsonrpc/response/translation_en.properties
index 4da846d76c2..40f8fc4d09a 100644
--- a/io.openems.common/src/io/openems/common/jsonrpc/response/translation_en.properties
+++ b/io.openems.common/src/io/openems/common/jsonrpc/response/translation_en.properties
@@ -8,4 +8,9 @@ storageDischarging = Storage Discharging
 consumption = Consumption
 stateOfCharge = State of Charge
 date/time = Date / Time
-notAvailable = not available
\ No newline at end of file
+notAvailable = not available
+detailData = Detailed evaluation of the producer, consumer and apps
+detailHint = * Please note that these values are already included in the performance data in your Total Overview.
+timeOfUse = Time of Use Tariff
+totalOverview = Total Overview
+generalData = General Data
\ No newline at end of file
diff --git a/io.openems.common/src/io/openems/common/oem/DummyOpenemsEdgeOem.java b/io.openems.common/src/io/openems/common/oem/DummyOpenemsEdgeOem.java
index cd0a08830ca..f5327adf50d 100644
--- a/io.openems.common/src/io/openems/common/oem/DummyOpenemsEdgeOem.java
+++ b/io.openems.common/src/io/openems/common/oem/DummyOpenemsEdgeOem.java
@@ -60,7 +60,10 @@ public SystemUpdateParams getSystemUpdateParams() {
 			.put("App.FENECON.Home", "https://fenecon.de/fenecon-home-10/") //
 			.put("App.FENECON.Home.20", "https://fenecon.de/fenecon-home-20-30/") //
 			.put("App.FENECON.Home.30", "https://fenecon.de/fenecon-home-20-30/") //
-			.put("App.FENECON.Commercial.92", "https://fenecon.de/fenecon-commercial/") //
+			.put("App.FENECON.Home6", "https://fenecon.de/fenecon-home-6-10-15/") //
+			.put("App.FENECON.Home10.Gen2", "https://fenecon.de/fenecon-home-6-10-15/") //
+			.put("App.FENECON.Home15", "https://fenecon.de/fenecon-home-6-10-15/") //
+			.put("App.FENECON.Commercial.92", "https://fenecon.de/fenecon-commercial-92/") //
 			.put("App.FENECON.Industrial.L.ILK710", "https://fenecon.de/fenecon-industrial-l/") //
 			.put("App.FENECON.Industrial.S.ISK010", "https://fenecon.de/fenecon-industrial-s/") //
 			.put("App.FENECON.Industrial.S.ISK110", "https://fenecon.de/fenecon-industrial-s/") //
@@ -71,19 +74,21 @@ public SystemUpdateParams getSystemUpdateParams() {
 			.put("App.TimeOfUseTariff.Hassfurt", "") //
 			.put("App.TimeOfUseTariff.RabotCharge", "") //
 			.put("App.TimeOfUseTariff.Stromdao", "") //
+			.put("App.TimeOfUseTariff.Swisspower", "") //
 			.put("App.TimeOfUseTariff.Tibber", "") //
 			.put("App.Api.ModbusTcp.ReadOnly", "") //
 			.put("App.Api.ModbusTcp.ReadWrite", "") //
 			.put("App.Api.RestJson.ReadOnly", "") //
 			.put("App.Api.RestJson.ReadWrite", "") //
 			.put("App.Timedata.InfluxDb", "")//
+			.put("App.Evcs.Alpitronic", "") //
+			.put("App.Evcs.Cluster", "") //
 			.put("App.Evcs.HardyBarth", "") //
-			.put("App.Evcs.Keba", "") //
 			.put("App.Evcs.IesKeywatt", "") //
-			.put("App.Evcs.Alpitronic", "") //
+			.put("App.Evcs.Keba", "") //
+			.put("App.Evcs.Mennekes.ReadOnly", "") //
 			.put("App.Evcs.Webasto.Next", "") //
 			.put("App.Evcs.Webasto.Unite", "") //
-			.put("App.Evcs.Cluster", "") //
 			.put("App.Hardware.KMtronic8Channel", "") //
 			.put("App.Heat.HeatPump", "") //
 			.put("App.Heat.CHP", "") //
diff --git a/io.openems.common/src/io/openems/common/oem/OpenemsEdgeOem.java b/io.openems.common/src/io/openems/common/oem/OpenemsEdgeOem.java
index 5f768f896f3..a4a0c63c948 100644
--- a/io.openems.common/src/io/openems/common/oem/OpenemsEdgeOem.java
+++ b/io.openems.common/src/io/openems/common/oem/OpenemsEdgeOem.java
@@ -1,5 +1,7 @@
 package io.openems.common.oem;
 
+import io.openems.common.types.Tuple;
+
 public interface OpenemsEdgeOem {
 
 	// NOTE: Following values are adopted from SunSpec "Common Model"
@@ -133,13 +135,20 @@ public default String getEntsoeToken() {
 		return null;
 	}
 
+	public record OAuthClientRegistration(String clientId, String clientSecret) {
+	}
+
+	public default OAuthClientRegistration getRabotChargeCredentials() {
+		return null;
+	}
+
 	/**
-	 * Gets the OEM Access-Key for Exchangerate.host (used by
-	 * TimeOfUseTariff.ENTSO-E).
+	 * Gets the OEM authorization for Battery.BMW.
 	 * 
 	 * @return the value
 	 */
-	public default String getExchangeRateAccesskey() {
+	public default Tuple getBmwBatteryAuth() {
 		return null;
 	}
+
 }
diff --git a/io.openems.common/src/io/openems/common/test/AbstractComponentConfig.java b/io.openems.common/src/io/openems/common/test/AbstractComponentConfig.java
index af56ff73a01..c8f81ad2338 100644
--- a/io.openems.common/src/io/openems/common/test/AbstractComponentConfig.java
+++ b/io.openems.common/src/io/openems/common/test/AbstractComponentConfig.java
@@ -1,5 +1,7 @@
 package io.openems.common.test;
 
+import static io.openems.common.utils.ReflectionUtils.invokeMethodViaReflection;
+
 import java.lang.annotation.Annotation;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
@@ -110,7 +112,7 @@ public Dictionary getAsProperties()
 			}
 
 			var key = method.getName().replace("_", ".");
-			var value = method.invoke(this);
+			var value = invokeMethodViaReflection(this, method);
 			if (value == null) {
 				throw new IllegalArgumentException("Configuration for [" + key + "] is null");
 			}
diff --git a/io.openems.common/src/io/openems/common/test/TestUtils.java b/io.openems.common/src/io/openems/common/test/TestUtils.java
new file mode 100644
index 00000000000..36ccf762ed7
--- /dev/null
+++ b/io.openems.common/src/io/openems/common/test/TestUtils.java
@@ -0,0 +1,35 @@
+package io.openems.common.test;
+
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.time.Instant;
+
+public class TestUtils {
+
+	private TestUtils() {
+	}
+
+	/**
+	 * Creates a {@link TimeLeapClock} for 1st January 2000 00:00.
+	 * 
+	 * @return the {@link TimeLeapClock}
+	 */
+	public static TimeLeapClock createDummyClock() {
+		return new TimeLeapClock(Instant.ofEpochSecond(1577836800) /* starts at 1. January 2020 00:00:00 */);
+	}
+
+	/**
+	 * Finds and returns an open port.
+	 *
+	 * 

+ * Source https://stackoverflow.com/a/26644672 + * + * @return an open port + * @throws IOException on error + */ + public static int findRandomOpenPortOnAllLocalInterfaces() throws IOException { + try (var socket = new ServerSocket(0);) { + return socket.getLocalPort(); + } + } +} diff --git a/io.openems.common/src/io/openems/common/timedata/CommonTimedataService.java b/io.openems.common/src/io/openems/common/timedata/CommonTimedataService.java index fdebd1f183c..097f7c5aad9 100644 --- a/io.openems.common/src/io/openems/common/timedata/CommonTimedataService.java +++ b/io.openems.common/src/io/openems/common/timedata/CommonTimedataService.java @@ -1,6 +1,5 @@ package io.openems.common.timedata; -import java.io.IOException; import java.time.Period; import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; @@ -10,46 +9,11 @@ import com.google.gson.JsonElement; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.common.exceptions.OpenemsException; import io.openems.common.jsonrpc.request.QueryHistoricTimeseriesDataRequest; -import io.openems.common.jsonrpc.request.QueryHistoricTimeseriesExportXlxsRequest; -import io.openems.common.jsonrpc.response.QueryHistoricTimeseriesExportXlsxResponse; -import io.openems.common.session.Language; import io.openems.common.types.ChannelAddress; public interface CommonTimedataService { - /** - * Handles a {@link QueryHistoricTimeseriesExportXlxsRequest}. Exports historic - * data to an Excel file. - * - * @param edgeId the Edge-ID - * @param request the {@link QueryHistoricTimeseriesExportXlxsRequest} request - * @param language the {@link Language} - * @return the {@link QueryHistoricTimeseriesExportXlsxResponse} - * @throws OpenemsNamedException on error - */ - public default QueryHistoricTimeseriesExportXlsxResponse handleQueryHistoricTimeseriesExportXlxsRequest( - String edgeId, QueryHistoricTimeseriesExportXlxsRequest request, Language language) - throws OpenemsNamedException { - var powerData = this.queryHistoricData(edgeId, request.getFromDate(), request.getToDate(), - QueryHistoricTimeseriesExportXlsxResponse.POWER_CHANNELS, new Resolution(15, ChronoUnit.MINUTES)); - - var energyData = this.queryHistoricEnergy(edgeId, request.getFromDate(), request.getToDate(), - QueryHistoricTimeseriesExportXlsxResponse.ENERGY_CHANNELS); - - if (powerData == null || energyData == null) { - return null; - } - - try { - return new QueryHistoricTimeseriesExportXlsxResponse(request.getId(), edgeId, request.getFromDate(), - request.getToDate(), powerData, energyData, language); - } catch (IOException e) { - throw new OpenemsException("QueryHistoricTimeseriesExportXlxsRequest failed: " + e.getMessage()); - } - } - /** * Calculates the time {@link Resolution} for the period. * diff --git a/io.openems.common/src/io/openems/common/timedata/Timeout.java b/io.openems.common/src/io/openems/common/timedata/Timeout.java new file mode 100644 index 00000000000..bb489594998 --- /dev/null +++ b/io.openems.common/src/io/openems/common/timedata/Timeout.java @@ -0,0 +1,55 @@ +package io.openems.common.timedata; + +import java.time.Clock; +import java.time.Duration; +import java.time.Instant; + +public class Timeout { + + private Instant entryTime = Instant.MIN; + private Duration timeout; + + private Timeout(Duration duration) { + this.timeout = duration; + } + + /** + * Get the {@link Timeout} of seconds. + * + * @param timeout the amount seconds + * @return the {@link Timeout} + */ + public static Timeout ofSeconds(int timeout) { + return new Timeout(Duration.ofSeconds(timeout)); + } + + /** + * Get the {@link Timeout} of minutes. + * + * @param timeout the amount minutes + * @return the {@link Timeout} + */ + public static Timeout ofMinutes(int timeout) { + return new Timeout(Duration.ofMinutes(timeout)); + } + + /** + * Sets the entry time. + * + * @param clock the {@link Clock} + */ + public void start(Clock clock) { + this.entryTime = Instant.now(clock); + } + + /** + * Checks the whether time elapsed. + * + * @param clock the {@link Clock} + * @return true if time is elapsed + */ + public boolean elapsed(Clock clock) { + return Instant.now(clock).isAfter(this.entryTime.plus(this.timeout)); + } + +} diff --git a/io.openems.common/src/io/openems/common/timedata/XlsxExportDetailData.java b/io.openems.common/src/io/openems/common/timedata/XlsxExportDetailData.java new file mode 100644 index 00000000000..94e40fafe63 --- /dev/null +++ b/io.openems.common/src/io/openems/common/timedata/XlsxExportDetailData.java @@ -0,0 +1,36 @@ +package io.openems.common.timedata; + +import java.util.EnumMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import io.openems.common.timedata.XlsxExportDetailData.XlsxExportDataEntry.HistoricTimedataSaveType; +import io.openems.common.types.ChannelAddress; +import io.openems.common.types.CurrencyConfig; + +public record XlsxExportDetailData(// + EnumMap> data, // + CurrencyConfig currency) { + + public Map> getChannelsBySaveType() { + return this.data().values().stream().flatMap(List::stream).collect(Collectors.groupingBy( + XlsxExportDataEntry::type, Collectors.mapping(XlsxExportDataEntry::channel, Collectors.toList()))); + } + + public enum XlsxExportCategory { + CONSUMPTION, PRODUCTION, TIME_OF_USE_TARIFF + } + + public record XlsxExportDataEntry(// + String alias, ChannelAddress channel, // + HistoricTimedataSaveType type // + ) { + + public enum HistoricTimedataSaveType { + POWER, ENERGY + } + + } + +} diff --git a/io.openems.common/src/io/openems/common/timedata/XlsxExportUtil.java b/io.openems.common/src/io/openems/common/timedata/XlsxExportUtil.java new file mode 100644 index 00000000000..0e3a815a464 --- /dev/null +++ b/io.openems.common/src/io/openems/common/timedata/XlsxExportUtil.java @@ -0,0 +1,119 @@ +package io.openems.common.timedata; + +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.List; +import java.util.Set; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.timedata.XlsxExportDetailData.XlsxExportCategory; +import io.openems.common.timedata.XlsxExportDetailData.XlsxExportDataEntry; +import io.openems.common.types.ChannelAddress; +import io.openems.common.types.CurrencyConfig; +import io.openems.common.types.EdgeConfig; +import io.openems.common.types.MeterType; +import io.openems.common.utils.JsonUtils; + +public class XlsxExportUtil { + + /** + * Gathers the detail data for excel export. + * + * @param edge the edge + * @return the currency represented as a CurrencyConfig + * @throws OpenemsNamedException if component isnt found + */ + private static CurrencyConfig getCurrency(EdgeConfig edge) throws OpenemsNamedException { + return edge.getComponent("_meta") // + .flatMap(t -> t.getProperty("currency")) // + .flatMap(t -> JsonUtils.getAsOptionalEnum(CurrencyConfig.class, t)) // + .orElse(CurrencyConfig.EUR); + } + + /** + * Gathers the detail data for excel export. + * + * @param edgeConfig the {@link EdgeConfig} + * @return the {@link XlsxExportDetailData} + * @throws OpenemsNamedException if component is not found + */ + public static XlsxExportDetailData getDetailData(EdgeConfig edgeConfig) throws OpenemsNamedException { + final var enumMap = new EnumMap>(XlsxExportCategory.class); + final var consumption = new ArrayList(); + final var production = new ArrayList(); + final var tou = new ArrayList(); + + enumMap.put(XlsxExportCategory.PRODUCTION, production); + enumMap.put(XlsxExportCategory.CONSUMPTION, consumption); + enumMap.put(XlsxExportCategory.TIME_OF_USE_TARIFF, tou); + + for (var component : edgeConfig.getComponents().values()) { + final var factory = edgeConfig.getFactories().get(component.getFactoryId()); + if (factory == null) { + continue; + } + for (var nature : factory.getNatureIds()) { + // Electricity meter + switch (nature) { + case Natures.METER -> { + final var props = component.getProperties(); + var meterType = JsonUtils.getAsOptionalEnum(MeterType.class, props.get("type")) + .orElse(null); + if (meterType != null) { + var list = switch (meterType) { + case CONSUMPTION_METERED, CONSUMPTION_NOT_METERED, MANAGED_CONSUMPTION_METERED -> consumption; + case PRODUCTION -> production; + case GRID, PRODUCTION_AND_CONSUMPTION -> null; + }; + if (list != null) { + list.add(new XlsxExportDataEntry(component.getAlias(), + new ChannelAddress(component.getId(), "ActivePower"), + XlsxExportDataEntry.HistoricTimedataSaveType.POWER)); + } + continue; + } + + final var activePowerType = getActivePowerType(component.getFactoryId()); + if (activePowerType == null) { + continue; + } + enumMap.get(activePowerType) + .add(new XlsxExportDataEntry(component.getAlias(), + new ChannelAddress(component.getId(), "ActivePower"), + XlsxExportDataEntry.HistoricTimedataSaveType.POWER)); + } + case Natures.TIME_OF_USE_TARIFF -> { + tou.add(new XlsxExportDataEntry(component.getAlias(), new ChannelAddress("_sum", "GridBuyPrice"), + XlsxExportDataEntry.HistoricTimedataSaveType.POWER)); + } + } + } + } + return new XlsxExportDetailData(enumMap, XlsxExportUtil.getCurrency(edgeConfig)); + } + + private static XlsxExportCategory getActivePowerType(String factoryId) { + if (Natures.PRODUCTION_NATURES.contains(factoryId)) { + return XlsxExportCategory.PRODUCTION; + } else if (Natures.CONSUMPTION_NATURES.contains(factoryId)) { + return XlsxExportCategory.CONSUMPTION; + } + return null; + } + + private static final class Natures { + public static final String METER = "io.openems.edge.meter.api.ElectricityMeter"; + public static final String TIME_OF_USE_TARIFF = "io.openems.edge.timeofusetariff.api.TimeOfUseTariff"; + public static final Set PRODUCTION_NATURES = Set.of("Simulator.PvInverter", "Fenecon.Dess.PvMeter", + "Fenecon.Mini.PvMeter", "Kaco.BlueplanetHybrid10.PvInverter", "PvInverter.Cluster", + "PV-Inverter.Fronius", "PV-Inverter.KACO.blueplanet", "PV-Inverter.SMA.SunnyTripower", + "PV-Inverter.Kostal", "PV-Inverter.Solarlog", "Simulator.ProductionMeter.Acting", + "SolarEdge.PV-Inverter"); + + public static final Set CONSUMPTION_NATURES = Set.of("GoodWe.EmergencyPowerMeter", + "Simulator.NRCMeter.Acting", "Evcs.AlpitronicHypercharger", "Evcs.Dezony", "Evcs.Goe.ChargerHome", + "Evcs.HardyBarth", "Evcs.Keba.KeContact", "Evcs.Ocpp.Abl", "Evcs.Ocpp.IesKeywattSingle", + "Evcs.Spelsberg.SMART", "Evcs.Webasto.Next", "Evcs.Webasto.Unite"); + } + +} diff --git a/io.openems.common/src/io/openems/common/timedata/XlsxWorksheetWrapper.java b/io.openems.common/src/io/openems/common/timedata/XlsxWorksheetWrapper.java new file mode 100644 index 00000000000..a39b7742d6c --- /dev/null +++ b/io.openems.common/src/io/openems/common/timedata/XlsxWorksheetWrapper.java @@ -0,0 +1,46 @@ +package io.openems.common.timedata; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Consumer; + +import org.dhatim.fastexcel.StyleSetter; +import org.dhatim.fastexcel.Worksheet; + +public class XlsxWorksheetWrapper { + public record XlsxCellWrapper(int c, int r, StyleSetter style) { + } + + private final Map> cellMap = new HashMap<>(); + private final Worksheet ws; + + public XlsxWorksheetWrapper(Worksheet ws) { + this.ws = ws; + } + + /** + * Gets a CellWrapper if exists; otherwise creates a new one and returns that. + * + * @param r row of the cell + * @param c column of the cell + * @return the XlsxCellWrapper + */ + public XlsxCellWrapper getCellWrapper(int r, int c) { + final var columns = this.cellMap.computeIfAbsent(r, row -> new HashMap<>()); + return columns.computeIfAbsent(c, col -> new XlsxCellWrapper(r, c, this.ws.style(r, c))); + } + + public void setAll() { + this.cellMap.values().stream().flatMap(map -> map.values().stream()).forEach(val -> val.style().set()); + } + + public void setForRange(int r1, int c1, int r2, int c2, Consumer styleSetterFunc) { + for (int row = r1; row <= r2; row++) { + for (int col = c1; col <= c2; col++) { + var cell = this.getCellWrapper(row, col); + styleSetterFunc.accept(cell); + } + } + } + +} \ No newline at end of file diff --git a/io.openems.common/src/io/openems/common/types/CurrencyConfig.java b/io.openems.common/src/io/openems/common/types/CurrencyConfig.java new file mode 100644 index 00000000000..8c401aefda3 --- /dev/null +++ b/io.openems.common/src/io/openems/common/types/CurrencyConfig.java @@ -0,0 +1,49 @@ +package io.openems.common.types; + +import java.util.Currency; + +/** + * The {@link ChannelId#CURRENCY} mandates the selection of the 'currency' + * configuration property of this specific type. Subsequently, this selected + * property is transformed into the corresponding {@link Currency} type before + * being written through {@link Meta#_setCurrency(Currency)}. + */ +public enum CurrencyConfig { + /** + * Euro. + */ + EUR("€", "Cent", 100f), + /** + * Swedish Krona. + */ + SEK("kr", "Öre", 100f), + /** + * Swiss Francs. + */ + CHF("Fr", "Rappen", 100f); + + private final String symbol; + + private final String underPart; + + private final float ratio; + + private CurrencyConfig(String symbol, String underPart, float ratio) { + this.symbol = symbol; + this.underPart = underPart; + this.ratio = ratio; + } + + public String getSymbol() { + return this.symbol; + } + + public String getUnderPart() { + return this.underPart; + } + + public float getRatio() { + return this.ratio; + } + +} diff --git a/io.openems.common/src/io/openems/common/types/EdgeConfig.java b/io.openems.common/src/io/openems/common/types/EdgeConfig.java index 1a7ad131b23..8014e47cae5 100644 --- a/io.openems.common/src/io/openems/common/types/EdgeConfig.java +++ b/io.openems.common/src/io/openems/common/types/EdgeConfig.java @@ -588,8 +588,7 @@ public static Component fromJson(String componentId, JsonElement json) throws Op var jPropertiesOpt = JsonUtils.getAsOptionalJsonObject(json, "properties"); if (jPropertiesOpt.isPresent()) { for (Entry entry : jPropertiesOpt.get().entrySet()) { - if (!ignorePropertyKey(entry.getKey()) - && !ignoreComponentPropertyKey(componentId, entry.getKey())) { + if (!ignorePropertyKey(entry.getKey())) { properties.put(entry.getKey(), entry.getValue()); } } @@ -1171,9 +1170,9 @@ public TreeMap getFactories() { } /** - * Builds the {@link ActualEdgeConfig}. + * Builds the ActualEdgeConfig. * - * @return {@link ActualEdgeConfig} + * @return ActualEdgeConfig */ public ActualEdgeConfig build() { return new ActualEdgeConfig(ImmutableSortedMap.copyOf(this.getComponents()), @@ -1191,16 +1190,16 @@ public EdgeConfig buildEdgeConfig() { } /** - * Creates an empty {@link ActualEdgeConfig}. + * Creates an empty ActualEdgeConfig. * - * @return {@link ActualEdgeConfig} + * @return ActualEdgeConfig */ public static ActualEdgeConfig empty() { return ActualEdgeConfig.create().build(); } /** - * Create a {@link ActualEdgeConfig.Builder} builder. + * Create a ActualEdgeConfig builder. * * @return a {@link Builder} */ @@ -1248,9 +1247,9 @@ public static EdgeConfig fromJson(JsonObject json) { private volatile JsonObject _json = null; /** - * Build from {@link ActualEdgeConfig}. + * Build from ActualEdgeConfig. * - * @param actual the {@link ActualEdgeConfig} + * @param actual the ActualEdgeConfig */ private EdgeConfig(ActualEdgeConfig actual) { this._actual = actual; @@ -1261,10 +1260,10 @@ private EdgeConfig(JsonObject json) { } /** - * Gets the {@link ActualEdgeConfig}. Either by parsing it from {@link #json} or - * by returning from cache. + * Gets the ActualEdgeConfig. Either by parsing it from {@link #json} or by + * returning from cache. * - * @return {@link ActualEdgeConfig}; empty on JSON parse error + * @return ActualEdgeConfig; empty on JSON parse error */ private synchronized ActualEdgeConfig getActual() { if (this._actual != null) { @@ -1465,25 +1464,4 @@ public static boolean ignorePropertyKey(String key) { default -> false; }; } - - /** - * Internal Method to decide whether a configuration property should be ignored. - * - * @param componentId the Component-ID - * @param key the property key - * @return true if it should get ignored - */ - public static boolean ignoreComponentPropertyKey(String componentId, String key) { - return switch (componentId) { - // Filter for _sum component - case "_sum" -> switch (key) { - case "productionMaxActivePower", "consumptionMaxActivePower", "gridMinActivePower", "gridMaxActivePower" -> - true; - - default -> false; - }; - - default -> false; - }; - } } diff --git a/io.openems.edge.meter.api/src/io/openems/edge/meter/api/MeterType.java b/io.openems.common/src/io/openems/common/types/MeterType.java similarity index 69% rename from io.openems.edge.meter.api/src/io/openems/edge/meter/api/MeterType.java rename to io.openems.common/src/io/openems/common/types/MeterType.java index 7809e7186d3..bb99d4bb8ae 100644 --- a/io.openems.edge.meter.api/src/io/openems/edge/meter/api/MeterType.java +++ b/io.openems.common/src/io/openems/common/types/MeterType.java @@ -1,14 +1,17 @@ -package io.openems.edge.meter.api; +package io.openems.common.types; /** - * Defines the type of the Meter. + * Defines the type of an ElectricityMeter. + * + *

+ * See "io.openems.edge.meter.api" for details. */ public enum MeterType { /** * Defines a Grid-Meter, i.e. a meter that is measuring at the grid connection * point (German: "Netzanschlusspunkt") */ - GRID, // + GRID, /** * Defines a Production-Meter, i.e. a meter that is measuring an electric * producer like a photovoltaics installation @@ -21,8 +24,12 @@ public enum MeterType { */ PRODUCTION_AND_CONSUMPTION, /** - * Defines a Consumption-Meter that is metered, i.e. a meter that is measuring - * an electric consumer like a heating-element or electric car. + * Defines a Consumption-Meter that metered, i.e. a meter that is measuring an + * electric consumer like a heating-element or electric car. + * + *

+ * Select this {@link MeterType} if the device is not actively managed by + * OpenEMS - see {@link #MANAGED_CONSUMPTION_METERED} otherwise. * *

* Note: Consumption is generally calculated using the data from Grid-Meter, @@ -31,6 +38,11 @@ public enum MeterType { * expected to be already measured by the Grid-Meter. */ CONSUMPTION_METERED, + /** + * Defines a Consumption-Meter that is actively managed by OpenEMS and metered + * (See {@link #CONSUMPTION_METERED}). + */ + MANAGED_CONSUMPTION_METERED, /** * Defines a Consumption-Meter that is NOT metered, i.e. a meter that is * measuring an electric consumer like a heating-element or electric car. diff --git a/io.openems.common/src/io/openems/common/types/Tuple.java b/io.openems.common/src/io/openems/common/types/Tuple.java new file mode 100644 index 00000000000..538fe9f0635 --- /dev/null +++ b/io.openems.common/src/io/openems/common/types/Tuple.java @@ -0,0 +1,18 @@ +package io.openems.common.types; + +public record Tuple(A a, B b) { + + /** + * Factory for a {@link Tuple}. + * + * @param Type of a + * @param Type of b + * @param a value a + * @param b value b + * @return a new Tuple + */ + public static Tuple of(A a, B b) { + return new Tuple<>(a, b); + } + +} diff --git a/io.openems.common/src/io/openems/common/utils/JsonUtils.java b/io.openems.common/src/io/openems/common/utils/JsonUtils.java index 591a960f80f..1ead2112db3 100644 --- a/io.openems.common/src/io/openems/common/utils/JsonUtils.java +++ b/io.openems.common/src/io/openems/common/utils/JsonUtils.java @@ -3,6 +3,7 @@ import static io.openems.common.utils.EnumUtils.toEnum; import java.net.Inet4Address; +import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; @@ -37,7 +38,11 @@ import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.types.OpenemsType; -public class JsonUtils { +public final class JsonUtils { + + private JsonUtils() { + } + /** * Provide a easy way to generate a JsonArray from a list using the given * convert function to add each element. @@ -291,6 +296,24 @@ public JsonObjectBuilder addProperty(String property, ZonedDateTime value) { return this; } + /** + * Add a {@link LocalDateTime} value to the {@link JsonObject}. + * + *

+ * The value gets added in the format of + * {@link DateTimeFormatter#ISO_LOCAL_DATE_TIME}. + * + * @param property the key + * @param value the value + * @return the {@link JsonObjectBuilder} + */ + public JsonObjectBuilder addProperty(String property, LocalDateTime value) { + if (value != null) { + this.j.addProperty(property, value.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); + } + return this; + } + /** * Add a {@link Boolean} value to the {@link JsonObject}. * @@ -1723,6 +1746,32 @@ public static ZonedDateTime getAsZonedDateWithZeroTime(JsonElement element, Stri } } + /** + * Takes a JSON in the form '2020-01-01T00:00:00' and converts it to a + * {@link LocalDateTime}. + * + * @param jElement the {@link JsonElement} + * @param memberName the name of the member of the JsonObject + * @return the {@link ZonedDateTime} + */ + public static LocalDateTime getAsLocalDateTime(JsonElement jElement, String memberName) + throws OpenemsNamedException { + return DateUtils.parseLocalDateTimeOrError(toString(toPrimitive(toSubElement(jElement, memberName)))); + } + + /** + * Takes a JSON in the form '2020-01-01T00:00:00Z' and converts it to a + * {@link ZonedDateTime}. + * + * @param jElement the {@link JsonElement} + * @param memberName the name of the member of the JsonObject + * @return the {@link ZonedDateTime} + */ + public static ZonedDateTime getAsZonedDateTime(JsonElement jElement, String memberName) + throws OpenemsNamedException { + return DateUtils.parseZonedDateTimeOrError(toString(toPrimitive(toSubElement(jElement, memberName)))); + } + /** * Takes a JSON in the form 'YYYY-MM-DD' and converts it to a * {@link ZonedDateTime}. diff --git a/io.openems.common/src/io/openems/common/utils/ReflectionUtils.java b/io.openems.common/src/io/openems/common/utils/ReflectionUtils.java index fc88d47a09c..ef924855595 100644 --- a/io.openems.common/src/io/openems/common/utils/ReflectionUtils.java +++ b/io.openems.common/src/io/openems/common/utils/ReflectionUtils.java @@ -1,21 +1,134 @@ package io.openems.common.utils; -import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +import io.openems.common.function.ThrowingRunnable; +import io.openems.common.function.ThrowingSupplier; public class ReflectionUtils { + public static class ReflectionException extends RuntimeException { + private static final long serialVersionUID = -8001364348945297741L; + + protected static ReflectionException from(Exception e) { + return new ReflectionException(e.getClass().getSimpleName() + ": " + e.getMessage()); + } + + public ReflectionException(String message) { + super(message); + } + } + private ReflectionUtils() { // no instance needed } + protected static void callGuarded(ThrowingRunnable runnable) throws ReflectionException { + try { + runnable.run(); + } catch (Exception e) { + throw ReflectionException.from(e); + } + } + + protected static T callGuarded(ThrowingSupplier supplier) throws ReflectionException { + try { + return supplier.get(); + } catch (Exception e) { + throw ReflectionException.from(e); + } + } + + /** + * Sets the value of a Field via Java Reflection. + * + * @param object the target object + * @param memberName the name the declared field + * @param value the value to be set + * @throws Exception on error + */ + public static void setAttributeViaReflection(Object object, String memberName, Object value) + throws ReflectionException { + var field = getField(object.getClass(), memberName); + callGuarded(() -> field.set(object, value)); + } + + /** + * Sets the value of a static Field via Java Reflection. + * + * @param clazz the {@link Class} + * @param memberName the name the declared field + * @param value the value to be set + * @throws Exception on error + */ + public static void setStaticAttributeViaReflection(Class clazz, String memberName, Object value) + throws ReflectionException { + var field = getField(clazz, memberName); + callGuarded(() -> field.set(null, value)); + } + + /** + * Gets the value of a Field via Java Reflection. + * + * @param the type of the value + * @param object the target object + * @param memberName the name the declared field + * @return the value + * @throws Exception on error + */ @SuppressWarnings("unchecked") - public static boolean setAttribute(Class clazz, T object, String memberName, Object value) - throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { + public static T getValueViaReflection(Object object, String memberName) throws ReflectionException { + var field = getField(object.getClass(), memberName); + return (T) callGuarded(() -> field.get(object)); + } + + /** + * Invokes a {@link Method} that takes no arguments via Java Reflection. + * + * @param the type of the result + * @param object the target object + * @param memberName the name of the method + * @return the result of the method + * @throws Exception on error + */ + public static T invokeMethodWithoutArgumentsViaReflection(Object object, String memberName) + throws ReflectionException { + var method = callGuarded(() -> object.getClass().getDeclaredMethod(memberName)); + return invokeMethodViaReflection(object, method); + } + + /** + * Invokes a {@link Method} via Java Reflection. + * + * @param the type of the result + * @param object the target object + * @param method the {@link Method} + * @param args the arguments to be set + * @return the result of the method + * @throws Exception on error + */ + @SuppressWarnings("unchecked") + public static T invokeMethodViaReflection(Object object, Method method, Object... args) + throws ReflectionException { + method.setAccessible(true); + return (T) callGuarded(() -> method.invoke(object, args)); + } + + /** + * Gets the {@link Class#getDeclaredField(String)} in the given {@link Class} or + * any of its superclasses. + * + * @param clazz the given {@link Class} + * @param memberName the name of the declared field + * @return a {@link Field} + * @throws ReflectionException if there is no such field + */ + public static Field getField(Class clazz, String memberName) throws ReflectionException { try { var field = clazz.getDeclaredField(memberName); field.setAccessible(true); - field.set(object, value); - return true; + return field; } catch (NoSuchFieldException e) { // Ignore. } @@ -23,9 +136,8 @@ public static boolean setAttribute(Class clazz, T object, Strin // classes. Class parent = clazz.getSuperclass(); if (parent == null) { - return false; // reached 'java.lang.Object' + throw new ReflectionException("Reached java.lang.Object"); } - return setAttribute((Class) parent, object, memberName, value); + return getField(parent, memberName); } - } diff --git a/io.openems.common/src/io/openems/common/utils/XmlUtils.java b/io.openems.common/src/io/openems/common/utils/XmlUtils.java index b921670e906..0a81ca749b8 100644 --- a/io.openems.common/src/io/openems/common/utils/XmlUtils.java +++ b/io.openems.common/src/io/openems/common/utils/XmlUtils.java @@ -1,5 +1,7 @@ package io.openems.common.utils; +import java.io.IOException; +import java.io.StringReader; import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -7,9 +9,15 @@ import java.util.stream.IntStream; import java.util.stream.Stream; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + import org.w3c.dom.DOMException; +import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; import io.openems.common.exceptions.OpenemsError; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; @@ -254,4 +262,26 @@ public static Stream stream(final Node node) { var childNodes = node.getChildNodes(); return IntStream.range(0, childNodes.getLength()).boxed().map(childNodes::item); } + + /** + * Parses the provided XML string and returns the root {@link Element} of the + * XML document. + * + * @param xml the XML string to parse + * @return the root {@link Element} of the parsed XML document + * @throws ParserConfigurationException if a DocumentBuilder cannot be created + * which satisfies the configuration + * requested + * @throws SAXException if any parse errors occur while + * processing the XML + * @throws IOException if an I/O error occurs during parsing + */ + public static Element getXmlRootDocument(String xml) + throws ParserConfigurationException, SAXException, IOException { + var dbFactory = DocumentBuilderFactory.newInstance(); + var dBuilder = dbFactory.newDocumentBuilder(); + var is = new InputSource(new StringReader(xml)); + var doc = dBuilder.parse(is); + return doc.getDocumentElement(); + } } diff --git a/io.openems.common/src/io/openems/common/websocket/AbstractWebsocket.java b/io.openems.common/src/io/openems/common/websocket/AbstractWebsocket.java index c76b34dd560..f313c175896 100644 --- a/io.openems.common/src/io/openems/common/websocket/AbstractWebsocket.java +++ b/io.openems.common/src/io/openems/common/websocket/AbstractWebsocket.java @@ -128,11 +128,17 @@ protected final boolean sendMessage(WebSocket ws, JsonrpcMessage message) { } private void sendMessageFailedLog(WebSocket ws, JsonrpcMessage message) { - this.logWarn(this.log, new StringBuilder() // - .append("[").append(generateWsDataString(ws)) // - .append("] Unable to send message: Connection is closed. ") // - .append(toShortString(simplifyJsonrpcMessage(message), 100)) // - .toString()); + final var b = new StringBuilder(); + + var wsDataString = generateWsDataString(ws); + if (!wsDataString.isEmpty()) { + b.append("[").append(generateWsDataString(ws)).append("] "); + } + + this.logWarn(this.log, // + b.append("Unable to send message: Connection is closed. ") // + .append(toShortString(simplifyJsonrpcMessage(message), 200)) // + .toString()); } /** diff --git a/io.openems.common/src/io/openems/common/websocket/DummyWebsocketServer.java b/io.openems.common/src/io/openems/common/websocket/DummyWebsocketServer.java index b815deeb71c..f4b2b6cf916 100644 --- a/io.openems.common/src/io/openems/common/websocket/DummyWebsocketServer.java +++ b/io.openems.common/src/io/openems/common/websocket/DummyWebsocketServer.java @@ -93,12 +93,7 @@ private DummyWebsocketServer(DummyWebsocketServer.Builder builder) { @Override protected WsData createWsData(WebSocket ws) { - return new WsData(ws) { - @Override - public String toString() { - return "DummyWebsocketServer.WsData []"; - } - }; + return new WsData(ws); } @Override diff --git a/io.openems.common/src/io/openems/common/websocket/MyDraft6455.java b/io.openems.common/src/io/openems/common/websocket/MyDraft6455.java index 3f549c9cb49..4c4968114c6 100644 --- a/io.openems.common/src/io/openems/common/websocket/MyDraft6455.java +++ b/io.openems.common/src/io/openems/common/websocket/MyDraft6455.java @@ -23,6 +23,7 @@ import org.java_websocket.enums.Opcode; import org.java_websocket.enums.ReadyState; import org.java_websocket.enums.Role; +import org.java_websocket.exceptions.IncompleteException; import org.java_websocket.exceptions.InvalidDataException; import org.java_websocket.exceptions.InvalidFrameException; import org.java_websocket.exceptions.InvalidHandshakeException; diff --git a/io.openems.common/src/io/openems/common/websocket/WebsocketUtils.java b/io.openems.common/src/io/openems/common/websocket/WebsocketUtils.java index e9f51ae5b04..cb80dc12b69 100644 --- a/io.openems.common/src/io/openems/common/websocket/WebsocketUtils.java +++ b/io.openems.common/src/io/openems/common/websocket/WebsocketUtils.java @@ -53,11 +53,11 @@ public static String parseRemoteIdentifier(WebSocket ws, Handshakedata handshake } /** - * Gets the toString() content of the WsData attachment of the WebSocket; or + * Gets the toLogString() content of the WsData attachment of the WebSocket; or * empty string if not available. * * @param ws the WebSocket - * @return the {@link WsData#toString()} content + * @return the {@link WsData#toLogString()} content */ public static String generateWsDataString(WebSocket ws) { if (ws == null) { @@ -67,6 +67,10 @@ public static String generateWsDataString(WebSocket ws) { if (wsData == null) { return ""; } - return wsData.toString(); + var logString = wsData.toLogString(); + if (logString == null) { + return ""; + } + return logString; } } diff --git a/io.openems.common/src/io/openems/common/websocket/WsData.java b/io.openems.common/src/io/openems/common/websocket/WsData.java index b43aedad7f7..852b4290125 100644 --- a/io.openems.common/src/io/openems/common/websocket/WsData.java +++ b/io.openems.common/src/io/openems/common/websocket/WsData.java @@ -21,14 +21,14 @@ * Objects of this class are used to store additional data with websocket * connections of WebSocketClient and WebSocketServer. */ -public abstract class WsData { +public class WsData { /** * Holds the WebSocket. */ private final WebSocket websocket; - protected WsData(WebSocket ws) { + public WsData(WebSocket ws) { this.websocket = ws; } @@ -138,10 +138,11 @@ public void handleJsonrpcResponse(JsonrpcResponse response) throws OpenemsNamedE } /** - * Provides a specific toString method. + * Provides a specific log string. * * @return a specific string for this instance */ - @Override - public abstract String toString(); + protected String toLogString() { + return ""; + } } diff --git a/io.openems.common/src/io/openems/common/worker/AbstractWorker.java b/io.openems.common/src/io/openems/common/worker/AbstractWorker.java index 29b371fd99b..c78b265fb2b 100644 --- a/io.openems.common/src/io/openems/common/worker/AbstractWorker.java +++ b/io.openems.common/src/io/openems/common/worker/AbstractWorker.java @@ -65,9 +65,7 @@ public void activate(String name) { * false */ public void modified(String name, boolean initiallyTriggerNextRun) { - if (!this.thread.isAlive() && !this.thread.isInterrupted() && !this.isStopped.get()) { - this.startWorker(name, initiallyTriggerNextRun); - } + this.startWorker(name, initiallyTriggerNextRun); } /** @@ -79,12 +77,13 @@ public void modified(String name) { this.modified(name, true); } - private void startWorker(String name, boolean autoTriggerNextRun) { + private synchronized void startWorker(String name, boolean autoTriggerNextRun) { if (name != null) { this.thread.setName(name); } - this.thread.start(); - + if (!this.thread.isAlive() && !this.thread.isInterrupted() && !this.isStopped.get()) { + this.thread.start(); + } if (autoTriggerNextRun) { this.triggerNextRun(); } diff --git a/io.openems.common/test/io/openems/common/jscalendar/JSCalendarTest.java b/io.openems.common/test/io/openems/common/jscalendar/JSCalendarTest.java new file mode 100644 index 00000000000..73061feb84a --- /dev/null +++ b/io.openems.common/test/io/openems/common/jscalendar/JSCalendarTest.java @@ -0,0 +1,136 @@ +package io.openems.common.jscalendar; + +import static io.openems.common.jscalendar.JSCalendar.RecurrenceFrequency.WEEKLY; +import static io.openems.common.test.TestUtils.createDummyClock; +import static io.openems.common.utils.JsonUtils.buildJsonObject; +import static io.openems.common.utils.JsonUtils.prettyToString; +import static io.openems.common.utils.UuidUtils.getNilUuid; +import static java.time.DayOfWeek.FRIDAY; +import static java.time.DayOfWeek.MONDAY; +import static java.time.DayOfWeek.SATURDAY; +import static java.time.DayOfWeek.SUNDAY; +import static java.time.DayOfWeek.THURSDAY; +import static java.time.DayOfWeek.TUESDAY; +import static java.time.DayOfWeek.WEDNESDAY; +import static java.util.function.Function.identity; +import static org.junit.Assert.assertEquals; + +import java.time.ZonedDateTime; + +import org.junit.Test; + +import com.google.gson.JsonObject; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.utils.JsonUtils; + +//CHECKSTYLE:OFF +public class JSCalendarTest { + // CHECKSTYLE:ON + + @Test + public void testWeekday() throws OpenemsNamedException { + var clock = createDummyClock(); + var sut = new JSCalendar.Task.Builder(getNilUuid(), ZonedDateTime.now(clock)) // + .setStart("2024-06-17T07:00:00") // + .addRecurrenceRule(b -> b // + .setFrequency(WEEKLY) // + .addByDay(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY)) // + .setPayload(buildJsonObject() // + .addProperty("sessionEnergy", 10000) // + .build()) // + .build(); + assertEquals(""" + { + "@type": "Task", + "uid": "00000000-0000-0000-0000-000000000000", + "updated": "2020-01-01T00:00:00Z", + "start": "2024-06-17T07:00:00", + "recurrenceRules": [ + { + "frequency": "weekly", + "byDay": [ + "mo", + "tu", + "we", + "th", + "fr" + ] + } + ], + "payload": { + "sessionEnergy": 10000 + } + }""", prettyToString(sut.toJson(identity()))); + + var next = sut.getNextOccurence(ZonedDateTime.now(clock)); + assertEquals("2024-06-17T07:00Z", next.toString()); + next = sut.getNextOccurence(next.plusSeconds(1)); + assertEquals("2024-06-18T07:00Z", next.toString()); + next = sut.getNextOccurence(next.plusSeconds(1)); + assertEquals("2024-06-19T07:00Z", next.toString()); + next = sut.getNextOccurence(next.plusSeconds(1)); + assertEquals("2024-06-20T07:00Z", next.toString()); + next = sut.getNextOccurence(next.plusSeconds(1)); + assertEquals("2024-06-21T07:00Z", next.toString()); + next = sut.getNextOccurence(next.plusSeconds(1)); // next week + assertEquals("2024-06-24T07:00Z", next.toString()); + next = sut.getNextOccurence(next); + assertEquals("2024-06-24T07:00Z", next.toString()); // same + + // Parse JSON + var fromJson = JSCalendar.Task.fromJson(JsonUtils.buildJsonArray() // + .add(sut.toJson(identity())) // + .build(), j -> j); + assertEquals(sut.toJson(identity()), fromJson.get(0).toJson(identity())); + } + + @Test + public void testWeekend() throws OpenemsNamedException { + var clock = createDummyClock(); + var sut = new JSCalendar.Task.Builder(getNilUuid(), ZonedDateTime.now(clock)) // + .setStart("2024-06-17T00:00:00") // + .addRecurrenceRule(b -> b // + .setFrequency(WEEKLY) // + .addByDay(SATURDAY, SUNDAY)) // + .setPayload(buildJsonObject() // + .addProperty("sessionEnergy", 10001) // + .build()) // + .build(); + assertEquals(""" + { + "@type": "Task", + "uid": "00000000-0000-0000-0000-000000000000", + "updated": "2020-01-01T00:00:00Z", + "start": "2024-06-17T00:00:00", + "recurrenceRules": [ + { + "frequency": "weekly", + "byDay": [ + "sa", + "su" + ] + } + ], + "payload": { + "sessionEnergy": 10001 + } + }""", prettyToString(sut.toJson(identity()))); + + var next = sut.getNextOccurence(ZonedDateTime.now(clock)); + assertEquals("2024-06-22T00:00Z", next.toString()); + next = sut.getNextOccurence(next.plusSeconds(1)); + assertEquals("2024-06-23T00:00Z", next.toString()); + next = sut.getNextOccurence(next.plusSeconds(1)); + assertEquals("2024-06-29T00:00Z", next.toString()); + next = sut.getNextOccurence(next.plusSeconds(1)); + assertEquals("2024-06-30T00:00Z", next.toString()); + next = sut.getNextOccurence(next.plusSeconds(1)); + assertEquals("2024-07-06T00:00Z", next.toString()); + + // Parse JSON + var fromJson = JSCalendar.Task.fromJson(sut.toJson(identity()), identity()); + assertEquals(sut.toJson(identity()), fromJson.toJson(identity())); + } + +} diff --git a/io.openems.common/test/io/openems/common/jsonrpc/response/CreateXlxsTest.java b/io.openems.common/test/io/openems/common/jsonrpc/response/CreateXlxsTest.java new file mode 100644 index 00000000000..f855b102bee --- /dev/null +++ b/io.openems.common/test/io/openems/common/jsonrpc/response/CreateXlxsTest.java @@ -0,0 +1,248 @@ +package io.openems.common.jsonrpc.response; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.Base64; +import java.util.EnumMap; +import java.util.List; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.UUID; +import java.util.function.Consumer; + +import com.google.common.collect.ImmutableSortedMap; +import com.google.gson.JsonElement; +import com.google.gson.JsonPrimitive; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.jsonrpc.response.QueryHistoricTimeseriesExportXlsxResponse.Channel; +import io.openems.common.session.Language; +import io.openems.common.timedata.XlsxExportDetailData; +import io.openems.common.timedata.XlsxExportDetailData.XlsxExportCategory; +import io.openems.common.timedata.XlsxExportDetailData.XlsxExportDataEntry; +import io.openems.common.timedata.XlsxExportDetailData.XlsxExportDataEntry.HistoricTimedataSaveType; +import io.openems.common.types.ChannelAddress; +import io.openems.common.types.CurrencyConfig; + +public class CreateXlxsTest { + + private static SortedMap getMockedEnergyData() { + return ImmutableSortedMap.naturalOrder() // + .put(Channel.GRID_BUY_ACTIVE_ENERGY, new JsonPrimitive(500)) // + .put(Channel.GRID_SELL_ACTIVE_ENERGY, new JsonPrimitive(0)) // + .put(Channel.PRODUCTION_ACTIVE_ENERGY, new JsonPrimitive(300)) // + .put(Channel.CONSUMPTION_ACTIVE_ENERGY, new JsonPrimitive(700)) // + .put(Channel.ESS_DC_CHARGE_ENERGY, new JsonPrimitive(100)) // + .put(Channel.ESS_DC_DISCHARGE_ENERGY, new JsonPrimitive(80)) // + .build(); + } + + private static SortedMap> getMockedPowerData() { + + SortedMap values = new TreeMap<>(); + values.put(Channel.GRID_ACTIVE_POWER, new JsonPrimitive(50)); + values.put(Channel.PRODUCTION_ACTIVE_POWER, new JsonPrimitive(50)); + values.put(Channel.CONSUMPTION_ACTIVE_POWER, new JsonPrimitive(50)); + values.put(Channel.ESS_DISCHARGE_POWER, new JsonPrimitive(50)); + values.put(Channel.ESS_SOC, new JsonPrimitive(50)); + values.put(new ChannelAddress("meter0", "ActivePower"), new JsonPrimitive(100)); + values.put(new ChannelAddress("meter1", "ActivePower"), new JsonPrimitive(412)); + values.put(new ChannelAddress("evcs0", "ChargePower"), new JsonPrimitive(75)); + values.put(new ChannelAddress("meter2", "ActivePower"), new JsonPrimitive(10)); + values.put(new ChannelAddress("_sum", "GridBuyPower"), new JsonPrimitive(292.5)); + + return ImmutableSortedMap.>naturalOrder() + .put(ZonedDateTime.of(2020, 07, 01, 0, 15, 0, 0, ZoneId.systemDefault()).plusMinutes(15), values) // + .put(ZonedDateTime.of(2020, 07, 01, 0, 30, 0, 0, ZoneId.systemDefault()).plusMinutes(15), values) // + .put(ZonedDateTime.of(2020, 07, 01, 0, 45, 0, 0, ZoneId.systemDefault()).plusMinutes(15), values) // + .put(ZonedDateTime.of(2020, 07, 01, 1, 0, 0, 0, ZoneId.systemDefault()).plusMinutes(15), values) // + .put(ZonedDateTime.of(2020, 07, 01, 1, 15, 0, 0, ZoneId.systemDefault()).plusMinutes(15), values) // + .put(ZonedDateTime.of(2020, 07, 01, 1, 30, 0, 0, ZoneId.systemDefault()).plusMinutes(15), values) // + .build(); + } + + private static XlsxExportDetailData getMockedDetailData() { + final var enumMap = new EnumMap>(XlsxExportCategory.class); + final var consumption = new ArrayList(); + final var production = new ArrayList(); + final var tou = new ArrayList(); + + enumMap.put(XlsxExportCategory.PRODUCTION, production); + enumMap.put(XlsxExportCategory.CONSUMPTION, consumption); + enumMap.put(XlsxExportCategory.TIME_OF_USE_TARIFF, tou); + + production.add(new XlsxExportDataEntry("PV-Dach", new ChannelAddress("meter0", "ActivePower"), + HistoricTimedataSaveType.POWER)); + production.add(new XlsxExportDataEntry("PV-Alm", new ChannelAddress("meter1", "ActivePower"), + HistoricTimedataSaveType.POWER)); + consumption.add(new XlsxExportDataEntry("Consumption Meter", new ChannelAddress("meter2", "ActivePower"), + HistoricTimedataSaveType.POWER)); + consumption.add(new XlsxExportDataEntry("Wallbox Garage", new ChannelAddress("evcs0", "ChargePower"), + HistoricTimedataSaveType.POWER)); + tou.add(new XlsxExportDataEntry("Dynamisch Gut", new ChannelAddress("_sum", "GridBuyPower"), + HistoricTimedataSaveType.POWER)); + + return new XlsxExportDetailData(enumMap, CurrencyConfig.EUR); + } + + /** + * Main Method for creating a excel export with mocked data. + * + * @param args not used + * @throws IOException if file cant be written + * @throws OpenemsNamedException requests fails + */ + public static void main(String[] args) throws IOException, OpenemsNamedException { + createFullXlsx(); + createHalfXlsx(); + createConsumptionOnlyXlsx(); + createProductionOnlyXlsx(); + createTouOnlyXlsx(); + createProductionAndTouXlsx(); + createConsumptionAndTouXlsx(); + createnSingleOfAllXlsx(); + } + + private static void createFullXlsx() throws IOException, OpenemsNamedException { + var fromDate = ZonedDateTime.of(2020, 07, 01, 0, 0, 0, 0, ZoneId.systemDefault()); + var toDate = ZonedDateTime.of(2020, 07, 02, 0, 0, 0, 0, ZoneId.systemDefault()); + + var powerData = CreateXlxsTest.getMockedPowerData(); + var energyData = CreateXlxsTest.getMockedEnergyData(); + var detailData = CreateXlxsTest.getMockedDetailData(); + + final var request = new QueryHistoricTimeseriesExportXlsxResponse(UUID.randomUUID(), "edge0", fromDate, toDate, + powerData, energyData, Language.EN, detailData); + + var payload = request.getPayload(); + + byte[] excelData = Base64.getDecoder().decode(payload); + + String filePath = ".\\..\\build\\fullTestPrint.xlsx"; + + try (FileOutputStream fos = new FileOutputStream(filePath)) { + fos.write(excelData); + System.out.println("Testfile created under: " + filePath); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private static void createHalfXlsx() throws IOException, OpenemsNamedException { + createTestPrint(".\\..\\build\\emptyTestPrint.xlsx", null, null, null); + } + + private static void createTestPrint(String filePath, Consumer> consProd, + Consumer> consCons, Consumer> consTou) + throws IOException, OpenemsNamedException { + final var enumMap = new EnumMap>(XlsxExportCategory.class); + final var consumption = new ArrayList(); + final var production = new ArrayList(); + final var tou = new ArrayList(); + + if (consProd != null) { + consProd.accept(production); + } + + if (consCons != null) { + consCons.accept(consumption); + } + + if (consTou != null) { + consTou.accept(tou); + } + + enumMap.put(XlsxExportCategory.PRODUCTION, production); + enumMap.put(XlsxExportCategory.CONSUMPTION, consumption); + enumMap.put(XlsxExportCategory.TIME_OF_USE_TARIFF, tou); + + var detailData = new XlsxExportDetailData(enumMap, CurrencyConfig.EUR); + + var fromDate = ZonedDateTime.of(2020, 07, 01, 0, 0, 0, 0, ZoneId.systemDefault()); + var toDate = ZonedDateTime.of(2020, 07, 02, 0, 0, 0, 0, ZoneId.systemDefault()); + + var powerData = CreateXlxsTest.getMockedPowerData(); + var energyData = CreateXlxsTest.getMockedEnergyData(); + + final var request = new QueryHistoricTimeseriesExportXlsxResponse(UUID.randomUUID(), "edge0", fromDate, toDate, + powerData, energyData, Language.EN, detailData); + + var payload = request.getPayload(); + + byte[] excelData = Base64.getDecoder().decode(payload); + + try (FileOutputStream fos = new FileOutputStream(filePath)) { + fos.write(excelData); + System.out.println("Testfile created under: " + filePath); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private static void createProductionOnlyXlsx() throws IOException, OpenemsNamedException { + createTestPrint(".\\..\\build\\prodTestPrint.xlsx", production -> { + production.add(new XlsxExportDataEntry("PV-Dach", new ChannelAddress("meter0", "ActivePower"), + HistoricTimedataSaveType.POWER)); + production.add(new XlsxExportDataEntry("PV-Alm", new ChannelAddress("meter1", "ActivePower"), + HistoricTimedataSaveType.POWER)); + }, null, null); + } + + private static void createConsumptionOnlyXlsx() throws IOException, OpenemsNamedException { + createTestPrint(".\\..\\build\\consTestPrint.xlsx", null, consumption -> { + consumption.add(new XlsxExportDataEntry("Consumption Meter", new ChannelAddress("meter2", "ActivePower"), + HistoricTimedataSaveType.POWER)); + consumption.add(new XlsxExportDataEntry("Wallbox Garage", new ChannelAddress("evcs0", "ChargePower"), + HistoricTimedataSaveType.POWER)); + }, null); + } + + private static void createTouOnlyXlsx() throws IOException, OpenemsNamedException { + createTestPrint(".\\..\\build\\touPrint.xlsx", null, null, tou -> { + tou.add(new XlsxExportDataEntry("Dynamisch Gut", new ChannelAddress("_sum", "GridBuyPower"), + HistoricTimedataSaveType.POWER)); + }); + } + + private static void createProductionAndTouXlsx() throws IOException, OpenemsNamedException { + createTestPrint(".\\..\\build\\prodAndTouPrint.xlsx", production -> { + production.add(new XlsxExportDataEntry("PV-Dach", new ChannelAddress("meter0", "ActivePower"), + HistoricTimedataSaveType.POWER)); + production.add(new XlsxExportDataEntry("PV-Alm", new ChannelAddress("meter1", "ActivePower"), + HistoricTimedataSaveType.POWER)); + }, null, tou -> { + tou.add(new XlsxExportDataEntry("Dynamisch Gut", new ChannelAddress("_sum", "GridBuyPower"), + HistoricTimedataSaveType.POWER)); + }); + + } + + private static void createConsumptionAndTouXlsx() throws IOException, OpenemsNamedException { + createTestPrint(".\\..\\build\\consAndTouPrint.xlsx", null, consumption -> { + consumption.add(new XlsxExportDataEntry("Consumption Meter", new ChannelAddress("meter2", "ActivePower"), + HistoricTimedataSaveType.POWER)); + consumption.add(new XlsxExportDataEntry("Wallbox Garage", new ChannelAddress("evcs0", "ChargePower"), + HistoricTimedataSaveType.POWER)); + }, tou -> { + tou.add(new XlsxExportDataEntry("Dynamisch Gut", new ChannelAddress("_sum", "GridBuyPower"), + HistoricTimedataSaveType.POWER)); + }); + } + + private static void createnSingleOfAllXlsx() throws IOException, OpenemsNamedException { + createTestPrint(".\\..\\build\\singleOfAllPrint.xlsx", production -> { + production.add(new XlsxExportDataEntry("PV-Alm", new ChannelAddress("meter1", "ActivePower"), + HistoricTimedataSaveType.POWER)); + }, consumption -> { + consumption.add(new XlsxExportDataEntry("Wallbox Garage", new ChannelAddress("evcs0", "ChargePower"), + HistoricTimedataSaveType.POWER)); + }, tou -> { + tou.add(new XlsxExportDataEntry("Dynamisch Gut", new ChannelAddress("_sum", "GridBuyPower"), + HistoricTimedataSaveType.POWER)); + }); + + } +} diff --git a/io.openems.common/test/io/openems/common/jsonrpc/response/QueryHistoricTimeseriesExportXlsxResponseTest.java b/io.openems.common/test/io/openems/common/jsonrpc/response/QueryHistoricTimeseriesExportXlsxResponseTest.java index d59804bff2e..e34f9f1a8a6 100644 --- a/io.openems.common/test/io/openems/common/jsonrpc/response/QueryHistoricTimeseriesExportXlsxResponseTest.java +++ b/io.openems.common/test/io/openems/common/jsonrpc/response/QueryHistoricTimeseriesExportXlsxResponseTest.java @@ -70,7 +70,7 @@ private byte[] generateXlsxFile() throws OpenemsNamedException, IOException { ) { var ws = workbook.newWorksheet("Export"); - Locale currentLocale = new Locale("en", "EN"); + Locale currentLocale = Locale.of("en", "EN"); var translationBundle = ResourceBundle.getBundle("io.openems.common.jsonrpc.response.translation", currentLocale); diff --git a/io.openems.common/test/io/openems/common/timedata/TimeoutTest.java b/io.openems.common/test/io/openems/common/timedata/TimeoutTest.java new file mode 100644 index 00000000000..15ef3d8b49d --- /dev/null +++ b/io.openems.common/test/io/openems/common/timedata/TimeoutTest.java @@ -0,0 +1,27 @@ +package io.openems.common.timedata; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.time.temporal.ChronoUnit; + +import org.junit.Test; + +import io.openems.common.test.TimeLeapClock; + +public class TimeoutTest { + + @Test + public void test() { + final var timeout = Timeout.ofSeconds(120); + final var timeLeap = new TimeLeapClock(); + timeout.start(timeLeap); + + timeLeap.leap(20, ChronoUnit.SECONDS); + assertFalse(timeout.elapsed(timeLeap)); + + timeLeap.leap(121, ChronoUnit.SECONDS); + assertTrue(timeout.elapsed(timeLeap)); + } + +} diff --git a/io.openems.common/test/io/openems/common/timedata/XlsxExportUtilTest.java b/io.openems.common/test/io/openems/common/timedata/XlsxExportUtilTest.java new file mode 100644 index 00000000000..1328f3a4ff9 --- /dev/null +++ b/io.openems.common/test/io/openems/common/timedata/XlsxExportUtilTest.java @@ -0,0 +1,92 @@ +package io.openems.common.timedata; + +import static io.openems.common.utils.JsonUtils.toJson; +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +import com.google.common.collect.ImmutableSortedMap; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.timedata.XlsxExportDetailData.XlsxExportCategory; +import io.openems.common.timedata.XlsxExportDetailData.XlsxExportDataEntry.HistoricTimedataSaveType; +import io.openems.common.types.EdgeConfig.ActualEdgeConfig; +import io.openems.common.types.EdgeConfig.Component; +import io.openems.common.types.EdgeConfig.Factory; +import io.openems.common.types.EdgeConfig.Factory.Property; + +public class XlsxExportUtilTest { + + @Test + public void testGetDetailData() throws OpenemsNamedException { + var edgeConfig = ActualEdgeConfig.create() // + .addComponent("meter0", + new Component("meter0", "My CONSUMPTION_METERED Meter", "Meter.Socomec.Threephase", + // Properties + ImmutableSortedMap.of("type", toJson("CONSUMPTION_METERED")), + // Channels + ImmutableSortedMap.of())) // + .addComponent("meter1", + new Component("meter1", "My CONSUMPTION_NOT_METERED Meter", "Meter.Socomec.Threephase", + // Properties + ImmutableSortedMap.of("type", toJson("CONSUMPTION_NOT_METERED")), + // Channels + ImmutableSortedMap.of())) // + .addComponent("meter2", new Component("meter2", "My PRODUCTION Meter", "Meter.Socomec.Threephase", + // Properties + ImmutableSortedMap.of("type", toJson("PRODUCTION")), + // Channels + ImmutableSortedMap.of())) // + .addComponent("meter3", + new Component("meter3", "My MANAGED_CONSUMPTION_METERED Meter", "Meter.Socomec.Threephase", + // Properties + ImmutableSortedMap.of("type", toJson("MANAGED_CONSUMPTION_METERED")), + // Channels + ImmutableSortedMap.of())) // + + .addFactory("Meter.Socomec.Threephase", + new Factory("Meter.Socomec.Threephase", "My Name", "My Description", // + new Property[] {}, // + // Natures + new String[] { "io.openems.edge.meter.api.ElectricityMeter" })) // + .buildEdgeConfig(); + + final var result = XlsxExportUtil.getDetailData(edgeConfig); + + var consumptions = result.data().get(XlsxExportCategory.CONSUMPTION); + assertEquals(3, consumptions.size()); + + { + var meter = consumptions.get(0); + assertEquals("My CONSUMPTION_METERED Meter", meter.alias()); + assertEquals("meter0/ActivePower", meter.channel().toString()); + assertEquals(HistoricTimedataSaveType.POWER, meter.type()); + } + { + var meter = consumptions.get(1); + assertEquals("My CONSUMPTION_NOT_METERED Meter", meter.alias()); + assertEquals("meter1/ActivePower", meter.channel().toString()); + assertEquals(HistoricTimedataSaveType.POWER, meter.type()); + } + { + var meter = consumptions.get(2); + assertEquals("My MANAGED_CONSUMPTION_METERED Meter", meter.alias()); + assertEquals("meter3/ActivePower", meter.channel().toString()); + assertEquals(HistoricTimedataSaveType.POWER, meter.type()); + } + + var productions = result.data().get(XlsxExportCategory.PRODUCTION); + assertEquals(1, productions.size()); + + { + var meter = productions.get(0); + assertEquals("My PRODUCTION Meter", meter.alias()); + assertEquals("meter2/ActivePower", meter.channel().toString()); + assertEquals(HistoricTimedataSaveType.POWER, meter.type()); + } + + var touts = result.data().get(XlsxExportCategory.TIME_OF_USE_TARIFF); + assertEquals(0, touts.size()); + } + +} diff --git a/io.openems.common/test/io/openems/common/utils/JsonUtilsTest.java b/io.openems.common/test/io/openems/common/utils/JsonUtilsTest.java index badcaae75dd..de06b6e2bd3 100644 --- a/io.openems.common/test/io/openems/common/utils/JsonUtilsTest.java +++ b/io.openems.common/test/io/openems/common/utils/JsonUtilsTest.java @@ -44,6 +44,7 @@ import static io.openems.common.utils.JsonUtils.getAsJsonArray; import static io.openems.common.utils.JsonUtils.getAsJsonElement; import static io.openems.common.utils.JsonUtils.getAsJsonObject; +import static io.openems.common.utils.JsonUtils.getAsLocalDateTime; import static io.openems.common.utils.JsonUtils.getAsLong; import static io.openems.common.utils.JsonUtils.getAsOptionalBoolean; import static io.openems.common.utils.JsonUtils.getAsOptionalDouble; @@ -64,6 +65,7 @@ import static io.openems.common.utils.JsonUtils.getAsStringOrElse; import static io.openems.common.utils.JsonUtils.getAsType; import static io.openems.common.utils.JsonUtils.getAsUUID; +import static io.openems.common.utils.JsonUtils.getAsZonedDateTime; import static io.openems.common.utils.JsonUtils.getAsZonedDateWithZeroTime; import static io.openems.common.utils.JsonUtils.getOptionalSubElement; import static io.openems.common.utils.JsonUtils.getSubElement; @@ -80,11 +82,13 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import java.net.Inet4Address; import java.net.UnknownHostException; +import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.List; @@ -148,6 +152,8 @@ private static final void assertAllThrow(Class expected .addProperty("Enum3", (Unit) null) // .addProperty("Inet4Address", "192.168.1.2") // .addProperty("UUID", "c48e2e28-09be-41d5-8e58-260d162991cc") // + .addProperty("ZonedDateTime", ZonedDateTime.of(1900, 1, 1, 0, 0, 0, 0, ZoneId.of("UTC"))) // + .addProperty("LocalDateTime", LocalDateTime.of(1900, 1, 1, 0, 0, 0, 0)) // .addPropertyIfNotNull("Boolean1", (Boolean) null) // .addPropertyIfNotNull("Boolean2", Boolean.FALSE) // .addPropertyIfNotNull("Double1", (Double) null) // @@ -680,6 +686,13 @@ public void testGetAsZonedDateTime() throws OpenemsNamedException { assertOpenemsError(JSON_NO_DATE_MEMBER, // () -> getAsZonedDateWithZeroTime(j, "foo", ZoneId.of("UTC")) // ); + + assertEquals("1900-01-01T00:00Z", getAsZonedDateTime(JSON_OBJECT, "ZonedDateTime").toString()); + } + + @Test + public void testGetAsLocalDateTime() throws OpenemsNamedException { + assertEquals("1900-01-01T00:00", getAsLocalDateTime(JSON_OBJECT, "LocalDateTime").toString()); } @Test @@ -744,6 +757,9 @@ public void testIsEmptyJsonArray() throws OpenemsNamedException { @Test public void testGenerateJsonArray() { + assertNull(generateJsonArray(null)); + assertEquals(JsonNull.INSTANCE, generateJsonArray(List.of(JsonNull.INSTANCE)).get(0)); + var list = List.of("foo", "bar"); var r = generateJsonArray(list, v -> new JsonPrimitive(v)); assertEquals("foo", r.get(0).getAsString()); diff --git a/io.openems.common/test/io/openems/common/websocket/ClientReconnectorWorkerTest.java b/io.openems.common/test/io/openems/common/websocket/ClientReconnectorWorkerTest.java index 3eb4161bd07..62373bfe85a 100644 --- a/io.openems.common/test/io/openems/common/websocket/ClientReconnectorWorkerTest.java +++ b/io.openems.common/test/io/openems/common/websocket/ClientReconnectorWorkerTest.java @@ -9,28 +9,15 @@ public class ClientReconnectorWorkerTest { - private static class MyWsData extends WsData { - - public MyWsData(WebSocket ws) { - super(ws); - } - - @Override - public String toString() { - return ""; - } - - } - - private static class MyWebsocketClient extends AbstractWebsocketClient { + private static class MyWebsocketClient extends AbstractWebsocketClient { public MyWebsocketClient(String name, URI serverUri) { super(name, serverUri); } @Override - protected MyWsData createWsData(WebSocket ws) { - return new MyWsData(ws); + protected WsData createWsData(WebSocket ws) { + return new WsData(ws); } @Override diff --git a/io.openems.common/test/io/openems/common/websocket/DummyWebsocketServerTest.java b/io.openems.common/test/io/openems/common/websocket/DummyWebsocketServerTest.java new file mode 100644 index 00000000000..172d3516be5 --- /dev/null +++ b/io.openems.common/test/io/openems/common/websocket/DummyWebsocketServerTest.java @@ -0,0 +1,15 @@ +package io.openems.common.websocket; + +import org.junit.Test; + +public class DummyWebsocketServerTest { + + @Test + public void test() { + var sut = DummyWebsocketServer.create() // + .build(); + sut.createWsData(null); + sut.stop(); + } + +} diff --git a/io.openems.common/test/io/openems/common/websocket/WebsocketUtilsTest.java b/io.openems.common/test/io/openems/common/websocket/WebsocketUtilsTest.java new file mode 100644 index 00000000000..25febb897d4 --- /dev/null +++ b/io.openems.common/test/io/openems/common/websocket/WebsocketUtilsTest.java @@ -0,0 +1,15 @@ +package io.openems.common.websocket; + +import static io.openems.common.websocket.WebsocketUtils.generateWsDataString; +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public class WebsocketUtilsTest { + + @Test + public void test() { + assertEquals("", generateWsDataString(null)); + } + +} diff --git a/io.openems.common/test/io/openems/common/websocket/WsDataTest.java b/io.openems.common/test/io/openems/common/websocket/WsDataTest.java new file mode 100644 index 00000000000..5dd1a7aad2f --- /dev/null +++ b/io.openems.common/test/io/openems/common/websocket/WsDataTest.java @@ -0,0 +1,17 @@ +package io.openems.common.websocket; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public class WsDataTest { + + @Test + public void test() { + var sut = new WsData(null); + assertEquals("", sut.toLogString()); + + sut.dispose(); + } + +} diff --git a/io.openems.edge.application/.classpath b/io.openems.edge.application/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.application/.classpath +++ b/io.openems.edge.application/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.application/EdgeApp.bndrun b/io.openems.edge.application/EdgeApp.bndrun index 11442b68bce..c1afa91dad3 100644 --- a/io.openems.edge.application/EdgeApp.bndrun +++ b/io.openems.edge.application/EdgeApp.bndrun @@ -1,5 +1,5 @@ -runfw: org.apache.felix.framework;version='[7.0.5,7.0.5]' --runee: JavaSE-17 +-runee: JavaSE-21 -runprovidedcapabilities: ${native_capability} -resolve.effective: active @@ -31,6 +31,7 @@ bnd.identity;id='org.ops4j.pax.logging.pax-logging-api',\ bnd.identity;id='org.ops4j.pax.logging.pax-logging-log4j2',\ bnd.identity;id='org.apache.felix.http.jetty',\ + bnd.identity;id='org.apache.felix.http.servlet-api',\ bnd.identity;id='org.apache.felix.webconsole',\ bnd.identity;id='org.apache.felix.webconsole.plugins.ds',\ bnd.identity;id='org.apache.felix.inventory',\ @@ -74,6 +75,7 @@ bnd.identity;id='io.openems.edge.controller.ess.delaycharge',\ bnd.identity;id='io.openems.edge.controller.ess.delayedselltogrid',\ bnd.identity;id='io.openems.edge.controller.ess.emergencycapacityreserve',\ + bnd.identity;id='io.openems.edge.controller.ess.fastfrequencyreserve',\ bnd.identity;id='io.openems.edge.controller.ess.fixactivepower',\ bnd.identity;id='io.openems.edge.controller.ess.fixstateofcharge',\ bnd.identity;id='io.openems.edge.controller.ess.gridoptimizedcharge',\ @@ -122,6 +124,7 @@ bnd.identity;id='io.openems.edge.evcs.goe.chargerhome',\ bnd.identity;id='io.openems.edge.evcs.hardybarth',\ bnd.identity;id='io.openems.edge.evcs.keba.kecontact',\ + bnd.identity;id='io.openems.edge.evcs.mennekes',\ bnd.identity;id='io.openems.edge.evcs.ocpp.abl',\ bnd.identity;id='io.openems.edge.evcs.ocpp.common',\ bnd.identity;id='io.openems.edge.evcs.ocpp.ies.keywatt.singleccs',\ @@ -139,6 +142,7 @@ bnd.identity;id='io.openems.edge.io.offgridswitch',\ bnd.identity;id='io.openems.edge.io.revpi',\ bnd.identity;id='io.openems.edge.io.shelly',\ + bnd.identity;id='io.openems.edge.io.siemenslogo',\ bnd.identity;id='io.openems.edge.io.wago',\ bnd.identity;id='io.openems.edge.io.weidmueller',\ bnd.identity;id='io.openems.edge.kaco.blueplanet.hybrid10',\ @@ -166,6 +170,7 @@ bnd.identity;id='io.openems.edge.meter.weidmueller',\ bnd.identity;id='io.openems.edge.meter.ziehl',\ bnd.identity;id='io.openems.edge.onewire.thermometer',\ + bnd.identity;id='io.openems.edge.predictor.lstm',\ bnd.identity;id='io.openems.edge.predictor.persistencemodel',\ bnd.identity;id='io.openems.edge.predictor.similardaymodel',\ bnd.identity;id='io.openems.edge.pvinverter.cluster',\ @@ -189,21 +194,22 @@ bnd.identity;id='io.openems.edge.timeofusetariff.groupe',\ bnd.identity;id='io.openems.edge.timeofusetariff.hassfurt',\ bnd.identity;id='io.openems.edge.timeofusetariff.rabotcharge',\ + bnd.identity;id='io.openems.edge.timeofusetariff.swisspower',\ bnd.identity;id='io.openems.edge.timeofusetariff.tibber',\ - + -runbundles: \ Java-WebSocket;version='[1.5.4,1.5.5)',\ - bcpkix;version='[1.70.0,1.70.1)',\ - bcprov;version='[1.70.0,1.70.1)',\ - bcutil;version='[1.70.0,1.70.1)',\ + bcpkix;version='[1.79.0,1.79.1)',\ + bcprov;version='[1.79.0,1.79.1)',\ + bcutil;version='[1.79.0,1.79.1)',\ com.fasterxml.aalto-xml;version='[1.3.3,1.3.4)',\ com.fazecast.jSerialComm;version='[2.10.4,2.10.5)',\ com.ghgande.j2mod;version='[3.2.1,3.2.2)',\ com.google.gson;version='[2.11.0,2.11.1)',\ - com.google.guava;version='[33.3.1,33.3.2)',\ + com.google.guava;version='[33.4.0,33.4.1)',\ com.google.guava.failureaccess;version='[1.0.2,1.0.3)',\ com.squareup.okio;version='[3.9.1,3.9.2)',\ - com.sun.jna;version='[5.15.0,5.15.1)',\ + com.sun.jna;version='[5.16.0,5.16.1)',\ io.openems.common;version=snapshot,\ io.openems.edge.application;version=snapshot,\ io.openems.edge.battery.api;version=snapshot,\ @@ -245,6 +251,7 @@ io.openems.edge.controller.ess.delaycharge;version=snapshot,\ io.openems.edge.controller.ess.delayedselltogrid;version=snapshot,\ io.openems.edge.controller.ess.emergencycapacityreserve;version=snapshot,\ + io.openems.edge.controller.ess.fastfrequencyreserve;version=snapshot,\ io.openems.edge.controller.ess.fixactivepower;version=snapshot,\ io.openems.edge.controller.ess.fixstateofcharge;version=snapshot,\ io.openems.edge.controller.ess.gridoptimizedcharge;version=snapshot,\ @@ -296,6 +303,7 @@ io.openems.edge.evcs.goe.chargerhome;version=snapshot,\ io.openems.edge.evcs.hardybarth;version=snapshot,\ io.openems.edge.evcs.keba.kecontact;version=snapshot,\ + io.openems.edge.evcs.mennekes;version=snapshot,\ io.openems.edge.evcs.ocpp.abl;version=snapshot,\ io.openems.edge.evcs.ocpp.common;version=snapshot,\ io.openems.edge.evcs.ocpp.ies.keywatt.singleccs;version=snapshot,\ @@ -314,6 +322,7 @@ io.openems.edge.io.offgridswitch;version=snapshot,\ io.openems.edge.io.revpi;version=snapshot,\ io.openems.edge.io.shelly;version=snapshot,\ + io.openems.edge.io.siemenslogo;version=snapshot,\ io.openems.edge.io.wago;version=snapshot,\ io.openems.edge.io.weidmueller;version=snapshot,\ io.openems.edge.kaco.blueplanet.hybrid10;version=snapshot,\ @@ -343,6 +352,7 @@ io.openems.edge.meter.ziehl;version=snapshot,\ io.openems.edge.onewire.thermometer;version=snapshot,\ io.openems.edge.predictor.api;version=snapshot,\ + io.openems.edge.predictor.lstm;version=snapshot,\ io.openems.edge.predictor.persistencemodel;version=snapshot,\ io.openems.edge.predictor.similardaymodel;version=snapshot,\ io.openems.edge.pvinverter.api;version=snapshot,\ @@ -371,6 +381,7 @@ io.openems.edge.timeofusetariff.groupe;version=snapshot,\ io.openems.edge.timeofusetariff.hassfurt;version=snapshot,\ io.openems.edge.timeofusetariff.rabotcharge;version=snapshot,\ + io.openems.edge.timeofusetariff.swisspower;version=snapshot,\ io.openems.edge.timeofusetariff.tibber;version=snapshot,\ io.openems.oem.openems;version=snapshot,\ io.openems.shared.influxdb;version=snapshot,\ @@ -393,9 +404,8 @@ io.openems.wrapper.retrofit-converter-scalars;version=snapshot,\ io.openems.wrapper.retrofit2;version=snapshot,\ io.openems.wrapper.sdnotify;version=snapshot,\ - io.reactivex.rxjava3.rxjava;version='[3.1.9,3.1.10)',\ + io.reactivex.rxjava3.rxjava;version='[3.1.10,3.1.11)',\ javax.jmdns;version='[3.4.1,3.4.2)',\ - javax.xml.soap-api;version='[1.4.0,1.4.1)',\ org.apache.commons.commons-codec;version='[1.17.1,1.17.2)',\ org.apache.commons.commons-compress;version='[1.27.1,1.27.2)',\ org.apache.commons.commons-csv;version='[1.11.0,1.11.1)',\ @@ -416,13 +426,13 @@ org.eclipse.jetty.io;version='[9.4.28,9.4.29)',\ org.eclipse.jetty.util;version='[9.4.28,9.4.29)',\ org.eclipse.paho.mqttv5.client;version='[1.2.5,1.2.6)',\ - org.jetbrains.kotlin.osgi-bundle;version='[2.0.20,2.0.21)',\ - org.jsoup;version='[1.18.1,1.18.2)',\ + org.jetbrains.kotlin.osgi-bundle;version='[2.1.0,2.1.1)',\ + org.jsoup;version='[1.18.3,1.18.4)',\ org.jsr-305;version='[3.0.2,3.0.3)',\ org.openmuc.jmbus;version='[3.3.0,3.3.1)',\ org.openmuc.jrxtx;version='[1.0.1,1.0.2)',\ - org.ops4j.pax.logging.pax-logging-api;version='[2.2.1,2.2.2)',\ - org.ops4j.pax.logging.pax-logging-log4j2;version='[2.2.1,2.2.2)',\ + org.ops4j.pax.logging.pax-logging-api;version='[2.2.7,2.2.8)',\ + org.ops4j.pax.logging.pax-logging-log4j2;version='[2.2.7,2.2.8)',\ org.osgi.service.component;version='[1.5.1,1.5.2)',\ org.osgi.util.function;version='[1.2.0,1.2.1)',\ org.osgi.util.promise;version='[1.3.0,1.3.1)',\ diff --git a/io.openems.edge.battery.api/.classpath b/io.openems.edge.battery.api/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.battery.api/.classpath +++ b/io.openems.edge.battery.api/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.battery.api/test/io/openems/edge/battery/protection/BatteryProtectionTest.java b/io.openems.edge.battery.api/test/io/openems/edge/battery/protection/BatteryProtectionTest.java index de029ad3ba5..74b3d0f4895 100644 --- a/io.openems.edge.battery.api/test/io/openems/edge/battery/protection/BatteryProtectionTest.java +++ b/io.openems.edge.battery.api/test/io/openems/edge/battery/protection/BatteryProtectionTest.java @@ -1,14 +1,24 @@ package io.openems.edge.battery.protection; +import static io.openems.edge.battery.api.Battery.ChannelId.CHARGE_MAX_CURRENT; +import static io.openems.edge.battery.api.Battery.ChannelId.DISCHARGE_MAX_CURRENT; +import static io.openems.edge.battery.api.Battery.ChannelId.MAX_CELL_TEMPERATURE; +import static io.openems.edge.battery.api.Battery.ChannelId.MAX_CELL_VOLTAGE; +import static io.openems.edge.battery.api.Battery.ChannelId.MIN_CELL_TEMPERATURE; +import static io.openems.edge.battery.api.Battery.ChannelId.MIN_CELL_VOLTAGE; +import static io.openems.edge.battery.protection.BatteryProtection.ChannelId.BP_CHARGE_BMS; +import static io.openems.edge.battery.protection.BatteryProtection.ChannelId.BP_DISCHARGE_BMS; +import static io.openems.edge.common.startstop.StartStoppable.ChannelId.START_STOP; +import static java.time.temporal.ChronoUnit.MINUTES; +import static java.time.temporal.ChronoUnit.SECONDS; + import java.time.Instant; import java.time.ZoneOffset; -import java.time.temporal.ChronoUnit; import org.junit.Test; import io.openems.common.channel.Unit; import io.openems.common.test.TimeLeapClock; -import io.openems.common.types.ChannelAddress; import io.openems.common.types.OpenemsType; import io.openems.edge.battery.protection.currenthandler.ChargeMaxCurrentHandler; import io.openems.edge.battery.protection.currenthandler.DischargeMaxCurrentHandler; @@ -18,7 +28,6 @@ import io.openems.edge.common.channel.Doc; import io.openems.edge.common.linecharacteristic.PolyLine; import io.openems.edge.common.startstop.StartStop; -import io.openems.edge.common.startstop.StartStoppable; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyComponentManager; @@ -102,22 +111,6 @@ public Doc doc() { private static final String BATTERY_ID = "battery0"; - private static final ChannelAddress BATTERY_START_STOP = new ChannelAddress(BATTERY_ID, - StartStoppable.ChannelId.START_STOP.id()); - private static final ChannelAddress BATTERY_BP_CHARGE_BMS = new ChannelAddress(BATTERY_ID, - BatteryProtection.ChannelId.BP_CHARGE_BMS.id()); - private static final ChannelAddress BATTERY_BP_DISCHARGE_BMS = new ChannelAddress(BATTERY_ID, - BatteryProtection.ChannelId.BP_DISCHARGE_BMS.id()); - private static final ChannelAddress BATTERY_MIN_CELL_VOLTAGE = new ChannelAddress(BATTERY_ID, "MinCellVoltage"); - private static final ChannelAddress BATTERY_MAX_CELL_VOLTAGE = new ChannelAddress(BATTERY_ID, "MaxCellVoltage"); - private static final ChannelAddress BATTERY_MIN_CELL_TEMPERATURE = new ChannelAddress(BATTERY_ID, - "MinCellTemperature"); - private static final ChannelAddress BATTERY_MAX_CELL_TEMPERATURE = new ChannelAddress(BATTERY_ID, - "MaxCellTemperature"); - private static final ChannelAddress BATTERY_CHARGE_MAX_CURRENT = new ChannelAddress(BATTERY_ID, "ChargeMaxCurrent"); - private static final ChannelAddress BATTERY_DISCHARGE_MAX_CURRENT = new ChannelAddress(BATTERY_ID, - "DischargeMaxCurrent"); - @Test public void test() throws Exception { final var battery = new DummyBattery(BATTERY_ID); @@ -137,169 +130,168 @@ public void test() throws Exception { .setForceCharge(FORCE_CHARGE) // .build()) // .build(); - new ComponentTest(new DummyBattery(BATTERY_ID)) // - .addComponent(battery) // + new ComponentTest(battery) // .next(new TestCase() // - .input(BATTERY_START_STOP, StartStop.START) // - .input(BATTERY_BP_CHARGE_BMS, 80) // - .input(BATTERY_BP_DISCHARGE_BMS, 80) // - .input(BATTERY_MIN_CELL_VOLTAGE, 2950) // - .input(BATTERY_MAX_CELL_VOLTAGE, 3300) // - .input(BATTERY_MIN_CELL_TEMPERATURE, 16) // - .input(BATTERY_MAX_CELL_TEMPERATURE, 17) // + .input(START_STOP, StartStop.START) // + .input(BP_CHARGE_BMS, 80) // + .input(BP_DISCHARGE_BMS, 80) // + .input(MIN_CELL_VOLTAGE, 2950) // + .input(MAX_CELL_VOLTAGE, 3300) // + .input(MIN_CELL_TEMPERATURE, 16) // + .input(MAX_CELL_TEMPERATURE, 17) // .onAfterProcessImage(() -> sut.apply()) // - .output(BATTERY_CHARGE_MAX_CURRENT, 0) // - .output(BATTERY_DISCHARGE_MAX_CURRENT, 0)) // + .output(CHARGE_MAX_CURRENT, 0) // + .output(DISCHARGE_MAX_CURRENT, 0)) // .next(new TestCase("open, but maxIncreaseAmpereLimit") // - .timeleap(clock, 2, ChronoUnit.SECONDS) // - .input(BATTERY_MIN_CELL_VOLTAGE, 3000) // + .timeleap(clock, 2, SECONDS) // + .input(MIN_CELL_VOLTAGE, 3000) // .onAfterProcessImage(() -> sut.apply()) // - .output(BATTERY_CHARGE_MAX_CURRENT, 1) // - .output(BATTERY_DISCHARGE_MAX_CURRENT, 1)) // + .output(CHARGE_MAX_CURRENT, 1) // + .output(DISCHARGE_MAX_CURRENT, 1)) // .next(new TestCase() // - .timeleap(clock, 2, ChronoUnit.SECONDS) // - .input(BATTERY_MIN_CELL_VOLTAGE, 3050) // + .timeleap(clock, 2, SECONDS) // + .input(MIN_CELL_VOLTAGE, 3050) // .onAfterProcessImage(() -> sut.apply()) // - .output(BATTERY_CHARGE_MAX_CURRENT, 2) // - .output(BATTERY_DISCHARGE_MAX_CURRENT, 2)) // + .output(CHARGE_MAX_CURRENT, 2) // + .output(DISCHARGE_MAX_CURRENT, 2)) // .next(new TestCase() // - .timeleap(clock, 10, ChronoUnit.SECONDS) // + .timeleap(clock, 10, SECONDS) // .onAfterProcessImage(() -> sut.apply()) // - .output(BATTERY_CHARGE_MAX_CURRENT, 7) // - .output(BATTERY_DISCHARGE_MAX_CURRENT, 7)) // + .output(CHARGE_MAX_CURRENT, 7) // + .output(DISCHARGE_MAX_CURRENT, 7)) // .next(new TestCase() // - .timeleap(clock, 10, ChronoUnit.MINUTES) // - .input(BATTERY_MAX_CELL_VOLTAGE, 3300) // + .timeleap(clock, 10, MINUTES) // + .input(MAX_CELL_VOLTAGE, 3300) // .onAfterProcessImage(() -> sut.apply()) // - .output(BATTERY_CHARGE_MAX_CURRENT, 80) // - .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) // + .output(CHARGE_MAX_CURRENT, 80) // + .output(DISCHARGE_MAX_CURRENT, 80)) // .next(new TestCase() // - .timeleap(clock, 10, ChronoUnit.MINUTES) // - .input(BATTERY_MAX_CELL_VOLTAGE, 3499) // + .timeleap(clock, 10, MINUTES) // + .input(MAX_CELL_VOLTAGE, 3499) // .onAfterProcessImage(() -> sut.apply()) // - .output(BATTERY_CHARGE_MAX_CURRENT, 54) // - .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) // + .output(CHARGE_MAX_CURRENT, 54) // + .output(DISCHARGE_MAX_CURRENT, 80)) // .next(new TestCase() // - .timeleap(clock, 10, ChronoUnit.MINUTES) // - .input(BATTERY_MAX_CELL_VOLTAGE, 3649) // + .timeleap(clock, 10, MINUTES) // + .input(MAX_CELL_VOLTAGE, 3649) // .onAfterProcessImage(() -> sut.apply()) // - .output(BATTERY_CHARGE_MAX_CURRENT, 2) // - .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) // + .output(CHARGE_MAX_CURRENT, 2) // + .output(DISCHARGE_MAX_CURRENT, 80)) // .next(new TestCase() // - .timeleap(clock, 10, ChronoUnit.MINUTES) // - .input(BATTERY_MAX_CELL_VOLTAGE, 3649) // + .timeleap(clock, 10, MINUTES) // + .input(MAX_CELL_VOLTAGE, 3649) // .onAfterProcessImage(() -> sut.apply()) // - .output(BATTERY_CHARGE_MAX_CURRENT, 2) // - .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) // + .output(CHARGE_MAX_CURRENT, 2) // + .output(DISCHARGE_MAX_CURRENT, 80)) // .next(new TestCase() // - .timeleap(clock, 10, ChronoUnit.MINUTES) // - .input(BATTERY_MAX_CELL_VOLTAGE, 3650) // + .timeleap(clock, 10, MINUTES) // + .input(MAX_CELL_VOLTAGE, 3650) // .onAfterProcessImage(() -> sut.apply()) // - .output(BATTERY_CHARGE_MAX_CURRENT, 0) // - .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) // + .output(CHARGE_MAX_CURRENT, 0) // + .output(DISCHARGE_MAX_CURRENT, 80)) // .next(new TestCase("Start Force-Discharge: wait 60 seconds") // - .timeleap(clock, 1, ChronoUnit.SECONDS) // - .input(BATTERY_MAX_CELL_VOLTAGE, 3660) // + .timeleap(clock, 1, SECONDS) // + .input(MAX_CELL_VOLTAGE, 3660) // .onAfterProcessImage(() -> sut.apply()) // - .output(BATTERY_CHARGE_MAX_CURRENT, 0) // - .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) // + .output(CHARGE_MAX_CURRENT, 0) // + .output(DISCHARGE_MAX_CURRENT, 80)) // .next(new TestCase("Start Force-Discharge") // - .timeleap(clock, 60, ChronoUnit.SECONDS) // - .input(BATTERY_MAX_CELL_VOLTAGE, 3660) // + .timeleap(clock, 60, SECONDS) // + .input(MAX_CELL_VOLTAGE, 3660) // .onAfterProcessImage(() -> sut.apply()) // - .output(BATTERY_CHARGE_MAX_CURRENT, -2) // - .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) // + .output(CHARGE_MAX_CURRENT, -2) // + .output(DISCHARGE_MAX_CURRENT, 80)) // .next(new TestCase("Force-Discharge") // - .timeleap(clock, 1, ChronoUnit.SECONDS) // - .input(BATTERY_MAX_CELL_VOLTAGE, 3640) // + .timeleap(clock, 1, SECONDS) // + .input(MAX_CELL_VOLTAGE, 3640) // .onAfterProcessImage(() -> sut.apply()) // - .output(BATTERY_CHARGE_MAX_CURRENT, -2) // - .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) // + .output(CHARGE_MAX_CURRENT, -2) // + .output(DISCHARGE_MAX_CURRENT, 80)) // .next(new TestCase("Block Charge #1") // - .timeleap(clock, 1, ChronoUnit.SECONDS) // - .input(BATTERY_MAX_CELL_VOLTAGE, 3639) // + .timeleap(clock, 1, SECONDS) // + .input(MAX_CELL_VOLTAGE, 3639) // .onAfterProcessImage(() -> sut.apply()) // - .output(BATTERY_CHARGE_MAX_CURRENT, -1) // - .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) // + .output(CHARGE_MAX_CURRENT, -1) // + .output(DISCHARGE_MAX_CURRENT, 80)) // .next(new TestCase("Block Charge #1 still reduce by 1") // - .timeleap(clock, 1, ChronoUnit.SECONDS) // - .input(BATTERY_MAX_CELL_VOLTAGE, 3638) // + .timeleap(clock, 1, SECONDS) // + .input(MAX_CELL_VOLTAGE, 3638) // .onAfterProcessImage(() -> sut.apply()) // - .output(BATTERY_CHARGE_MAX_CURRENT, -1) // - .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) // + .output(CHARGE_MAX_CURRENT, -1) // + .output(DISCHARGE_MAX_CURRENT, 80)) // .next(new TestCase("Block Charge #1 still reduce by 1") // - .timeleap(clock, 1, ChronoUnit.SECONDS) // - .input(BATTERY_MAX_CELL_VOLTAGE, 3610) // + .timeleap(clock, 1, SECONDS) // + .input(MAX_CELL_VOLTAGE, 3610) // .onAfterProcessImage(() -> sut.apply()) // - .output(BATTERY_CHARGE_MAX_CURRENT, 0) // - .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) // + .output(CHARGE_MAX_CURRENT, 0) // + .output(DISCHARGE_MAX_CURRENT, 80)) // .next(new TestCase("Block Charge #2") // - .timeleap(clock, 1, ChronoUnit.SECONDS) // - .input(BATTERY_MAX_CELL_VOLTAGE, 3600) // + .timeleap(clock, 1, SECONDS) // + .input(MAX_CELL_VOLTAGE, 3600) // .onAfterProcessImage(() -> sut.apply()) // - .output(BATTERY_CHARGE_MAX_CURRENT, 0) // - .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) // + .output(CHARGE_MAX_CURRENT, 0) // + .output(DISCHARGE_MAX_CURRENT, 80)) // .next(new TestCase("Start Force-Discharge again") // - .timeleap(clock, 1, ChronoUnit.SECONDS) // - .input(BATTERY_MAX_CELL_VOLTAGE, 3660) // + .timeleap(clock, 1, SECONDS) // + .input(MAX_CELL_VOLTAGE, 3660) // .onAfterProcessImage(() -> sut.apply()) // - .output(BATTERY_CHARGE_MAX_CURRENT, -2) // - .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) // + .output(CHARGE_MAX_CURRENT, -2) // + .output(DISCHARGE_MAX_CURRENT, 80)) // .next(new TestCase("Force-Discharge") // - .timeleap(clock, 1, ChronoUnit.SECONDS) // - .input(BATTERY_MAX_CELL_VOLTAGE, 3640) // + .timeleap(clock, 1, SECONDS) // + .input(MAX_CELL_VOLTAGE, 3640) // .onAfterProcessImage(() -> sut.apply()) // - .output(BATTERY_CHARGE_MAX_CURRENT, -2) // - .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) // + .output(CHARGE_MAX_CURRENT, -2) // + .output(DISCHARGE_MAX_CURRENT, 80)) // .next(new TestCase("Block Charge #1") // - .timeleap(clock, 1, ChronoUnit.SECONDS) // - .input(BATTERY_MAX_CELL_VOLTAGE, 3639) // + .timeleap(clock, 1, SECONDS) // + .input(MAX_CELL_VOLTAGE, 3639) // .onAfterProcessImage(() -> sut.apply()) // - .output(BATTERY_CHARGE_MAX_CURRENT, -1) // - .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) // + .output(CHARGE_MAX_CURRENT, -1) // + .output(DISCHARGE_MAX_CURRENT, 80)) // .next(new TestCase("Block Charge #1 still reduce by 1") // - .timeleap(clock, 1, ChronoUnit.SECONDS) // - .input(BATTERY_MAX_CELL_VOLTAGE, 3638) // + .timeleap(clock, 1, SECONDS) // + .input(MAX_CELL_VOLTAGE, 3638) // .onAfterProcessImage(() -> sut.apply()) // - .output(BATTERY_CHARGE_MAX_CURRENT, -1) // - .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) // + .output(CHARGE_MAX_CURRENT, -1) // + .output(DISCHARGE_MAX_CURRENT, 80)) // .next(new TestCase("Block Charge #1 still reduce by 1") // - .timeleap(clock, 1, ChronoUnit.SECONDS) // - .input(BATTERY_MAX_CELL_VOLTAGE, 3637) // + .timeleap(clock, 1, SECONDS) // + .input(MAX_CELL_VOLTAGE, 3637) // .onAfterProcessImage(() -> sut.apply()) // - .output(BATTERY_CHARGE_MAX_CURRENT, 0) // - .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) // + .output(CHARGE_MAX_CURRENT, 0) // + .output(DISCHARGE_MAX_CURRENT, 80)) // .next(new TestCase("Block Charge #2") // - .timeleap(clock, 1, ChronoUnit.SECONDS) // - .input(BATTERY_MAX_CELL_VOLTAGE, 3600) // + .timeleap(clock, 1, SECONDS) // + .input(MAX_CELL_VOLTAGE, 3600) // .onAfterProcessImage(() -> sut.apply()) // - .output(BATTERY_CHARGE_MAX_CURRENT, 0) // - .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) // + .output(CHARGE_MAX_CURRENT, 0) // + .output(DISCHARGE_MAX_CURRENT, 80)) // .next(new TestCase("Block Charge #3") // - .timeleap(clock, 1, ChronoUnit.SECONDS) // - .input(BATTERY_MAX_CELL_VOLTAGE, 3450) // + .timeleap(clock, 1, SECONDS) // + .input(MAX_CELL_VOLTAGE, 3450) // .onAfterProcessImage(() -> sut.apply()) // - .output(BATTERY_CHARGE_MAX_CURRENT, 0) // - .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) // + .output(CHARGE_MAX_CURRENT, 0) // + .output(DISCHARGE_MAX_CURRENT, 80)) // .next(new TestCase("Finish Force-Discharge") // - .timeleap(clock, 1, ChronoUnit.SECONDS) // - .input(BATTERY_MAX_CELL_VOLTAGE, 3449) // + .timeleap(clock, 1, SECONDS) // + .input(MAX_CELL_VOLTAGE, 3449) // .onAfterProcessImage(() -> sut.apply()) // - .output(BATTERY_CHARGE_MAX_CURRENT, 0) // - .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) // + .output(CHARGE_MAX_CURRENT, 0) // + .output(DISCHARGE_MAX_CURRENT, 80)) // .next(new TestCase() // - .timeleap(clock, 1, ChronoUnit.SECONDS) // - .input(BATTERY_MAX_CELL_VOLTAGE, 3400) // + .timeleap(clock, 1, SECONDS) // + .input(MAX_CELL_VOLTAGE, 3400) // .onAfterProcessImage(() -> sut.apply()) // - .output(BATTERY_CHARGE_MAX_CURRENT, 0) // - .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) // + .output(CHARGE_MAX_CURRENT, 0) // + .output(DISCHARGE_MAX_CURRENT, 80)) // .next(new TestCase("Allow Charge") // - .timeleap(clock, 1, ChronoUnit.SECONDS) // - .input(BATTERY_MAX_CELL_VOLTAGE, 3350) // + .timeleap(clock, 1, SECONDS) // + .input(MAX_CELL_VOLTAGE, 3350) // .onAfterProcessImage(() -> sut.apply()) // - .output(BATTERY_CHARGE_MAX_CURRENT, 1) // - .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) // + .output(CHARGE_MAX_CURRENT, 1) // + .output(DISCHARGE_MAX_CURRENT, 80)) // ; } diff --git a/io.openems.edge.battery.bmw/.classpath b/io.openems.edge.battery.bmw/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.battery.bmw/.classpath +++ b/io.openems.edge.battery.bmw/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.battery.bmw/bnd.bnd b/io.openems.edge.battery.bmw/bnd.bnd index 8f2cc726fe3..9fbb783f822 100644 --- a/io.openems.edge.battery.bmw/bnd.bnd +++ b/io.openems.edge.battery.bmw/bnd.bnd @@ -8,8 +8,10 @@ Bundle-Version: 1.0.0.${tstamp} com.ghgande.j2mod,\ io.openems.common,\ io.openems.edge.battery.api,\ + io.openems.edge.bridge.http,\ io.openems.edge.bridge.modbus,\ - io.openems.edge.common + io.openems.edge.common,\ + io.openems.edge.io.api,\ -testpath: \ ${testpath} \ No newline at end of file diff --git a/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/BatteryBmw.java b/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/BatteryBmw.java new file mode 100644 index 00000000000..c6d0dcd6fcf --- /dev/null +++ b/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/BatteryBmw.java @@ -0,0 +1,595 @@ +package io.openems.edge.battery.bmw; + +import io.openems.common.channel.AccessMode; +import io.openems.common.channel.Level; +import io.openems.common.channel.PersistencePriority; +import io.openems.common.channel.Unit; +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.types.OpenemsType; +import io.openems.edge.battery.api.Battery; +import io.openems.edge.battery.bmw.enums.BatteryState; +import io.openems.edge.battery.bmw.enums.BatteryStateCommand; +import io.openems.edge.battery.bmw.statemachine.GoRunningSubState; +import io.openems.edge.battery.bmw.statemachine.StateMachine.State; +import io.openems.edge.bridge.modbus.api.ModbusComponent; +import io.openems.edge.common.channel.Channel; +import io.openems.edge.common.channel.Doc; +import io.openems.edge.common.channel.StateChannel; +import io.openems.edge.common.channel.WriteChannel; +import io.openems.edge.common.channel.value.Value; +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.common.startstop.StartStop; +import io.openems.edge.common.startstop.StartStoppable; + +public interface BatteryBmw extends Battery, ModbusComponent, OpenemsComponent, StartStoppable { + + public static enum ChannelId implements io.openems.edge.common.channel.ChannelId { + STATE_MACHINE(Doc.of(State.values()) // + .text("Current state of State-Machine")), // + RUN_FAILED(Doc.of(Level.FAULT) // + .text("Running the Logic failed")), // + ERROR_BATTERY_TYPE(Doc.of(Level.FAULT) // + .text("Configuring the Battery Type not successful!")), // + UNEXPECTED_STOPPED_STATE(Doc.of(Level.FAULT) // + .text("Unexpected battery state in \"Stopped\"!")), + UNEXPECTED_RUNNING_STATE(Doc.of(Level.FAULT) // + .text("Unexpected battery state in \"Running\"!")), + SOC_RAW_VALUE(Doc.of(OpenemsType.INTEGER)// + .unit(Unit.THOUSANDTH)// + .accessMode(AccessMode.READ_ONLY)), // + TIMEOUT_START_BATTERY(Doc.of(Level.FAULT) // + .text("The maximum start time is passed")), // + TIMEOUT_STOP_BATTERY(Doc.of(Level.FAULT) // + .text("The maximum stop time is passed")), // + GO_RUNNING_STATE_MACHINE(Doc.of(GoRunningSubState.values()) // + .text("Current State of GoRunning State-Machine")), // + /* + * ErrBits1 + */ + + UNSPECIFIED_ERROR(Doc.of(Level.FAULT) // + .text("Unspecified Error - Cell-config-Error, Slave-count-Error")), // + + LOW_VOLTAGE_ERROR(Doc.of(Level.FAULT) // + .text("Low Voltage Error - Cell voltage minimal")), // + + HIGH_VOLTAGE_ERROR(Doc.of(Level.FAULT) // + .text("High Voltage Error - Cell voltage maximal")), // + + CHARGE_CURRENT_ERROR(Doc.of(Level.FAULT) // + .text("Charge Current Error - Imax-HW, Imax-SW, I-High (e.g. current dependend on temperature")), // + + DISCHARGE_CURRENT_ERROR(Doc.of(Level.FAULT) // + .text("Discharge Current Error - Imax-HW, Imax-SW, I-High (e.g. current dependend on temperature")), // + + CHARGE_POWER_ERROR(Doc.of(Level.FAULT) // + .text("Charge Power Error")), // + + DISCHARGE_POWER_ERROR(Doc.of(Level.FAULT) // + .text("Discharge Power Error")), // + + LOW_SOC_ERROR(Doc.of(Level.FAULT) // + .text("Low SOC Error")), // + + HIGH_SOC_ERROR(Doc.of(Level.FAULT) // + .text("High SOC Error")), // + + LOW_TEMPERATURE_ERROR(Doc.of(Level.FAULT) // + .text("Low Temperature Error - Cell temperature minimal")), // + + HIGH_TEMPERATURE_ERROR(Doc.of(Level.FAULT) // + .text("High Temperature Error - Cell temperature maximal")), // + + INSULATION_ERROR(Doc.of(Level.FAULT) // + .text("Insulation Error - I-Diff error (self test error, I-Diff > |300 mA|)")), // + + CONTACTOR_ERROR(Doc.of(Level.FAULT) // + .text("Contactor Error (contactor feedback signals")), // + + SENSOR_ERROR(Doc.of(Level.FAULT) // + .text("Sensor Error - Current sensor error")), // + + IMBALANCE_ERROR(Doc.of(Level.FAULT) // + .text("Imbalance Error - Static and dynamic cell imbalance (voltage)")), // + + COMMUNICATION_ERROR(Doc.of(Level.FAULT) // + .text("Communication Error - Batcom Error (Timeout), Master-Slave Can Error (Timeout)")), // + + /* + * ErrBits2 + */ + + CONTAINER_ERROR(Doc.of(Level.FAULT) // + .text("Container/(Room) Error")), // + + SOH_ERROR(Doc.of(Level.FAULT) // + .text("SOH Error")), // + + RACK_STING_ERROR(Doc.of(Level.FAULT) // + .text("Rack/String Error")), // + + /* + * WarnBits1 + */ + + UNSPECIFIED_WARNING(Doc.of(Level.WARNING) // + .text("Unspecified Warning - Cell-config-Error, Slave-count-Error")), // + + LOW_VOLTAGE_WARNING(Doc.of(Level.WARNING) // + .text("Low Voltage Error - Cell voltage high")), // + + HIGH_VOLTAGE_WARNING(Doc.of(Level.WARNING) // + .text("High Voltage Warning - Cell voltage high")), // + + CHARGE_CURRENT_WARNING(Doc.of(Level.WARNING) // + .text("Charge Current Warning - Imax-HW, Imax-SW, I-High (e.g. current dependend on temperature")), // + + DISCHARGE_CURRENT_WARNING(Doc.of(Level.WARNING) // + .text("Discharge Current Warning - Imax-HW, Imax-SW, I-High (e.g. current dependend on temperature")), // + + CHARGE_POWER_WARNING(Doc.of(Level.WARNING) // + .text("Charge Power Warning")), // + + DISCHARGE_POWER_WARNING(Doc.of(Level.WARNING) // + .text("Discharge Power Warning")), // + + LOW_SOC_WARNING(Doc.of(Level.WARNING) // + .text("Low SOC Warning")), // + + HIGH_SOC_WARNING(Doc.of(Level.WARNING) // + .text("High SOC Warning")), // + + LOW_TEMPERATURE_WARNING(Doc.of(Level.WARNING) // + .text("Low Temperature Warning - Cell temperature high")), // + + HIGH_TEMPERATURE_WARNING(Doc.of(Level.WARNING) // + .text("High Temperature Warning - Cell temperature high")), // + + INSULATION_WARNING(Doc.of(Level.WARNING) // + .text("Insulation Warning - I-Diff error (self test error, I-Diff > |300 mA|)")), // + + CONTACTOR_WARNING(Doc.of(Level.WARNING) // + .text("Contactor Warning (contactor feedback signals")), // + + SENSOR_WARNING(Doc.of(Level.WARNING) // + .text("Sensor Warning - Current sensor error")), // + + IMBALANCE_WARNING(Doc.of(Level.WARNING) // + .text("Imbalance Warning - Static and dynamic cell imbalance (voltage)")), // + + COMMUNICATION_WARNING(Doc.of(Level.WARNING) // + .text("Communication Warning - Batcom Error (Timeout), Master-Slave Can Error (Timeout)")), // + + // WarnBits2 + CONTAINER_WARNING(Doc.of(Level.WARNING) // + .text("Container/(Room) Warning")), // + + SOH_WARNING(Doc.of(Level.WARNING) // + .text("SOH Warning")), // + + RACK_STING_WARNING(Doc.of(Level.WARNING) // + .text("Rack/String Warning - min. 1 string is in error condition (disconnected)")), // + + // Read / write channels + BATTERY_STATE_COMMAND(Doc.of(BatteryStateCommand.values()) // + .accessMode(AccessMode.READ_WRITE)), // + + BATTERY_STATE(Doc.of(BatteryState.values()) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.HIGH)), // + + MAX_OPERATING_CURRENT(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_ONLY) // + .unit(Unit.AMPERE) // + .text("Absolute maximum operating (max. discharge current) current of battery")), // + + MIN_OPERATING_CURRENT(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_ONLY) // + .unit(Unit.AMPERE) // + .text("Absolute minimum operating current (max. charge current) of battery")), // + + MAX_DYNAMIC_VOLTAGE(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_ONLY) // + .unit(Unit.DEZIVOLT)), // + + MIN_DYNAMIC_VOLTAGE(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_ONLY) // + .unit(Unit.DEZIVOLT)), // + + CONNECTED_STRING_NUMBER(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_ONLY)), // + + INSTALLED_STRING_NUMBER(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_ONLY)), // + + BATTERY_TOTAL_SOC(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_ONLY) // + .unit(Unit.TENTHOUSANDTH) // + .text("Battery state of charge calculated for all strings, which are available (connected and not connected)")), // + + BATTERY_SOC(Doc.of(OpenemsType.DOUBLE) // + .accessMode(AccessMode.READ_ONLY) // + .unit(Unit.TENTHOUSANDTH) // + .onChannelChange(BatteryBmwImpl::updateSoc)), // + + REMAINING_CHARGE_CAPACITY(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_ONLY) // + .unit(Unit.AMPERE_HOURS)), // + + REMAINING_DISCHARGE_CAPACITY(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_ONLY) // + .unit(Unit.AMPERE_HOURS) // + .text("Remaining discharge capacity - Ah possible to charge from now on")), // + + REMANING_CHARGE_ENERGY(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_ONLY) // + .unit(Unit.KILOWATT_HOURS) // + .text("Remaining energy to charge")), // + + REMANING_DISCHARGE_ENERGY(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_ONLY) // + .unit(Unit.KILOWATT_HOURS) // + .text("Remaining energy to discharge")), // + + NOMINAL_ENERGY(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_ONLY) // + .unit(Unit.KILOWATT_HOURS) // + .text("Battery Nominal Energy (connected Racks)")), // + + NOMINAL_ENERGY_TOTAL(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_ONLY) // + .unit(Unit.KILOWATT_HOURS) // + .text("Battery Nominal Energy (all Racks)")), // + + NOMINAL_CAPACITY(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_ONLY) // + .unit(Unit.AMPERE_HOURS) // + .text("Battery Nominal Capacity (connected Racks)")), // + + // External voltage (at DC connector) of the battery + LINK_VOLTAGE(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_ONLY) // + .unit(Unit.DEZIVOLT) // + .onChannelChange(BatteryBmwImpl::updateVoltage)), // + + INTERNAL_VOLTAGE(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_ONLY) // + .unit(Unit.DEZIVOLT) // + .onChannelChange(BatteryBmwImpl::updateVoltage)), // + + AVG_BATTERY_TEMPERATURE(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.DEZIDEGREE_CELSIUS)), // + + MIN_BATTERY_TEMPERATURE(Doc.of(OpenemsType.INTEGER)// + .unit(Unit.DEZIDEGREE_CELSIUS)), // + + MAX_BATTERY_TEMPERATURE(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.DEZIDEGREE_CELSIUS)), // + + INSULATION_RESISTANCE(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_ONLY) // + .unit(Unit.KILOOHM) // + .text("Insulation Resistanc")), // + + DISCHARGE_MAX_CURRENT_HIGH_RESOLUTION(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_ONLY) // + .unit(Unit.DEZIAMPERE) // + .text("Battery maximum limit dynamic current (max. discharge current)")), // + + CHARGE_MAX_CURRENT_HIGH_RESOLUTION(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_ONLY) // + .unit(Unit.DEZIAMPERE) // + .text("Battery minimum limit dynamic current (max. charge current)")), // + + FULL_CYCLE_COUNT(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_ONLY) // + .text("Battery Full Cycles Count - count complete energy throughputs")), // + + // actual not implemented @ BCS side + HEART_BEAT(Doc.of(OpenemsType.INTEGER)// + .accessMode(AccessMode.READ_ONLY) // + .text("Life sign signal")), // + + AVG_CELL_TEMPERATURE(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.DEGREE_CELSIUS)), // + ; // + + private final Doc doc; + + private ChannelId(Doc doc) { + this.doc = doc; + } + + @Override + public Doc doc() { + return this.doc; + } + + } + + /** + * Gets the Channel for {@link ChannelId#STATE_MACHINE}. + * + * @return the Channel + */ + public default Channel getStateMachineChannel() { + return this.channel(ChannelId.STATE_MACHINE); + } + + /** + * Gets the {@link StateChannel} for {@link ChannelId#STATE_MACHINE}. + * + * @return the Channel {@link Value} + */ + public default Value getStateMachine() { + return this.getStateMachineChannel().value(); + } + + /** + * Internal method to set the 'nextValue' on {@link ChannelId#STATE_MACHINE} + * Channel. + * + * @param value the next value + */ + public default void _setStateMachine(State value) { + this.getStateMachineChannel().setNextValue(value); + } + + /** + * Gets the Channel for {@link ChannelId#RUN_FAILED}. + * + * @return the Channel + */ + public default Channel getRunFailedChannel() { + return this.channel(ChannelId.RUN_FAILED); + } + + /** + * Internal method to set the 'nextValue' on {@link ChannelId#RUN_FAILED} + * Channel. + * + * @param value the next value + */ + public default void _setRunFailed(boolean value) { + this.getRunFailedChannel().setNextValue(value); + } + + /** + * Gets the Channel for {@link ChannelId#UNEXPECTED_STOPPED_STATE}. + * + * @return the Channel + */ + public default StateChannel getUnexpectedStoppedStateChannel() { + return this.channel(ChannelId.UNEXPECTED_STOPPED_STATE); + } + + /** + * Gets the {@link StateChannel} for {@link ChannelId#UNEXPECTED_STOPPED_STATE}. + * + * @return the Channel {@link Value} + */ + public default Value getUnexpectedStoppedState() { + return this.getUnexpectedStoppedStateChannel().value(); + } + + /** + * Internal method to set the 'nextValue' on + * {@link ChannelId#UNEXPECTED_STOPPED_STATE} Channel. + * + * @param value the next value + */ + public default void _setUnexpectedStoppedState(boolean value) { + this.getUnexpectedStoppedStateChannel().setNextValue(value); + } + + /** + * Gets the Channel for {@link ChannelId#UNEXPECTED_RUNNING_STATE}. + * + * @return the Channel + */ + public default StateChannel getUnexpectedRunningStateChannel() { + return this.channel(ChannelId.UNEXPECTED_RUNNING_STATE); + } + + /** + * Gets the {@link StateChannel} for {@link ChannelId#UNEXPECTED_RUNNING_STATE}. + * + * @return the Channel {@link Value} + */ + public default Value getUnexpectedRunningState() { + return this.getUnexpectedRunningStateChannel().value(); + } + + /** + * Internal method to set the 'nextValue' on + * {@link ChannelId#UNEXPECTED_RUNNING_STATE} Channel. + * + * @param value the next value + */ + public default void _setUnexpectedRunningState(boolean value) { + this.getUnexpectedRunningStateChannel().setNextValue(value); + } + + /** + * Gets the Channel for {@link ChannelId#TIMEOUT_START_BATTERY}. + * + * @return the Channel + */ + public default StateChannel getTimeoutStartBatteryChannel() { + return this.channel(ChannelId.TIMEOUT_START_BATTERY); + } + + /** + * Gets the {@link StateChannel} for {@link ChannelId#TIMEOUT_START_BATTERY}. + * + * @return the Channel {@link Value} + */ + public default Value getTimeoutStartBattery() { + return this.getTimeoutStartBatteryChannel().value(); + } + + /** + * Internal method to set the 'nextValue' on + * {@link ChannelId#TIMEOUT_START_BATTERY} Channel. + * + * @param value the next value + */ + public default void _setTimeoutStartBattery(Boolean value) { + this.getTimeoutStartBatteryChannel().setNextValue(value); + } + + /** + * Gets the Channel for {@link ChannelId#TIMEOUT_STOP_BATTERY}. + * + * @return the Channel + */ + public default StateChannel getTimeoutStopBatteryChannel() { + return this.channel(ChannelId.TIMEOUT_STOP_BATTERY); + } + + /** + * Gets the {@link StateChannel} for {@link ChannelId#TIMEOUT_STOP_BATTERY}. + * + * @return the Channel {@link Value} + */ + public default Value getTimeoutStopBattery() { + return this.getTimeoutStopBatteryChannel().value(); + } + + /** + * Internal method to set the 'nextValue' on + * {@link ChannelId#TIMEOUT_STOP_BATTERY} Channel. + * + * @param value the next value + */ + public default void _setTimeoutStopBattery(Boolean value) { + this.getTimeoutStopBatteryChannel().setNextValue(value); + } + + /** + * Gets the Channel for {@link ChannelId#BATTERY_STATE_COMMAND}. + * + * @return the Channel {@link Channel} + */ + public default WriteChannel getBatteryStateCommandChannel() { + return this.channel(ChannelId.BATTERY_STATE_COMMAND); + } + + /** + * See {@link ChannelId#BATTERY_STATE_COMMAND}. + * + * @return the Channel {@link Value} + */ + public default Value getBatteryStateCommand() { + return this.getBatteryStateCommandChannel().value(); + } + + /** + * See {@link ChannelId#BATTERY_STATE_COMMAND}. + * + * @param value the next write value + * @throws OpenemsNamedException on error + */ + public default void setBatteryStateCommand(BatteryStateCommand value) throws OpenemsNamedException { + this.getBatteryStateCommandChannel().setNextWriteValue(value); + } + + /** + * Gets the Channel for {@link ChannelId#BATTERY_STATE}. + * + * @return the Channel {@link Channel} + */ + public default Channel getBatteryStateChannel() { + return this.channel(ChannelId.BATTERY_STATE); + } + + /** + * See {@link ChannelId#BATTERY_STATE}. + * + * @return the Channel {@link Value} + */ + public default Value getBatteryState() { + return this.getBatteryStateChannel().value(); + } + + /** + * Gets the Channel for {@link ChannelId#LINK_VOLTAGE}. + * + * @return the Channel + */ + public default Channel getLinkVoltageChannel() { + return this.channel(ChannelId.LINK_VOLTAGE); + } + + /** + * Gets the LinkVoltage, see {@link ChannelId#LINK_VOLTAGE}. + * + * @return the Channel {@link Value} + */ + public default Value getLinkVoltage() { + return this.getLinkVoltageChannel().value(); + } + + /* + * Gets the Channel for {@link ChannelId#INTERNAL_VOLTAGE}. + * + * @return the Channel + */ + public default Channel getInternalVoltageChannel() { + return this.channel(ChannelId.INTERNAL_VOLTAGE); + } + + /** + * Gets the {@link StateChannel} for {@link ChannelId#INTERNAL_VOLTAGE}. + * + * @return the Channel {@link Value} + */ + public default Value getInternalVoltage() { + return this.getInternalVoltageChannel().value(); + } + + /** + * Gets the Channel for {@link ChannelId#GO_RUNNING_STATE_MACHINE}. + * + * @return the Channel + */ + public default Channel getGoRunningStateMachineChannel() { + return this.channel(ChannelId.GO_RUNNING_STATE_MACHINE); + } + + /** + * Internal method to set the 'nextValue' on + * {@link ChannelId#GO_RUNNING_STATE_MACHINE} Channel. + * + * @param value the next value + */ + public default void _setGoRunningStateMachine(GoRunningSubState value) { + this.getGoRunningStateMachineChannel().setNextValue(value); + } + + /** + * Gets the Channel for {@link ChannelId#SOC_RAW_VALUE}. + * + * @return the Channel + */ + public default Channel getSocRawValueChannel() { + return this.channel(ChannelId.SOC_RAW_VALUE); + } + + /** + * Internal method to set the 'nextValue' on {@link ChannelId#SOC_RAW_VALUE} + * Channel. + * + * @param value the next value + */ + public default void _setSocRawValue(int value) { + this.getSocRawValueChannel().setNextValue(value); + } + + /** + * Gets the target Start/Stop mode from config or StartStop-Channel. + * + * @return {@link StartStop} + */ + public StartStop getStartStopTarget(); +} \ No newline at end of file diff --git a/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/BatteryBmwImpl.java b/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/BatteryBmwImpl.java new file mode 100644 index 00000000000..959dfcfcf01 --- /dev/null +++ b/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/BatteryBmwImpl.java @@ -0,0 +1,420 @@ +package io.openems.edge.battery.bmw; + +import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.DIRECT_1_TO_1; +import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.INVERT; +import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SCALE_FACTOR_MINUS_1; +import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SCALE_FACTOR_MINUS_2; + +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; + +import org.osgi.service.cm.ConfigurationAdmin; +import org.osgi.service.component.ComponentContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.ConfigurationPolicy; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; +import org.osgi.service.component.annotations.ReferencePolicyOption; +import org.osgi.service.event.Event; +import org.osgi.service.event.EventHandler; +import org.osgi.service.event.propertytypes.EventTopics; +import org.osgi.service.metatype.annotations.Designate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.openems.common.channel.AccessMode; +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.oem.OpenemsEdgeOem; +import io.openems.edge.battery.api.Battery; +import io.openems.edge.battery.bmw.enums.BatteryState; +import io.openems.edge.battery.bmw.enums.BatteryStateCommand; +import io.openems.edge.battery.bmw.statemachine.Context; +import io.openems.edge.battery.bmw.statemachine.StateMachine; +import io.openems.edge.battery.bmw.statemachine.StateMachine.State; +import io.openems.edge.battery.protection.BatteryProtection; +import io.openems.edge.bridge.http.api.BridgeHttp; +import io.openems.edge.bridge.http.api.BridgeHttp.Endpoint; +import io.openems.edge.bridge.http.api.BridgeHttpFactory; +import io.openems.edge.bridge.http.api.HttpMethod; +import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent; +import io.openems.edge.bridge.modbus.api.BridgeModbus; +import io.openems.edge.bridge.modbus.api.BridgeModbusTcp; +import io.openems.edge.bridge.modbus.api.ElementToChannelConverter; +import io.openems.edge.bridge.modbus.api.ModbusComponent; +import io.openems.edge.bridge.modbus.api.ModbusProtocol; +import io.openems.edge.bridge.modbus.api.element.BitsWordElement; +import io.openems.edge.bridge.modbus.api.element.DummyRegisterElement; +import io.openems.edge.bridge.modbus.api.element.SignedWordElement; +import io.openems.edge.bridge.modbus.api.element.UnsignedWordElement; +import io.openems.edge.bridge.modbus.api.task.FC16WriteRegistersTask; +import io.openems.edge.bridge.modbus.api.task.FC4ReadInputRegistersTask; +import io.openems.edge.common.channel.Channel; +import io.openems.edge.common.component.ComponentManager; +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.common.event.EdgeEventConstants; +import io.openems.edge.common.modbusslave.ModbusSlave; +import io.openems.edge.common.modbusslave.ModbusSlaveNatureTable; +import io.openems.edge.common.modbusslave.ModbusSlaveTable; +import io.openems.edge.common.startstop.StartStop; +import io.openems.edge.common.startstop.StartStoppable; +import io.openems.edge.common.taskmanager.Priority; +import io.openems.edge.common.type.TypeUtils; + +@Designate(ocd = Config.class, factory = true) +@Component(// + name = "Battery.BMW", // + immediate = true, // + configurationPolicy = ConfigurationPolicy.REQUIRE // +) +@EventTopics({ // + EdgeEventConstants.TOPIC_CYCLE_BEFORE_PROCESS_IMAGE // +}) +public class BatteryBmwImpl extends AbstractOpenemsModbusComponent + implements ModbusComponent, BatteryBmw, Battery, OpenemsComponent, EventHandler, ModbusSlave, StartStoppable { + + private static final int INNER_RESISTANCE = 200; // [mOhm] + private static final double MIN_ALLOWED_SOC = 4d; + private static final double MAX_ALLOWED_SOC = 96d; + private static final String URI_LOGIN = "login"; + + private final Logger log = LoggerFactory.getLogger(BatteryBmwImpl.class); + private final StateMachine stateMachine = new StateMachine(State.UNDEFINED); + + private Config config; + private BatteryProtection batteryProtection = null; + private final AtomicReference startStopTarget = new AtomicReference<>(StartStop.UNDEFINED); + + @Reference + private BmwToken token; + + @Reference + private ConfigurationAdmin cm; + + @Reference + private ComponentManager componentManager; + + @Reference + private OpenemsEdgeOem oem; + + @Reference + private BridgeHttpFactory httpBridgeFactory; + private BridgeHttp httpBridge; + + @Reference(policy = ReferencePolicy.STATIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.MANDATORY) + protected void setModbus(BridgeModbus modbus) { + super.setModbus(modbus); + } + + public BatteryBmwImpl() { + super(// + OpenemsComponent.ChannelId.values(), // + ModbusComponent.ChannelId.values(), // + Battery.ChannelId.values(), // + StartStoppable.ChannelId.values(), // + BatteryProtection.ChannelId.values(), // + BatteryBmw.ChannelId.values() // + ); + } + + @Activate + private void activate(ComponentContext context, Config config) throws OpenemsNamedException { + this.config = config; + if (super.activate(context, config.id(), config.alias(), config.enabled(), config.modbusUnitId(), this.cm, + "Modbus", config.modbus_id())) { + return; + } + this._setInnerResistance(INNER_RESISTANCE); + + // Initialize Battery-Protection + this.batteryProtection = BatteryProtection.create(this) // + .applyBatteryProtectionDefinition(new BatteryProtectionDefinition(), this.componentManager) // + .build(); + + this.httpBridge = this.httpBridgeFactory.get(); + + final var url = this.getUrl((BridgeModbusTcp) this.getBridgeModbus(), URI_LOGIN, ""); + + var auth = this.oem.getBmwBatteryAuth(); + if (auth == null) { + return; + } + final var userName = auth.a(); + final var password = auth.b(); + var outData = "{userCredentials: {name: \"" + userName + "\" , password: \"" + password + "\"}}"; + var map = Map.of(// + "Content-Type", "application/json"); + final var endPoint = new Endpoint(url, HttpMethod.POST, BridgeHttp.DEFAULT_CONNECT_TIMEOUT, + BridgeHttp.DEFAULT_READ_TIMEOUT, outData, map); + this.token.fetchToken(endPoint); + } + + public String getToken() { + return this.token.getToken(); + } + + @Deactivate + protected void deactivate() { + this.httpBridgeFactory.unget(this.httpBridge); + this.httpBridge = null; + super.deactivate(); + } + + @Override + protected ModbusProtocol defineModbusProtocol() { + return new ModbusProtocol(this, // + new FC16WriteRegistersTask(1400, // + m(BatteryBmw.ChannelId.BATTERY_STATE_COMMAND, new UnsignedWordElement(1400)) // + ), + + new FC4ReadInputRegistersTask(1000, Priority.HIGH, + m(BatteryBmw.ChannelId.BATTERY_STATE, new UnsignedWordElement(1000)), + + m(new BitsWordElement(1001, this) // ErrBits1 + .bit(0, BatteryBmw.ChannelId.UNSPECIFIED_ERROR) // + .bit(1, BatteryBmw.ChannelId.LOW_VOLTAGE_ERROR) // + .bit(2, BatteryBmw.ChannelId.HIGH_VOLTAGE_ERROR) // + .bit(3, BatteryBmw.ChannelId.CHARGE_CURRENT_ERROR) // + .bit(4, BatteryBmw.ChannelId.DISCHARGE_CURRENT_ERROR) // + .bit(5, BatteryBmw.ChannelId.CHARGE_POWER_ERROR) // + .bit(6, BatteryBmw.ChannelId.DISCHARGE_POWER_ERROR) // + .bit(7, BatteryBmw.ChannelId.LOW_SOC_ERROR) // + .bit(8, BatteryBmw.ChannelId.HIGH_SOC_ERROR) // + .bit(9, BatteryBmw.ChannelId.LOW_TEMPERATURE_ERROR) // + .bit(10, BatteryBmw.ChannelId.HIGH_TEMPERATURE_ERROR) // + .bit(11, BatteryBmw.ChannelId.INSULATION_ERROR) // + .bit(12, BatteryBmw.ChannelId.CONTACTOR_ERROR) // + .bit(13, BatteryBmw.ChannelId.SENSOR_ERROR) // + .bit(14, BatteryBmw.ChannelId.IMBALANCE_ERROR) // + .bit(15, BatteryBmw.ChannelId.COMMUNICATION_ERROR) // + + ), // + + m(new BitsWordElement(1002, this) // ErrBits2 + .bit(0, BatteryBmw.ChannelId.CONTAINER_ERROR) // + .bit(1, BatteryBmw.ChannelId.SOH_ERROR) // + .bit(2, BatteryBmw.ChannelId.RACK_STING_ERROR) // + ), // + + m(new BitsWordElement(1003, this) // WarnBits1 + .bit(0, BatteryBmw.ChannelId.UNSPECIFIED_WARNING) // + .bit(1, BatteryBmw.ChannelId.LOW_VOLTAGE_WARNING) // + .bit(2, BatteryBmw.ChannelId.HIGH_VOLTAGE_WARNING) // + .bit(3, BatteryBmw.ChannelId.CHARGE_CURRENT_WARNING) // + .bit(4, BatteryBmw.ChannelId.DISCHARGE_CURRENT_WARNING) // + .bit(5, BatteryBmw.ChannelId.CHARGE_POWER_WARNING) // + .bit(6, BatteryBmw.ChannelId.DISCHARGE_POWER_WARNING) // + .bit(7, BatteryBmw.ChannelId.LOW_SOC_WARNING) // + .bit(8, BatteryBmw.ChannelId.HIGH_SOC_WARNING) // + .bit(9, BatteryBmw.ChannelId.LOW_TEMPERATURE_WARNING) // + .bit(10, BatteryBmw.ChannelId.HIGH_TEMPERATURE_WARNING) // + .bit(11, BatteryBmw.ChannelId.INSULATION_WARNING) // + .bit(12, BatteryBmw.ChannelId.CONTACTOR_WARNING) // + .bit(13, BatteryBmw.ChannelId.SENSOR_WARNING) // + .bit(14, BatteryBmw.ChannelId.IMBALANCE_WARNING) // + .bit(15, BatteryBmw.ChannelId.COMMUNICATION_WARNING) // + ), // + + m(new BitsWordElement(1004, this) // WarnBits2 + .bit(0, BatteryBmw.ChannelId.CONTAINER_WARNING) // + .bit(1, BatteryBmw.ChannelId.SOH_WARNING) // + .bit(2, BatteryBmw.ChannelId.RACK_STING_WARNING) // + ), // + + new DummyRegisterElement(1005), // + m(BatteryBmw.ChannelId.MAX_OPERATING_CURRENT, new SignedWordElement(1006)), // + m(BatteryBmw.ChannelId.MIN_OPERATING_CURRENT, new SignedWordElement(1007)), // + m(Battery.ChannelId.CHARGE_MAX_VOLTAGE, new SignedWordElement(1008), SCALE_FACTOR_MINUS_1), // + m(Battery.ChannelId.DISCHARGE_MIN_VOLTAGE, new SignedWordElement(1009), SCALE_FACTOR_MINUS_1), // + m(BatteryProtection.ChannelId.BP_DISCHARGE_BMS, new SignedWordElement(1010)), // + m(BatteryProtection.ChannelId.BP_CHARGE_BMS, new SignedWordElement(1011), INVERT), + m(BatteryBmw.ChannelId.MAX_DYNAMIC_VOLTAGE, new UnsignedWordElement(1012)), + m(BatteryBmw.ChannelId.MIN_DYNAMIC_VOLTAGE, new UnsignedWordElement(1013)), + m(BatteryBmw.ChannelId.CONNECTED_STRING_NUMBER, new UnsignedWordElement(1014)), // + m(BatteryBmw.ChannelId.INSTALLED_STRING_NUMBER, new UnsignedWordElement(1015)), // + m(BatteryBmw.ChannelId.BATTERY_TOTAL_SOC, new UnsignedWordElement(1016)), + m(BatteryBmw.ChannelId.BATTERY_SOC, new UnsignedWordElement(1017)), + m(BatteryBmw.ChannelId.REMAINING_CHARGE_CAPACITY, new UnsignedWordElement(1018)), // + m(BatteryBmw.ChannelId.REMAINING_DISCHARGE_CAPACITY, new UnsignedWordElement(1019)), // + m(BatteryBmw.ChannelId.REMANING_CHARGE_ENERGY, new UnsignedWordElement(1020)), // + m(BatteryBmw.ChannelId.REMANING_DISCHARGE_ENERGY, new UnsignedWordElement(1021)), // + m(BatteryBmw.ChannelId.NOMINAL_ENERGY, new UnsignedWordElement(1022)), // + m(BatteryBmw.ChannelId.NOMINAL_ENERGY_TOTAL, new UnsignedWordElement(1023)), // + m(BatteryBmw.ChannelId.NOMINAL_CAPACITY, new UnsignedWordElement(1024)), // + m(Battery.ChannelId.CAPACITY, new UnsignedWordElement(1025)), // + m(Battery.ChannelId.SOH, new UnsignedWordElement(1026), SCALE_FACTOR_MINUS_2), // + // TODO battery.api + m(BatteryBmw.ChannelId.LINK_VOLTAGE, new UnsignedWordElement(1027)), // + m(BatteryBmw.ChannelId.INTERNAL_VOLTAGE, new UnsignedWordElement(1028)), // + m(Battery.ChannelId.CURRENT, new SignedWordElement(1029), SCALE_FACTOR_MINUS_1), // + m(BatteryBmw.ChannelId.AVG_BATTERY_TEMPERATURE, new UnsignedWordElement(1030), + ElementToChannelConverter.SCALE_FACTOR_MINUS_1), // + m(Battery.ChannelId.MIN_CELL_TEMPERATURE, new SignedWordElement(1031), SCALE_FACTOR_MINUS_1), // + m(Battery.ChannelId.MAX_CELL_TEMPERATURE, new SignedWordElement(1032), SCALE_FACTOR_MINUS_1), // + m(Battery.ChannelId.MIN_CELL_VOLTAGE, new SignedWordElement(1033), DIRECT_1_TO_1), // + m(Battery.ChannelId.MAX_CELL_VOLTAGE, new SignedWordElement(1034), DIRECT_1_TO_1), // + m(BatteryBmw.ChannelId.AVG_CELL_TEMPERATURE, new UnsignedWordElement(1035), + ElementToChannelConverter.SCALE_FACTOR_MINUS_2), // + new DummyRegisterElement(1036), // + m(BatteryBmw.ChannelId.INSULATION_RESISTANCE, new UnsignedWordElement(1037)), // + new DummyRegisterElement(1038, 1040), // + m(BatteryBmw.ChannelId.DISCHARGE_MAX_CURRENT_HIGH_RESOLUTION, new UnsignedWordElement(1041)), // + m(BatteryBmw.ChannelId.CHARGE_MAX_CURRENT_HIGH_RESOLUTION, new UnsignedWordElement(1042)), // + m(BatteryBmw.ChannelId.FULL_CYCLE_COUNT, new UnsignedWordElement(1043)) // + )); + } + + @Override + public String debugLog() { + return Battery.generateDebugLog(this, this.stateMachine); + } + + @Override + public ModbusSlaveTable getModbusSlaveTable(AccessMode accessMode) { + return new ModbusSlaveTable(// + OpenemsComponent.getModbusSlaveNatureTable(accessMode), // + Battery.getModbusSlaveNatureTable(accessMode), // + ModbusSlaveNatureTable.of(BatteryBmw.class, accessMode, 100) // + .build()); + } + + @Override + public void handleEvent(Event event) { + if (!this.isEnabled()) { + return; + } + switch (event.getTopic()) { + case EdgeEventConstants.TOPIC_CYCLE_BEFORE_PROCESS_IMAGE -> { + this.batteryProtection.apply(); + this.handleStateMachine(); + } + } + } + + /** + * Handles the State-Machine. + */ + private void handleStateMachine() { + final var currentState = this.stateMachine.getCurrentState(); + this._setStateMachine(currentState); + + // Initialize 'Start-Stop' Channel + this._setStartStop(StartStop.UNDEFINED); + + try { + var context = new Context(this, this.componentManager.getClock(), this.httpBridge); + this.stateMachine.run(context); + this._setRunFailed(false); + } catch (OpenemsNamedException e) { + this._setRunFailed(true); + this.logError(this.log, "StateMachine failed: " + e.getMessage()); + } + } + + @Override + public void setStartStop(StartStop value) { + if (this.startStopTarget.getAndSet(value) != value) { + this.stateMachine.forceNextState(State.UNDEFINED); + } + } + + @Override + public StartStop getStartStopTarget() { + return switch (this.config.startStop()) { + case AUTO -> this.startStopTarget.get(); + case START -> StartStop.START; + case STOP -> StartStop.STOP; + }; + } + + protected synchronized void updateSoc() { + Channel batterySocChannel = this.channel(BatteryBmw.ChannelId.BATTERY_SOC); + var batterySoc = batterySocChannel.value(); + var soc = batterySoc.asOptional().map(t -> { + var calculatedBatterySoc = (int) ((t / 100.0 - MIN_ALLOWED_SOC) + * (100.0 / (MAX_ALLOWED_SOC - MIN_ALLOWED_SOC)) * 10.0); + this._setSocRawValue(calculatedBatterySoc); + if (calculatedBatterySoc < 0) { + return 0; + } + if (calculatedBatterySoc > 1000) { + return 100; + } + return calculatedBatterySoc / 10; + }).orElse(null); + this._setSoc(soc); + } + + /** + * The internal voltage is preferred when the battery is started, as the + * internal voltage is more accurate than the junction voltage. + */ + protected synchronized void updateVoltage() { + Integer batteryVoltage = null; + if (this.getInternalVoltage().isDefined() && this.getLinkVoltage().isDefined()) { + if (this.isStarted()) { + batteryVoltage = this.getInternalVoltage().get(); + } else { + batteryVoltage = this.getLinkVoltage().get(); + } + } + this._setVoltage(TypeUtils.divide(batteryVoltage, 10)); + } + + /** + * Start the battery. + */ + public void startBattery() { + try { + this.setBatteryStateCommand(BatteryStateCommand.CLOSE_CONTACTOR); + } catch (OpenemsNamedException e) { + this.logError(this.log, "Battery can not start : " + e.getMessage()); + } + } + + /** + * Stop the battery. + */ + public void stopBattery() { + try { + this.setBatteryStateCommand(BatteryStateCommand.OPEN_CONTACTOR); + } catch (OpenemsNamedException e) { + this.logError(this.log, "Battery can not stop : " + e.getMessage()); + } + } + + /** + * Check if battery is in running state. + * + * @return true if battery is started + */ + public boolean isRunning() { + return this.getBatteryState().asEnum() == BatteryState.OPERATION; + } + + /** + * Check if battery is in stopped state. + * + * @return true if battery is stopped + */ + public boolean isShutdown() { + final var batterySystemState = this.getBatteryState().asEnum(); + return batterySystemState == BatteryState.READY || batterySystemState == BatteryState.OFF + || batterySystemState == BatteryState.UNDEFINED; + } + + /** + * Gets the URL from ip address, uri and battery id. + * + * @param bridge the modbus bridge + * @param uri the uri + * @param id the battery id + * @return the url + */ + public String getUrl(BridgeModbusTcp bridge, String uri, String id) { + return "http://" + bridge.getIpAddress().getHostAddress() + "/" + uri + "/" + id; + } + +} \ No newline at end of file diff --git a/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/BatteryProtectionDefinition.java b/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/BatteryProtectionDefinition.java new file mode 100644 index 00000000000..d2a3a99f50a --- /dev/null +++ b/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/BatteryProtectionDefinition.java @@ -0,0 +1,94 @@ +package io.openems.edge.battery.bmw; + +import io.openems.edge.battery.protection.force.ForceCharge; +import io.openems.edge.battery.protection.force.ForceDischarge; +import io.openems.edge.common.linecharacteristic.PolyLine; + +public class BatteryProtectionDefinition implements io.openems.edge.battery.protection.BatteryProtectionDefinition { + @Override + public int getInitialBmsMaxEverChargeCurrent() { + return 135; // [A] + } + + @Override + public int getInitialBmsMaxEverDischargeCurrent() { + return 135; // [A] + } + + // Over voltage Protection + @Override + public PolyLine getChargeVoltageToPercent() { + return PolyLine.create() // + .addPoint(3304, 1) // + .addPoint(Math.nextUp(3304), 1) // + .addPoint(4160, 1) // + .addPoint(4164, 0.2) // + .addPoint(4174, 0.05) // + .addPoint(Math.nextDown(4180), 0.01) // + .addPoint(4180, 0) // + .build(); + } + + // Low Voltage protection + @Override + public PolyLine getDischargeVoltageToPercent() { + return PolyLine.create() // + .addPoint(3304, 0) // + .addPoint(Math.nextUp(3308), 0.01) // + .addPoint(3314, 0.1) // + .addPoint(3340, 1) // + .addPoint(4180, 1) // + .addPoint(Math.nextUp(4180), 1) // + .build(); + } + + @Override + public PolyLine getChargeTemperatureToPercent() { + return PolyLine.create() // + .addPoint(-20, 0.01) // + .addPoint(Math.nextUp(-20), 0.01) // + .addPoint(25, 1) // + .addPoint(35, 1) // + .addPoint(Math.nextDown(44), 0.01) // + .addPoint(45, 0) // + .build(); + } + + @Override + public PolyLine getDischargeTemperatureToPercent() { + return PolyLine.create() // + .addPoint(-36, 0.01) // + .addPoint(Math.nextUp(-36), 0.01) // + .addPoint(5, 1) // + .addPoint(35, 1) // + .addPoint(Math.nextDown(44), 0.01) // + .addPoint(45, 0) // + .build(); + } + + @Override + public ForceDischarge.Params getForceDischargeParams() { + return new ForceDischarge.Params(4186, 4183, 4180); + } + + @Override + public ForceCharge.Params getForceChargeParams() { + return new ForceCharge.Params(3296, 3298, 3300); + + } + + @Override + public Double getMaxIncreaseAmperePerSecond() { + return 1.; // [A] per second + } + + @Override + public PolyLine getChargeSocToPercent() { + return PolyLine.empty(); + } + + @Override + public PolyLine getDischargeSocToPercent() { + return PolyLine.empty(); + } +} \ No newline at end of file diff --git a/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/BmwBattery.java b/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/BmwBattery.java deleted file mode 100644 index b5afa188d90..00000000000 --- a/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/BmwBattery.java +++ /dev/null @@ -1,288 +0,0 @@ -package io.openems.edge.battery.bmw; - -import org.osgi.service.event.EventHandler; - -import io.openems.common.channel.AccessMode; -import io.openems.common.channel.PersistencePriority; -import io.openems.common.channel.Unit; -import io.openems.common.types.OpenemsType; -import io.openems.edge.battery.bmw.enums.BmsState; -import io.openems.edge.battery.bmw.enums.ErrorBits1; -import io.openems.edge.battery.bmw.enums.ErrorBits2; -import io.openems.edge.battery.bmw.enums.InfoBits; -import io.openems.edge.battery.bmw.enums.State; -import io.openems.edge.battery.bmw.enums.WarningBits1; -import io.openems.edge.battery.bmw.enums.WarningBits2; -import io.openems.edge.common.channel.BooleanDoc; -import io.openems.edge.common.channel.Doc; -import io.openems.edge.common.channel.IntegerDoc; -import io.openems.edge.common.component.OpenemsComponent; - -public interface BmwBattery extends OpenemsComponent, EventHandler { - - public static enum ChannelId implements io.openems.edge.common.channel.ChannelId { - - HEART_BEAT_DEBUG(Doc.of(OpenemsType.INTEGER)), // - HEART_BEAT(new IntegerDoc() // - .accessMode(AccessMode.READ_WRITE) // - .onChannelSetNextWriteMirrorToDebugChannel(ChannelId.HEART_BEAT_DEBUG)), - - BMS_STATE_COMMAND(Doc.of(OpenemsType.INTEGER).accessMode(AccessMode.READ_WRITE)), // - - BMS_STATE_COMMAND_RESET_DEBUG(Doc.of(OpenemsType.BOOLEAN)), // - BMS_STATE_COMMAND_RESET(new BooleanDoc() // - .accessMode(AccessMode.READ_WRITE) // - .onChannelSetNextWriteMirrorToDebugChannel(ChannelId.BMS_STATE_COMMAND_RESET_DEBUG)), - - BMS_STATE_COMMAND_CLEAR_ERROR_DEBUG(Doc.of(OpenemsType.BOOLEAN)), // - BMS_STATE_COMMAND_CLEAR_ERROR(new BooleanDoc() // - .accessMode(AccessMode.READ_WRITE) // - .onChannelSetNextWriteMirrorToDebugChannel(ChannelId.BMS_STATE_COMMAND_CLEAR_ERROR_DEBUG)), - - BMS_STATE_COMMAND_CLOSE_PRECHARGE_DEBUG(Doc.of(OpenemsType.BOOLEAN)), // - BMS_STATE_COMMAND_CLOSE_PRECHARGE(new BooleanDoc() // - .accessMode(AccessMode.READ_WRITE) // - .onChannelSetNextWriteMirrorToDebugChannel(ChannelId.BMS_STATE_COMMAND_CLOSE_PRECHARGE_DEBUG)), - - BMS_STATE_COMMAND_ERROR_DEBUG(Doc.of(OpenemsType.BOOLEAN)), // - BMS_STATE_COMMAND_ERROR(new BooleanDoc() // - .accessMode(AccessMode.READ_WRITE) // - .onChannelSetNextWriteMirrorToDebugChannel(ChannelId.BMS_STATE_COMMAND_ERROR_DEBUG)), - - BMS_STATE_COMMAND_CLOSE_CONTACTOR_DEBUG(Doc.of(OpenemsType.BOOLEAN)), // - BMS_STATE_COMMAND_CLOSE_CONTACTOR(new BooleanDoc() // - .accessMode(AccessMode.READ_WRITE) // - .onChannelSetNextWriteMirrorToDebugChannel(ChannelId.BMS_STATE_COMMAND_CLOSE_CONTACTOR_DEBUG)), - - BMS_STATE_COMMAND_WAKE_UP_FROM_STOP_DEBUG(Doc.of(OpenemsType.BOOLEAN)), // - BMS_STATE_COMMAND_WAKE_UP_FROM_STOP(new BooleanDoc() // - .accessMode(AccessMode.READ_WRITE) // - .onChannelSetNextWriteMirrorToDebugChannel(ChannelId.BMS_STATE_COMMAND_WAKE_UP_FROM_STOP_DEBUG)), - - BMS_STATE_COMMAND_ENABLE_BATTERY_DEBUG(Doc.of(OpenemsType.BOOLEAN)), // - BMS_STATE_COMMAND_ENABLE_BATTERY(new BooleanDoc() // - .accessMode(AccessMode.READ_WRITE) // - .onChannelSetNextWriteMirrorToDebugChannel(ChannelId.BMS_STATE_COMMAND_ENABLE_BATTERY_DEBUG)), - - OPERATING_STATE_INVERTER_DEBUG(Doc.of(OpenemsType.INTEGER)), // - OPERATING_STATE_INVERTER(new IntegerDoc() // - .accessMode(AccessMode.READ_WRITE) // - .onChannelSetNextWriteMirrorToDebugChannel(ChannelId.OPERATING_STATE_INVERTER_DEBUG)), - - DC_LINK_VOLTAGE_DEBUG(Doc.of(OpenemsType.INTEGER)), // - DC_LINK_VOLTAGE(new IntegerDoc() // - .accessMode(AccessMode.READ_WRITE) // - .unit(Unit.VOLT).onChannelSetNextWriteMirrorToDebugChannel(ChannelId.DC_LINK_VOLTAGE_DEBUG)), - - DC_LINK_CURRENT_DEBUG(Doc.of(OpenemsType.INTEGER)), // - DC_LINK_CURRENT(new IntegerDoc() // - .accessMode(AccessMode.READ_WRITE) // - .onChannelSetNextWriteMirrorToDebugChannel(ChannelId.DC_LINK_VOLTAGE_DEBUG)), - - OPERATION_MODE_REQUEST_GRANTED_DEBUG(Doc.of(OpenemsType.INTEGER)), // - OPERATION_MODE_REQUEST_GRANTED(new IntegerDoc() // - .accessMode(AccessMode.READ_WRITE) // - .onChannelSetNextWriteMirrorToDebugChannel(ChannelId.OPERATION_MODE_REQUEST_GRANTED_DEBUG)), - - OPERATION_MODE_REQUEST_CANCELED_DEBUG(Doc.of(OpenemsType.INTEGER)), // - OPERATION_MODE_REQUEST_CANCELED(new IntegerDoc() // - .accessMode(AccessMode.READ_WRITE) // - .onChannelSetNextWriteMirrorToDebugChannel(ChannelId.OPERATION_MODE_REQUEST_CANCELED_DEBUG)), - - CONNECTION_STRATEGY_HIGH_SOC_FIRST_DEBUG(Doc.of(OpenemsType.BOOLEAN)), // - CONNECTION_STRATEGY_HIGH_SOC_FIRST(new BooleanDoc() // - .accessMode(AccessMode.READ_WRITE) // - .onChannelSetNextWriteMirrorToDebugChannel(ChannelId.CONNECTION_STRATEGY_HIGH_SOC_FIRST_DEBUG)), - - CONNECTION_STRATEGY_LOW_SOC_FIRST_DEBUG(Doc.of(OpenemsType.BOOLEAN)), // - CONNECTION_STRATEGY_LOW_SOC_FIRST(new BooleanDoc() // - .accessMode(AccessMode.READ_WRITE) // - .onChannelSetNextWriteMirrorToDebugChannel(ChannelId.CONNECTION_STRATEGY_LOW_SOC_FIRST_DEBUG)), - - SYSTEM_TIME_DEBUG(Doc.of(OpenemsType.INTEGER)), // - SYSTEM_TIME(new IntegerDoc() // - .accessMode(AccessMode.READ_WRITE) // - .onChannelSetNextWriteMirrorToDebugChannel(ChannelId.SYSTEM_TIME_DEBUG)), - - // Read only channels - LIFE_SIGN(Doc.of(OpenemsType.INTEGER) // - .accessMode(AccessMode.READ_ONLY)), // - - BMS_STATE(Doc.of(BmsState.values()) // - .accessMode(AccessMode.READ_ONLY)), // - - ERROR_BITS_1(Doc.of(ErrorBits1.values()) // - .accessMode(AccessMode.READ_ONLY)), // - - ERROR_BITS_2(Doc.of(ErrorBits2.values()) // - .accessMode(AccessMode.READ_ONLY)), // - - WARNING_BITS_1(Doc.of(WarningBits1.values()) // - .accessMode(AccessMode.READ_ONLY)), // - - WARNING_BITS_2(Doc.of(WarningBits2.values()) // - .accessMode(AccessMode.READ_ONLY)), // - - INFO_BITS(Doc.of(InfoBits.values()) // - .accessMode(AccessMode.READ_ONLY)), // - - MAXIMUM_OPERATING_CURRENT(Doc.of(OpenemsType.INTEGER) // - .accessMode(AccessMode.READ_ONLY) // - .unit(Unit.AMPERE)), // - - MINIMUM_OPERATING_CURRENT(Doc.of(OpenemsType.INTEGER) // - .accessMode(AccessMode.READ_ONLY) // - .unit(Unit.AMPERE)), // - - MAXIMUM_OPERATING_VOLTAGE(Doc.of(OpenemsType.INTEGER) // - .accessMode(AccessMode.READ_ONLY) // - .unit(Unit.VOLT)), // - - MINIMUM_OPERATING_VOLTAGE(Doc.of(OpenemsType.INTEGER) // - .accessMode(AccessMode.READ_ONLY) // - .unit(Unit.VOLT)), // - - MAXIMUM_LIMIT_DYNAMIC_VOLTAGE(Doc.of(OpenemsType.INTEGER) // - .accessMode(AccessMode.READ_ONLY) // - .unit(Unit.VOLT)), // - - MINIMUM_LIMIT_DYNAMIC_VOLTAGE(Doc.of(OpenemsType.INTEGER) // - .accessMode(AccessMode.READ_ONLY) // - .unit(Unit.VOLT)), // - - NUMBER_OF_STRINGS_CONNECTED(Doc.of(OpenemsType.INTEGER) // - .accessMode(AccessMode.READ_ONLY)), // - - NUMBER_OF_STRINGS_INSTALLED(Doc.of(OpenemsType.INTEGER) // - .accessMode(AccessMode.READ_ONLY)), // - - SOC_ALL_STRINGS(Doc.of(OpenemsType.INTEGER) // - .accessMode(AccessMode.READ_ONLY) // - .unit(Unit.PERCENT)), // - - SOC_CONNECTED_STRINGS(Doc.of(OpenemsType.INTEGER) // - .accessMode(AccessMode.READ_ONLY) // - .unit(Unit.PERCENT)), // - - REMAINING_CHARGE_CAPACITY(Doc.of(OpenemsType.INTEGER) // - .accessMode(AccessMode.READ_ONLY) // - .unit(Unit.AMPERE_HOURS)), // - - REMAINING_DISCHARGE_CAPACITY(Doc.of(OpenemsType.INTEGER) // - .accessMode(AccessMode.READ_ONLY) // - .unit(Unit.AMPERE_HOURS)), // - - REMAINING_CHARGE_ENERGY(Doc.of(OpenemsType.INTEGER) // - .accessMode(AccessMode.READ_ONLY) // - .unit(Unit.KILOWATT_HOURS)), // - - REMAINING_DISCHARGE_ENERGY(Doc.of(OpenemsType.INTEGER) // - .accessMode(AccessMode.READ_ONLY) // - .unit(Unit.KILOWATT_HOURS)), // - - NOMINAL_ENERGY(Doc.of(OpenemsType.INTEGER) // - .accessMode(AccessMode.READ_ONLY) // - .unit(Unit.KILOWATT_HOURS)), // - - TOTAL_ENERGY(Doc.of(OpenemsType.INTEGER) // - .accessMode(AccessMode.READ_ONLY) // - .unit(Unit.KILOWATT_HOURS)), // - - NOMINAL_CAPACITY(Doc.of(OpenemsType.INTEGER) // - .accessMode(AccessMode.READ_ONLY) // - .unit(Unit.AMPERE_HOURS)), // - - TOTAL_CAPACITY(Doc.of(OpenemsType.INTEGER) // - .accessMode(AccessMode.READ_ONLY) // - .unit(Unit.AMPERE_HOURS)), // - - DC_VOLTAGE_CONNECTED_RACKS(Doc.of(OpenemsType.INTEGER) // - .accessMode(AccessMode.READ_ONLY) // - .unit(Unit.VOLT)), // - - DC_VOLTAGE_AVERAGE(Doc.of(OpenemsType.INTEGER) // - .accessMode(AccessMode.READ_ONLY) // - .unit(Unit.VOLT)), // - - DC_CURRENT(Doc.of(OpenemsType.INTEGER) // - .accessMode(AccessMode.READ_ONLY) // - .unit(Unit.MILLIAMPERE)), // - - AVERAGE_TEMPERATURE(Doc.of(OpenemsType.INTEGER) // - .accessMode(AccessMode.READ_ONLY) // - .unit(Unit.DEZIDEGREE_CELSIUS)), // - - MINIMUM_TEMPERATURE(Doc.of(OpenemsType.INTEGER) // - .accessMode(AccessMode.READ_ONLY) // - .unit(Unit.DEZIDEGREE_CELSIUS)), // - - MAXIMUM_TEMPERATURE(Doc.of(OpenemsType.INTEGER) // - .accessMode(AccessMode.READ_ONLY) // - .unit(Unit.DEZIDEGREE_CELSIUS)), // - - AVERAGE_CELL_VOLTAGE(Doc.of(OpenemsType.INTEGER) // - .accessMode(AccessMode.READ_ONLY) // - .unit(Unit.MILLIVOLT)), // - - INTERNAL_RESISTANCE(Doc.of(OpenemsType.INTEGER) // - .accessMode(AccessMode.READ_ONLY) // - .unit(Unit.MILLIOHM)), // - - INSULATION_RESISTANCE(Doc.of(OpenemsType.INTEGER) // - .accessMode(AccessMode.READ_ONLY) // - .unit(Unit.KILOOHM)), // - - CONTAINER_TEMPERATURE(Doc.of(OpenemsType.INTEGER) // - .accessMode(AccessMode.READ_ONLY) // - .unit(Unit.DEZIDEGREE_CELSIUS)), // - - AMBIENT_TEMPERATURE(Doc.of(OpenemsType.INTEGER) // - .accessMode(AccessMode.READ_ONLY) // - .unit(Unit.DEZIDEGREE_CELSIUS)), // - - HUMIDITY_CONTAINER(Doc.of(OpenemsType.INTEGER) // - .accessMode(AccessMode.READ_ONLY) // - .unit(Unit.PERCENT)), // - - MAXIMUM_LIMIT_DYNAMIC_CURRENT_HIGH_RES(Doc.of(OpenemsType.INTEGER) // - .accessMode(AccessMode.READ_ONLY) // - .unit(Unit.MILLIAMPERE)), // - - MINIMUM_LIMIT_DYNAMIC_CURRENT_HIGH_RES(Doc.of(OpenemsType.INTEGER) // - .accessMode(AccessMode.READ_ONLY) // - .unit(Unit.MILLIAMPERE)), // - - FULL_CYCLE_COUNT(Doc.of(OpenemsType.INTEGER) // - .accessMode(AccessMode.READ_ONLY)), // - - OPERATING_TIME_COUNT(Doc.of(OpenemsType.INTEGER) // - .accessMode(AccessMode.READ_ONLY) // - .unit(Unit.HOUR)), // - - COM_PRO_VERSION(Doc.of(OpenemsType.INTEGER) // - .accessMode(AccessMode.READ_ONLY)), // - - SERIAL_NUMBER(Doc.of(OpenemsType.INTEGER) // - .persistencePriority(PersistencePriority.HIGH) // - .accessMode(AccessMode.READ_ONLY)), // - - SOFTWARE_VERSION(Doc.of(OpenemsType.INTEGER) // - .accessMode(AccessMode.READ_ONLY)), // - - STATE_MACHINE(Doc.of(State.values()) // - .accessMode(AccessMode.READ_ONLY)), // - ; - - private final Doc doc; - - private ChannelId(Doc doc) { - this.doc = doc; - } - - @Override - public Doc doc() { - return this.doc; - } - - } -} \ No newline at end of file diff --git a/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/BmwBatteryImpl.java b/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/BmwBatteryImpl.java deleted file mode 100644 index 7c660f82094..00000000000 --- a/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/BmwBatteryImpl.java +++ /dev/null @@ -1,458 +0,0 @@ -package io.openems.edge.battery.bmw; - -import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.DIRECT_1_TO_1; -import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.INVERT; -import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SCALE_FACTOR_2; -import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SCALE_FACTOR_MINUS_1; -import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SCALE_FACTOR_MINUS_2; - -import java.time.LocalDateTime; - -import org.osgi.service.cm.ConfigurationAdmin; -import org.osgi.service.component.ComponentContext; -import org.osgi.service.component.annotations.Activate; -import org.osgi.service.component.annotations.Component; -import org.osgi.service.component.annotations.ConfigurationPolicy; -import org.osgi.service.component.annotations.Deactivate; -import org.osgi.service.component.annotations.Reference; -import org.osgi.service.component.annotations.ReferenceCardinality; -import org.osgi.service.component.annotations.ReferencePolicy; -import org.osgi.service.component.annotations.ReferencePolicyOption; -import org.osgi.service.event.Event; -import org.osgi.service.event.EventHandler; -import org.osgi.service.event.propertytypes.EventTopics; -import org.osgi.service.metatype.annotations.Designate; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import io.openems.common.channel.AccessMode; -import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.edge.battery.api.Battery; -import io.openems.edge.battery.bmw.enums.BmsState; -import io.openems.edge.battery.bmw.enums.State; -import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent; -import io.openems.edge.bridge.modbus.api.BridgeModbus; -import io.openems.edge.bridge.modbus.api.ModbusComponent; -import io.openems.edge.bridge.modbus.api.ModbusProtocol; -import io.openems.edge.bridge.modbus.api.element.BitsWordElement; -import io.openems.edge.bridge.modbus.api.element.SignedWordElement; -import io.openems.edge.bridge.modbus.api.element.UnsignedDoublewordElement; -import io.openems.edge.bridge.modbus.api.element.UnsignedWordElement; -import io.openems.edge.bridge.modbus.api.task.FC16WriteRegistersTask; -import io.openems.edge.bridge.modbus.api.task.FC4ReadInputRegistersTask; -import io.openems.edge.common.channel.BooleanWriteChannel; -import io.openems.edge.common.channel.EnumReadChannel; -import io.openems.edge.common.channel.IntegerWriteChannel; -import io.openems.edge.common.component.OpenemsComponent; -import io.openems.edge.common.event.EdgeEventConstants; -import io.openems.edge.common.modbusslave.ModbusSlave; -import io.openems.edge.common.modbusslave.ModbusSlaveTable; -import io.openems.edge.common.startstop.StartStop; -import io.openems.edge.common.startstop.StartStoppable; -import io.openems.edge.common.taskmanager.Priority; - -@Designate(ocd = Config.class, factory = true) -@Component(// - name = "Bmw.Battery", // - immediate = true, // - configurationPolicy = ConfigurationPolicy.REQUIRE // -) -@EventTopics({ // - EdgeEventConstants.TOPIC_CYCLE_AFTER_PROCESS_IMAGE // -}) -public class BmwBatteryImpl extends AbstractOpenemsModbusComponent - implements BmwBattery, Battery, ModbusComponent, OpenemsComponent, EventHandler, ModbusSlave, StartStoppable { - - private static final Integer OPEN_CONTACTORS = 0; - private static final Integer CLOSE_CONTACTORS = 4; - - private final Logger log = LoggerFactory.getLogger(BmwBatteryImpl.class); - - @Reference - private ConfigurationAdmin cm; - - @Override - @Reference(policy = ReferencePolicy.STATIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.MANDATORY) - protected void setModbus(BridgeModbus modbus) { - super.setModbus(modbus); - } - - private State state = State.UNDEFINED; - private Config config; - private LocalDateTime errorDelayIsOver = null; - private int unsuccessfulStarts = 0; - private LocalDateTime startAttemptTime = null; - private LocalDateTime pendingTimestamp; - - public BmwBatteryImpl() { - super(// - OpenemsComponent.ChannelId.values(), // - ModbusComponent.ChannelId.values(), // - Battery.ChannelId.values(), // - StartStoppable.ChannelId.values(), // - BmwBattery.ChannelId.values() // - ); - } - - @Activate - private void activate(ComponentContext context, Config config) throws OpenemsNamedException { - this.config = config; - if (super.activate(context, config.id(), config.alias(), config.enabled(), config.modbusUnitId(), this.cm, - "Modbus", config.modbus_id())) { - return; - } - } - - @Override - @Deactivate - protected void deactivate() { - super.deactivate(); - } - - private void handleStateMachine() { - var readyForWorking = false; - switch (this.getStateMachineState()) { - case ERROR: - this.clearError(); - // TODO Reset BMS? anything else? - this.errorDelayIsOver = LocalDateTime.now().plusSeconds(this.config.errorDelay()); - this.setStateMachineState(State.ERRORDELAY); - break; - case ERRORDELAY: - if (LocalDateTime.now().isAfter(this.errorDelayIsOver)) { - this.errorDelayIsOver = null; - if (this.isError()) { - this.setStateMachineState(State.ERROR); - } else { - this.setStateMachineState(State.OFF); - } - } - break; - case INIT: - if (this.isSystemRunning()) { - this.setStateMachineState(State.RUNNING); - this.unsuccessfulStarts = 0; - this.startAttemptTime = null; - } else if (this.startAttemptTime.plusSeconds(this.config.maxStartTime()).isBefore(LocalDateTime.now())) { - this.startAttemptTime = null; - this.unsuccessfulStarts++; - this.stopSystem(); - this.setStateMachineState(State.STOPPING); - if (this.unsuccessfulStarts >= this.config.maxStartAttempts()) { - this.errorDelayIsOver = LocalDateTime.now().plusSeconds(this.config.startUnsuccessfulDelay()); - this.setStateMachineState(State.ERRORDELAY); - this.unsuccessfulStarts = 0; - } - } - break; - case OFF: - this.logDebug(this.log, "in case 'OFF'; try to start the system"); - this.startSystem(); - this.logDebug(this.log, "set state to 'INIT'"); - this.setStateMachineState(State.INIT); - this.startAttemptTime = LocalDateTime.now(); - break; - case RUNNING: - if (this.isError()) { - this.setStateMachineState(State.ERROR); - } else if (!this.isSystemRunning()) { - this.setStateMachineState(State.UNDEFINED); - } else { - this.setStateMachineState(State.RUNNING); - readyForWorking = true; - } - break; - case STOPPING: - if (this.isError()) { - this.setStateMachineState(State.ERROR); - } else if (this.isSystemStopped()) { - this.setStateMachineState(State.OFF); - } - break; - case UNDEFINED: - if (this.isError()) { - this.setStateMachineState(State.ERROR); - } else if (this.isSystemStopped()) { - this.setStateMachineState(State.OFF); - } else if (this.isSystemRunning()) { - this.setStateMachineState(State.RUNNING); - } else if (this.isSystemStatePending()) { - this.setStateMachineState(State.PENDING); - } - break; - case PENDING: - if (this.pendingTimestamp == null) { - this.pendingTimestamp = LocalDateTime.now(); - } - if (this.pendingTimestamp.plusSeconds(this.config.pendingTolerance()).isBefore(LocalDateTime.now())) { - // System state could not be determined, stop and start it - this.pendingTimestamp = null; - this.stopSystem(); - this.setStateMachineState(State.OFF); - } else if (this.isError()) { - this.setStateMachineState(State.ERROR); - this.pendingTimestamp = null; - } else if (this.isSystemStopped()) { - this.setStateMachineState(State.OFF); - this.pendingTimestamp = null; - } else if (this.isSystemRunning()) { - this.setStateMachineState(State.RUNNING); - this.pendingTimestamp = null; - } - break; - case STANDBY: - break; - } - - // this.getReadyForWorking().setNextValue(readyForWorking); - if (readyForWorking) { - this._setStartStop(StartStop.START); - } else { - this._setStartStop(StartStop.STOP); - } - } - - private void clearError() { - BooleanWriteChannel clearErrorChannel = this.channel(BmwBattery.ChannelId.BMS_STATE_COMMAND_CLEAR_ERROR); - try { - clearErrorChannel.setNextWriteValue(true); - } catch (OpenemsNamedException e) { - // TODO should Fault state channel, but after start stop feature - this.logError(this.log, "Error while trying to reset the system!"); - } - } - - @Override - public void handleEvent(Event event) { - if (!this.isEnabled()) { - return; - } - switch (event.getTopic()) { - case EdgeEventConstants.TOPIC_CYCLE_AFTER_PROCESS_IMAGE: - this.handleBatteryState(); - break; - } - } - - private void handleBatteryState() { - switch (this.config.batteryState()) { - case DEFAULT: - this.handleStateMachine(); - break; - case OFF: - this.stopSystem(); - break; - case ON: - this.startSystem(); - break; - } - } - - // TODO Check this needed or not - // public void shutDownBattery() { - // SymmetricEss ess; - // try { - // ess = this.manager.getComponent(this.config.Inverter_id()); - // } catch (OpenemsNamedException e1) { - // - // e1.printStackTrace(); - // return; - // } - // int activePowerInverter = ess.getActivePower().value().orElse(0); - // int reactivePowerInverter = ess.getReactivePower().value().orElse(0); - // - // if (activePowerInverter == 0 && reactivePowerInverter == 0) { - // IntegerWriteChannel commandChannel = - // this.channel(BMWChannelId.BMS_STATE_COMMAND); - // try { - // commandChannel.setNextWriteValue(OPEN_CONTACTORS); - // } catch (OpenemsNamedException e) { - // log.error("Problem occurred during send start command"); - // } - // } - // - // } - - private boolean isSystemRunning() { - EnumReadChannel bmsStateChannel = this.channel(BmwBattery.ChannelId.BMS_STATE); - BmsState bmsState = bmsStateChannel.value().asEnum(); - return bmsState == BmsState.OPERATION; - } - - private boolean isSystemStopped() { - EnumReadChannel bmsStateChannel = this.channel(BmwBattery.ChannelId.BMS_STATE); - BmsState bmsState = bmsStateChannel.value().asEnum(); - return bmsState == BmsState.OFF; - } - - /** - * Checks whether system has an undefined state. - * - * @return true if system is neither running nor stopped - */ - private boolean isSystemStatePending() { - return !this.isSystemRunning() && !this.isSystemStopped(); - } - - private boolean isError() { - EnumReadChannel bmsStateChannel = this.channel(BmwBattery.ChannelId.BMS_STATE); - BmsState bmsState = bmsStateChannel.value().asEnum(); - return bmsState == BmsState.ERROR; - } - - @Override - public String debugLog() { - return "SoC:" + this.getSoc() // - + "|Discharge:" + this.getDischargeMinVoltage() + ";" + this.getDischargeMaxCurrent() // - + "|Charge:" + this.getChargeMaxVoltage() + ";" + this.getChargeMaxCurrent() // - + "|State:" + this.state.asCamelCase(); - } - - private void startSystem() { - // TODO Currently not necessary, Battery starts itself?! - this.log.debug("Start system"); - IntegerWriteChannel commandChannel = this.channel(BmwBattery.ChannelId.BMS_STATE_COMMAND); - try { - commandChannel.setNextWriteValue(CLOSE_CONTACTORS); - } catch (OpenemsNamedException e) { - // TODO Auto-generated catch block - this.logError(this.log, "Problem occurred during send start command"); - } - } - - private void stopSystem() { - // TODO Currently not necessary, Battery starts itself?! - this.log.debug("Stop system"); - IntegerWriteChannel commandChannel = this.channel(BmwBattery.ChannelId.BMS_STATE_COMMAND); - try { - commandChannel.setNextWriteValue(OPEN_CONTACTORS); - } catch (OpenemsNamedException e) { - this.logError(this.log, "Problem occurred during send stopping command"); - } - } - - private State getStateMachineState() { - return this.state; - } - - private void setStateMachineState(State state) { - this.state = state; - this.channel(BmwBattery.ChannelId.STATE_MACHINE).setNextValue(this.state); - } - - @Override - protected ModbusProtocol defineModbusProtocol() { - return new ModbusProtocol(this, // - new FC16WriteRegistersTask(1399, // - m(BmwBattery.ChannelId.HEART_BEAT, new UnsignedWordElement(1399)), // - m(BmwBattery.ChannelId.BMS_STATE_COMMAND, new UnsignedWordElement(1400)), // - m(BmwBattery.ChannelId.OPERATING_STATE_INVERTER, new UnsignedWordElement(1401)), // - m(BmwBattery.ChannelId.DC_LINK_VOLTAGE, new UnsignedWordElement(1402), SCALE_FACTOR_MINUS_1), // - m(BmwBattery.ChannelId.DC_LINK_CURRENT, new UnsignedWordElement(1403)), // - m(BmwBattery.ChannelId.OPERATION_MODE_REQUEST_GRANTED, new UnsignedWordElement(1404)), // - m(BmwBattery.ChannelId.OPERATION_MODE_REQUEST_CANCELED, new UnsignedWordElement(1405)), // - m(new BitsWordElement(1406, this) // - .bit(1, BmwBattery.ChannelId.CONNECTION_STRATEGY_HIGH_SOC_FIRST) // - .bit(0, BmwBattery.ChannelId.CONNECTION_STRATEGY_LOW_SOC_FIRST) // - ), // - m(BmwBattery.ChannelId.SYSTEM_TIME, new UnsignedDoublewordElement(1407)) // - ), - - new FC4ReadInputRegistersTask(999, Priority.HIGH, - // not defined by "BCS_HL-SW_Operating-Instructions_V1.0.2_under_work_ChL.pdf" - m(BmwBattery.ChannelId.LIFE_SIGN, new UnsignedWordElement(999)), - m(BmwBattery.ChannelId.BMS_STATE, new UnsignedWordElement(1000)), // - m(BmwBattery.ChannelId.ERROR_BITS_1, new UnsignedWordElement(1001)), // - m(BmwBattery.ChannelId.ERROR_BITS_2, new UnsignedWordElement(1002)), // - m(BmwBattery.ChannelId.WARNING_BITS_1, new UnsignedWordElement(1003)), // - m(BmwBattery.ChannelId.WARNING_BITS_2, new UnsignedWordElement(1004)), // - // not defined by "BCS_HL-SW_Operating-Instructions_V1.0.2_under_work_ChL.pdf" - m(BmwBattery.ChannelId.INFO_BITS, new UnsignedWordElement(1005)), - m(BmwBattery.ChannelId.MAXIMUM_OPERATING_CURRENT, new SignedWordElement(1006)), // - m(BmwBattery.ChannelId.MINIMUM_OPERATING_CURRENT, new SignedWordElement(1007)), // - m(Battery.ChannelId.CHARGE_MAX_VOLTAGE, new UnsignedWordElement(1008), SCALE_FACTOR_MINUS_1), // - m(Battery.ChannelId.DISCHARGE_MIN_VOLTAGE, new UnsignedWordElement(1009), SCALE_FACTOR_MINUS_1), // - m(Battery.ChannelId.DISCHARGE_MAX_CURRENT, new SignedWordElement(1010)), // - m(Battery.ChannelId.CHARGE_MAX_CURRENT, new SignedWordElement(1011), INVERT), // - m(BmwBattery.ChannelId.MAXIMUM_LIMIT_DYNAMIC_VOLTAGE, new UnsignedWordElement(1012), - SCALE_FACTOR_MINUS_1), // - m(BmwBattery.ChannelId.MINIMUM_LIMIT_DYNAMIC_VOLTAGE, new UnsignedWordElement(1013), - SCALE_FACTOR_MINUS_1), // - m(BmwBattery.ChannelId.NUMBER_OF_STRINGS_CONNECTED, new UnsignedWordElement(1014)), // - m(BmwBattery.ChannelId.NUMBER_OF_STRINGS_INSTALLED, new UnsignedWordElement(1015)), // - m(BmwBattery.ChannelId.SOC_ALL_STRINGS, new UnsignedWordElement(1016), SCALE_FACTOR_MINUS_2), // - m(Battery.ChannelId.SOC, new UnsignedWordElement(1017), SCALE_FACTOR_MINUS_2), // - m(BmwBattery.ChannelId.REMAINING_CHARGE_CAPACITY, new UnsignedWordElement(1018)), // - m(BmwBattery.ChannelId.REMAINING_DISCHARGE_CAPACITY, new UnsignedWordElement(1019)), // - m(BmwBattery.ChannelId.REMAINING_CHARGE_ENERGY, new UnsignedWordElement(1020)), // - m(BmwBattery.ChannelId.REMAINING_DISCHARGE_ENERGY, new UnsignedWordElement(1021)), // - m(BmwBattery.ChannelId.NOMINAL_ENERGY, new UnsignedWordElement(1022)), // - m(BmwBattery.ChannelId.TOTAL_ENERGY, new UnsignedWordElement(1023)), // - m(BmwBattery.ChannelId.NOMINAL_CAPACITY, new UnsignedWordElement(1024)), // - m(Battery.ChannelId.CAPACITY, new UnsignedWordElement(1025)), // - m(Battery.ChannelId.SOH, new UnsignedWordElement(1026), SCALE_FACTOR_MINUS_2), // - m(Battery.ChannelId.VOLTAGE, new UnsignedWordElement(1027), SCALE_FACTOR_MINUS_1), // - m(BmwBattery.ChannelId.DC_VOLTAGE_AVERAGE, new UnsignedWordElement(1028), - SCALE_FACTOR_MINUS_1)), // - new FC4ReadInputRegistersTask(1029, Priority.HIGH, // - m(new SignedWordElement(1029)) // - .m(BmwBattery.ChannelId.DC_CURRENT, SCALE_FACTOR_MINUS_1) // - .m(Battery.ChannelId.CURRENT, SCALE_FACTOR_MINUS_1) // - .build()), // - new FC4ReadInputRegistersTask(1030, Priority.HIGH, // - m(BmwBattery.ChannelId.AVERAGE_TEMPERATURE, new SignedWordElement(1030))), // - new FC4ReadInputRegistersTask(1031, Priority.HIGH, // - m(new SignedWordElement(1031)) // - .m(BmwBattery.ChannelId.MINIMUM_TEMPERATURE, DIRECT_1_TO_1) // - .m(Battery.ChannelId.MIN_CELL_TEMPERATURE, DIRECT_1_TO_1) // - .build()), // - new FC4ReadInputRegistersTask(1032, Priority.HIGH, // - m(new SignedWordElement(1032)) // - .m(BmwBattery.ChannelId.MAXIMUM_TEMPERATURE, DIRECT_1_TO_1) // - .m(Battery.ChannelId.MAX_CELL_TEMPERATURE, DIRECT_1_TO_1) // - .build()), // - new FC4ReadInputRegistersTask(1033, Priority.HIGH, - m(Battery.ChannelId.MIN_CELL_VOLTAGE, new UnsignedWordElement(1033)), // - m(Battery.ChannelId.MAX_CELL_VOLTAGE, new UnsignedWordElement(1034)), // - m(BmwBattery.ChannelId.AVERAGE_CELL_VOLTAGE, new UnsignedWordElement(1035)), // - // not defined by "BCS_HL-SW_Operating-Instructions_V1.0.2_under_work_ChL.pdf" - m(BmwBattery.ChannelId.INTERNAL_RESISTANCE, new UnsignedWordElement(1036)), - m(BmwBattery.ChannelId.INSULATION_RESISTANCE, new UnsignedWordElement(1037), DIRECT_1_TO_1), // - // not defined by "BCS_HL-SW_Operating-Instructions_V1.0.2_under_work_ChL.pdf" - m(BmwBattery.ChannelId.CONTAINER_TEMPERATURE, new UnsignedWordElement(1038), - SCALE_FACTOR_MINUS_1), - // not defined by "BCS_HL-SW_Operating-Instructions_V1.0.2_under_work_ChL.pdf" - m(BmwBattery.ChannelId.AMBIENT_TEMPERATURE, new UnsignedWordElement(1039), - SCALE_FACTOR_MINUS_1), - // not defined by "BCS_HL-SW_Operating-Instructions_V1.0.2_under_work_ChL.pdf" - m(BmwBattery.ChannelId.HUMIDITY_CONTAINER, new UnsignedWordElement(1040), SCALE_FACTOR_MINUS_1), - m(BmwBattery.ChannelId.MAXIMUM_LIMIT_DYNAMIC_CURRENT_HIGH_RES, new SignedWordElement(1041), - SCALE_FACTOR_2), // - m(BmwBattery.ChannelId.MINIMUM_LIMIT_DYNAMIC_CURRENT_HIGH_RES, new SignedWordElement(1042), - SCALE_FACTOR_2), // - m(BmwBattery.ChannelId.FULL_CYCLE_COUNT, new UnsignedWordElement(1043)), // - // not defined by "BCS_HL-SW_Operating-Instructions_V1.0.2_under_work_ChL.pdf" - m(BmwBattery.ChannelId.OPERATING_TIME_COUNT, new UnsignedDoublewordElement(1044)), - // not defined by "BCS_HL-SW_Operating-Instructions_V1.0.2_under_work_ChL.pdf" - m(BmwBattery.ChannelId.COM_PRO_VERSION, new UnsignedDoublewordElement(1046)), - // not defined by "BCS_HL-SW_Operating-Instructions_V1.0.2_under_work_ChL.pdf" - // not defined by "BCS_HL-SW_Operating-Instructions_V1.0.2_under_work_ChL.pdf" - m(BmwBattery.ChannelId.SERIAL_NUMBER, new UnsignedDoublewordElement(1048)), - // not defined by "BCS_HL-SW_Operating-Instructions_V1.0.2_under_work_ChL.pdf" - m(BmwBattery.ChannelId.SOFTWARE_VERSION, new UnsignedDoublewordElement(1050)) // - )); - } - - @Override - public ModbusSlaveTable getModbusSlaveTable(AccessMode accessMode) { - return new ModbusSlaveTable(// - OpenemsComponent.getModbusSlaveNatureTable(accessMode), // - Battery.getModbusSlaveNatureTable(accessMode) // - ); - } - - @Override - public void setStartStop(StartStop value) throws OpenemsNamedException { - // TODO Auto-generated method stub - } -} diff --git a/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/BmwToken.java b/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/BmwToken.java new file mode 100644 index 00000000000..843ccae9eee --- /dev/null +++ b/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/BmwToken.java @@ -0,0 +1,71 @@ +package io.openems.edge.battery.bmw; + +import java.time.Duration; + +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceScope; +import org.osgi.service.component.annotations.ServiceScope; + +import io.openems.common.types.HttpStatus; +import io.openems.edge.bridge.http.api.BridgeHttp; +import io.openems.edge.bridge.http.api.BridgeHttp.Endpoint; +import io.openems.edge.bridge.http.api.HttpError; +import io.openems.edge.bridge.http.api.HttpResponse; +import io.openems.edge.bridge.http.time.DelayTimeProvider; +import io.openems.edge.bridge.http.time.DelayTimeProviderChain; + +@Component(scope = ServiceScope.SINGLETON, service = BmwToken.class) +public class BmwToken { + private static final int FETCH_TOKEN_DELAY = 30; + + private String token; + private BridgeHttp http; + + @Activate + public BmwToken(@Reference(scope = ReferenceScope.PROTOTYPE_REQUIRED) BridgeHttp http) { + this.http = http; + } + + /** + * Fetch the token. + * + * @param endpoint the endpoint + */ + public synchronized void fetchToken(Endpoint endpoint) { + // TODO check if the token is expires + this.http.subscribeJsonTime(new BmwTokenDelayProvider(), endpoint, (a, b) -> { + if (a == null) { + return; + } + var jsonObject = a.data().getAsJsonObject(); + this.token = jsonObject.getAsJsonPrimitive("jwtToken").getAsString(); + }); + } + + public String getToken() { + return this.token; + } + + private static final class BmwTokenDelayProvider implements DelayTimeProvider { + + @Override + public Delay onFirstRunDelay() { + return DelayTimeProviderChain.fixedDelay(Duration.ofSeconds(FETCH_TOKEN_DELAY)).getDelay(); + } + + @Override + public Delay onErrorRunDelay(HttpError error) { + return DelayTimeProviderChain.fixedDelay(Duration.ofSeconds(FETCH_TOKEN_DELAY)).getDelay(); + } + + @Override + public Delay onSuccessRunDelay(HttpResponse result) { + if (result.status() != HttpStatus.OK) { + return DelayTimeProviderChain.fixedDelay(Duration.ofSeconds(FETCH_TOKEN_DELAY)).getDelay(); + } + return DelayTimeProviderChain.runNeverAgain().getDelay(); + } + } +} \ No newline at end of file diff --git a/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/Config.java b/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/Config.java index 1f7c6d2d852..6f8252dff6c 100644 --- a/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/Config.java +++ b/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/Config.java @@ -3,15 +3,15 @@ import org.osgi.service.metatype.annotations.AttributeDefinition; import org.osgi.service.metatype.annotations.ObjectClassDefinition; -import io.openems.edge.battery.bmw.enums.BatteryState; +import io.openems.edge.common.startstop.StartStopConfig; @ObjectClassDefinition(// name = "Battery BMW", // description = "Implements the BMW battery rack system.") -@interface Config { +public @interface Config { @AttributeDefinition(name = "Component-ID", description = "Unique ID of this Component") - String id() default "bms0"; + String id() default "battery0"; @AttributeDefinition(name = "Alias", description = "Human-readable name of this Component; defaults to Component-ID") String alias() default ""; @@ -19,33 +19,17 @@ @AttributeDefinition(name = "Is enabled?", description = "Is this Component enabled?") boolean enabled() default true; + @AttributeDefinition(name = "Start/stop behaviour?", description = "Should this Component be forced to start or stop?") + StartStopConfig startStop() default StartStopConfig.AUTO; + @AttributeDefinition(name = "Modbus-ID", description = "ID of Modbus bridge.") String modbus_id() default "modbus0"; @AttributeDefinition(name = "Modbus Unit-ID", description = "The Unit-ID of the Modbus device.") - int modbusUnitId() default 0; - - @AttributeDefinition(name = "Battery state", description = "Switches the battery into the given state, if default is used, battery state is set automatically") - BatteryState batteryState() default BatteryState.DEFAULT; - - @AttributeDefinition(name = "Error Delay", description = "When an error occurs, system will remain the given time in error delay state") - long errorDelay() default 600; + int modbusUnitId(); @AttributeDefinition(name = "Modbus target filter", description = "This is auto-generated by 'Modbus-ID'.") String Modbus_target() default "(enabled=true)"; - @AttributeDefinition(name = "Max Start Attempts", description = "Sets the counter how many time the system should try to start") - int maxStartAttempts() default 5; - - @AttributeDefinition(name = "Max Start Time", description = "Max Time in seconds allowed for starting the system") - int maxStartTime() default 30; - - @AttributeDefinition(name = "Start Not Successful Delay Time", description = "Sets the delay time in seconds how long the system should be stopped if it was not able to start") - int startUnsuccessfulDelay() default 3600; - - @AttributeDefinition(name = "Pending Tolerance", description = "time in seconds, that is waited if system status cannot be determined e.g. in case of reading errors") - int pendingTolerance() default 15; - String webconsole_configurationFactory_nameHint() default "Battery BMW [{id}]"; - } \ No newline at end of file diff --git a/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/enums/BatteryState.java b/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/enums/BatteryState.java index 2ea190ab4af..d510f7a35ca 100644 --- a/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/enums/BatteryState.java +++ b/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/enums/BatteryState.java @@ -1,8 +1,38 @@ package io.openems.edge.battery.bmw.enums; -public enum BatteryState { - DEFAULT, // - ON, // - OFF; +import io.openems.common.types.OptionsEnum; -} +public enum BatteryState implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + OFF(0, "Off"), // + INIT(2, "Init"), // + STANDBY(4, "Standby"), // + READY(8, "Ready"), // + OPERATION(16, "Operation"), // + ERROR(32, "Error"), // + PRE_CHARGE(256, "Precharge") // + ; + + private final int value; + private final String name; + + private BatteryState(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } +} \ No newline at end of file diff --git a/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/enums/BatteryStateCommand.java b/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/enums/BatteryStateCommand.java new file mode 100644 index 00000000000..6fcdef77cc1 --- /dev/null +++ b/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/enums/BatteryStateCommand.java @@ -0,0 +1,48 @@ +package io.openems.edge.battery.bmw.enums; + +import io.openems.common.types.OptionsEnum; + +public enum BatteryStateCommand implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + OPEN_CONTACTOR(0, "Open Contactor"), // + RESERVED_ENABLE_BATTERY(1, "Reserved Enable Battery"), // + RESERVED_OPERATION(2, "Reserved Operation"), // + CLOSE_CONTACTOR(4, "Close Contactor"), // + CLOSE_PRE_CHARGE(8, "Close Pre-Charge"), // + RES_BMS_STT_CMD_BIT_4(16, "Reserved BMS State Command Bit 4"), // + RES_BMS_STT_CMD_BIT_5(32, "Reserved BMS State Command Bit 5"), // + RES_BMS_STT_CMD_BIT_6(64, "Reserved BMS State Command Bit 6"), // + RES_BMS_STT_CMD_BIT_7(128, "Reserved BMS State Command Bit 7"), // + RES_BMS_STT_CMD_BIT_8(256, "Reserved BMS State Command Bit 8"), // + RES_BMS_STT_CMD_BIT_9(512, "Reserved BMS State Command Bit 9"), // + RES_BMS_STT_CMD_BIT_10(1024, "Reserved BMS State Command Bit 10"), // + RES_BMS_STT_CMD_BIT_11(2048, "Reserved BMS State Command Bit 11"), // + RES_BMS_STT_CMD_BIT_12(4096, "Reserved BMS State Command Bit 12"), // + ReservedD_BMS_SLEEP(8192, "Reservedd BMS-Sleep"), // + CLEAR_BMS_ERROR(16384, "Clear BMS Error"), // + RESET_BMS(32768, "Reset BMS") // + ; + + private final int value; + private final String name; + + private BatteryStateCommand(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } +} diff --git a/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/enums/BmsState.java b/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/enums/BmsState.java deleted file mode 100644 index d6448af0836..00000000000 --- a/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/enums/BmsState.java +++ /dev/null @@ -1,41 +0,0 @@ -package io.openems.edge.battery.bmw.enums; - -import io.openems.common.types.OptionsEnum; - -public enum BmsState implements OptionsEnum { - UNDEFINED(-1, "Undefined"), // - OFF(0, "Off"), // - INIT(2, "Init"), // - STANDBY(4, "Standby"), // - READY(8, "Ready"), // - OPERATION(16, "Operation"), // - ERROR(32, "Error"), // - PRE_HEAT(64, "Pre-Heat"), // - PRE_HEAT_COMPLETED(128, "Pre-Heat completed"), // - PRE_CHARGE(256, "Precharge"), // - PRE_CHARGE_COMPLETED(512, "Precharge completed"), // - STATE_UNKNOWN(32768, "Unknown undefined"); - - private final int value; - private final String name; - - private BmsState(int value, String name) { - this.value = value; - this.name = name; - } - - @Override - public int getValue() { - return this.value; - } - - @Override - public String getName() { - return this.name; - } - - @Override - public OptionsEnum getUndefined() { - return UNDEFINED; - } -} diff --git a/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/enums/BmsStateCommand.java b/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/enums/BmsStateCommand.java deleted file mode 100644 index a7b3f3ff006..00000000000 --- a/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/enums/BmsStateCommand.java +++ /dev/null @@ -1,36 +0,0 @@ -package io.openems.edge.battery.bmw.enums; - -import io.openems.common.types.OptionsEnum; - -public enum BmsStateCommand implements OptionsEnum { - UNDEFINED(-1, "Undefined"), // - ENABLE_RESERVED_BATTERY(0, "Enable aux power supply"), // - WAKE_UP(1, "Wake up from stop"), // - CLOSE_CONTACTOR(2, "Close Contactor"), // - CLOSE_PRECHARGE(3, "Close Precharge"), // - CLEAR_ERROR(14, "Clear BMS Error"), // - RESET_BMS(15, "Reset BMS"); - - private final int value; - private final String name; - - private BmsStateCommand(int value, String name) { - this.value = value; - this.name = name; - } - - @Override - public int getValue() { - return this.value; - } - - @Override - public String getName() { - return this.name; - } - - @Override - public OptionsEnum getUndefined() { - return UNDEFINED; - } -} diff --git a/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/enums/ErrorBits1.java b/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/enums/ErrorBits1.java deleted file mode 100644 index 750bdfe08ac..00000000000 --- a/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/enums/ErrorBits1.java +++ /dev/null @@ -1,46 +0,0 @@ -package io.openems.edge.battery.bmw.enums; - -import io.openems.common.types.OptionsEnum; - -public enum ErrorBits1 implements OptionsEnum { - UNDEFINED(-1, "Undefined"), // - UNSPECIFIED(0, "Unspecified Error"), // - LOW_VOLTAGE(1, "Low Voltage Error"), // - HIGH_VOLTAGE(2, "High Voltage Error"), // - CHARGE_CURRENT(3, "Charge Current Error"), // - DISCHARGE_CURRENT(4, "Discharge Current Error"), // - CHARGE_POWER(5, "Charge Power Error"), // - DISCHARGE_POWER(6, "Discharge Power Error"), // - LOW_SOC(7, "Low SoC Error"), // - HIGH_SOC(8, "High SoC Error"), // - LOW_TEMPERATURE(9, "Low Temperature Error"), // - HIGH_TEMPERATURE(10, "High Temperature Error"), // - INSULATION(11, "Insulation Error"), // - CONTACTOR_FUSE(12, "Contactor/Fuse Error"), // - SENSOR(13, "Sensor Error"), // - IMBALANCE(14, "Imbalance Error"), // - COMMUNICATION(15, "Communication Error"); - - private final int value; - private final String name; - - private ErrorBits1(int value, String name) { - this.value = value; - this.name = name; - } - - @Override - public int getValue() { - return this.value; - } - - @Override - public String getName() { - return this.name; - } - - @Override - public OptionsEnum getUndefined() { - return UNDEFINED; - } -} diff --git a/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/enums/ErrorBits2.java b/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/enums/ErrorBits2.java deleted file mode 100644 index f4057036302..00000000000 --- a/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/enums/ErrorBits2.java +++ /dev/null @@ -1,33 +0,0 @@ -package io.openems.edge.battery.bmw.enums; - -import io.openems.common.types.OptionsEnum; - -public enum ErrorBits2 implements OptionsEnum { - UNDEFINED(-1, "Undefined"), // - CONTAINER(0, "Container Error"), // - SOH(1, "SoH Error"), // - RACK_STRING(2, "Rack/String Error"); - - private final int value; - private final String name; - - private ErrorBits2(int value, String name) { - this.value = value; - this.name = name; - } - - @Override - public int getValue() { - return this.value; - } - - @Override - public String getName() { - return this.name; - } - - @Override - public OptionsEnum getUndefined() { - return UNDEFINED; - } -} diff --git a/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/enums/State.java b/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/enums/State.java index bb3e784428f..f709ce765bd 100644 --- a/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/enums/State.java +++ b/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/enums/State.java @@ -36,4 +36,4 @@ public OptionsEnum getUndefined() { return UNDEFINED; } -} +} \ No newline at end of file diff --git a/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/enums/WarningBits1.java b/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/enums/WarningBits1.java deleted file mode 100644 index fd5e407ecbd..00000000000 --- a/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/enums/WarningBits1.java +++ /dev/null @@ -1,46 +0,0 @@ -package io.openems.edge.battery.bmw.enums; - -import io.openems.common.types.OptionsEnum; - -public enum WarningBits1 implements OptionsEnum { - UNDEFINED(-1, "Undefined"), // - UNSPECIFIED(0, "Unspecified Warning"), // - LOW_VOLTAGE(1, "Low Voltage Warning"), // - HIGH_VOLTAGE(2, "High Voltage Warning"), // - CHARGE_CURRENT(3, "Charge Current Warning"), // - DISCHARGE_CURRENT(4, "Discharge Current Warning"), // - CHARGE_POWER(5, "Charge Power Warning"), // - DISCHARGE_POWER(6, "Discharge Power Warning"), // - LOW_SOC(7, "Low SoC Warning"), // - HIGH_SOC(8, "High SoC Warning"), // - LOW_TEMPERATURE(9, "Low Temperature Warning"), // - HIGH_TEMPERATURE(10, "High Temperature Warning"), // - INSULATION(11, "Insulation Warning"), // - CONTACTOR_FUSE(12, "Contactor/Fuse Warning"), // - SENSOR(13, "Sensor Warning"), // - IMBALANCE(14, "Imbalance Warning"), // - COMMUNICATION(15, "Communication Warning"); - - private final int value; - private final String name; - - private WarningBits1(int value, String name) { - this.value = value; - this.name = name; - } - - @Override - public int getValue() { - return this.value; - } - - @Override - public String getName() { - return this.name; - } - - @Override - public OptionsEnum getUndefined() { - return UNDEFINED; - } -} diff --git a/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/enums/WarningBits2.java b/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/enums/WarningBits2.java deleted file mode 100644 index 9b2a4686753..00000000000 --- a/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/enums/WarningBits2.java +++ /dev/null @@ -1,33 +0,0 @@ -package io.openems.edge.battery.bmw.enums; - -import io.openems.common.types.OptionsEnum; - -public enum WarningBits2 implements OptionsEnum { - UNDEFINED(-1, "Undefined"), // - CONTAINER(0, "Container Warning"), // - SOH(1, "SoH Warning"), // - RACK_STRING(2, "Rack/String Warning"); - - private final int value; - private final String name; - - private WarningBits2(int value, String name) { - this.value = value; - this.name = name; - } - - @Override - public int getValue() { - return this.value; - } - - @Override - public String getName() { - return this.name; - } - - @Override - public OptionsEnum getUndefined() { - return UNDEFINED; - } -} diff --git a/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/statemachine/Context.java b/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/statemachine/Context.java new file mode 100644 index 00000000000..84075427b05 --- /dev/null +++ b/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/statemachine/Context.java @@ -0,0 +1,59 @@ +package io.openems.edge.battery.bmw.statemachine; + +import java.time.Clock; +import java.util.Map; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.edge.battery.bmw.BatteryBmwImpl; +import io.openems.edge.bridge.http.api.BridgeHttp; +import io.openems.edge.bridge.http.api.BridgeHttp.Endpoint; +import io.openems.edge.bridge.http.api.HttpMethod; +import io.openems.edge.bridge.modbus.api.BridgeModbusTcp; +import io.openems.edge.common.statemachine.AbstractContext; + +public class Context extends AbstractContext { + + private static final String SPLIT_REGEX = "(?<=\\D)(?=\\d)|(?<=\\d)(?=\\D)"; + + protected final Clock clock; + protected final BridgeHttp httpBridge; + + public Context(BatteryBmwImpl parent, Clock clock, BridgeHttp httpBridge) { + super(parent); + this.clock = clock; + this.httpBridge = httpBridge; + } + + protected void setComponent(Context context, String uri) throws OpenemsNamedException { + final var battery = context.getParent(); + final var http = context.httpBridge; + var map = Map.of(// + "Authorization", "Bearer " + battery.getToken(), // + "Content-Type", "application/json"); + final var id = battery.id().split(SPLIT_REGEX); + final var url = battery.getUrl((BridgeModbusTcp) battery.getBridgeModbus(), uri, id[1]); + var postData = "{data: {data: \"1\"}}"; + final var endPoint = new Endpoint(url, HttpMethod.POST, BridgeHttp.DEFAULT_CONNECT_TIMEOUT, + BridgeHttp.DEFAULT_READ_TIMEOUT, postData, map); + http.request(endPoint); + } + + /** + * Gets the endpoint. + * + * @param uri the uri state or release + * @param outdata write data + * @return {@link Endpoint} + */ + public Endpoint getEndpoint(String uri, String outdata) { + final var battery = this.getParent(); + var map = Map.of(// + "Authorization", "Bearer " + battery.getToken(), // + "Content-Type", "application/json"); + final var id = battery.id().split(SPLIT_REGEX); + final var url = battery.getUrl((BridgeModbusTcp) battery.getBridgeModbus(), uri, id[1]); + final var endPoint = new Endpoint(url, HttpMethod.GET, BridgeHttp.DEFAULT_CONNECT_TIMEOUT, + BridgeHttp.DEFAULT_READ_TIMEOUT, outdata, map); + return endPoint; + } +} \ No newline at end of file diff --git a/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/statemachine/ErrorHandler.java b/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/statemachine/ErrorHandler.java new file mode 100644 index 00000000000..b99d4a1699a --- /dev/null +++ b/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/statemachine/ErrorHandler.java @@ -0,0 +1,32 @@ +package io.openems.edge.battery.bmw.statemachine; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.edge.battery.bmw.statemachine.StateMachine.State; +import io.openems.edge.common.statemachine.StateHandler; + +public class ErrorHandler extends StateHandler { + + @Override + protected void onEntry(Context context) throws OpenemsNamedException { + final var battery = context.getParent(); + battery.stop(); + } + + @Override + public State runAndGetNextState(Context context) { + final var battery = context.getParent(); + if (!battery.hasFaults() && battery.isShutdown()) { + return State.STOPPED; + } + return State.ERROR; + } + + @Override + protected void onExit(Context context) { + final var battery = context.getParent(); + battery._setUnexpectedRunningState(false); + battery._setUnexpectedStoppedState(false); + battery._setTimeoutStartBattery(false); + battery._setTimeoutStopBattery(false); + } +} diff --git a/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/statemachine/GoRunningHandler.java b/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/statemachine/GoRunningHandler.java new file mode 100644 index 00000000000..3caa207e347 --- /dev/null +++ b/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/statemachine/GoRunningHandler.java @@ -0,0 +1,164 @@ +package io.openems.edge.battery.bmw.statemachine; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.exceptions.OpenemsException; +import io.openems.common.timedata.Timeout; +import io.openems.common.utils.EnumUtils; +import io.openems.common.utils.JsonUtils; +import io.openems.edge.battery.bmw.statemachine.StateMachine.State; +import io.openems.edge.bridge.http.api.BridgeHttp.Endpoint; +import io.openems.edge.bridge.http.api.BridgeHttpCycle.CycleEndpoint; +import io.openems.edge.bridge.http.api.HttpResponse; +import io.openems.edge.common.statemachine.StateHandler; + +public class GoRunningHandler extends StateHandler { + + private static final int TIMEOUT = 120; // [s] + private static final String URI_STATE = "bcsPowerState"; + private static final String URI_RELEASE = "releaseStatus"; + private final Logger log = LoggerFactory.getLogger(GoRunningHandler.class); + + private boolean resultState = false; + private boolean resultRelease = false; + + private CycleEndpoint cycleStateEndpoint; + private CycleEndpoint cycleReleaseEndpoint; + + protected static record GoRunningState(GoRunningSubState subState) { + } + + protected GoRunningState goRunningState; + private final Timeout timeout = Timeout.ofSeconds(TIMEOUT); + + @Override + protected void onEntry(Context context) throws OpenemsNamedException { + this.timeout.start(context.clock); + this.goRunningState = new GoRunningState(GoRunningSubState.CHECK_BCS_POWER_STATE); + this.cycleStateEndpoint = this.subscribeHttpEndpoints(context, URI_STATE); + this.cycleReleaseEndpoint = this.subscribeHttpEndpoints(context, URI_RELEASE); + } + + @Override + public State runAndGetNextState(Context context) throws OpenemsNamedException { + final var battery = context.getParent(); + var nextSubState = this.getNextSubState(context); + battery._setGoRunningStateMachine(nextSubState); + + if (nextSubState != this.goRunningState.subState) { + // Record State changes + this.goRunningState = new GoRunningState(nextSubState); + } else if (this.timeout.elapsed(context.clock)) { + // Handle GoRunningHandler State-timeout + throw new OpenemsException("Timeout [" + TIMEOUT + "s] in GoRunning-" + this.goRunningState.subState); + } + + if (nextSubState == GoRunningSubState.ERROR) { + return State.ERROR; + } + + if (nextSubState == GoRunningSubState.FINISHED) { + return State.RUNNING; + } + + return State.GO_RUNNING; + } + + private GoRunningSubState getNextSubState(Context context) throws OpenemsNamedException { + final var battery = context.getParent(); + return switch (this.goRunningState.subState) { + case CHECK_BCS_POWER_STATE -> { + yield this.resultState// + ? GoRunningSubState.CHECK_BCS_INVERTER_RELEASE // + : GoRunningSubState.ACTIVATE_BCS_POWER_STATE; + } + case ACTIVATE_BCS_POWER_STATE -> { + context.setComponent(context, URI_STATE); + if (!this.resultState) { + yield GoRunningSubState.ACTIVATE_BCS_POWER_STATE; + } + yield GoRunningSubState.CHECK_BCS_INVERTER_RELEASE; + } + case CHECK_BCS_INVERTER_RELEASE -> { + yield this.resultRelease// + ? GoRunningSubState.START_BATTERY // + : GoRunningSubState.ACTIVATE_INVERTER_RELEASE; + } + case ACTIVATE_INVERTER_RELEASE -> { + context.setComponent(context, URI_RELEASE); + if (!this.resultRelease) { + yield GoRunningSubState.ACTIVATE_INVERTER_RELEASE; + } + yield GoRunningSubState.START_BATTERY; + } + case START_BATTERY -> { + battery.startBattery(); + if (battery.isRunning()) { + yield GoRunningSubState.FINISHED; + } + yield GoRunningSubState.START_BATTERY; + } + case ERROR, UNDEFINED -> GoRunningSubState.ERROR; + case FINISHED -> GoRunningSubState.FINISHED; + }; + } + + private CycleEndpoint subscribeHttpEndpoints(Context context, String uri) { + final var endPoint = context.getEndpoint(uri, null); + return context.httpBridge.subscribeCycle(// + 1, // + endPoint, // + success -> this.getAndSetResult(success, endPoint), // + error -> context.logWarn(this.log, "Failed to retrieve component value from URI [ " + endPoint.url() + + " ] : " + error.getMessage())); + } + + /** + * Get the result and set the condition for power state and inverter release + * state. + * + * @param success the Success + * @param endPoint the Endpoint + * @throws OpenemsNamedException on error + */ + private void getAndSetResult(HttpResponse success, Endpoint endPoint) throws OpenemsNamedException { + var dataObject = JsonUtils.parse(success.data()).getAsJsonObject().get("data"); + var value = 0; + if (dataObject != null && !dataObject.isJsonNull()) { + value = dataObject.getAsInt(); + } + if (this.containsUrl(endPoint.url(), URI_STATE)) { + this.resultState = value == 1; + } + if (this.containsUrl(endPoint.url(), URI_RELEASE)) { + this.resultRelease = value == 1; + } + } + + /** + * Check for url. + * + * @param url the url string + * @param uri the uri to compare and check + * @return boolean if the String available + */ + public boolean containsUrl(String url, String uri) { + if (url == null || url.isEmpty()) { + return false; + } + return url.contains(uri); + } + + @Override + protected String debugLog() { + return State.GO_RUNNING.asCamelCase() + "-" + EnumUtils.nameAsCamelCase(this.goRunningState.subState); + } + + @Override + protected void onExit(Context context) throws OpenemsNamedException { + context.httpBridge.removeCycleEndpoint(this.cycleStateEndpoint); + context.httpBridge.removeCycleEndpoint(this.cycleReleaseEndpoint); + } +} diff --git a/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/statemachine/GoRunningSubState.java b/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/statemachine/GoRunningSubState.java new file mode 100644 index 00000000000..693c6080b42 --- /dev/null +++ b/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/statemachine/GoRunningSubState.java @@ -0,0 +1,37 @@ +package io.openems.edge.battery.bmw.statemachine; + +import io.openems.common.types.OptionsEnum; + +public enum GoRunningSubState implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + CHECK_BCS_POWER_STATE(0, "Check BCS"), // + ACTIVATE_BCS_POWER_STATE(1, "Activate BCS power state"), // + ACTIVATE_INVERTER_RELEASE(2, "Activate inverter release"), // + CHECK_BCS_INVERTER_RELEASE(3, "Check BCS after activation"), // + START_BATTERY(4, "Start battery"), // + FINISHED(5, "Finished"), // + ERROR(6, "Error");// + + private final int value; + private final String name; + + private GoRunningSubState(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } +} diff --git a/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/statemachine/GoStoppedHandler.java b/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/statemachine/GoStoppedHandler.java new file mode 100644 index 00000000000..ec9fe8fbbf5 --- /dev/null +++ b/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/statemachine/GoStoppedHandler.java @@ -0,0 +1,41 @@ +package io.openems.edge.battery.bmw.statemachine; + +import java.time.Duration; +import java.time.Instant; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.edge.battery.bmw.statemachine.StateMachine.State; +import io.openems.edge.common.statemachine.StateHandler; + +public class GoStoppedHandler extends StateHandler { + + private static final int TIMEOUT = 120; + private Instant timeAtEntry = Instant.MIN; + + @Override + protected void onEntry(Context context) throws OpenemsNamedException { + this.timeAtEntry = Instant.now(context.clock); + } + + @Override + public State runAndGetNextState(Context context) throws OpenemsNamedException { + final var battery = context.getParent(); + if (battery.hasFaults()) { + return State.ERROR; + } + + var isTimeout = Duration.between(this.timeAtEntry, // + Instant.now(context.clock)).getSeconds() > TIMEOUT; + battery._setTimeoutStopBattery(isTimeout); + if (isTimeout) { + return State.ERROR; + } + + if (battery.isShutdown()) { + return State.STOPPED; + } + + battery.stopBattery(); + return State.GO_STOPPED; + } +} diff --git a/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/statemachine/RunningHandler.java b/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/statemachine/RunningHandler.java new file mode 100644 index 00000000000..e05d2f7bb67 --- /dev/null +++ b/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/statemachine/RunningHandler.java @@ -0,0 +1,24 @@ +package io.openems.edge.battery.bmw.statemachine; + +import io.openems.edge.battery.bmw.statemachine.StateMachine.State; +import io.openems.edge.common.startstop.StartStop; +import io.openems.edge.common.statemachine.StateHandler; + +public class RunningHandler extends StateHandler { + + @Override + public State runAndGetNextState(Context context) { + final var battery = context.getParent(); + if (battery.hasFaults()) { + return State.ERROR; + } + + if (!battery.isRunning()) { + battery._setUnexpectedRunningState(true); + return State.ERROR; + } + + battery._setStartStop(StartStop.START); + return State.RUNNING; + } +} diff --git a/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/statemachine/StateMachine.java b/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/statemachine/StateMachine.java new file mode 100644 index 00000000000..d0e027ebc25 --- /dev/null +++ b/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/statemachine/StateMachine.java @@ -0,0 +1,70 @@ +package io.openems.edge.battery.bmw.statemachine; + +import io.openems.common.types.OptionsEnum; +import io.openems.edge.common.statemachine.AbstractStateMachine; +import io.openems.edge.common.statemachine.StateHandler; + +public class StateMachine extends AbstractStateMachine { + + public enum State implements io.openems.edge.common.statemachine.State, OptionsEnum { + UNDEFINED(-1), // + + GO_RUNNING(10), // + RUNNING(11), // + + GO_STOPPED(20), // + STOPPED(21), // + + ERROR(30), // + ; + + private final int value; + + private State(int value) { + this.value = value; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name(); + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } + + @Override + public State[] getStates() { + return State.values(); + } + } + + public StateMachine(State initialState) { + super(initialState); + } + + @Override + public StateHandler getStateHandler(State state) { + switch (state) { + case UNDEFINED: + return new UndefinedHandler(); + case GO_RUNNING: + return new GoRunningHandler(); + case RUNNING: + return new RunningHandler(); + case GO_STOPPED: + return new GoStoppedHandler(); + case STOPPED: + return new StoppedHandler(); + case ERROR: + return new ErrorHandler(); + } + throw new IllegalArgumentException("Unknown State [" + state + "]"); + } +} \ No newline at end of file diff --git a/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/statemachine/StoppedHandler.java b/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/statemachine/StoppedHandler.java new file mode 100644 index 00000000000..f82d36f8863 --- /dev/null +++ b/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/statemachine/StoppedHandler.java @@ -0,0 +1,24 @@ +package io.openems.edge.battery.bmw.statemachine; + +import io.openems.edge.battery.bmw.statemachine.StateMachine.State; +import io.openems.edge.common.startstop.StartStop; +import io.openems.edge.common.statemachine.StateHandler; + +public class StoppedHandler extends StateHandler { + + @Override + public State runAndGetNextState(Context context) { + final var battery = context.getParent(); + if (battery.hasFaults()) { + return State.ERROR; + } + + if (!battery.isShutdown()) { + battery._setUnexpectedStoppedState(true); + return State.ERROR; + } + + battery._setStartStop(StartStop.STOP); + return State.STOPPED; + } +} diff --git a/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/statemachine/UndefinedHandler.java b/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/statemachine/UndefinedHandler.java new file mode 100644 index 00000000000..a3b2f983e81 --- /dev/null +++ b/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/statemachine/UndefinedHandler.java @@ -0,0 +1,22 @@ +package io.openems.edge.battery.bmw.statemachine; + +import io.openems.edge.battery.bmw.statemachine.StateMachine.State; +import io.openems.edge.common.statemachine.StateHandler; + +public class UndefinedHandler extends StateHandler { + + @Override + public State runAndGetNextState(Context context) { + final var battery = context.getParent(); + return switch (battery.getStartStopTarget()) { + case UNDEFINED -> State.UNDEFINED; + case START -> { + if (battery.hasFaults()) { + yield State.ERROR; + } + yield State.GO_RUNNING; + } + case STOP -> State.GO_STOPPED; + }; + } +} diff --git a/io.openems.edge.battery.bmw/test/io/openems/edge/battery/bmw/BmwBatteryImplTest.java b/io.openems.edge.battery.bmw/test/io/openems/edge/battery/bmw/BmwBatteryImplTest.java index fd5aa8847db..c54180f8ff5 100644 --- a/io.openems.edge.battery.bmw/test/io/openems/edge/battery/bmw/BmwBatteryImplTest.java +++ b/io.openems.edge.battery.bmw/test/io/openems/edge/battery/bmw/BmwBatteryImplTest.java @@ -1,33 +1,160 @@ package io.openems.edge.battery.bmw; +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.temporal.ChronoUnit; +import java.util.Collections; +import java.util.concurrent.atomic.AtomicReference; + import org.junit.Test; +import org.osgi.service.event.Event; +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.oem.DummyOpenemsEdgeOem; +import io.openems.common.test.TimeLeapClock; +import io.openems.common.types.ChannelAddress; +import io.openems.common.utils.JsonUtils; import io.openems.edge.battery.bmw.enums.BatteryState; +import io.openems.edge.battery.bmw.statemachine.StateMachine; +import io.openems.edge.bridge.http.api.HttpError; +import io.openems.edge.bridge.http.api.HttpMethod; +import io.openems.edge.bridge.http.api.HttpResponse; +import io.openems.edge.bridge.http.dummy.DummyBridgeHttp; +import io.openems.edge.bridge.http.dummy.DummyBridgeHttpFactory; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; +import io.openems.edge.common.event.EdgeEventConstants; +import io.openems.edge.common.startstop.StartStopConfig; +import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; +import io.openems.edge.common.test.DummyComponentManager; import io.openems.edge.common.test.DummyConfigurationAdmin; public class BmwBatteryImplTest { - private static final String BATTERY_ID = "battery0"; + private static final String BATTERY_ID = "battery1"; private static final String MODBUS_ID = "modbus0"; + private static final ChannelAddress STATEMACHINE = new ChannelAddress(BATTERY_ID, "StateMachine"); + private static final ChannelAddress BATTERY_STATE = new ChannelAddress(BATTERY_ID, "BatteryState"); + + private static enum Operation { + POWER_STATE, RELEASE_STATE + } @Test - public void test() throws Exception { - new ComponentTest(new BmwBatteryImpl()) // + public void startBattery() throws Exception { + final var clock = new TimeLeapClock(Instant.parse("2020-01-01T01:00:00.00Z"), ZoneOffset.UTC); + final var fetcher = DummyBridgeHttpFactory.dummyEndpointFetcher(); + final var executor = DummyBridgeHttpFactory.dummyBridgeHttpExecutor(clock, true); + final var dataState = new AtomicReference("{data: \"0\"}"); + final var dataRelease = new AtomicReference("{data: \"0\"}"); + + fetcher.addEndpointHandler(t -> { + + var operation = t.url().contains("bcsPowerState") // + ? Operation.POWER_STATE // + : Operation.RELEASE_STATE; + + if (t.body() == "{userCredentials: {name: \"foo\", password: \"foo_Password\"}}") { + return HttpResponse.ok("token"); + } + + if (t.method() == HttpMethod.GET) { + return switch (operation) { + case POWER_STATE -> HttpResponse.ok(dataState.get()); + case RELEASE_STATE -> HttpResponse.ok(dataRelease.get()); + }; + } + if (t.method() == HttpMethod.POST) { + switch (operation) { + case POWER_STATE -> { + try { + var jsonElement = JsonUtils.parse(t.body()); + var update = jsonElement.getAsJsonObject().get("data"); + dataState.set(update.toString()); + return HttpResponse.ok(""); + } catch (OpenemsNamedException e) { + throw HttpError.ResponseError.notFound(); + } + } + case RELEASE_STATE -> { + try { + var jsonElement = JsonUtils.parse(t.body()); + var update = jsonElement.getAsJsonObject().get("data"); + dataRelease.set(update.toString()); + return HttpResponse.ok(""); + } catch (OpenemsNamedException e) { + throw HttpError.ResponseError.notFound(); + } + } + } + ; + } + throw HttpError.ResponseError.notFound(); + }); + + var dummyCycleSubscriber = DummyBridgeHttpFactory.cycleSubscriber(); + + var test = new ComponentTest(new BatteryBmwImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("componentManager", new DummyComponentManager(clock)) // + .addReference("oem", new DummyOpenemsEdgeOem()) // + .addReference("token", new BmwToken(new DummyBridgeHttp()))// + .addReference("httpBridgeFactory", DummyBridgeHttpFactory.ofBridgeImpl(// + () -> dummyCycleSubscriber, // + () -> fetcher, // + () -> executor)) // + .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)// + .withIpAddress("127.0.0.1")) // .activate(MyConfig.create() // .setId(BATTERY_ID) // .setModbusId(MODBUS_ID) // - .setBatteryState(BatteryState.DEFAULT) // - .setErrorDelay(0) // - .setMaxStartAttempts(0) // - .setMaxStartTime(0) // - .setPendingTolerance(0) // - .setStartUnsuccessfulDelay(0) // - .build()) // - ; - } + .setModbusUnitId(1) // + .setStartStop(StartStopConfig.START) // + .build());// + test.next(new TestCase("1")// + .output(STATEMACHINE, StateMachine.State.UNDEFINED)); + test.next(new TestCase("2")// + .onAfterProcessImage(() -> { + dummyCycleSubscriber.handleEvent( + new Event(EdgeEventConstants.TOPIC_CYCLE_BEFORE_PROCESS_IMAGE, Collections.emptyMap())); + })); + test.next(new TestCase("3")// + .timeleap(clock, 10, ChronoUnit.SECONDS));// + test.next(new TestCase("4")// + .onAfterProcessImage(() -> { + dummyCycleSubscriber.handleEvent( + new Event(EdgeEventConstants.TOPIC_CYCLE_BEFORE_PROCESS_IMAGE, Collections.emptyMap())); + })); + test.next(new TestCase("5")// + .output(STATEMACHINE, StateMachine.State.GO_RUNNING)); + test.next(new TestCase("6")// + .onAfterProcessImage(() -> { + dummyCycleSubscriber.handleEvent( + new Event(EdgeEventConstants.TOPIC_CYCLE_BEFORE_PROCESS_IMAGE, Collections.emptyMap())); + })); + test.next(new TestCase("7")// + .timeleap(clock, 10, ChronoUnit.SECONDS));// + test.next(new TestCase("8")// + .onAfterProcessImage(() -> { + dummyCycleSubscriber.handleEvent( + new Event(EdgeEventConstants.TOPIC_CYCLE_BEFORE_PROCESS_IMAGE, Collections.emptyMap())); + })); + test.next(new TestCase("9")// + .input(BATTERY_STATE, BatteryState.OPERATION)); + test.next(new TestCase("10")// + .onAfterProcessImage(() -> { + dummyCycleSubscriber.handleEvent( + new Event(EdgeEventConstants.TOPIC_CYCLE_BEFORE_PROCESS_IMAGE, Collections.emptyMap())); + })); + test.next(new TestCase("11")// + .timeleap(clock, 10, ChronoUnit.SECONDS));// + test.next(new TestCase("12")// + .onAfterProcessImage(() -> { + dummyCycleSubscriber.handleEvent( + new Event(EdgeEventConstants.TOPIC_CYCLE_BEFORE_PROCESS_IMAGE, Collections.emptyMap())); + })); + test.next(new TestCase("13")// + .output(STATEMACHINE, StateMachine.State.RUNNING)); + } } diff --git a/io.openems.edge.battery.bmw/test/io/openems/edge/battery/bmw/MyConfig.java b/io.openems.edge.battery.bmw/test/io/openems/edge/battery/bmw/MyConfig.java index 5d23ae84e08..92c66eafcca 100644 --- a/io.openems.edge.battery.bmw/test/io/openems/edge/battery/bmw/MyConfig.java +++ b/io.openems.edge.battery.bmw/test/io/openems/edge/battery/bmw/MyConfig.java @@ -2,21 +2,16 @@ import io.openems.common.test.AbstractComponentConfig; import io.openems.common.utils.ConfigUtils; -import io.openems.edge.battery.bmw.enums.BatteryState; +import io.openems.edge.common.startstop.StartStopConfig; @SuppressWarnings("all") public class MyConfig extends AbstractComponentConfig implements Config { protected static class Builder { - private String id; - private String modbusId; + private String id = null; + private String modbusId = null; private int modbusUnitId; - private BatteryState batteryState; - private long errorDelay; - private int maxStartAttempts; - private int maxStartTime; - private int startUnsuccessfulDelay; - private int pendingTolerance; + private StartStopConfig startStop; private Builder() { } @@ -31,33 +26,13 @@ public Builder setModbusId(String modbusId) { return this; } - public Builder setBatteryState(BatteryState batteryState) { - this.batteryState = batteryState; + public Builder setStartStop(StartStopConfig startStop) { + this.startStop = startStop; return this; } - public Builder setErrorDelay(long errorDelay) { - this.errorDelay = errorDelay; - return this; - } - - public Builder setMaxStartAttempts(int maxStartAttempts) { - this.maxStartAttempts = maxStartAttempts; - return this; - } - - public Builder setMaxStartTime(int maxStartTime) { - this.maxStartTime = maxStartTime; - return this; - } - - public Builder setStartUnsuccessfulDelay(int startUnsuccessfulDelay) { - this.startUnsuccessfulDelay = startUnsuccessfulDelay; - return this; - } - - public Builder setPendingTolerance(int pendingTolerance) { - this.pendingTolerance = pendingTolerance; + public Builder setModbusUnitId(int modbusUnitId) { + this.modbusUnitId = modbusUnitId; return this; } @@ -98,33 +73,7 @@ public int modbusUnitId() { } @Override - public BatteryState batteryState() { - return this.builder.batteryState; - } - - @Override - public long errorDelay() { - return this.builder.errorDelay; - } - - @Override - public int maxStartAttempts() { - return this.builder.maxStartAttempts; - } - - @Override - public int maxStartTime() { - return this.builder.maxStartTime; + public StartStopConfig startStop() { + return this.builder.startStop; } - - @Override - public int startUnsuccessfulDelay() { - return this.builder.startUnsuccessfulDelay; - } - - @Override - public int pendingTolerance() { - return this.builder.pendingTolerance; - } - } \ No newline at end of file diff --git a/io.openems.edge.battery.bydcommercial/.classpath b/io.openems.edge.battery.bydcommercial/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.battery.bydcommercial/.classpath +++ b/io.openems.edge.battery.bydcommercial/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.battery.bydcommercial/src/io/openems/edge/battery/bydcommercial/BydBatteryBoxCommercialC130.java b/io.openems.edge.battery.bydcommercial/src/io/openems/edge/battery/bydcommercial/BydBatteryBoxCommercialC130.java index 2dedfc9f26a..72d31daa444 100644 --- a/io.openems.edge.battery.bydcommercial/src/io/openems/edge/battery/bydcommercial/BydBatteryBoxCommercialC130.java +++ b/io.openems.edge.battery.bydcommercial/src/io/openems/edge/battery/bydcommercial/BydBatteryBoxCommercialC130.java @@ -787,27 +787,27 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { LEVEL1_CHARGE_CURRENT_HIGH(Doc.of(Level.WARNING) // .text("Cluster 2 Charge Current High Alarm Level 2")), // - LEVEL2_CELL_VOLTAGE_HIGH(Doc.of(Level.FAULT) // + LEVEL2_CELL_VOLTAGE_HIGH(Doc.of(Level.WARNING) // .text("Cluster 3 Cell Voltage High Alarm Level 3")), // - LEVEL2_CELL_VOLTAGE_LOW(Doc.of(Level.FAULT) // + LEVEL2_CELL_VOLTAGE_LOW(Doc.of(Level.WARNING) // .text("Cluster 3 Cell Voltage Low Alarm Level 3")), // - LEVEL2_CELL_VOLTAGE_DIFF_TOO_BIG(Doc.of(Level.FAULT) // + LEVEL2_CELL_VOLTAGE_DIFF_TOO_BIG(Doc.of(Level.WARNING) // .text("Alarm Level 3 Battery Cells Unbalanced")), // - LEVEL2_DISCHARGE_TEMP_HIGH(Doc.of(Level.FAULT) // + LEVEL2_DISCHARGE_TEMP_HIGH(Doc.of(Level.WARNING) // .text("Cluster 3 Cell Discharge Temperature High Alarm Level 3")), // - LEVEL2_DISCHARGE_TEMP_LOW(Doc.of(Level.FAULT) // + LEVEL2_DISCHARGE_TEMP_LOW(Doc.of(Level.WARNING) // .text("Cluster 3 Cell Discharge Temperature Low Alarm Level 3")), // - LEVEL2_CHARGE_TEMP_HIGH(Doc.of(Level.FAULT) // + LEVEL2_CHARGE_TEMP_HIGH(Doc.of(Level.WARNING) // .text("Cluster 3 Cell Charge Temperature High Alarm Level 3")), // - LEVEL2_CHARGE_TEMP_LOW(Doc.of(Level.FAULT) // + LEVEL2_CHARGE_TEMP_LOW(Doc.of(Level.WARNING) // .text("Cluster 3 Cell Charge Temperature Low Alarm Level 3")), // - LEVEL2_TEMP_DIFF_TOO_BIG(Doc.of(Level.FAULT) // + LEVEL2_TEMP_DIFF_TOO_BIG(Doc.of(Level.WARNING) // .text("Cluster 3 Cell Temperature Diff High Alarm Level 3")), // - LEVEL2_POWER_POLE_HIGH(Doc.of(Level.FAULT) // + LEVEL2_POWER_POLE_HIGH(Doc.of(Level.WARNING) // .text("Cluster 3 Cell Temperature High Alarm Level 3")), // - LEVEL2_DISCHARGE_CURRENT_HIGH(Doc.of(Level.FAULT) // + LEVEL2_DISCHARGE_CURRENT_HIGH(Doc.of(Level.WARNING) // .text("Cluster 3 Discharge Current High Alarm Level 3")), // - LEVEL2_CHARGE_CURRENT_HIGH(Doc.of(Level.FAULT) // + LEVEL2_CHARGE_CURRENT_HIGH(Doc.of(Level.WARNING) // .text("Cluster 3 Charge Current High Alarm Level 3")), // ALARM_LEVEL_1_TOTAL_VOLTAGE_DIFF_HIGH(Doc.of(Level.WARNING) // @@ -822,69 +822,69 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { .text("Cluster 1 Total Voltage Low Alarm Level 1")), // ALARM_LEVEL_1_TOTAL_VOLTAGE_HIGH(Doc.of(Level.WARNING) // .text("Cluster 1 Total Voltage High Alarm Level 1")), // - ALARM_FUSE(Doc.of(Level.FAULT) // + ALARM_FUSE(Doc.of(Level.WARNING) // .text(" Fuse Alarm")), // SHIELDED_SWITCH_STATE(Doc.of(Level.WARNING) // .text("Shielded switch state")), // ALARM_BAU_COMMUNICATION(Doc.of(Level.WARNING) // .text("BAU Communication Alarm")), // - ALARM_INSULATION_CHECK(Doc.of(Level.FAULT) // + ALARM_INSULATION_CHECK(Doc.of(Level.WARNING) // .text("Inuslation Resistance Alarm")), // ALARM_CURRENT_SENSOR(Doc.of(Level.WARNING) // .text("Current Sensor Alarm")), // ALARM_BCU_BMU_COMMUNICATION(Doc.of(Level.WARNING) // .text("BCU BMU Communication Alarm")), // - ALARM_CONTACTOR_ADHESION(Doc.of(Level.FAULT)// + ALARM_CONTACTOR_ADHESION(Doc.of(Level.WARNING)// .text("Contactor Adhesion Alarm ")), // ALARM_BCU_NTC(Doc.of(Level.WARNING) // .text("BCU NTC Alarm")), // ALARM_SLAVE_CONTROL_SUMMARY(Doc.of(Level.WARNING) // .text("Slave Control Summary Alarm")), // - FAILURE_INITIALIZATION(Doc.of(Level.FAULT) // + FAILURE_INITIALIZATION(Doc.of(Level.WARNING) // .text("Initialization failure")), // - FAILURE_EEPROM(Doc.of(Level.FAULT) // + FAILURE_EEPROM(Doc.of(Level.WARNING) // .text("EEPROM fault")), // - FAILURE_EEPROM2(Doc.of(Level.FAULT) // + FAILURE_EEPROM2(Doc.of(Level.WARNING) // .text("EEPROM2 fault")), // - FAILURE_INTRANET_COMMUNICATION(Doc.of(Level.FAULT) // + FAILURE_INTRANET_COMMUNICATION(Doc.of(Level.WARNING) // .text("Intranet communication fault")), // - FAILURE_TEMP_SAMPLING_LINE(Doc.of(Level.FAULT) // + FAILURE_TEMP_SAMPLING_LINE(Doc.of(Level.WARNING) // .text("Temperature sampling line fault")), // - FAILURE_BALANCING_MODULE(Doc.of(Level.FAULT) // + FAILURE_BALANCING_MODULE(Doc.of(Level.WARNING) // .text("Balancing module fault")), // - FAILURE_TEMP_SENSOR(Doc.of(Level.FAULT) // + FAILURE_TEMP_SENSOR(Doc.of(Level.WARNING) // .text("Temperature sensor fault")), // - FAILURE_TEMP_SAMPLING(Doc.of(Level.FAULT) // + FAILURE_TEMP_SAMPLING(Doc.of(Level.WARNING) // .text("Temperature sampling fault")), // - FAILURE_VOLTAGE_SAMPLING(Doc.of(Level.FAULT) // + FAILURE_VOLTAGE_SAMPLING(Doc.of(Level.WARNING) // .text("Voltage sampling fault")), // - FAILURE_VOLTAGE_SAMPLING_LINE(Doc.of(Level.FAULT) // + FAILURE_VOLTAGE_SAMPLING_LINE(Doc.of(Level.WARNING) // .text("Voltage sampling Line fault")), // - FAILURE_SLAVE_UNIT_INITIALIZATION(Doc.of(Level.FAULT) // + FAILURE_SLAVE_UNIT_INITIALIZATION(Doc.of(Level.WARNING) // .text("Failure Slave Unit Initialization")), - FAILURE_CONNECTING_LINE(Doc.of(Level.FAULT) // + FAILURE_CONNECTING_LINE(Doc.of(Level.WARNING) // .text("Connecting Line Failure")), // - FAILURE_SAMPLING_CHIP(Doc.of(Level.FAULT) // + FAILURE_SAMPLING_CHIP(Doc.of(Level.WARNING) // .text("Sampling Chip Failure")), // - FAILURE_CONTACTOR(Doc.of(Level.FAULT) // + FAILURE_CONTACTOR(Doc.of(Level.WARNING) // .text("Contactor Failure")), // - FAILURE_PASSIVE_BALANCE(Doc.of(Level.FAULT) // + FAILURE_PASSIVE_BALANCE(Doc.of(Level.WARNING) // .text("Passive Balance Failure")), // - FAILURE_PASSIVE_BALANCE_TEMP(Doc.of(Level.FAULT) // + FAILURE_PASSIVE_BALANCE_TEMP(Doc.of(Level.WARNING) // .text("Passive Balance Temp Failure")), // - FAILURE_ACTIVE_BALANCE(Doc.of(Level.FAULT) // + FAILURE_ACTIVE_BALANCE(Doc.of(Level.WARNING) // .text("Active Balance Failure")), // - FAILURE_LTC6803(Doc.of(Level.FAULT) // + FAILURE_LTC6803(Doc.of(Level.WARNING) // .text("LTC6803 sfault")), // - FAILURE_CONNECTOR_WIRE(Doc.of(Level.FAULT) // + FAILURE_CONNECTOR_WIRE(Doc.of(Level.WARNING) // .text("connector wire fault")), // - FAILURE_SAMPLING_WIRE(Doc.of(Level.FAULT) // + FAILURE_SAMPLING_WIRE(Doc.of(Level.WARNING) // .text("sampling wire fault")), // - PRECHARGE_TAKING_TOO_LONG(Doc.of(Level.FAULT) // + PRECHARGE_TAKING_TOO_LONG(Doc.of(Level.WARNING) // .text("precharge time was too long")), // NEED_CHARGE(Doc.of(Level.WARNING) // .text("Battery Need Charge")), // - FAULT(Doc.of(Level.FAULT) // + FAULT(Doc.of(Level.WARNING) // .text("battery fault state")), // STATE_MACHINE(Doc.of(State.values()) // .text("Current State of State-Machine")), // @@ -928,76 +928,76 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { .text("ALARM LEVEL 2 SOH LOWER")), // LEVEL1_PACK_TEMP_HIGH(Doc.of(Level.WARNING) // .text("ALARM LEVEL 2 PACK TEMP HIGH")), // - LEVEL2_SYSTEM_VOLTAGE_HIGH(Doc.of(Level.FAULT) // + LEVEL2_SYSTEM_VOLTAGE_HIGH(Doc.of(Level.WARNING) // .text("ALARM LEVEL 3 SYSTEM VOLTAGE HIGH")), // - LEVEL2_SYSTEM_VOLTAGE_LOW(Doc.of(Level.FAULT) // + LEVEL2_SYSTEM_VOLTAGE_LOW(Doc.of(Level.WARNING) // .text("ALARM LEVEL 3 SYSTEM VOLTAGE LOW")), // - LEVEL2_SYSTEM_VOLTAGE_UNBALANCED(Doc.of(Level.FAULT) // + LEVEL2_SYSTEM_VOLTAGE_UNBALANCED(Doc.of(Level.WARNING) // .text("ALARM LEVEL 3 SYSTEM VOLTAGE UNBALANCED")), // - LEVEL2_INSULATION_RESISTANCE_LOWER(Doc.of(Level.FAULT) // + LEVEL2_INSULATION_RESISTANCE_LOWER(Doc.of(Level.WARNING) // .text("ALARM LEVEL 3 INSULATION RESISTANCE LOWER")), // - LEVEL2_POS_INSULATION_RESISTANCE_LOWER(Doc.of(Level.FAULT) // + LEVEL2_POS_INSULATION_RESISTANCE_LOWER(Doc.of(Level.WARNING) // .text("ALARM LEVEL 3 POS INSULATION RESISTANCE LOWER")), // - LEVEL2_NEG_INSULATION_RESISTANCE_LOWER(Doc.of(Level.FAULT) // + LEVEL2_NEG_INSULATION_RESISTANCE_LOWER(Doc.of(Level.WARNING) // .text("ALARM LEVEL 3 NEG INSULATION RESISTANCE LOWER")), // - LEVEL2_SYSTEM_SOC_LOWER(Doc.of(Level.FAULT) // + LEVEL2_SYSTEM_SOC_LOWER(Doc.of(Level.WARNING) // .text("ALARM LEVEL 3 SYSTEM SOC LOWER")), // - LEVEL2_SYSTEM_SOC_HIGH(Doc.of(Level.FAULT) // + LEVEL2_SYSTEM_SOC_HIGH(Doc.of(Level.WARNING) // .text("ALARM LEVEL 3 SYSTEM SOC HIGH")), // LEVEL2_SOH_LOWER(Doc.of(Level.WARNING) // .text("ALARM LEVEL 3 SOH LOWER")), // - LEVEL2_PACK_TEMP_HIGH(Doc.of(Level.FAULT) // + LEVEL2_PACK_TEMP_HIGH(Doc.of(Level.WARNING) // .text("ALARM LEVEL 3 PACK TEMP HIGH")), // - SLAVE_11_COMMUNICATION_ERROR(Doc.of(Level.FAULT)// + SLAVE_11_COMMUNICATION_ERROR(Doc.of(Level.WARNING)// .text("Master control and Slave control Communication Fault 1 SLAVE_CTRL_11")), // - SLAVE_12_COMMUNICATION_ERROR(Doc.of(Level.FAULT)// + SLAVE_12_COMMUNICATION_ERROR(Doc.of(Level.WARNING)// .text("Master control and Slave control Communication Fault 1 SLAVE_CTRL_12")), // - SLAVE_13_COMMUNICATION_ERROR(Doc.of(Level.FAULT)// + SLAVE_13_COMMUNICATION_ERROR(Doc.of(Level.WARNING)// .text("Master control and Slave control Communication Fault 1 SLAVE_CTRL_13")), // - SLAVE_14_COMMUNICATION_ERROR(Doc.of(Level.FAULT)// + SLAVE_14_COMMUNICATION_ERROR(Doc.of(Level.WARNING)// .text("Master control and Slave control Communication Fault 1 SLAVE_CTRL_14")), // - SLAVE_15_COMMUNICATION_ERROR(Doc.of(Level.FAULT)// + SLAVE_15_COMMUNICATION_ERROR(Doc.of(Level.WARNING)// .text("Master control and Slave control Communication Fault 1 SLAVE_CTRL_15")), // - SLAVE_16_COMMUNICATION_ERROR(Doc.of(Level.FAULT)// + SLAVE_16_COMMUNICATION_ERROR(Doc.of(Level.WARNING)// .text("Master control and Slave control Communication Fault 1 SLAVE_CTRL_16")), // - SLAVE_17_COMMUNICATION_ERROR(Doc.of(Level.FAULT)// + SLAVE_17_COMMUNICATION_ERROR(Doc.of(Level.WARNING)// .text("Master control and Slave control Communication Fault 1 SLAVE_CTRL_17")), // - SLAVE_18_COMMUNICATION_ERROR(Doc.of(Level.FAULT) // + SLAVE_18_COMMUNICATION_ERROR(Doc.of(Level.WARNING) // .text("Master control and Slave control Communication Fault 2 SLAVE_CTRL_18")), // - SLAVE_19_COMMUNICATION_ERROR(Doc.of(Level.FAULT) // + SLAVE_19_COMMUNICATION_ERROR(Doc.of(Level.WARNING) // .text("Master control and Slave control Communication Fault 2 SLAVE_CTRL_19")), // - SLAVE_20_COMMUNICATION_ERROR(Doc.of(Level.FAULT) // + SLAVE_20_COMMUNICATION_ERROR(Doc.of(Level.WARNING) // .text("Master control and Slave control Communication Fault 2 SLAVE_CTRL_20")), // - SLAVE_21_COMMUNICATION_ERROR(Doc.of(Level.FAULT) // + SLAVE_21_COMMUNICATION_ERROR(Doc.of(Level.WARNING) // .text("Master control and Slave control Communication Fault 2 SLAVE_CTRL_21")), // - SLAVE_22_COMMUNICATION_ERROR(Doc.of(Level.FAULT) // + SLAVE_22_COMMUNICATION_ERROR(Doc.of(Level.WARNING) // .text("Master control and Slave control Communication Fault 2 SLAVE_CTRL_22")), // - SLAVE_23_COMMUNICATION_ERROR(Doc.of(Level.FAULT) // + SLAVE_23_COMMUNICATION_ERROR(Doc.of(Level.WARNING) // .text("Master control and Slave control Communication Fault 2 SLAVE_CTRL_23")), // - SLAVE_24_COMMUNICATION_ERROR(Doc.of(Level.FAULT) // + SLAVE_24_COMMUNICATION_ERROR(Doc.of(Level.WARNING) // .text("Master control and Slave control Communication Fault 2 SLAVE_CTRL_24")), // - SLAVE_25_COMMUNICATION_ERROR(Doc.of(Level.FAULT) // + SLAVE_25_COMMUNICATION_ERROR(Doc.of(Level.WARNING) // .text("Master control and Slave control Communication Fault 2 SLAVE_CTRL_25")), // - SLAVE_26_COMMUNICATION_ERROR(Doc.of(Level.FAULT) // + SLAVE_26_COMMUNICATION_ERROR(Doc.of(Level.WARNING) // .text("Master control and Slave control Communication Fault 2 SLAVE_CTRL_26")), // - SLAVE_27_COMMUNICATION_ERROR(Doc.of(Level.FAULT) // + SLAVE_27_COMMUNICATION_ERROR(Doc.of(Level.WARNING) // .text("Master control and Slave control Communication Fault 2 SLAVE_CTRL_27")), // - SLAVE_28_COMMUNICATION_ERROR(Doc.of(Level.FAULT) // + SLAVE_28_COMMUNICATION_ERROR(Doc.of(Level.WARNING) // .text("Master control and Slave control Communication Fault 2 SLAVE_CTRL_28")), // - SLAVE_29_COMMUNICATION_ERROR(Doc.of(Level.FAULT) // + SLAVE_29_COMMUNICATION_ERROR(Doc.of(Level.WARNING) // .text("Master control and Slave control Communication Fault 2 SLAVE_CTRL_29")), // - SLAVE_30_COMMUNICATION_ERROR(Doc.of(Level.FAULT) // + SLAVE_30_COMMUNICATION_ERROR(Doc.of(Level.WARNING) // .text("Master control and Slave control Communication Fault 2 SLAVE_CTRL_30")), // - SLAVE_31_COMMUNICATION_ERROR(Doc.of(Level.FAULT) // + SLAVE_31_COMMUNICATION_ERROR(Doc.of(Level.WARNING) // .text("Master control and Slave control Communication Fault 2 SLAVE_CTRL_31")), // - SLAVE_32_COMMUNICATION_ERROR(Doc.of(Level.FAULT) // + SLAVE_32_COMMUNICATION_ERROR(Doc.of(Level.WARNING) // .text("Master control and Slave control Communication Fault 2 SLAVE_CTRL_31")), // // OpenEMS Faults - RUN_FAILED(Doc.of(Level.FAULT) // + RUN_FAILED(Doc.of(Level.WARNING) // .text("Running the Logic failed")), // - MAX_START_ATTEMPTS(Doc.of(Level.FAULT) // + MAX_START_ATTEMPTS(Doc.of(Level.WARNING) // .text("The maximum number of start attempts failed")), // - MAX_STOP_ATTEMPTS(Doc.of(Level.FAULT) // + MAX_STOP_ATTEMPTS(Doc.of(Level.WARNING) // .text("The maximum number of stop attempts failed")), // ; diff --git a/io.openems.edge.battery.bydcommercial/src/io/openems/edge/battery/bydcommercial/BydBatteryBoxCommercialC130Impl.java b/io.openems.edge.battery.bydcommercial/src/io/openems/edge/battery/bydcommercial/BydBatteryBoxCommercialC130Impl.java index 0dae16c2fcd..b838288b319 100644 --- a/io.openems.edge.battery.bydcommercial/src/io/openems/edge/battery/bydcommercial/BydBatteryBoxCommercialC130Impl.java +++ b/io.openems.edge.battery.bydcommercial/src/io/openems/edge/battery/bydcommercial/BydBatteryBoxCommercialC130Impl.java @@ -4,6 +4,7 @@ import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SCALE_FACTOR_2; import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SCALE_FACTOR_MINUS_1; import static io.openems.edge.bridge.modbus.api.ModbusUtils.readElementOnce; +import static io.openems.edge.bridge.modbus.api.ModbusUtils.FunctionCode.FC3; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; @@ -964,7 +965,7 @@ public StartStop getStartStopTarget() { BydBatteryBoxCommercialC130Impl.this.isModbusProtocolInitialized = true; // Try to read MODULE_QTY Register - readElementOnce(this.getModbusProtocol(), ModbusUtils::doNotRetry, new UnsignedWordElement(0x210D)) + readElementOnce(FC3, this.getModbusProtocol(), ModbusUtils::doNotRetry, new UnsignedWordElement(0x210D)) .thenAccept(moduleQtyValue -> { if (moduleQtyValue != null) { // Register is available -> add Registers for current hardware to protocol diff --git a/io.openems.edge.battery.bydcommercial/test/io/openems/edge/battery/bydcommercial/BydBatteryBoxCommercialC130ImplTest.java b/io.openems.edge.battery.bydcommercial/test/io/openems/edge/battery/bydcommercial/BydBatteryBoxCommercialC130ImplTest.java index 91d0b85e1c1..1b87571c888 100644 --- a/io.openems.edge.battery.bydcommercial/test/io/openems/edge/battery/bydcommercial/BydBatteryBoxCommercialC130ImplTest.java +++ b/io.openems.edge.battery.bydcommercial/test/io/openems/edge/battery/bydcommercial/BydBatteryBoxCommercialC130ImplTest.java @@ -1,28 +1,30 @@ package io.openems.edge.battery.bydcommercial; +import static io.openems.edge.common.startstop.StartStopConfig.AUTO; + import org.junit.Test; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; -import io.openems.edge.common.startstop.StartStopConfig; +import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; +import io.openems.edge.common.test.DummyComponentManager; import io.openems.edge.common.test.DummyConfigurationAdmin; public class BydBatteryBoxCommercialC130ImplTest { - private static final String BATTERY_ID = "battery0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new BydBatteryBoxCommercialC130Impl()) // + .addReference("componentManager", new DummyComponentManager()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(BATTERY_ID) // - .setModbusId(MODBUS_ID) // - .setStartStop(StartStopConfig.AUTO) // + .setId("battery0") // + .setModbusId("modbus0") // + .setStartStop(AUTO) // .build()) // - ; + .next(new TestCase()) // + .deactivate(); } } diff --git a/io.openems.edge.battery.fenecon.commercial/.classpath b/io.openems.edge.battery.fenecon.commercial/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.battery.fenecon.commercial/.classpath +++ b/io.openems.edge.battery.fenecon.commercial/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.battery.fenecon.commercial/test/io/openems/edge/battery/fenecon/commercial/BatteryFeneconCommercialImplTest.java b/io.openems.edge.battery.fenecon.commercial/test/io/openems/edge/battery/fenecon/commercial/BatteryFeneconCommercialImplTest.java index 1226ccc9ffc..17038a5c183 100644 --- a/io.openems.edge.battery.fenecon.commercial/test/io/openems/edge/battery/fenecon/commercial/BatteryFeneconCommercialImplTest.java +++ b/io.openems.edge.battery.fenecon.commercial/test/io/openems/edge/battery/fenecon/commercial/BatteryFeneconCommercialImplTest.java @@ -1,17 +1,20 @@ package io.openems.edge.battery.fenecon.commercial; -import java.time.Instant; -import java.time.ZoneOffset; +import static io.openems.common.test.TestUtils.createDummyClock; +import static io.openems.edge.battery.api.Battery.ChannelId.CHARGE_MAX_CURRENT; +import static io.openems.edge.battery.api.Battery.ChannelId.DISCHARGE_MAX_CURRENT; +import static io.openems.edge.battery.api.Battery.ChannelId.SOC; +import static io.openems.edge.battery.fenecon.commercial.BatteryFeneconCommercial.ChannelId.BATTERY_SOC; +import static io.openems.edge.battery.fenecon.commercial.BatteryFeneconCommercial.ChannelId.RUNNING; +import static io.openems.edge.battery.fenecon.commercial.BatteryFeneconCommercial.ChannelId.STATE_MACHINE; +import static io.openems.edge.common.startstop.StartStoppable.ChannelId.START_STOP; +import static io.openems.edge.io.test.DummyInputOutput.ChannelId.INPUT_OUTPUT7; import org.junit.Test; -import io.openems.common.test.TimeLeapClock; -import io.openems.common.types.ChannelAddress; -import io.openems.edge.battery.api.Battery; import io.openems.edge.battery.fenecon.commercial.statemachine.StateMachine; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.startstop.StartStopConfig; -import io.openems.edge.common.startstop.StartStoppable; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyComponentManager; @@ -20,42 +23,23 @@ public class BatteryFeneconCommercialImplTest { - private static final String BATTERY_ID = "battery0"; - private static final String MODBUS_ID = "modbus0"; - private static final String IO_ID = "io0"; - - private static final ChannelAddress STATE_MACHINE = new ChannelAddress(BATTERY_ID, - BatteryFeneconCommercial.ChannelId.STATE_MACHINE.id()); - private static final ChannelAddress RUNNING = new ChannelAddress(BATTERY_ID, - BatteryFeneconCommercial.ChannelId.RUNNING.id()); - private static final ChannelAddress BATTERY_RELAY = new ChannelAddress(IO_ID, "InputOutput7"); - private static final ChannelAddress START_STOP = new ChannelAddress(BATTERY_ID, - StartStoppable.ChannelId.START_STOP.id()); - private static final ChannelAddress BATTERY_SOC = new ChannelAddress(BATTERY_ID, - BatteryFeneconCommercial.ChannelId.BATTERY_SOC.id()); - private static final ChannelAddress BATTERY_MAX_DISCHARGE_CURRENT = new ChannelAddress(BATTERY_ID, - Battery.ChannelId.DISCHARGE_MAX_CURRENT.id()); - private static final ChannelAddress BATTERY_MAX_CHARGE_CURRENT = new ChannelAddress(BATTERY_ID, - Battery.ChannelId.CHARGE_MAX_CURRENT.id()); - private static final ChannelAddress SOC = new ChannelAddress(BATTERY_ID, Battery.ChannelId.SOC.id()); - @Test public void startBattery() throws Exception { - final var clock = new TimeLeapClock(Instant.parse("2020-01-01T01:00:00.00Z"), ZoneOffset.UTC); + final var clock = createDummyClock(); new ComponentTest(new BatteryFeneconCommercialImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager(clock)) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // - .addComponent(new DummyInputOutput(IO_ID))// + .addReference("setModbus", new DummyModbusBridge("modbus0")) // + .addComponent(new DummyInputOutput("io0"))// .activate(MyConfig.create() // - .setId(BATTERY_ID) // - .setModbusId(MODBUS_ID) // + .setId("battery0") // + .setModbusId("modbus0") // .setModbusUnitId(1) // .setStartStop(StartStopConfig.START) // .setBatteryStartStopRelay("io0/InputOutput7")// .build())// .next(new TestCase("Battery Relay false, starting") // - .input(BATTERY_RELAY, false)// + .input("io0", INPUT_OUTPUT7, false)// .input(RUNNING, false)// Switched Off .output(STATE_MACHINE, StateMachine.State.UNDEFINED))// .next(new TestCase() // @@ -63,18 +47,18 @@ public void startBattery() throws Exception { .next(new TestCase()// .output(STATE_MACHINE, StateMachine.State.GO_RUNNING))// .next(new TestCase()// - .input(BATTERY_RELAY, true))// + .input("io0", INPUT_OUTPUT7, true))// .next(new TestCase() // .output(STATE_MACHINE, StateMachine.State.GO_RUNNING))// .next(new TestCase()// .input(RUNNING, true)// - .input(BATTERY_RELAY, false))// + .input("io0", INPUT_OUTPUT7, false))// .next(new TestCase() // .output(STATE_MACHINE, StateMachine.State.GO_RUNNING))// .next(new TestCase("Battery Running")// .output(STATE_MACHINE, StateMachine.State.RUNNING))// .next(new TestCase("Battery Running")// - .output(BATTERY_RELAY, false)// + .output("io0", INPUT_OUTPUT7, false)// .output(RUNNING, true) // .output(STATE_MACHINE, StateMachine.State.RUNNING))// @@ -83,21 +67,21 @@ public void startBattery() throws Exception { @Test public void stopBattery() throws Exception { - final var clock = new TimeLeapClock(Instant.parse("2020-01-01T01:00:00.00Z"), ZoneOffset.UTC); + final var clock = createDummyClock(); new ComponentTest(new BatteryFeneconCommercialImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager(clock)) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // - .addComponent(new DummyInputOutput(IO_ID))// + .addReference("setModbus", new DummyModbusBridge("modbus0")) // + .addComponent(new DummyInputOutput("io0"))// .activate(MyConfig.create() // - .setId(BATTERY_ID) // - .setModbusId(MODBUS_ID) // + .setId("battery0") // + .setModbusId("modbus0") // .setModbusUnitId(1) // .setStartStop(StartStopConfig.STOP) // .setBatteryStartStopRelay("io0/InputOutput7")// .build())// .next(new TestCase("Battery Running")// - .input(BATTERY_RELAY, false)// + .input("io0", INPUT_OUTPUT7, false)// .input(RUNNING, true) // .input(STATE_MACHINE, StateMachine.State.RUNNING))// .next(new TestCase("Stopping") // @@ -105,7 +89,7 @@ public void stopBattery() throws Exception { .next(new TestCase()// .output(STATE_MACHINE, StateMachine.State.GO_STOPPED))// .next(new TestCase()// - .input(BATTERY_RELAY, true)) // + .input("io0", INPUT_OUTPUT7, true)) // .next(new TestCase()// .output(STATE_MACHINE, StateMachine.State.STOPPED))// @@ -114,15 +98,15 @@ public void stopBattery() throws Exception { @Test public void socManipulationMin() throws Exception { - final var clock = new TimeLeapClock(Instant.parse("2020-01-01T01:00:00.00Z"), ZoneOffset.UTC); + final var clock = createDummyClock(); new ComponentTest(new BatteryFeneconCommercialImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager(clock)) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // - .addComponent(new DummyInputOutput(IO_ID))// + .addReference("setModbus", new DummyModbusBridge("modbus0")) // + .addComponent(new DummyInputOutput("io0"))// .activate(MyConfig.create() // - .setId(BATTERY_ID) // - .setModbusId(MODBUS_ID) // + .setId("battery0") // + .setModbusId("modbus0") // .setModbusUnitId(1) // .setStartStop(StartStopConfig.START) // .setBatteryStartStopRelay("io0/InputOutput7")// @@ -130,22 +114,22 @@ public void socManipulationMin() throws Exception { .next(new TestCase("Soc")// .input(RUNNING, true) // .input(BATTERY_SOC, 10) // - .input(BATTERY_MAX_DISCHARGE_CURRENT, 0) // - .input(BATTERY_MAX_CHARGE_CURRENT, 10000) // + .input(DISCHARGE_MAX_CURRENT, 0) // + .input(CHARGE_MAX_CURRENT, 10000) // .output(SOC, 10)); } @Test public void socManipulationMax() throws Exception { - final var clock = new TimeLeapClock(Instant.parse("2020-01-01T01:00:00.00Z"), ZoneOffset.UTC); + final var clock = createDummyClock(); new ComponentTest(new BatteryFeneconCommercialImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager(clock)) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // - .addComponent(new DummyInputOutput(IO_ID))// + .addReference("setModbus", new DummyModbusBridge("modbus0")) // + .addComponent(new DummyInputOutput("io0"))// .activate(MyConfig.create() // - .setId(BATTERY_ID) // - .setModbusId(MODBUS_ID) // + .setId("battery0") // + .setModbusId("modbus0") // .setModbusUnitId(1) // .setStartStop(StartStopConfig.START) // .setBatteryStartStopRelay("io0/InputOutput7")// @@ -153,8 +137,8 @@ public void socManipulationMax() throws Exception { .next(new TestCase("Soc")// .input(RUNNING, true) // .input(BATTERY_SOC, 98) // - .input(BATTERY_MAX_DISCHARGE_CURRENT, 100000) // - .input(BATTERY_MAX_CHARGE_CURRENT, 0) // + .input(DISCHARGE_MAX_CURRENT, 100000) // + .input(CHARGE_MAX_CURRENT, 0) // .output(SOC, 100)); } } diff --git a/io.openems.edge.battery.fenecon.commercial/test/io/openems/edge/battery/fenecon/commercial/DynamicChannelsAndSerialNumbersTest.java b/io.openems.edge.battery.fenecon.commercial/test/io/openems/edge/battery/fenecon/commercial/DynamicChannelsAndSerialNumbersTest.java index b28b1513542..a9db6249dc3 100644 --- a/io.openems.edge.battery.fenecon.commercial/test/io/openems/edge/battery/fenecon/commercial/DynamicChannelsAndSerialNumbersTest.java +++ b/io.openems.edge.battery.fenecon.commercial/test/io/openems/edge/battery/fenecon/commercial/DynamicChannelsAndSerialNumbersTest.java @@ -1,10 +1,14 @@ package io.openems.edge.battery.fenecon.commercial; +import static io.openems.edge.battery.fenecon.commercial.BatteryFeneconCommercial.ChannelId.MASTER_MCU_HARDWARE_VERSION; +import static io.openems.edge.battery.fenecon.commercial.BatteryFeneconCommercial.ChannelId.NUMBER_OF_CELLS_PER_MODULE; +import static io.openems.edge.battery.fenecon.commercial.BatteryFeneconCommercial.ChannelId.NUMBER_OF_MODULES_PER_TOWER; +import static io.openems.edge.battery.fenecon.commercial.BatteryFeneconCommercial.ChannelId.NUMBER_OF_TOWERS; +import static io.openems.edge.battery.fenecon.commercial.BatteryFeneconCommercialImpl.VERSION_CONVERTER; import static org.junit.Assert.assertEquals; import org.junit.Test; -import io.openems.common.types.ChannelAddress; import io.openems.edge.battery.api.Battery; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.startstop.StartStopConfig; @@ -16,24 +20,10 @@ public class DynamicChannelsAndSerialNumbersTest { - private static final String BATTERY_ID = "battery0"; - private static final String MODBUS_ID = "modbus0"; - private static final String IO_ID = "io0"; - private static final int TOWERS = 1; private static final int MODULES = 10; private static final int CELLS = 120;/* Read from register as cells*modules */ - private static final ChannelAddress NUMBER_OF_MODULES_PER_TOWER = new ChannelAddress(BATTERY_ID, - "NumberOfModulesPerTower"); - private static final ChannelAddress NUMBER_OF_TOWERS = new ChannelAddress(BATTERY_ID, "NumberOfTowers"); - private static final ChannelAddress NUMBER_OF_CELLS_PER_MODULE = new ChannelAddress(BATTERY_ID, - "NumberOfCellsPerModule"); - private static final ChannelAddress SUB_MASTER_HARDWARE_VERSION = new ChannelAddress(BATTERY_ID, - "Tower0SubMasterHardwareVersion"); - private static final ChannelAddress MASTER_MCU_HARDWARE_VERSION = new ChannelAddress(BATTERY_ID, - "MasterMcuHardwareVersion"); - @Test public void testSerialNum() throws Exception { var battery = new BatteryFeneconCommercialImpl(); @@ -41,11 +31,11 @@ public void testSerialNum() throws Exception { var componentTest = new ComponentTest(battery) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // - .addComponent(new DummyInputOutput(IO_ID))// + .addReference("setModbus", new DummyModbusBridge("modbus0")) // + .addComponent(new DummyInputOutput("io0"))// .activate(MyConfig.create() // - .setId(BATTERY_ID) // - .setModbusId(MODBUS_ID) // + .setId("battery0") // + .setModbusId("modbus0") // .setModbusUnitId(0) // .setBatteryStartStopRelay("io0/InputOutput0")// .setStartStop(StartStopConfig.AUTO) // @@ -56,10 +46,10 @@ public void testSerialNum() throws Exception { .input(NUMBER_OF_TOWERS, TOWERS) // .input(NUMBER_OF_MODULES_PER_TOWER, MODULES) // .input(NUMBER_OF_CELLS_PER_MODULE, CELLS) // - .input(SUB_MASTER_HARDWARE_VERSION, "109101BM60")); + .input("battery0", "Tower0SubMasterHardwareVersion", "109101BM60")); checkDynamicChannels(battery, TOWERS, MODULES, CELLS / MODULES); - assertEquals("011910MB06", BatteryFeneconCommercialImpl.VERSION_CONVERTER.elementToChannel("109101BM60")); + assertEquals("011910MB06", VERSION_CONVERTER.elementToChannel("109101BM60")); componentTest.next(new TestCase()); componentTest.next(new TestCase()); @@ -72,7 +62,7 @@ public void testSerialNum() throws Exception { .input(NUMBER_OF_CELLS_PER_MODULE, CELLS) // .input(MASTER_MCU_HARDWARE_VERSION, "100201MS50")); - assertEquals("012010SM05", BatteryFeneconCommercialImpl.VERSION_CONVERTER.elementToChannel("100201MS50")); + assertEquals("012010SM05", VERSION_CONVERTER.elementToChannel("100201MS50")); } /** diff --git a/io.openems.edge.battery.fenecon.home/.classpath b/io.openems.edge.battery.fenecon.home/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.battery.fenecon.home/.classpath +++ b/io.openems.edge.battery.fenecon.home/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/BatteryFeneconHome.java b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/BatteryFeneconHome.java index 0ea7cc798ae..d4298dde150 100644 --- a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/BatteryFeneconHome.java +++ b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/BatteryFeneconHome.java @@ -673,7 +673,7 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId STATE_MACHINE(Doc.of(State.values()) // .text("Current State of State-Machine")), // - RUN_FAILED(Doc.of(Level.FAULT) // + RUN_FAILED(Doc.of(Level.WARNING) // .text("Running the Logic failed")), // LOW_MIN_VOLTAGE_WARNING(Doc.of(Level.WARNING) // .text("Low min voltage warning " diff --git a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/BatteryFeneconHomeImpl.java b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/BatteryFeneconHomeImpl.java index efa68f7d43d..3130b3ba4fc 100644 --- a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/BatteryFeneconHomeImpl.java +++ b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/BatteryFeneconHomeImpl.java @@ -3,6 +3,7 @@ import static io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent.BitConverter.INVERT; import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SCALE_FACTOR_MINUS_1; import static io.openems.edge.bridge.modbus.api.ModbusUtils.readElementOnce; +import static io.openems.edge.bridge.modbus.api.ModbusUtils.FunctionCode.FC3; import java.time.Instant; import java.util.List; @@ -117,17 +118,12 @@ public BatteryFeneconHomeImpl() { BatteryProtection.ChannelId.values(), // BatteryFeneconHome.ChannelId.values() // ); - this.updateHardwareType(BatteryFeneconHomeHardwareType.DEFAULT); } @Activate private void activate(ComponentContext context, Config config) throws OpenemsException { this.config = config; - - // Predefine BatteryProtection. Later adapted to the hardware type. - this.batteryProtection = BatteryProtection.create(this) // - .applyBatteryProtectionDefinition(new FeneconHomeBatteryProtection52(), this.componentManager) // - .build(); + this.updateHardwareType(BatteryFeneconHomeHardwareType.DEFAULT); // initialize to default if (super.activate(context, config.id(), config.alias(), config.enabled(), config.modbusUnitId(), this.cm, "Modbus", config.modbus_id())) { @@ -194,7 +190,7 @@ private void handleStateMachine() { @Override protected ModbusProtocol defineModbusProtocol() { return new ModbusProtocol(this, // - new FC3ReadRegistersTask(500, Priority.LOW, // + new FC3ReadRegistersTask(500, Priority.HIGH, // m(new BitsWordElement(500, this) // .bit(0, BatteryFeneconHome.ChannelId.RACK_PRE_ALARM_CELL_OVER_VOLTAGE) // .bit(1, BatteryFeneconHome.ChannelId.RACK_PRE_ALARM_CELL_UNDER_VOLTAGE) // @@ -271,10 +267,7 @@ protected ModbusProtocol defineModbusProtocol() { .bit(6, BatteryFeneconHome.ChannelId.FAULT_POSITION_BCU_7) // .bit(7, BatteryFeneconHome.ChannelId.FAULT_POSITION_BCU_8) // .bit(8, BatteryFeneconHome.ChannelId.FAULT_POSITION_BCU_9) // - .bit(9, BatteryFeneconHome.ChannelId.FAULT_POSITION_BCU_10))// - ), // - - new FC3ReadRegistersTask(506, Priority.LOW, // + .bit(9, BatteryFeneconHome.ChannelId.FAULT_POSITION_BCU_10)), // m(Battery.ChannelId.VOLTAGE, new UnsignedWordElement(506), SCALE_FACTOR_MINUS_1), // [V] m(Battery.ChannelId.CURRENT, new SignedWordElement(507), SCALE_FACTOR_MINUS_1), // [A] m(Battery.ChannelId.SOC, new UnsignedWordElement(508), SCALE_FACTOR_MINUS_1), // [%] @@ -351,7 +344,7 @@ protected ModbusProtocol defineModbusProtocol() { */ private void detectHardwareType() throws OpenemsException { // Set Battery-Protection - readElementOnce(this.getModbusProtocol(), ModbusUtils::retryOnNull, new UnsignedWordElement(10019)) + readElementOnce(FC3, this.getModbusProtocol(), ModbusUtils::retryOnNull, new UnsignedWordElement(10019)) .thenAccept(value -> { if (value == null) { return; @@ -1066,7 +1059,7 @@ public BridgeModbus getModbus() { } @Override - public ModbusProtocol getDefinedModbusProtocol() throws OpenemsException { + public ModbusProtocol getDefinedModbusProtocol() { return this.getModbusProtocol(); } diff --git a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/FeneconHomeBatteryProtection64.java b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/FeneconHomeBatteryProtection64.java index bbad1e67bde..65dec26fc83 100644 --- a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/FeneconHomeBatteryProtection64.java +++ b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/FeneconHomeBatteryProtection64.java @@ -25,8 +25,8 @@ public PolyLine getChargeVoltageToPercent() { .addPoint(Math.nextUp(3000), 1) // .addPoint(3450, 1) // .addPoint(3540, 0.08) // - .addPoint(Math.nextDown(3550), 0.08) // - .addPoint(3550, 0) // + .addPoint(Math.nextDown(3580), 0.08) // + .addPoint(3580, 0) // .build(); } @@ -70,7 +70,7 @@ public PolyLine getDischargeSocToPercent() { @Override public ForceDischarge.Params getForceDischargeParams() { - return new ForceDischarge.Params(3600, 3540, 3450); + return new ForceDischarge.Params(3630, 3540, 3450); } @Override diff --git a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/ModbusHelper.java b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/ModbusHelper.java index b072d1ebdc4..4f95aa64a66 100644 --- a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/ModbusHelper.java +++ b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/ModbusHelper.java @@ -1,24 +1,22 @@ package io.openems.edge.battery.fenecon.home; -import io.openems.common.exceptions.OpenemsException; import io.openems.edge.bridge.modbus.api.BridgeModbus; import io.openems.edge.bridge.modbus.api.ModbusProtocol; public interface ModbusHelper { /** - * Get modbus bridge. + * Get the {@link BridgeModbus}. * - * @return modbus bridge. + * @return the {@link BridgeModbus} */ public BridgeModbus getModbus(); /** - * Get defined modbus protocol. + * Get defined {@link ModbusProtocol}. * - * @return modbus protocol - * @throws OpenemsException on error + * @return the {@link ModbusProtocol} */ - public ModbusProtocol getDefinedModbusProtocol() throws OpenemsException; + public ModbusProtocol getDefinedModbusProtocol(); } diff --git a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/statemachine/Context.java b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/statemachine/Context.java index a2828143f76..6cb49490bb6 100644 --- a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/statemachine/Context.java +++ b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/statemachine/Context.java @@ -32,8 +32,4 @@ public Context(BatteryFeneconHome parent, Clock clock, Boolean batteryStartUpRel this.modbusCommunicationFailed = modbusCommunicationFailed; this.retryModbusCommunication = retryModbusCommunication; } - - protected void retryModbusCommunication() { - this.getParent().retryModbusCommunication(); - } } \ No newline at end of file diff --git a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/statemachine/GoStoppedHandler.java b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/statemachine/GoStoppedHandler.java index a41a0bf4837..d539b27df30 100644 --- a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/statemachine/GoStoppedHandler.java +++ b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/statemachine/GoStoppedHandler.java @@ -12,26 +12,25 @@ public class GoStoppedHandler extends StateHandler { private static int TIMEOUT = 2100; // [35 minutes in seconds] private Instant timeAtEntry = Instant.MIN; - private boolean didProtocolAdd = false; + private boolean isProtocolAdded = false; @Override - protected void onEntry(Context context) throws OpenemsNamedException { - final var battery = context.getParent(); - final var modbus = battery.getModbus(); - modbus.removeProtocol(battery.id()); - this.didProtocolAdd = false; + protected void onEntry(Context context) { + // Remove the protocol to trigger BMS timeout + this.removeProtocol(context); + this.timeAtEntry = Instant.now(context.clock); } @Override public State runAndGetNextState(Context context) throws OpenemsException { - final var battery = context.getParent(); var now = Instant.now(context.clock); - if (Duration.between(this.timeAtEntry, now).getSeconds() > TIMEOUT && !this.didProtocolAdd) { - this.addAndRetryModbusProtocol(context); + if (Duration.between(this.timeAtEntry, now).getSeconds() > TIMEOUT && !this.isProtocolAdded) { + this.addProtocol(context); return State.GO_STOPPED; } + final var battery = context.getParent(); if (battery.getModbusCommunicationFailed()) { return State.STOPPED; } @@ -40,12 +39,25 @@ public State runAndGetNextState(Context context) throws OpenemsException { return State.GO_STOPPED; } - private void addAndRetryModbusProtocol(Context context) throws OpenemsException { + @Override + protected void onExit(Context context) throws OpenemsNamedException { + // Make sure to leave this GoStoppedHandler with added protocol + if (!this.isProtocolAdded) { + this.addProtocol(context); + } + } + + private void addProtocol(Context context) { final var battery = context.getParent(); final var modbus = battery.getModbus(); + this.isProtocolAdded = true; modbus.addProtocol(battery.id(), battery.getDefinedModbusProtocol()); - modbus.retryModbusCommunication(battery.id()); - this.didProtocolAdd = true; } + private void removeProtocol(Context context) { + final var battery = context.getParent(); + final var modbus = battery.getModbus(); + this.isProtocolAdded = false; + modbus.removeProtocol(battery.id()); + } } diff --git a/io.openems.edge.battery.fenecon.home/test/io/openems/edge/battery/fenecon/home/BatteryFeneconHomeImplTest.java b/io.openems.edge.battery.fenecon.home/test/io/openems/edge/battery/fenecon/home/BatteryFeneconHomeImplTest.java index 432eccdcb6e..77062d93093 100644 --- a/io.openems.edge.battery.fenecon.home/test/io/openems/edge/battery/fenecon/home/BatteryFeneconHomeImplTest.java +++ b/io.openems.edge.battery.fenecon.home/test/io/openems/edge/battery/fenecon/home/BatteryFeneconHomeImplTest.java @@ -1,21 +1,38 @@ package io.openems.edge.battery.fenecon.home; +import static io.openems.common.test.TestUtils.createDummyClock; +import static io.openems.edge.battery.api.Battery.ChannelId.CHARGE_MAX_CURRENT; +import static io.openems.edge.battery.api.Battery.ChannelId.CURRENT; +import static io.openems.edge.battery.api.Battery.ChannelId.MAX_CELL_VOLTAGE; +import static io.openems.edge.battery.api.Battery.ChannelId.MIN_CELL_VOLTAGE; +import static io.openems.edge.battery.api.Battery.ChannelId.SOC; +import static io.openems.edge.battery.fenecon.home.BatteryFeneconHome.ChannelId.BMS_CONTROL; +import static io.openems.edge.battery.fenecon.home.BatteryFeneconHome.ChannelId.LOW_MIN_VOLTAGE_FAULT; +import static io.openems.edge.battery.fenecon.home.BatteryFeneconHome.ChannelId.LOW_MIN_VOLTAGE_FAULT_BATTERY_STOPPED; +import static io.openems.edge.battery.fenecon.home.BatteryFeneconHome.ChannelId.LOW_MIN_VOLTAGE_WARNING; +import static io.openems.edge.battery.fenecon.home.BatteryFeneconHome.ChannelId.NUMBER_OF_MODULES_PER_TOWER; +import static io.openems.edge.battery.fenecon.home.BatteryFeneconHome.ChannelId.NUMBER_OF_TOWERS; +import static io.openems.edge.battery.fenecon.home.BatteryFeneconHome.ChannelId.STATE_MACHINE; +import static io.openems.edge.battery.fenecon.home.BatteryFeneconHome.ChannelId.TOWER_0_BMS_SOFTWARE_VERSION; +import static io.openems.edge.battery.fenecon.home.BatteryFeneconHome.ChannelId.TOWER_1_BMS_SOFTWARE_VERSION; +import static io.openems.edge.battery.fenecon.home.BatteryFeneconHome.ChannelId.TOWER_2_BMS_SOFTWARE_VERSION; +import static io.openems.edge.battery.fenecon.home.BatteryFeneconHome.ChannelId.TOWER_3_BMS_SOFTWARE_VERSION; +import static io.openems.edge.battery.fenecon.home.BatteryFeneconHome.ChannelId.TOWER_4_BMS_SOFTWARE_VERSION; +import static io.openems.edge.battery.fenecon.home.BatteryFeneconHomeImpl.DEFAULT_CRITICAL_MIN_VOLTAGE; +import static io.openems.edge.battery.fenecon.home.BatteryFeneconHomeImpl.TIMEOUT; +import static io.openems.edge.battery.protection.BatteryProtection.ChannelId.BP_CHARGE_BMS; +import static io.openems.edge.battery.protection.BatteryProtection.ChannelId.BP_CHARGE_MAX_SOC; +import static io.openems.edge.bridge.modbus.api.ModbusComponent.ChannelId.MODBUS_COMMUNICATION_FAILED; +import static io.openems.edge.io.test.DummyInputOutput.ChannelId.INPUT_OUTPUT4; +import static java.lang.Math.round; +import static java.time.temporal.ChronoUnit.SECONDS; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; -import java.time.Instant; -import java.time.ZoneOffset; -import java.time.temporal.ChronoUnit; - import org.junit.Test; import io.openems.common.function.ThrowingRunnable; -import io.openems.common.test.TimeLeapClock; -import io.openems.common.types.ChannelAddress; -import io.openems.edge.battery.api.Battery; -import io.openems.edge.battery.fenecon.home.statemachine.StateMachine; -import io.openems.edge.battery.protection.BatteryProtection; -import io.openems.edge.bridge.modbus.api.ModbusComponent; +import io.openems.edge.battery.fenecon.home.statemachine.StateMachine.State; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.startstop.StartStopConfig; import io.openems.edge.common.test.AbstractComponentTest.TestCase; @@ -26,51 +43,6 @@ public class BatteryFeneconHomeImplTest { - private static final String BATTERY_ID = "battery0"; - private static final String MODBUS_ID = "modbus0"; - private static final String IO_ID = "io0"; - - private static final ChannelAddress STATE_MACHINE = new ChannelAddress(BATTERY_ID, - BatteryFeneconHome.ChannelId.STATE_MACHINE.id()); - private static final ChannelAddress LOW_MIN_VOLTAGE_WARNING = new ChannelAddress(BATTERY_ID, - BatteryFeneconHome.ChannelId.LOW_MIN_VOLTAGE_WARNING.id()); - private static final ChannelAddress LOW_MIN_VOLTAGE_FAULT = new ChannelAddress(BATTERY_ID, - BatteryFeneconHome.ChannelId.LOW_MIN_VOLTAGE_FAULT.id()); - private static final ChannelAddress LOW_MIN_VOLTAGE_FAULT_BATTERY_STOPPED = new ChannelAddress(BATTERY_ID, - BatteryFeneconHome.ChannelId.LOW_MIN_VOLTAGE_FAULT_BATTERY_STOPPED.id()); - private static final ChannelAddress MODBUS_COMMUNICATION_FAILED = new ChannelAddress(BATTERY_ID, - ModbusComponent.ChannelId.MODBUS_COMMUNICATION_FAILED.id()); - private static final ChannelAddress BMS_CONTROL = new ChannelAddress(BATTERY_ID, - BatteryFeneconHome.ChannelId.BMS_CONTROL.id()); - private static final ChannelAddress BP_CHARGE_BMS = new ChannelAddress(BATTERY_ID, - BatteryProtection.ChannelId.BP_CHARGE_BMS.id()); - private static final ChannelAddress MAX_CELL_VOLTAGE = new ChannelAddress(BATTERY_ID, - Battery.ChannelId.MAX_CELL_VOLTAGE.id()); - private static final ChannelAddress CHARGE_MAX_CURRENT = new ChannelAddress(BATTERY_ID, - Battery.ChannelId.CHARGE_MAX_CURRENT.id()); - private static final ChannelAddress CURRENT = new ChannelAddress(BATTERY_ID, Battery.ChannelId.CURRENT.id()); - private static final ChannelAddress MIN_CELL_VOLTAGE = new ChannelAddress(BATTERY_ID, - Battery.ChannelId.MIN_CELL_VOLTAGE.id()); - private static final ChannelAddress TOWER_0_BMS_SOFTWARE_VERSION = new ChannelAddress(BATTERY_ID, - BatteryFeneconHome.ChannelId.TOWER_0_BMS_SOFTWARE_VERSION.id()); - private static final ChannelAddress TOWER_1_BMS_SOFTWARE_VERSION = new ChannelAddress(BATTERY_ID, - BatteryFeneconHome.ChannelId.TOWER_1_BMS_SOFTWARE_VERSION.id()); - private static final ChannelAddress TOWER_2_BMS_SOFTWARE_VERSION = new ChannelAddress(BATTERY_ID, - BatteryFeneconHome.ChannelId.TOWER_2_BMS_SOFTWARE_VERSION.id()); - private static final ChannelAddress TOWER_3_BMS_SOFTWARE_VERSION = new ChannelAddress(BATTERY_ID, - BatteryFeneconHome.ChannelId.TOWER_3_BMS_SOFTWARE_VERSION.id()); - private static final ChannelAddress TOWER_4_BMS_SOFTWARE_VERSION = new ChannelAddress(BATTERY_ID, - BatteryFeneconHome.ChannelId.TOWER_4_BMS_SOFTWARE_VERSION.id()); - private static final ChannelAddress NUMBER_OF_TOWERS = new ChannelAddress(BATTERY_ID, - BatteryFeneconHome.ChannelId.NUMBER_OF_TOWERS.id()); - private static final ChannelAddress NUMBER_OF_MODULES_PER_TOWER = new ChannelAddress(BATTERY_ID, - BatteryFeneconHome.ChannelId.NUMBER_OF_MODULES_PER_TOWER.id()); - private static final ChannelAddress BP_CHARGE_MAX_SOC = new ChannelAddress(BATTERY_ID, - BatteryProtection.ChannelId.BP_CHARGE_MAX_SOC.id()); - private static final ChannelAddress SOC = new ChannelAddress(BATTERY_ID, Battery.ChannelId.SOC.id()); - - private static final ChannelAddress BATTERY_RELAY = new ChannelAddress(IO_ID, "InputOutput4"); - private static ThrowingRunnable assertLog(BatteryFeneconHomeImpl sut, String message) { return () -> assertEquals(message, sut.stateMachine.debugLog()); } @@ -82,41 +54,41 @@ private static ThrowingRunnable assertLog(BatteryFeneconHomeImpl sut, */ @Test public void test() throws Exception { - final var clock = new TimeLeapClock(Instant.parse("2020-01-01T01:00:00.00Z"), ZoneOffset.UTC); + final var clock = createDummyClock(); var sut = new BatteryFeneconHomeImpl(); new ComponentTest(sut) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager(clock)) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // - .addComponent(new DummyInputOutput(IO_ID))// + .addReference("setModbus", new DummyModbusBridge("modbus0")) // + .addComponent(new DummyInputOutput("io0"))// .activate(MyConfig.create() // - .setId(BATTERY_ID) // - .setModbusId(MODBUS_ID) // + .setId("battery0") // + .setModbusId("modbus0") // .setModbusUnitId(0) // .setStartStop(StartStopConfig.START) // .setBatteryStartUpRelay("io0/InputOutput4")// .build())// .next(new TestCase() // .inputForce(MODBUS_COMMUNICATION_FAILED, true) // - .input(BATTERY_RELAY, false) // Switch OFF + .input("io0", INPUT_OUTPUT4, false) // Switch OFF .input(BMS_CONTROL, false) // Switched OFF .onBeforeProcessImage(assertLog(sut, "Undefined")) // - .output(STATE_MACHINE, StateMachine.State.UNDEFINED)) // + .output(STATE_MACHINE, State.UNDEFINED)) // .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-Undefined")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // + .output(STATE_MACHINE, State.GO_RUNNING)) // .next(new TestCase()// .onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayOn"))) // .next(new TestCase()// - .input(BATTERY_RELAY, true) // Switch ON + .input("io0", INPUT_OUTPUT4, true) // Switch ON .onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayOn"))) // .next(new TestCase()// - .input(BATTERY_RELAY, true) // Switch ON + .input("io0", INPUT_OUTPUT4, true) // Switch ON .onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayHold")) - .onAfterProcessImage(() -> clock.leap(11, ChronoUnit.SECONDS))) // + .onAfterProcessImage(() -> clock.leap(11, SECONDS))) // .next(new TestCase() // - .input(BATTERY_RELAY, false) // Switch OFF + .input("io0", INPUT_OUTPUT4, false) // Switch OFF .onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayOff"))) // .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-RetryModbusCommunication"))) // @@ -129,17 +101,17 @@ public void test() throws Exception { .next(new TestCase()// .onBeforeProcessImage(assertLog(sut, "Running")) // - .output(STATE_MACHINE, StateMachine.State.RUNNING)) // + .output(STATE_MACHINE, State.RUNNING)) // // Ramp-Up ChargeMaxCurrent (0.1 A / Second) .next(new TestCase() // .input(BP_CHARGE_BMS, 40) // .input(MAX_CELL_VOLTAGE, 3000)) // .next(new TestCase("Ramp up") // - .timeleap(clock, 100, ChronoUnit.SECONDS) // + .timeleap(clock, 100, SECONDS) // .output(CHARGE_MAX_CURRENT, 10)) // .next(new TestCase() // - .timeleap(clock, 300, ChronoUnit.SECONDS) // + .timeleap(clock, 300, SECONDS) // .output(CHARGE_MAX_CURRENT, 40)) // Full Battery @@ -151,7 +123,7 @@ public void test() throws Exception { .next(new TestCase() // .input(BP_CHARGE_BMS, 40)) // .next(new TestCase() // - .timeleap(clock, 100, ChronoUnit.SECONDS) // + .timeleap(clock, 100, SECONDS) // .output(CHARGE_MAX_CURRENT, 25)) // ; } @@ -163,54 +135,54 @@ public void test() throws Exception { */ @Test public void test2() throws Exception { - final var clock = new TimeLeapClock(Instant.parse("2020-01-01T01:00:00.00Z"), ZoneOffset.UTC); + final var clock = createDummyClock(); var sut = new BatteryFeneconHomeImpl(); new ComponentTest(sut) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager(clock)) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // - .addComponent(new DummyInputOutput(IO_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // + .addComponent(new DummyInputOutput("io0")) // .activate(MyConfig.create() // - .setId(BATTERY_ID) // - .setModbusId(MODBUS_ID) // + .setId("battery0") // + .setModbusId("modbus0") // .setModbusUnitId(0) // .setStartStop(StartStopConfig.START) // .setBatteryStartUpRelay("io0/InputOutput4")// .build())// .next(new TestCase()// - .input(BATTERY_RELAY, true) // + .input("io0", INPUT_OUTPUT4, true) // .input(BMS_CONTROL, false) // Switched Off - .output(STATE_MACHINE, StateMachine.State.UNDEFINED))// + .output(STATE_MACHINE, State.UNDEFINED))// .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-Undefined")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // + .output(STATE_MACHINE, State.GO_RUNNING)) // .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayOn")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING))// + .output(STATE_MACHINE, State.GO_RUNNING))// .next(new TestCase()// - .input(BATTERY_RELAY, true) // Switch ON + .input("io0", INPUT_OUTPUT4, true) // Switch ON .onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayHold")) - .onAfterProcessImage(() -> clock.leap(11, ChronoUnit.SECONDS))) // + .onAfterProcessImage(() -> clock.leap(11, SECONDS))) // .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayOff")) // .input(BMS_CONTROL, true) // Switched On - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // + .output(STATE_MACHINE, State.GO_RUNNING)) // .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayOff")) // - .input(BATTERY_RELAY, false) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // + .input("io0", INPUT_OUTPUT4, false) // + .output(STATE_MACHINE, State.GO_RUNNING)) // .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-RetryModbusCommunication")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // + .output(STATE_MACHINE, State.GO_RUNNING)) // .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-WaitForBmsControl")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // + .output(STATE_MACHINE, State.GO_RUNNING)) // .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-WaitForModbusCommunication")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // + .output(STATE_MACHINE, State.GO_RUNNING)) // .next(new TestCase() // - .output(STATE_MACHINE, StateMachine.State.RUNNING)); + .output(STATE_MACHINE, State.RUNNING)); } /** @@ -225,37 +197,37 @@ public void test3() throws Exception { new ComponentTest(sut) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // - .addComponent(new DummyInputOutput(IO_ID))// + .addReference("setModbus", new DummyModbusBridge("modbus0")) // + .addComponent(new DummyInputOutput("io0"))// .activate(MyConfig.create() // - .setId(BATTERY_ID) // - .setModbusId(MODBUS_ID) // + .setId("battery0") // + .setModbusId("modbus0") // .setModbusUnitId(0) // .setStartStop(StartStopConfig.START) // .setBatteryStartUpRelay("io0/InputOutput4")// .build()) // .next(new TestCase() // - .input(BATTERY_RELAY, false) // + .input("io0", INPUT_OUTPUT4, false) // .input(BMS_CONTROL, true) // Switched On - .output(STATE_MACHINE, StateMachine.State.UNDEFINED))// + .output(STATE_MACHINE, State.UNDEFINED))// .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-Undefined")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // + .output(STATE_MACHINE, State.GO_RUNNING)) // .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayOff")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING))// + .output(STATE_MACHINE, State.GO_RUNNING))// .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-RetryModbusCommunication")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING))// + .output(STATE_MACHINE, State.GO_RUNNING))// .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-WaitForBmsControl")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // + .output(STATE_MACHINE, State.GO_RUNNING)) // .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-WaitForModbusCommunication")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // + .output(STATE_MACHINE, State.GO_RUNNING)) // .next(new TestCase() // - .output(STATE_MACHINE, StateMachine.State.RUNNING)); + .output(STATE_MACHINE, State.RUNNING)); } /** @@ -265,53 +237,53 @@ public void test3() throws Exception { */ @Test public void test4() throws Exception { - final var clock = new TimeLeapClock(Instant.parse("2020-01-01T01:00:00.00Z"), ZoneOffset.UTC); + final var clock = createDummyClock(); var sut = new BatteryFeneconHomeImpl(); new ComponentTest(sut) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager(clock)) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // - .addComponent(new DummyInputOutput(IO_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // + .addComponent(new DummyInputOutput("io0")) // .activate(MyConfig.create() // - .setId(BATTERY_ID) // - .setModbusId(MODBUS_ID) // + .setId("battery0") // + .setModbusId("modbus0") // .setModbusUnitId(0) // .setStartStop(StartStopConfig.START) // .setBatteryStartUpRelay("io0/InputOutput4") // .build()) // .next(new TestCase() // - .input(BATTERY_RELAY, false) // + .input("io0", INPUT_OUTPUT4, false) // .input(BMS_CONTROL, false) // Switched Off - .output(STATE_MACHINE, StateMachine.State.UNDEFINED)) // + .output(STATE_MACHINE, State.UNDEFINED)) // .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-Undefined")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // + .output(STATE_MACHINE, State.GO_RUNNING)) // .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayOn")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // + .output(STATE_MACHINE, State.GO_RUNNING)) // .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayOn")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // + .output(STATE_MACHINE, State.GO_RUNNING)) // // Ex; after long time if hard switch turned on.... .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayOn")) // .input(BMS_CONTROL, true) // Switched On - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // + .output(STATE_MACHINE, State.GO_RUNNING)) // .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayOn")) // - .input(BATTERY_RELAY, true) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // + .input("io0", INPUT_OUTPUT4, true) // + .output(STATE_MACHINE, State.GO_RUNNING)) // .next(new TestCase()// - .input(BATTERY_RELAY, true) // Switch ON + .input("io0", INPUT_OUTPUT4, true) // Switch ON .onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayHold")) - .onAfterProcessImage(() -> clock.leap(11, ChronoUnit.SECONDS))) // + .onAfterProcessImage(() -> clock.leap(11, SECONDS))) // .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayOff")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING))// + .output(STATE_MACHINE, State.GO_RUNNING))// .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayOff")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)); // + .output(STATE_MACHINE, State.GO_RUNNING)); // } @Test @@ -334,224 +306,221 @@ public void testGetHardwareTypeFromRegisterValue() { @Test public void testMinVoltageGoStopped() throws Exception { - final var clock = new TimeLeapClock(Instant.parse("2020-01-01T01:00:00.00Z"), ZoneOffset.UTC); + final var clock = createDummyClock(); var sut = new BatteryFeneconHomeImpl(); new ComponentTest(sut) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager(clock)) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // - .addComponent(new DummyInputOutput(IO_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // + .addComponent(new DummyInputOutput("io0")) // .activate(MyConfig.create() // - .setId(BATTERY_ID) // - .setModbusId(MODBUS_ID) // + .setId("battery0") // + .setModbusId("modbus0") // .setModbusUnitId(0) // .setStartStop(StartStopConfig.START) // .setBatteryStartUpRelay("io0/InputOutput4")// .build())// .next(new TestCase() // - .input(BATTERY_RELAY, false) // + .input("io0", INPUT_OUTPUT4, false) // .input(BMS_CONTROL, true) // Switched On - .output(STATE_MACHINE, StateMachine.State.UNDEFINED))// + .output(STATE_MACHINE, State.UNDEFINED))// .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-Undefined")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // + .output(STATE_MACHINE, State.GO_RUNNING)) // .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayOff")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING))// + .output(STATE_MACHINE, State.GO_RUNNING))// .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-RetryModbusCommunication")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING))// + .output(STATE_MACHINE, State.GO_RUNNING))// .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-WaitForBmsControl")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // + .output(STATE_MACHINE, State.GO_RUNNING)) // .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-WaitForModbusCommunication")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // + .output(STATE_MACHINE, State.GO_RUNNING)) // .next(new TestCase() // - .output(STATE_MACHINE, StateMachine.State.RUNNING)) + .output(STATE_MACHINE, State.RUNNING)) /* * Critical min voltage */ .next(new TestCase("MinCellVoltage below critical value") // - .input(MIN_CELL_VOLTAGE, (BatteryFeneconHomeImpl.DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) // + .input(MIN_CELL_VOLTAGE, (DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) // .input(CURRENT, 0) // .output(LOW_MIN_VOLTAGE_WARNING, true) // .output(LOW_MIN_VOLTAGE_FAULT, false) // - .output(STATE_MACHINE, StateMachine.State.RUNNING) // + .output(STATE_MACHINE, State.RUNNING) // .output(LOW_MIN_VOLTAGE_FAULT_BATTERY_STOPPED, false) // - .onAfterControllersCallbacks( - () -> clock.leap(BatteryFeneconHomeImpl.TIMEOUT - 10, ChronoUnit.SECONDS))) // + .onAfterControllersCallbacks(() -> clock.leap(TIMEOUT - 10, SECONDS))) // .next(new TestCase("MinCellVoltage below critical value - charging resets time") // - .input(MIN_CELL_VOLTAGE, (BatteryFeneconHomeImpl.DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) // + .input(MIN_CELL_VOLTAGE, (DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) // .input(CURRENT, -300) // .output(LOW_MIN_VOLTAGE_WARNING, true) // .output(LOW_MIN_VOLTAGE_FAULT, false) // .output(LOW_MIN_VOLTAGE_FAULT_BATTERY_STOPPED, false)) // .next(new TestCase("MinCellVoltage below critical value - timer starts again") // .input(CURRENT, 0) // - .input(MIN_CELL_VOLTAGE, (BatteryFeneconHomeImpl.DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) // + .input(MIN_CELL_VOLTAGE, (DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) // .output(LOW_MIN_VOLTAGE_WARNING, true) // .output(LOW_MIN_VOLTAGE_FAULT, false) // .output(LOW_MIN_VOLTAGE_FAULT_BATTERY_STOPPED, false) // - .onAfterControllersCallbacks( - () -> clock.leap(BatteryFeneconHomeImpl.TIMEOUT - 10, ChronoUnit.SECONDS))) // + .onAfterControllersCallbacks(() -> clock.leap(TIMEOUT - 10, SECONDS))) // .next(new TestCase("MinCellVoltage below critical value - time not passed") // .input(CURRENT, 0) // - .input(MIN_CELL_VOLTAGE, (BatteryFeneconHomeImpl.DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) // + .input(MIN_CELL_VOLTAGE, (DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) // .output(LOW_MIN_VOLTAGE_WARNING, true) // .output(LOW_MIN_VOLTAGE_FAULT, false) // .output(LOW_MIN_VOLTAGE_FAULT_BATTERY_STOPPED, false) // - .onAfterControllersCallbacks(() -> clock.leap(15, ChronoUnit.SECONDS))) // + .onAfterControllersCallbacks(() -> clock.leap(15, SECONDS))) // .next(new TestCase("MinCellVoltage below critical value - time passed") // .input(CURRENT, 0) // - .input(MIN_CELL_VOLTAGE, (BatteryFeneconHomeImpl.DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) // + .input(MIN_CELL_VOLTAGE, (DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) // .output(LOW_MIN_VOLTAGE_FAULT, true) // .output(LOW_MIN_VOLTAGE_WARNING, false) // .output(LOW_MIN_VOLTAGE_FAULT_BATTERY_STOPPED, false) // - .output(STATE_MACHINE, StateMachine.State.RUNNING)) // + .output(STATE_MACHINE, State.RUNNING)) // .next(new TestCase("MinCellVoltage below critical value - error") // .input(LOW_MIN_VOLTAGE_FAULT, true) // .input(CURRENT, 0) // - .input(MIN_CELL_VOLTAGE, (BatteryFeneconHomeImpl.DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) // + .input(MIN_CELL_VOLTAGE, (DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) // .output(LOW_MIN_VOLTAGE_FAULT, true) // .output(LOW_MIN_VOLTAGE_WARNING, false) // .output(LOW_MIN_VOLTAGE_FAULT_BATTERY_STOPPED, false)) // .next(new TestCase() // - .output(STATE_MACHINE, StateMachine.State.ERROR)) // + .output(STATE_MACHINE, State.ERROR)) // .next(new TestCase("MinCellVoltage below critical value - go stopped") // .input(LOW_MIN_VOLTAGE_FAULT, true) // .input(CURRENT, 0) // // MinCellVoltage would be null, but there is not DummyTimedata for not to test // "getPastValues" - .input(MIN_CELL_VOLTAGE, (BatteryFeneconHomeImpl.DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) // + .input(MIN_CELL_VOLTAGE, (DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) // .output(LOW_MIN_VOLTAGE_FAULT, true) // .output(LOW_MIN_VOLTAGE_WARNING, false) // .output(LOW_MIN_VOLTAGE_FAULT_BATTERY_STOPPED, false) // - .output(STATE_MACHINE, StateMachine.State.GO_STOPPED) // - .onAfterControllersCallbacks(() -> clock.leap(2_100, ChronoUnit.SECONDS))) // 35 minutes + .output(STATE_MACHINE, State.GO_STOPPED) // + .onAfterControllersCallbacks(() -> clock.leap(2_100, SECONDS))) // 35 minutes .next(new TestCase() // .input(MODBUS_COMMUNICATION_FAILED, true) // ) // .next(new TestCase("MinCellVoltage below critical value - stopped") // .input(CURRENT, 0) // .input(MODBUS_COMMUNICATION_FAILED, true) // - .input(MIN_CELL_VOLTAGE, (BatteryFeneconHomeImpl.DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) // + .input(MIN_CELL_VOLTAGE, (DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) // .output(LOW_MIN_VOLTAGE_WARNING, false) // .output(LOW_MIN_VOLTAGE_FAULT, false) // .output(LOW_MIN_VOLTAGE_FAULT_BATTERY_STOPPED, true) // - .output(STATE_MACHINE, StateMachine.State.STOPPED) // + .output(STATE_MACHINE, State.STOPPED) // ); } @Test public void testMinVoltageCharging() throws Exception { - final var clock = new TimeLeapClock(Instant.parse("2020-01-01T01:00:00.00Z"), ZoneOffset.UTC); + final var clock = createDummyClock(); var sut = new BatteryFeneconHomeImpl(); new ComponentTest(sut) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager(clock)) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // - .addComponent(new DummyInputOutput(IO_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // + .addComponent(new DummyInputOutput("io0")) // .activate(MyConfig.create() // - .setId(BATTERY_ID) // - .setModbusId(MODBUS_ID) // + .setId("battery0") // + .setModbusId("modbus0") // .setModbusUnitId(0) // .setStartStop(StartStopConfig.START) // .setBatteryStartUpRelay("io0/InputOutput4")// .build())// .next(new TestCase() // - .input(BATTERY_RELAY, false) // + .input("io0", INPUT_OUTPUT4, false) // .input(BMS_CONTROL, true) // Switched On - .output(STATE_MACHINE, StateMachine.State.UNDEFINED))// + .output(STATE_MACHINE, State.UNDEFINED))// .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-Undefined")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // + .output(STATE_MACHINE, State.GO_RUNNING)) // .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayOff")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING))// + .output(STATE_MACHINE, State.GO_RUNNING))// .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-RetryModbusCommunication")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING))// + .output(STATE_MACHINE, State.GO_RUNNING))// .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-WaitForBmsControl")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // + .output(STATE_MACHINE, State.GO_RUNNING)) // .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-WaitForModbusCommunication")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // + .output(STATE_MACHINE, State.GO_RUNNING)) // .next(new TestCase() // - .output(STATE_MACHINE, StateMachine.State.RUNNING)) + .output(STATE_MACHINE, State.RUNNING)) /* * Critical min voltage */ .next(new TestCase("MinCellVoltage below critical value") // - .input(MIN_CELL_VOLTAGE, (BatteryFeneconHomeImpl.DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) // + .input(MIN_CELL_VOLTAGE, (DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) // .input(CURRENT, 0) // .output(LOW_MIN_VOLTAGE_WARNING, true) // .output(LOW_MIN_VOLTAGE_FAULT, false) // - .output(STATE_MACHINE, StateMachine.State.RUNNING) // + .output(STATE_MACHINE, State.RUNNING) // .output(LOW_MIN_VOLTAGE_FAULT_BATTERY_STOPPED, false) // - .onAfterControllersCallbacks( - () -> clock.leap(BatteryFeneconHomeImpl.TIMEOUT - 10, ChronoUnit.SECONDS))) // + .onAfterControllersCallbacks(() -> clock.leap(TIMEOUT - 10, SECONDS))) // .next(new TestCase("MinCellVoltage below critical value - charging resets time") // - .input(MIN_CELL_VOLTAGE, (BatteryFeneconHomeImpl.DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) // + .input(MIN_CELL_VOLTAGE, (DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) // .input(CURRENT, -300) // .output(LOW_MIN_VOLTAGE_WARNING, true) // .output(LOW_MIN_VOLTAGE_FAULT, false) // - .output(STATE_MACHINE, StateMachine.State.RUNNING) // + .output(STATE_MACHINE, State.RUNNING) // .output(LOW_MIN_VOLTAGE_FAULT_BATTERY_STOPPED, false)) // .next(new TestCase("MinCellVoltage below critical value - charging") // .input(CURRENT, -2000) // - .input(MIN_CELL_VOLTAGE, (BatteryFeneconHomeImpl.DEFAULT_CRITICAL_MIN_VOLTAGE + 50)) // + .input(MIN_CELL_VOLTAGE, (DEFAULT_CRITICAL_MIN_VOLTAGE + 50)) // .output(LOW_MIN_VOLTAGE_WARNING, false) // .output(LOW_MIN_VOLTAGE_FAULT, false) // - .output(STATE_MACHINE, StateMachine.State.RUNNING) // + .output(STATE_MACHINE, State.RUNNING) // .output(LOW_MIN_VOLTAGE_FAULT_BATTERY_STOPPED, false) // ); } @Test public void testNumberOfTowers() throws Exception { - final var clock = new TimeLeapClock(Instant.parse("2020-01-01T01:00:00.00Z"), ZoneOffset.UTC); + final var clock = createDummyClock(); var sut = new BatteryFeneconHomeImpl(); new ComponentTest(sut) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager(clock)) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // - .addComponent(new DummyInputOutput(IO_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // + .addComponent(new DummyInputOutput("io0")) // .activate(MyConfig.create() // - .setId(BATTERY_ID) // - .setModbusId(MODBUS_ID) // + .setId("battery0") // + .setModbusId("modbus0") // .setModbusUnitId(0) // .setStartStop(StartStopConfig.START) // - .setBatteryStartUpRelay("io0/InputOutput4")// - .build())// + .setBatteryStartUpRelay("io0/InputOutput4") // + .build()) // .next(new TestCase() // - .input(BATTERY_RELAY, false) // + .input("io0", INPUT_OUTPUT4, false) // .input(BMS_CONTROL, true) // Switched On - .output(STATE_MACHINE, StateMachine.State.UNDEFINED))// + .output(STATE_MACHINE, State.UNDEFINED))// .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-Undefined")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // + .output(STATE_MACHINE, State.GO_RUNNING)) // .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayOff")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING))// + .output(STATE_MACHINE, State.GO_RUNNING))// .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-RetryModbusCommunication")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING))// + .output(STATE_MACHINE, State.GO_RUNNING))// .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-WaitForBmsControl")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // + .output(STATE_MACHINE, State.GO_RUNNING)) // .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-WaitForModbusCommunication")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // + .output(STATE_MACHINE, State.GO_RUNNING)) // .next(new TestCase() // - .output(STATE_MACHINE, StateMachine.State.RUNNING)) + .output(STATE_MACHINE, State.RUNNING)) .next(new TestCase() // .output(NUMBER_OF_TOWERS, null)) .next(new TestCase() // @@ -610,37 +579,37 @@ public void testBatteryProtectionSocLimitations() throws Exception { new ComponentTest(sut) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // - .addComponent(new DummyInputOutput(IO_ID))// + .addReference("setModbus", new DummyModbusBridge("modbus0")) // + .addComponent(new DummyInputOutput("io0"))// .activate(MyConfig.create() // - .setId(BATTERY_ID) // - .setModbusId(MODBUS_ID) // + .setId("battery0") // + .setModbusId("modbus0") // .setModbusUnitId(0) // .setStartStop(StartStopConfig.START) // .setBatteryStartUpRelay("io0/InputOutput4")// .build()) // .next(new TestCase() // - .input(BATTERY_RELAY, false) // + .input("io0", INPUT_OUTPUT4, false) // .input(BMS_CONTROL, true) // Switched On - .output(STATE_MACHINE, StateMachine.State.UNDEFINED))// + .output(STATE_MACHINE, State.UNDEFINED))// .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-Undefined")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // + .output(STATE_MACHINE, State.GO_RUNNING)) // .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayOff")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING))// + .output(STATE_MACHINE, State.GO_RUNNING))// .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-RetryModbusCommunication")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING))// + .output(STATE_MACHINE, State.GO_RUNNING))// .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-WaitForBmsControl")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // + .output(STATE_MACHINE, State.GO_RUNNING)) // .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-WaitForModbusCommunication")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // + .output(STATE_MACHINE, State.GO_RUNNING)) // .next(new TestCase() // - .output(STATE_MACHINE, StateMachine.State.RUNNING)) // + .output(STATE_MACHINE, State.RUNNING)) // .next(new TestCase() // .output(BP_CHARGE_MAX_SOC, 40)) // @@ -648,23 +617,45 @@ public void testBatteryProtectionSocLimitations() throws Exception { .input(SOC, 97) // .output(SOC, 97)) // .next(new TestCase() // - .output(BP_CHARGE_MAX_SOC, (int) Math.round(40 * 0.625))) // + .output(BP_CHARGE_MAX_SOC, round(40 * 0.625F))) // .next(new TestCase() // .input(SOC, 98)) // .next(new TestCase() // - .output(BP_CHARGE_MAX_SOC, (int) Math.round(40 * 0.4))) // + .output(BP_CHARGE_MAX_SOC, round(40 * 0.4F))) // .next(new TestCase() // .input(SOC, 99)) // .next(new TestCase() // - .output(BP_CHARGE_MAX_SOC, (int) Math.round(40 * 0.2))) // + .output(BP_CHARGE_MAX_SOC, round(40 * 0.2F))) // .next(new TestCase() // .input(SOC, 100)) // .next(new TestCase() // - .output(BP_CHARGE_MAX_SOC, (int) Math.round(40 * 0.05))) // + .output(BP_CHARGE_MAX_SOC, round(40 * 0.05F))) // .next(new TestCase() // .input(SOC, 99)) // .next(new TestCase() // - .output(BP_CHARGE_MAX_SOC, (int) Math.round(40 * 0.2)) // + .output(BP_CHARGE_MAX_SOC, round(40 * 0.2F)) // ); } + + @Test + public void testReadModbus() throws Exception { + var sut = new BatteryFeneconHomeImpl(); + new ComponentTest(sut) // + .addReference("cm", new DummyConfigurationAdmin()) // + .addReference("componentManager", new DummyComponentManager()) // + .addReference("setModbus", new DummyModbusBridge("modbus0") // + .withRegister(18000, (byte) 0x00, (byte) 0x00)) // TOWER_4_BMS_SOFTWARE_VERSION + .activate(MyConfig.create() // + .setId("battery0") // + .setModbusId("modbus0") // + .setModbusUnitId(0) // + .setStartStop(StartStopConfig.START) // + .setBatteryStartUpRelay("io0/InputOutput4")// + .build()) // + + .next(new TestCase() // + .output(BatteryFeneconHome.ChannelId.TOWER_4_BMS_SOFTWARE_VERSION, 0)) // + + .deactivate(); + } } diff --git a/io.openems.edge.battery.fenecon.home/test/io/openems/edge/battery/fenecon/home/TowersAndModulesTest.java b/io.openems.edge.battery.fenecon.home/test/io/openems/edge/battery/fenecon/home/TowersAndModulesTest.java index ff8b7353731..9fff171c345 100644 --- a/io.openems.edge.battery.fenecon.home/test/io/openems/edge/battery/fenecon/home/TowersAndModulesTest.java +++ b/io.openems.edge.battery.fenecon.home/test/io/openems/edge/battery/fenecon/home/TowersAndModulesTest.java @@ -1,11 +1,15 @@ package io.openems.edge.battery.fenecon.home; +import static io.openems.edge.battery.fenecon.home.BatteryFeneconHome.ChannelId.BATTERY_HARDWARE_TYPE; +import static io.openems.edge.battery.fenecon.home.BatteryFeneconHome.ChannelId.NUMBER_OF_MODULES_PER_TOWER; +import static io.openems.edge.battery.fenecon.home.BatteryFeneconHome.ChannelId.TOWER_0_BMS_SOFTWARE_VERSION; +import static io.openems.edge.battery.fenecon.home.BatteryFeneconHome.ChannelId.TOWER_1_BMS_SOFTWARE_VERSION; +import static io.openems.edge.battery.fenecon.home.BatteryFeneconHome.ChannelId.TOWER_2_BMS_SOFTWARE_VERSION; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import org.junit.Test; -import io.openems.common.types.ChannelAddress; import io.openems.edge.battery.api.Battery; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.channel.ChannelId; @@ -17,19 +21,6 @@ public class TowersAndModulesTest { - private static final String BATTERY_ID = "battery0"; - private static final String MODBUS_ID = "modbus0"; - - private static final ChannelAddress NUMBER_OF_MODULES_PER_TOWER = new ChannelAddress(BATTERY_ID, - "NumberOfModulesPerTower"); - private static final ChannelAddress TOWER_0_BMS_SOFTWARE_VERSION = new ChannelAddress(BATTERY_ID, - "Tower0BmsSoftwareVersion"); - private static final ChannelAddress TOWER_1_BMS_SOFTWARE_VERSION = new ChannelAddress(BATTERY_ID, - "Tower1BmsSoftwareVersion"); - private static final ChannelAddress TOWER_2_BMS_SOFTWARE_VERSION = new ChannelAddress(BATTERY_ID, - "Tower2BmsSoftwareVersion"); - private static final ChannelAddress BATTERY_HARDWARE_TYPE = new ChannelAddress(BATTERY_ID, "BatteryHardwareType"); - private static final int TOWERS = 1; private static final int MODULES = 5; private static final int CELLS = 14; @@ -40,10 +31,10 @@ public void testChannelsCreatedDynamically() throws Exception { var componentTest = new ComponentTest(battery) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(BATTERY_ID) // - .setModbusId(MODBUS_ID) // + .setId("battery0") // + .setModbusId("modbus0") // .setModbusUnitId(0) // .setBatteryStartUpRelay("io0/Relay4") // .setStartStop(StartStopConfig.AUTO) // diff --git a/io.openems.edge.battery.soltaro/.classpath b/io.openems.edge.battery.soltaro/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.battery.soltaro/.classpath +++ b/io.openems.edge.battery.soltaro/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/BatterySoltaroClusterVersionB.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/BatterySoltaroClusterVersionB.java index 1276244de00..867c5b99a9f 100644 --- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/BatterySoltaroClusterVersionB.java +++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/BatterySoltaroClusterVersionB.java @@ -52,80 +52,80 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { .accessMode(AccessMode.READ_WRITE)), // // StateChannels - MASTER_ALARM_COMMUNICATION_ERROR_WITH_SUBMASTER(Doc.of(Level.FAULT) // + MASTER_ALARM_COMMUNICATION_ERROR_WITH_SUBMASTER(Doc.of(Level.WARNING) // .text("Communication error with submaster")), - MASTER_ALARM_PCS_EMS_COMMUNICATION_FAILURE(Doc.of(Level.FAULT) // + MASTER_ALARM_PCS_EMS_COMMUNICATION_FAILURE(Doc.of(Level.WARNING) // .text("PCS/EMS communication failure alarm")), - MASTER_ALARM_PCS_EMS_CONTROL_FAIL(Doc.of(Level.FAULT) // + MASTER_ALARM_PCS_EMS_CONTROL_FAIL(Doc.of(Level.WARNING) // .text("PCS/EMS control fail alarm")), MASTER_ALARM_LEVEL_1_INSULATION(Doc.of(Level.WARNING) // .text("System insulation alarm level 1")), - MASTER_ALARM_LEVEL_2_INSULATION(Doc.of(Level.FAULT) // + MASTER_ALARM_LEVEL_2_INSULATION(Doc.of(Level.WARNING) // .text("System insulation alarm level 2")), - RACK_1_LEVEL_2_ALARM(Doc.of(Level.FAULT) // + RACK_1_LEVEL_2_ALARM(Doc.of(Level.WARNING) // .text("Rack 1 Level 2 Alarm")), - RACK_1_PCS_CONTROL_FAULT(Doc.of(Level.FAULT) // + RACK_1_PCS_CONTROL_FAULT(Doc.of(Level.WARNING) // .text("Rack 1 PCS control fault")), - RACK_1_COMMUNICATION_WITH_MASTER_ERROR(Doc.of(Level.FAULT) // + RACK_1_COMMUNICATION_WITH_MASTER_ERROR(Doc.of(Level.WARNING) // .text("Rack 1 Communication with master error")), - RACK_1_DEVICE_ERROR(Doc.of(Level.FAULT) // + RACK_1_DEVICE_ERROR(Doc.of(Level.WARNING) // .text("Rack 1 Device error")), - RACK_1_CYCLE_OVER_CURRENT(Doc.of(Level.FAULT) // + RACK_1_CYCLE_OVER_CURRENT(Doc.of(Level.WARNING) // .text("Rack 1 Cycle over current")), - RACK_1_VOLTAGE_DIFFERENCE(Doc.of(Level.FAULT) // + RACK_1_VOLTAGE_DIFFERENCE(Doc.of(Level.WARNING) // .text("Rack 1 Voltage difference")), - RACK_2_LEVEL_2_ALARM(Doc.of(Level.FAULT) // + RACK_2_LEVEL_2_ALARM(Doc.of(Level.WARNING) // .text("Rack 2 Level 2 Alarm")), - RACK_2_PCS_CONTROL_FAULT(Doc.of(Level.FAULT) // + RACK_2_PCS_CONTROL_FAULT(Doc.of(Level.WARNING) // .text("Rack 2 PCS control fault")), - RACK_2_COMMUNICATION_WITH_MASTER_ERROR(Doc.of(Level.FAULT) // + RACK_2_COMMUNICATION_WITH_MASTER_ERROR(Doc.of(Level.WARNING) // .text("Rack 2 Communication with master error")), - RACK_2_DEVICE_ERROR(Doc.of(Level.FAULT) // + RACK_2_DEVICE_ERROR(Doc.of(Level.WARNING) // .text("Rack 2 Device error")), - RACK_2_CYCLE_OVER_CURRENT(Doc.of(Level.FAULT) // + RACK_2_CYCLE_OVER_CURRENT(Doc.of(Level.WARNING) // .text("Rack 1 Cycle over current")), - RACK_2_VOLTAGE_DIFFERENCE(Doc.of(Level.FAULT) // + RACK_2_VOLTAGE_DIFFERENCE(Doc.of(Level.WARNING) // .text("Rack 1 Voltage difference")), - RACK_3_LEVEL_2_ALARM(Doc.of(Level.FAULT) // + RACK_3_LEVEL_2_ALARM(Doc.of(Level.WARNING) // .text("Rack 3 Level 2 Alarm")), - RACK_3_PCS_CONTROL_FAULT(Doc.of(Level.FAULT) // + RACK_3_PCS_CONTROL_FAULT(Doc.of(Level.WARNING) // .text("Rack 3 PCS control fault")), - RACK_3_COMMUNICATION_WITH_MASTER_ERROR(Doc.of(Level.FAULT) // + RACK_3_COMMUNICATION_WITH_MASTER_ERROR(Doc.of(Level.WARNING) // .text("Rack 3 Communication with master error")), - RACK_3_DEVICE_ERROR(Doc.of(Level.FAULT) // + RACK_3_DEVICE_ERROR(Doc.of(Level.WARNING) // .text("Rack 3 Device error")), - RACK_3_CYCLE_OVER_CURRENT(Doc.of(Level.FAULT) // + RACK_3_CYCLE_OVER_CURRENT(Doc.of(Level.WARNING) // .text("Rack 1 Cycle over current")), - RACK_3_VOLTAGE_DIFFERENCE(Doc.of(Level.FAULT) // + RACK_3_VOLTAGE_DIFFERENCE(Doc.of(Level.WARNING) // .text("Rack 1 Voltage difference")), - RACK_4_LEVEL_2_ALARM(Doc.of(Level.FAULT) // + RACK_4_LEVEL_2_ALARM(Doc.of(Level.WARNING) // .text("Rack 4 Level 2 Alarm")), - RACK_4_PCS_CONTROL_FAULT(Doc.of(Level.FAULT) // + RACK_4_PCS_CONTROL_FAULT(Doc.of(Level.WARNING) // .text("Rack 4 PCS control fault")), - RACK_4_COMMUNICATION_WITH_MASTER_ERROR(Doc.of(Level.FAULT) // + RACK_4_COMMUNICATION_WITH_MASTER_ERROR(Doc.of(Level.WARNING) // .text("Rack 4 Communication with master error")), - RACK_4_DEVICE_ERROR(Doc.of(Level.FAULT) // + RACK_4_DEVICE_ERROR(Doc.of(Level.WARNING) // .text("Rack 4 Device error")), - RACK_4_CYCLE_OVER_CURRENT(Doc.of(Level.FAULT) // + RACK_4_CYCLE_OVER_CURRENT(Doc.of(Level.WARNING) // .text("Rack 1 Cycle over current")), - RACK_4_VOLTAGE_DIFFERENCE(Doc.of(Level.FAULT) // + RACK_4_VOLTAGE_DIFFERENCE(Doc.of(Level.WARNING) // .text("Rack 1 Voltage difference")), - RACK_5_LEVEL_2_ALARM(Doc.of(Level.FAULT) // + RACK_5_LEVEL_2_ALARM(Doc.of(Level.WARNING) // .text("Rack 5 Level 2 Alarm")), - RACK_5_PCS_CONTROL_FAULT(Doc.of(Level.FAULT) // + RACK_5_PCS_CONTROL_FAULT(Doc.of(Level.WARNING) // .text("Rack 5 PCS control fault")), - RACK_5_COMMUNICATION_WITH_MASTER_ERROR(Doc.of(Level.FAULT) // + RACK_5_COMMUNICATION_WITH_MASTER_ERROR(Doc.of(Level.WARNING) // .text("Rack 5 Communication with master error")), - RACK_5_DEVICE_ERROR(Doc.of(Level.FAULT) // + RACK_5_DEVICE_ERROR(Doc.of(Level.WARNING) // .text("Rack 5 Device error")), - RACK_5_CYCLE_OVER_CURRENT(Doc.of(Level.FAULT) // + RACK_5_CYCLE_OVER_CURRENT(Doc.of(Level.WARNING) // .text("Rack 1 Cycle over current")), - RACK_5_VOLTAGE_DIFFERENCE(Doc.of(Level.FAULT) // + RACK_5_VOLTAGE_DIFFERENCE(Doc.of(Level.WARNING) // .text("Rack 1 Voltage difference")),; private final Doc doc; diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/SingleRack.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/SingleRack.java index 45d65c88cfb..e5d4529f972 100644 --- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/SingleRack.java +++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/SingleRack.java @@ -1,5 +1,14 @@ package io.openems.edge.battery.soltaro.cluster.versionb; +import static io.openems.common.channel.AccessMode.READ_WRITE; +import static io.openems.common.channel.Level.OK; +import static io.openems.common.channel.Level.WARNING; +import static io.openems.common.channel.Unit.DEZIDEGREE_CELSIUS; +import static io.openems.common.channel.Unit.MILLIAMPERE; +import static io.openems.common.channel.Unit.MILLIVOLT; +import static io.openems.common.channel.Unit.NONE; +import static io.openems.common.channel.Unit.PERCENT; +import static io.openems.common.types.OpenemsType.INTEGER; import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SCALE_FACTOR_2; import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SCALE_FACTOR_MINUS_1; @@ -9,10 +18,6 @@ import java.util.Map; import java.util.Optional; -import io.openems.common.channel.AccessMode; -import io.openems.common.channel.Level; -import io.openems.common.channel.Unit; -import io.openems.common.types.OpenemsType; import io.openems.edge.battery.soltaro.common.enums.ChargeIndication; import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent; import io.openems.edge.bridge.modbus.api.element.BitsWordElement; @@ -426,104 +431,102 @@ private Map> createChannelMap() { private Map createChannelIdMap() { Map map = new HashMap<>(); - this.addEntry(map, KEY_VOLTAGE, new IntegerDoc().unit(Unit.MILLIVOLT)); - this.addEntry(map, KEY_CURRENT, new IntegerDoc().unit(Unit.MILLIAMPERE)); + this.addEntry(map, KEY_VOLTAGE, new IntegerDoc().unit(MILLIVOLT)); + this.addEntry(map, KEY_CURRENT, new IntegerDoc().unit(MILLIAMPERE)); this.addEntry(map, KEY_CHARGE_INDICATION, Doc.of(ChargeIndication.values())); - this.addEntry(map, KEY_SOC, new IntegerDoc().unit(Unit.PERCENT)); - this.addEntry(map, KEY_SOH, new IntegerDoc().unit(Unit.PERCENT)); - this.addEntry(map, KEY_MAX_CELL_VOLTAGE_ID, new IntegerDoc().unit(Unit.NONE)); - this.addEntry(map, KEY_MAX_CELL_VOLTAGE, new IntegerDoc().unit(Unit.MILLIVOLT)); - this.addEntry(map, KEY_MIN_CELL_VOLTAGE_ID, new IntegerDoc().unit(Unit.NONE)); - this.addEntry(map, KEY_MIN_CELL_VOLTAGE, new IntegerDoc().unit(Unit.MILLIVOLT)); - this.addEntry(map, KEY_MAX_CELL_TEMPERATURE_ID, new IntegerDoc().unit(Unit.NONE)); - this.addEntry(map, KEY_MAX_CELL_TEMPERATURE, new IntegerDoc().unit(Unit.DEZIDEGREE_CELSIUS)); - this.addEntry(map, KEY_MIN_CELL_TEMPERATURE_ID, new IntegerDoc().unit(Unit.NONE)); - this.addEntry(map, KEY_MIN_CELL_TEMPERATURE, new IntegerDoc().unit(Unit.DEZIDEGREE_CELSIUS)); - this.addEntry(map, KEY_ALARM_LEVEL_2_CELL_DISCHA_TEMP_LOW, Doc.of(Level.FAULT) + this.addEntry(map, KEY_SOC, new IntegerDoc().unit(PERCENT)); + this.addEntry(map, KEY_SOH, new IntegerDoc().unit(PERCENT)); + this.addEntry(map, KEY_MAX_CELL_VOLTAGE_ID, new IntegerDoc().unit(NONE)); + this.addEntry(map, KEY_MAX_CELL_VOLTAGE, new IntegerDoc().unit(MILLIVOLT)); + this.addEntry(map, KEY_MIN_CELL_VOLTAGE_ID, new IntegerDoc().unit(NONE)); + this.addEntry(map, KEY_MIN_CELL_VOLTAGE, new IntegerDoc().unit(MILLIVOLT)); + this.addEntry(map, KEY_MAX_CELL_TEMPERATURE_ID, new IntegerDoc().unit(NONE)); + this.addEntry(map, KEY_MAX_CELL_TEMPERATURE, new IntegerDoc().unit(DEZIDEGREE_CELSIUS)); + this.addEntry(map, KEY_MIN_CELL_TEMPERATURE_ID, new IntegerDoc().unit(NONE)); + this.addEntry(map, KEY_MIN_CELL_TEMPERATURE, new IntegerDoc().unit(DEZIDEGREE_CELSIUS)); + this.addEntry(map, KEY_ALARM_LEVEL_2_CELL_DISCHA_TEMP_LOW, Doc.of(WARNING) .text("Rack" + this.rackNumber + " Cell Discharge Temperature Low Alarm Level 2")); /* Bit 15 */ - this.addEntry(map, KEY_ALARM_LEVEL_2_CELL_DISCHA_TEMP_HIGH, Doc.of(Level.FAULT) + this.addEntry(map, KEY_ALARM_LEVEL_2_CELL_DISCHA_TEMP_HIGH, Doc.of(WARNING) .text("Rack" + this.rackNumber + " Cell Discharge Temperature High Alarm Level 2")); /* Bit 14 */ this.addEntry(map, KEY_ALARM_LEVEL_2_GR_TEMPERATURE_HIGH, - Doc.of(Level.FAULT).text("Rack" + this.rackNumber + " GR Temperature High Alarm Level 2")); /* Bit 10 */ - this.addEntry(map, KEY_ALARM_LEVEL_2_CELL_CHA_TEMP_LOW, Doc.of(Level.FAULT) + Doc.of(WARNING).text("Rack" + this.rackNumber + " GR Temperature High Alarm Level 2")); /* Bit 10 */ + this.addEntry(map, KEY_ALARM_LEVEL_2_CELL_CHA_TEMP_LOW, Doc.of(WARNING) .text("Rack" + this.rackNumber + " Cell Charge Temperature Low Alarm Level 2")); /* Bit 7 */ - this.addEntry(map, KEY_ALARM_LEVEL_2_CELL_CHA_TEMP_HIGH, Doc.of(Level.FAULT) + this.addEntry(map, KEY_ALARM_LEVEL_2_CELL_CHA_TEMP_HIGH, Doc.of(WARNING) .text("Rack" + this.rackNumber + " Cell Charge Temperature High Alarm Level 2")); /* Bit 6 */ - this.addEntry(map, KEY_ALARM_LEVEL_2_DISCHA_CURRENT_HIGH, Doc.of(Level.FAULT) - .text("Rack" + this.rackNumber + " Discharge Current High Alarm Level 2")); /* Bit 5 */ + this.addEntry(map, KEY_ALARM_LEVEL_2_DISCHA_CURRENT_HIGH, + Doc.of(WARNING).text("Rack" + this.rackNumber + " Discharge Current High Alarm Level 2")); /* Bit 5 */ this.addEntry(map, KEY_ALARM_LEVEL_2_TOTAL_VOLTAGE_LOW, - Doc.of(Level.FAULT).text("Rack" + this.rackNumber + " Total Voltage Low Alarm Level 2")); /* Bit 4 */ + Doc.of(WARNING).text("Rack" + this.rackNumber + " Total Voltage Low Alarm Level 2")); /* Bit 4 */ this.addEntry(map, KEY_ALARM_LEVEL_2_CELL_VOLTAGE_LOW, - Doc.of(Level.FAULT).text("Cluster 1 Cell Voltage Low Alarm Level 2")); /* Bit 3 */ + Doc.of(WARNING).text("Cluster 1 Cell Voltage Low Alarm Level 2")); /* Bit 3 */ this.addEntry(map, KEY_ALARM_LEVEL_2_CHA_CURRENT_HIGH, - Doc.of(Level.FAULT).text("Rack" + this.rackNumber + " Charge Current High Alarm Level 2")); /* Bit 2 */ + Doc.of(WARNING).text("Rack" + this.rackNumber + " Charge Current High Alarm Level 2")); /* Bit 2 */ this.addEntry(map, KEY_ALARM_LEVEL_2_TOTAL_VOLTAGE_HIGH, - Doc.of(Level.FAULT).text("Rack" + this.rackNumber + " Total Voltage High Alarm Level 2")); /* Bit 1 */ + Doc.of(WARNING).text("Rack" + this.rackNumber + " Total Voltage High Alarm Level 2")); /* Bit 1 */ this.addEntry(map, KEY_ALARM_LEVEL_2_CELL_VOLTAGE_HIGH, - Doc.of(Level.FAULT).text("Rack" + this.rackNumber + " Cell Voltage High Alarm Level 2")); /* Bit 0 */ - this.addEntry(map, KEY_ALARM_LEVEL_1_CELL_DISCHA_TEMP_LOW, Doc.of(Level.WARNING) + Doc.of(WARNING).text("Rack" + this.rackNumber + " Cell Voltage High Alarm Level 2")); /* Bit 0 */ + this.addEntry(map, KEY_ALARM_LEVEL_1_CELL_DISCHA_TEMP_LOW, Doc.of(WARNING) .text("Rack" + this.rackNumber + " Cell Discharge Temperature Low Alarm Level 1")); /* Bit 15 */ - this.addEntry(map, KEY_ALARM_LEVEL_1_CELL_DISCHA_TEMP_HIGH, Doc.of(Level.WARNING) + this.addEntry(map, KEY_ALARM_LEVEL_1_CELL_DISCHA_TEMP_HIGH, Doc.of(WARNING) .text("Rack" + this.rackNumber + " Cell Discharge Temperature High Alarm Level 1")); /* Bit 14 */ - this.addEntry(map, KEY_ALARM_LEVEL_1_TOTAL_VOLTAGE_DIFF_HIGH, Doc.of(Level.WARNING) - .text("Rack" + this.rackNumber + " Total Voltage Diff High Alarm Level 1")); /* Bit 13 */ - this.addEntry(map, KEY_ALARM_LEVEL_1_CELL_VOLTAGE_DIFF_HIGH, Doc.of(Level.WARNING) - .text("Rack" + this.rackNumber + " Cell Voltage Diff High Alarm Level 1")); /* Bit 11 */ - this.addEntry(map, KEY_ALARM_LEVEL_1_GR_TEMPERATURE_HIGH, Doc.of(Level.WARNING) - .text("Rack" + this.rackNumber + " GR Temperature High Alarm Level 1")); /* Bit 10 */ - this.addEntry(map, KEY_ALARM_LEVEL_1_CELL_TEMP_DIFF_HIGH, Doc.of(Level.WARNING) + this.addEntry(map, KEY_ALARM_LEVEL_1_TOTAL_VOLTAGE_DIFF_HIGH, + Doc.of(WARNING).text("Rack" + this.rackNumber + " Total Voltage Diff High Alarm Level 1")); /* Bit 13 */ + this.addEntry(map, KEY_ALARM_LEVEL_1_CELL_VOLTAGE_DIFF_HIGH, + Doc.of(WARNING).text("Rack" + this.rackNumber + " Cell Voltage Diff High Alarm Level 1")); /* Bit 11 */ + this.addEntry(map, KEY_ALARM_LEVEL_1_GR_TEMPERATURE_HIGH, + Doc.of(WARNING).text("Rack" + this.rackNumber + " GR Temperature High Alarm Level 1")); /* Bit 10 */ + this.addEntry(map, KEY_ALARM_LEVEL_1_CELL_TEMP_DIFF_HIGH, Doc.of(WARNING) .text("Rack" + this.rackNumber + " Cell temperature Diff High Alarm Level 1")); /* Bit 9 */ this.addEntry(map, KEY_ALARM_LEVEL_1_SOC_LOW, - Doc.of(Level.WARNING).text("Rack" + this.rackNumber + " SOC Low Alarm Level 1")); /* Bit 8 */ - this.addEntry(map, KEY_ALARM_LEVEL_1_CELL_CHA_TEMP_LOW, Doc.of(Level.WARNING) + Doc.of(WARNING).text("Rack" + this.rackNumber + " SOC Low Alarm Level 1")); /* Bit 8 */ + this.addEntry(map, KEY_ALARM_LEVEL_1_CELL_CHA_TEMP_LOW, Doc.of(WARNING) .text("Rack" + this.rackNumber + " Cell Charge Temperature Low Alarm Level 1")); /* Bit 7 */ - this.addEntry(map, KEY_ALARM_LEVEL_1_CELL_CHA_TEMP_HIGH, Doc.of(Level.WARNING) + this.addEntry(map, KEY_ALARM_LEVEL_1_CELL_CHA_TEMP_HIGH, Doc.of(WARNING) .text("Rack" + this.rackNumber + " Cell Charge Temperature High Alarm Level 1")); /* Bit 6 */ - this.addEntry(map, KEY_ALARM_LEVEL_1_DISCHA_CURRENT_HIGH, Doc.of(Level.WARNING) - .text("Rack" + this.rackNumber + " Discharge Current High Alarm Level 1")); /* Bit 5 */ + this.addEntry(map, KEY_ALARM_LEVEL_1_DISCHA_CURRENT_HIGH, + Doc.of(WARNING).text("Rack" + this.rackNumber + " Discharge Current High Alarm Level 1")); /* Bit 5 */ this.addEntry(map, KEY_ALARM_LEVEL_1_TOTAL_VOLTAGE_LOW, - Doc.of(Level.WARNING).text("Rack" + this.rackNumber + " Total Voltage Low Alarm Level 1")); /* Bit 4 */ + Doc.of(WARNING).text("Rack" + this.rackNumber + " Total Voltage Low Alarm Level 1")); /* Bit 4 */ this.addEntry(map, KEY_ALARM_LEVEL_1_CELL_VOLTAGE_LOW, - Doc.of(Level.WARNING).text("Rack" + this.rackNumber + " Cell Voltage Low Alarm Level 1")); /* Bit 3 */ - this.addEntry(map, KEY_ALARM_LEVEL_1_CHA_CURRENT_HIGH, Doc.of(Level.WARNING) - .text("Rack" + this.rackNumber + " Charge Current High Alarm Level 1")); /* Bit 2 */ + Doc.of(WARNING).text("Rack" + this.rackNumber + " Cell Voltage Low Alarm Level 1")); /* Bit 3 */ + this.addEntry(map, KEY_ALARM_LEVEL_1_CHA_CURRENT_HIGH, + Doc.of(WARNING).text("Rack" + this.rackNumber + " Charge Current High Alarm Level 1")); /* Bit 2 */ this.addEntry(map, KEY_ALARM_LEVEL_1_TOTAL_VOLTAGE_HIGH, - Doc.of(Level.WARNING).text("Rack" + this.rackNumber + " Total Voltage High Alarm Level 1")); /* Bit 1 */ + Doc.of(WARNING).text("Rack" + this.rackNumber + " Total Voltage High Alarm Level 1")); /* Bit 1 */ this.addEntry(map, KEY_ALARM_LEVEL_1_CELL_VOLTAGE_HIGH, - Doc.of(Level.WARNING).text("Rack" + this.rackNumber + " Cell Voltage High Alarm Level 1")); /* Bit 0 */ + Doc.of(WARNING).text("Rack" + this.rackNumber + " Cell Voltage High Alarm Level 1")); /* Bit 0 */ this.addEntry(map, KEY_RUN_STATE, Doc.of(Enums.ClusterRunState.values())); // - this.addEntry(map, KEY_FAILURE_INITIALIZATION, Doc.of(Level.FAULT).text("Initialization failure")); /* Bit */ - this.addEntry(map, KEY_FAILURE_EEPROM, Doc.of(Level.FAULT).text("EEPROM fault")); /* Bit 11 */ + this.addEntry(map, KEY_FAILURE_INITIALIZATION, Doc.of(WARNING).text("Initialization failure")); /* Bit */ + this.addEntry(map, KEY_FAILURE_EEPROM, Doc.of(WARNING).text("EEPROM fault")); /* Bit 11 */ this.addEntry(map, KEY_FAILURE_INTRANET_COMMUNICATION, - Doc.of(Level.FAULT).text("Internal communication fault")); /* Bit 10 */ + Doc.of(WARNING).text("Internal communication fault")); /* Bit 10 */ this.addEntry(map, KEY_FAILURE_TEMPERATURE_SENSOR_CABLE, - Doc.of(Level.FAULT).text("Temperature sensor cable fault")); /* Bit 9 */ - this.addEntry(map, KEY_FAILURE_BALANCING_MODULE, Doc.of(Level.OK).text("Balancing module fault")); /* Bit 8 */ - this.addEntry(map, KEY_FAILURE_TEMPERATURE_PCB, Doc.of(Level.FAULT).text("Temperature PCB error")); /* Bit 7 */ - this.addEntry(map, KEY_FAILURE_GR_TEMPERATURE, Doc.of(Level.FAULT).text("GR Temperature error")); /* Bit 6 */ - this.addEntry(map, KEY_FAILURE_TEMP_SENSOR, Doc.of(Level.FAULT).text("Temperature sensor fault")); /* Bit 5 */ - this.addEntry(map, KEY_FAILURE_TEMP_SAMPLING, - Doc.of(Level.FAULT).text("Temperature sampling fault")); /* Bit 4 */ - this.addEntry(map, KEY_FAILURE_VOLTAGE_SAMPLING, - Doc.of(Level.FAULT).text("Voltage sampling fault")); /* Bit 3 */ - this.addEntry(map, KEY_FAILURE_LTC6803, Doc.of(Level.FAULT).text("LTC6803 fault")); /* Bit 2 */ - this.addEntry(map, KEY_FAILURE_CONNECTOR_WIRE, Doc.of(Level.FAULT).text("connector wire fault")); /* Bit 1 */ - this.addEntry(map, KEY_FAILURE_SAMPLING_WIRE, Doc.of(Level.FAULT).text("sampling wire fault")); /* Bit 0 */ - this.addEntry(map, KEY_SLEEP, Doc.of(OpenemsType.INTEGER).accessMode(AccessMode.READ_WRITE)); - this.addEntry(map, KEY_RESET, Doc.of(OpenemsType.INTEGER).accessMode(AccessMode.READ_WRITE)); + Doc.of(WARNING).text("Temperature sensor cable fault")); /* Bit 9 */ + this.addEntry(map, KEY_FAILURE_BALANCING_MODULE, Doc.of(OK).text("Balancing module fault")); /* Bit 8 */ + this.addEntry(map, KEY_FAILURE_TEMPERATURE_PCB, Doc.of(WARNING).text("Temperature PCB error")); /* Bit 7 */ + this.addEntry(map, KEY_FAILURE_GR_TEMPERATURE, Doc.of(WARNING).text("GR Temperature error")); /* Bit 6 */ + this.addEntry(map, KEY_FAILURE_TEMP_SENSOR, Doc.of(WARNING).text("Temperature sensor fault")); /* Bit 5 */ + this.addEntry(map, KEY_FAILURE_TEMP_SAMPLING, Doc.of(WARNING).text("Temperature sampling fault")); /* Bit 4 */ + this.addEntry(map, KEY_FAILURE_VOLTAGE_SAMPLING, Doc.of(WARNING).text("Voltage sampling fault")); /* Bit 3 */ + this.addEntry(map, KEY_FAILURE_LTC6803, Doc.of(WARNING).text("LTC6803 fault")); /* Bit 2 */ + this.addEntry(map, KEY_FAILURE_CONNECTOR_WIRE, Doc.of(WARNING).text("connector wire fault")); /* Bit 1 */ + this.addEntry(map, KEY_FAILURE_SAMPLING_WIRE, Doc.of(WARNING).text("sampling wire fault")); /* Bit 0 */ + this.addEntry(map, KEY_SLEEP, Doc.of(INTEGER).accessMode(READ_WRITE)); + this.addEntry(map, KEY_RESET, Doc.of(INTEGER).accessMode(READ_WRITE)); // Cell voltages formatted like: "RACK_1_BATTERY_000_VOLTAGE" for (var i = 0; i < this.numberOfSlaves; i++) { for (var j = i * VOLTAGE_SENSORS_PER_MODULE; j < (i + 1) * VOLTAGE_SENSORS_PER_MODULE; j++) { var key = this.getSingleCellPrefix(j) + "_" + VOLTAGE; - this.addEntry(map, key, new IntegerDoc().unit(Unit.MILLIVOLT)); + this.addEntry(map, key, new IntegerDoc().unit(MILLIVOLT)); } } // Cell temperatures formatted like : "RACK_1_BATTERY_000_TEMPERATURE" for (var i = 0; i < this.numberOfSlaves; i++) { for (var j = i * TEMPERATURE_SENSORS_PER_MODULE; j < (i + 1) * TEMPERATURE_SENSORS_PER_MODULE; j++) { var key = this.getSingleCellPrefix(j) + "_" + TEMPERATURE; - this.addEntry(map, key, new IntegerDoc().unit(Unit.DEZIDEGREE_CELSIUS)); + this.addEntry(map, key, new IntegerDoc().unit(DEZIDEGREE_CELSIUS)); } } diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/BatterySoltaroClusterVersionC.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/BatterySoltaroClusterVersionC.java index 99f8fea4c9f..cec303bdbf7 100644 --- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/BatterySoltaroClusterVersionC.java +++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/BatterySoltaroClusterVersionC.java @@ -265,82 +265,82 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId // Master BMS Alarm Registers MASTER_EMS_COMMUNICATION_FAILURE(Doc.of(Level.WARNING) // .text("Master EMS Communication Failure")), - MASTER_PCS_CONTROL_FAILURE(Doc.of(Level.FAULT) // + MASTER_PCS_CONTROL_FAILURE(Doc.of(Level.WARNING) // .text("Master PCS Control Failure")), - MASTER_PCS_COMMUNICATION_FAILURE(Doc.of(Level.FAULT) // + MASTER_PCS_COMMUNICATION_FAILURE(Doc.of(Level.WARNING) // .text("Master PCS Communication Failure")), // Rack #1 cannot be paralleled to DC Bus reasons - RACK_1_LEVEL_2_ALARM(Doc.of(Level.FAULT) // + RACK_1_LEVEL_2_ALARM(Doc.of(Level.WARNING) // .text("Rack 1 Level 2 Alarm")), - RACK_1_PCS_CONTROL_FAILURE(Doc.of(Level.FAULT) // + RACK_1_PCS_CONTROL_FAILURE(Doc.of(Level.WARNING) // .text("Rack 1 PCS Control Failure")), - RACK_1_COMMUNICATION_TO_MASTER_FAILURE(Doc.of(Level.FAULT) // + RACK_1_COMMUNICATION_TO_MASTER_FAILURE(Doc.of(Level.WARNING) // .text("Rack 1 Communication to Master BMS Failure")), - RACK_1_HARDWARE_FAILURE(Doc.of(Level.FAULT) // + RACK_1_HARDWARE_FAILURE(Doc.of(Level.WARNING) // .text("Rack 1 Hardware Failure")), - RACK_1_OVER_CURRENT(Doc.of(Level.FAULT) // + RACK_1_OVER_CURRENT(Doc.of(Level.WARNING) // .text("Rack 1 Too big circulating Current among clusters (>4A)")), - RACK_1_VOLTAGE_DIFFERENCE(Doc.of(Level.FAULT) // + RACK_1_VOLTAGE_DIFFERENCE(Doc.of(Level.WARNING) // .text("Rack 1 Too big boltage difference among clusters (>50V)")), // Rack #2 cannot be paralleled to DC Bus reasons - RACK_2_LEVEL_2_ALARM(Doc.of(Level.FAULT) // + RACK_2_LEVEL_2_ALARM(Doc.of(Level.WARNING) // .text("Rack 2 Level 2 Alarm")), - RACK_2_PCS_CONTROL_FAILURE(Doc.of(Level.FAULT) // + RACK_2_PCS_CONTROL_FAILURE(Doc.of(Level.WARNING) // .text("Rack 2 PCS Control Failure")), - RACK_2_COMMUNICATION_TO_MASTER_FAILURE(Doc.of(Level.FAULT) // + RACK_2_COMMUNICATION_TO_MASTER_FAILURE(Doc.of(Level.WARNING) // .text("Rack 2 Communication to Master BMS Failure")), - RACK_2_HARDWARE_FAILURE(Doc.of(Level.FAULT) // + RACK_2_HARDWARE_FAILURE(Doc.of(Level.WARNING) // .text("Rack 2 Hardware Failure")), - RACK_2_OVER_CURRENT(Doc.of(Level.FAULT) // + RACK_2_OVER_CURRENT(Doc.of(Level.WARNING) // .text("Rack 2 Too big circulating Current among clusters (>4A)")), - RACK_2_VOLTAGE_DIFFERENCE(Doc.of(Level.FAULT) // + RACK_2_VOLTAGE_DIFFERENCE(Doc.of(Level.WARNING) // .text("Rack 2 Too big boltage difference among clusters (>50V)")), // Rack #3 cannot be paralleled to DC Bus reasons - RACK_3_LEVEL_2_ALARM(Doc.of(Level.FAULT) // + RACK_3_LEVEL_2_ALARM(Doc.of(Level.WARNING) // .text("Rack 3 Level 2 Alarm")), - RACK_3_PCS_CONTROL_FAILURE(Doc.of(Level.FAULT) // + RACK_3_PCS_CONTROL_FAILURE(Doc.of(Level.WARNING) // .text("Rack 3 PCS Control Failure")), - RACK_3_COMMUNICATION_TO_MASTER_FAILURE(Doc.of(Level.FAULT) // + RACK_3_COMMUNICATION_TO_MASTER_FAILURE(Doc.of(Level.WARNING) // .text("Rack 3 Communication to Master BMS Failure")), - RACK_3_HARDWARE_FAILURE(Doc.of(Level.FAULT) // + RACK_3_HARDWARE_FAILURE(Doc.of(Level.WARNING) // .text("Rack 3 Hardware Failure")), - RACK_3_OVER_CURRENT(Doc.of(Level.FAULT) // + RACK_3_OVER_CURRENT(Doc.of(Level.WARNING) // .text("Rack 3 Too big circulating Current among clusters (>4A)")), - RACK_3_VOLTAGE_DIFFERENCE(Doc.of(Level.FAULT) // + RACK_3_VOLTAGE_DIFFERENCE(Doc.of(Level.WARNING) // .text("Rack 3 Too big boltage difference among clusters (>50V)")), // Rack #4 cannot be paralleled to DC Bus reasons - RACK_4_LEVEL_2_ALARM(Doc.of(Level.FAULT) // + RACK_4_LEVEL_2_ALARM(Doc.of(Level.WARNING) // .text("Rack 4 Level 2 Alarm")), - RACK_4_PCS_CONTROL_FAILURE(Doc.of(Level.FAULT) // + RACK_4_PCS_CONTROL_FAILURE(Doc.of(Level.WARNING) // .text("Rack 4 PCS Control Failure")), - RACK_4_COMMUNICATION_TO_MASTER_FAILURE(Doc.of(Level.FAULT) // + RACK_4_COMMUNICATION_TO_MASTER_FAILURE(Doc.of(Level.WARNING) // .text("Rack 4 Communication to Master BMS Failure")), - RACK_4_HARDWARE_FAILURE(Doc.of(Level.FAULT) // + RACK_4_HARDWARE_FAILURE(Doc.of(Level.WARNING) // .text("Rack 4 Hardware Failure")), - RACK_4_OVER_CURRENT(Doc.of(Level.FAULT) // + RACK_4_OVER_CURRENT(Doc.of(Level.WARNING) // .text("Rack 4 Too big circulating Current among clusters (>4A)")), - RACK_4_VOLTAGE_DIFFERENCE(Doc.of(Level.FAULT) // + RACK_4_VOLTAGE_DIFFERENCE(Doc.of(Level.WARNING) // .text("Rack 4 Too big boltage difference among clusters (>50V)")), // Rack #5 cannot be paralleled to DC Bus reasons - RACK_5_LEVEL_2_ALARM(Doc.of(Level.FAULT) // + RACK_5_LEVEL_2_ALARM(Doc.of(Level.WARNING) // .text("Rack 5 Level 2 Alarm")), - RACK_5_PCS_CONTROL_FAILURE(Doc.of(Level.FAULT) // + RACK_5_PCS_CONTROL_FAILURE(Doc.of(Level.WARNING) // .text("Rack 5 PCS Control Failure")), - RACK_5_COMMUNICATION_TO_MASTER_FAILURE(Doc.of(Level.FAULT) // + RACK_5_COMMUNICATION_TO_MASTER_FAILURE(Doc.of(Level.WARNING) // .text("Rack 5 Communication to Master BMS Failure")), - RACK_5_HARDWARE_FAILURE(Doc.of(Level.FAULT) // + RACK_5_HARDWARE_FAILURE(Doc.of(Level.WARNING) // .text("Rack 5 Hardware Failure")), - RACK_5_OVER_CURRENT(Doc.of(Level.FAULT) // + RACK_5_OVER_CURRENT(Doc.of(Level.WARNING) // .text("Rack 5 Too big circulating Current among clusters (>4A)")), - RACK_5_VOLTAGE_DIFFERENCE(Doc.of(Level.FAULT) // + RACK_5_VOLTAGE_DIFFERENCE(Doc.of(Level.WARNING) // .text("Rack 5 Too big boltage difference among clusters (>50V)")), // OpenEMS Faults - RUN_FAILED(Doc.of(Level.FAULT) // + RUN_FAILED(Doc.of(Level.WARNING) // .text("Running the Logic failed")), // - MAX_START_ATTEMPTS(Doc.of(Level.FAULT) // + MAX_START_ATTEMPTS(Doc.of(Level.WARNING) // .text("The maximum number of start attempts failed")), // - MAX_STOP_ATTEMPTS(Doc.of(Level.FAULT) // + MAX_STOP_ATTEMPTS(Doc.of(Level.WARNING) // .text("The maximum number of stop attempts failed")), // NUMBER_OF_MODULES_PER_TOWER(Doc.of(OpenemsType.INTEGER) // .persistencePriority(PersistencePriority.HIGH) // diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/BatterySoltaroClusterVersionCImpl.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/BatterySoltaroClusterVersionCImpl.java index 56c3d916474..6b493c42f7a 100644 --- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/BatterySoltaroClusterVersionCImpl.java +++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/BatterySoltaroClusterVersionCImpl.java @@ -3,6 +3,7 @@ import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SCALE_FACTOR_2; import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SCALE_FACTOR_MINUS_1; import static io.openems.edge.bridge.modbus.api.ModbusUtils.readElementOnce; +import static io.openems.edge.bridge.modbus.api.ModbusUtils.FunctionCode.FC3; import java.util.LinkedList; import java.util.Optional; @@ -648,7 +649,7 @@ private void calculateCapacity(int numberOfTowers, int numberOfModules) { * @throws OpenemsException on error */ private CompletableFuture getNumberOfModules() { - return readElementOnce(this.getModbusProtocol(), ModbusUtils::retryOnNull, + return readElementOnce(FC3, this.getModbusProtocol(), ModbusUtils::retryOnNull, new UnsignedWordElement(0x20C1 /* No of modules for 1st tower */)); } @@ -675,7 +676,7 @@ private void checkNumberOfTowers(BiPredicate retryPredica } // Read next address in Queue - readElementOnce(this.getModbusProtocol(), retryPredicate, new UnsignedWordElement(address)) + readElementOnce(FC3, this.getModbusProtocol(), retryPredicate, new UnsignedWordElement(address)) .thenAccept(numberOfModules -> { if (numberOfModules == null) { // Read error -> this tower does not exist. Stop here. diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/RackChannel.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/RackChannel.java index ec13258ac7c..daee5253fa6 100644 --- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/RackChannel.java +++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/RackChannel.java @@ -463,27 +463,27 @@ public enum RackChannel { LEVEL1_TOTAL_VOLTAGE_HIGH(Doc.of(Level.WARNING) // .text("Total Voltage High Alarm Level 1")), // // Alarm Level 2 - LEVEL2_DISCHARGE_TEMP_LOW(Doc.of(Level.FAULT) // + LEVEL2_DISCHARGE_TEMP_LOW(Doc.of(Level.WARNING) // .text("Discharge Temperature Low Alarm Level 2")), // - LEVEL2_DISCHARGE_TEMP_HIGH(Doc.of(Level.FAULT) // + LEVEL2_DISCHARGE_TEMP_HIGH(Doc.of(Level.WARNING) // .text("Discharge Temperature High Alarm Level 2")), // - LEVEL2_INSULATION_VALUE(Doc.of(Level.FAULT) // + LEVEL2_INSULATION_VALUE(Doc.of(Level.WARNING) // .text("Insulation Value Failure Alarm Level 2")), // - LEVEL2_POWER_POLE_TEMP_HIGH(Doc.of(Level.FAULT) // + LEVEL2_POWER_POLE_TEMP_HIGH(Doc.of(Level.WARNING) // .text("Power Pole temperature too high Alarm Level 2")), // - LEVEL2_CHARGE_TEMP_LOW(Doc.of(Level.FAULT) // + LEVEL2_CHARGE_TEMP_LOW(Doc.of(Level.WARNING) // .text("Cell Charge Temperature Low Alarm Level 2")), // - LEVEL2_CHARGE_TEMP_HIGH(Doc.of(Level.FAULT) // + LEVEL2_CHARGE_TEMP_HIGH(Doc.of(Level.WARNING) // .text("Charge Temperature High Alarm Level 2")), // - LEVEL2_DISCHARGE_CURRENT_HIGH(Doc.of(Level.FAULT) // + LEVEL2_DISCHARGE_CURRENT_HIGH(Doc.of(Level.WARNING) // .text("Discharge Current High Alarm Level 2")), // - LEVEL2_TOTAL_VOLTAGE_LOW(Doc.of(Level.FAULT) // + LEVEL2_TOTAL_VOLTAGE_LOW(Doc.of(Level.WARNING) // .text("Total Voltage Low Alarm Level 2")), // - LEVEL2_CELL_VOLTAGE_LOW(Doc.of(Level.FAULT) // + LEVEL2_CELL_VOLTAGE_LOW(Doc.of(Level.WARNING) // .text("Cell Voltage Low Alarm Level 2")), // - LEVEL2_CHARGE_CURRENT_HIGH(Doc.of(Level.FAULT) // + LEVEL2_CHARGE_CURRENT_HIGH(Doc.of(Level.WARNING) // .text("Charge Current High Alarm Level 2")), // - LEVEL2_TOTAL_VOLTAGE_HIGH(Doc.of(Level.FAULT) // + LEVEL2_TOTAL_VOLTAGE_HIGH(Doc.of(Level.WARNING) // .text("Total Voltage High Alarm Level 2")), // LEVEL2_CELL_VOLTAGE_HIGH(Doc.of(Level.INFO) // .text("Cell Voltage High Alarm Level 2")), // diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versiona/BatterySoltaroSingleRackVersionA.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versiona/BatterySoltaroSingleRackVersionA.java index 9ee4f1c6abf..7d65c2aacd1 100644 --- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versiona/BatterySoltaroSingleRackVersionA.java +++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versiona/BatterySoltaroSingleRackVersionA.java @@ -681,29 +681,29 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { .text("Cluster 1 Total Voltage High Alarm Level 1")), // ALARM_LEVEL_1_CELL_VOLTAGE_HIGH(Doc.of(Level.WARNING) // .text("Cluster 1 Cell Voltage High Alarm Level 1")), // - FAILURE_INITIALIZATION(Doc.of(Level.FAULT) // + FAILURE_INITIALIZATION(Doc.of(Level.WARNING) // .text("Initialization failure")), // - FAILURE_EEPROM(Doc.of(Level.FAULT) // + FAILURE_EEPROM(Doc.of(Level.WARNING) // .text("EEPROM fault")), // - FAILURE_INTRANET_COMMUNICATION(Doc.of(Level.FAULT) // + FAILURE_INTRANET_COMMUNICATION(Doc.of(Level.WARNING) // .text("Intranet communication fault")), // - FAILURE_TEMP_SAMPLING_LINE(Doc.of(Level.FAULT) // + FAILURE_TEMP_SAMPLING_LINE(Doc.of(Level.WARNING) // .text("Temperature sampling line fault")), // - FAILURE_BALANCING_MODULE(Doc.of(Level.FAULT) // + FAILURE_BALANCING_MODULE(Doc.of(Level.WARNING) // .text("Balancing module fault")), // - FAILURE_TEMP_SENSOR(Doc.of(Level.FAULT) // + FAILURE_TEMP_SENSOR(Doc.of(Level.WARNING) // .text("Temperature sensor fault")), // - FAILURE_TEMP_SAMPLING(Doc.of(Level.FAULT) // + FAILURE_TEMP_SAMPLING(Doc.of(Level.WARNING) // .text("Temperature sampling fault")), // - FAILURE_VOLTAGE_SAMPLING(Doc.of(Level.FAULT) // + FAILURE_VOLTAGE_SAMPLING(Doc.of(Level.WARNING) // .text("Voltage sampling fault")), // - FAILURE_LTC6803(Doc.of(Level.FAULT) // + FAILURE_LTC6803(Doc.of(Level.WARNING) // .text("LTC6803 fault")), // FAILURE_CONNECTOR_WIRE(Doc.of(Level.WARNING) // .text("connector wire fault")), // - FAILURE_SAMPLING_WIRE(Doc.of(Level.FAULT) // + FAILURE_SAMPLING_WIRE(Doc.of(Level.WARNING) // .text("sampling wire fault")), // - PRECHARGE_TAKING_TOO_LONG(Doc.of(Level.FAULT) // + PRECHARGE_TAKING_TOO_LONG(Doc.of(Level.WARNING) // .text("precharge time was too long")), // STATE_MACHINE(Doc.of(State.values()) // .text("Current State of State-Machine")), // diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionb/BatterySoltaroSingleRackVersionB.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionb/BatterySoltaroSingleRackVersionB.java index ecc741e2cc2..8434481d85c 100644 --- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionb/BatterySoltaroSingleRackVersionB.java +++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionb/BatterySoltaroSingleRackVersionB.java @@ -995,11 +995,11 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId .text("precharge time was too long")), // OpenEMS Faults - RUN_FAILED(Doc.of(Level.FAULT) // + RUN_FAILED(Doc.of(Level.WARNING) // .text("Running the Logic failed")), // - MAX_START_ATTEMPTS(Doc.of(Level.FAULT) // + MAX_START_ATTEMPTS(Doc.of(Level.WARNING) // .text("The maximum number of start attempts failed")), // - MAX_STOP_ATTEMPTS(Doc.of(Level.FAULT) // + MAX_STOP_ATTEMPTS(Doc.of(Level.WARNING) // .text("The maximum number of stop attempts failed")), // ; diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionb/BatterySoltaroSingleRackVersionBImpl.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionb/BatterySoltaroSingleRackVersionBImpl.java index 2bc1e0c7db0..a87a7fe5106 100644 --- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionb/BatterySoltaroSingleRackVersionBImpl.java +++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionb/BatterySoltaroSingleRackVersionBImpl.java @@ -5,6 +5,7 @@ import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SCALE_FACTOR_2; import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SCALE_FACTOR_MINUS_1; import static io.openems.edge.bridge.modbus.api.ModbusUtils.readElementOnce; +import static io.openems.edge.bridge.modbus.api.ModbusUtils.FunctionCode.FC3; import java.util.Optional; import java.util.concurrent.CompletableFuture; @@ -917,7 +918,8 @@ protected ModbusProtocol defineModbusProtocol() { * @return the Number of Modules as a {@link CompletableFuture}. */ private CompletableFuture getNumberOfModules() { - return readElementOnce(this.getModbusProtocol(), ModbusUtils::retryOnNull, new UnsignedWordElement(0x20C1)); + return readElementOnce(FC3, this.getModbusProtocol(), ModbusUtils::retryOnNull, + new UnsignedWordElement(0x20C1)); } /** diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionc/BatterySoltaroSingleRackVersionC.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionc/BatterySoltaroSingleRackVersionC.java index 28a54d5b9af..1f57a137c60 100644 --- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionc/BatterySoltaroSingleRackVersionC.java +++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionc/BatterySoltaroSingleRackVersionC.java @@ -514,29 +514,29 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId // Faults and warnings // Alarm Level 2 - LEVEL2_DISCHARGE_TEMP_LOW(Doc.of(Level.FAULT) // + LEVEL2_DISCHARGE_TEMP_LOW(Doc.of(Level.WARNING) // .text("Discharge Temperature Low Alarm Level 2")), // - LEVEL2_DISCHARGE_TEMP_HIGH(Doc.of(Level.FAULT) // + LEVEL2_DISCHARGE_TEMP_HIGH(Doc.of(Level.WARNING) // .text("Discharge Temperature High Alarm Level 2")), // - LEVEL2_INSULATION_VALUE(Doc.of(Level.FAULT) // + LEVEL2_INSULATION_VALUE(Doc.of(Level.WARNING) // .text("Insulation Value Failure Alarm Level 2")), // - LEVEL2_POWER_POLE_TEMP_HIGH(Doc.of(Level.FAULT) // + LEVEL2_POWER_POLE_TEMP_HIGH(Doc.of(Level.WARNING) // .text("Power Pole temperature too high Alarm Level 2")), // - LEVEL2_CHARGE_TEMP_LOW(Doc.of(Level.FAULT) // + LEVEL2_CHARGE_TEMP_LOW(Doc.of(Level.WARNING) // .text("Cell Charge Temperature Low Alarm Level 2")), // - LEVEL2_CHARGE_TEMP_HIGH(Doc.of(Level.FAULT) // + LEVEL2_CHARGE_TEMP_HIGH(Doc.of(Level.WARNING) // .text("Charge Temperature High Alarm Level 2")), // - LEVEL2_DISCHARGE_CURRENT_HIGH(Doc.of(Level.FAULT) // + LEVEL2_DISCHARGE_CURRENT_HIGH(Doc.of(Level.WARNING) // .text("Discharge Current High Alarm Level 2")), // - LEVEL2_TOTAL_VOLTAGE_LOW(Doc.of(Level.FAULT) // + LEVEL2_TOTAL_VOLTAGE_LOW(Doc.of(Level.WARNING) // .text("Total Voltage Low Alarm Level 2")), // - LEVEL2_CELL_VOLTAGE_LOW(Doc.of(Level.FAULT) // + LEVEL2_CELL_VOLTAGE_LOW(Doc.of(Level.WARNING) // .text("Cell Voltage Low Alarm Level 2")), // - LEVEL2_CHARGE_CURRENT_HIGH(Doc.of(Level.FAULT) // + LEVEL2_CHARGE_CURRENT_HIGH(Doc.of(Level.WARNING) // .text("Charge Current High Alarm Level 2")), // - LEVEL2_TOTAL_VOLTAGE_HIGH(Doc.of(Level.FAULT) // + LEVEL2_TOTAL_VOLTAGE_HIGH(Doc.of(Level.WARNING) // .text("Total Voltage High Alarm Level 2")), // - LEVEL2_CELL_VOLTAGE_HIGH(Doc.of(Level.FAULT) // + LEVEL2_CELL_VOLTAGE_HIGH(Doc.of(Level.WARNING) // .text("Cell Voltage High Alarm Level 2")), // // Alarm Level 1 @@ -672,11 +672,11 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId .text("Slave 20 communication error")), // // OpenEMS Faults - RUN_FAILED(Doc.of(Level.FAULT) // + RUN_FAILED(Doc.of(Level.WARNING) // .text("Running the Logic failed")), // - MAX_START_ATTEMPTS(Doc.of(Level.FAULT) // + MAX_START_ATTEMPTS(Doc.of(Level.WARNING) // .text("The maximum number of start attempts failed")), // - MAX_STOP_ATTEMPTS(Doc.of(Level.FAULT) // + MAX_STOP_ATTEMPTS(Doc.of(Level.WARNING) // .text("The maximum number of stop attempts failed")), // ; diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionc/BatterySoltaroSingleRackVersionCImpl.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionc/BatterySoltaroSingleRackVersionCImpl.java index 69feb15eeaa..988a530da27 100644 --- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionc/BatterySoltaroSingleRackVersionCImpl.java +++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionc/BatterySoltaroSingleRackVersionCImpl.java @@ -4,6 +4,7 @@ import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SCALE_FACTOR_2; import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SCALE_FACTOR_MINUS_1; import static io.openems.edge.bridge.modbus.api.ModbusUtils.readElementOnce; +import static io.openems.edge.bridge.modbus.api.ModbusUtils.FunctionCode.FC3; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicReference; @@ -154,7 +155,8 @@ private void calculateCapacity(Integer numberOfModules) { * @return the Number of Modules as a {@link CompletableFuture}. */ private CompletableFuture getNumberOfModules() { - return readElementOnce(this.getModbusProtocol(), ModbusUtils::retryOnNull, new UnsignedWordElement(0x20C1)); + return readElementOnce(FC3, this.getModbusProtocol(), ModbusUtils::retryOnNull, + new UnsignedWordElement(0x20C1)); } @Override diff --git a/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/cluster/versionb/BatterySoltaroClusterVersionBImplTest.java b/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/cluster/versionb/BatterySoltaroClusterVersionBImplTest.java index eda7ce07667..be7f6db76ce 100644 --- a/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/cluster/versionb/BatterySoltaroClusterVersionBImplTest.java +++ b/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/cluster/versionb/BatterySoltaroClusterVersionBImplTest.java @@ -1,9 +1,13 @@ package io.openems.edge.battery.soltaro.cluster.versionb; +import static io.openems.edge.battery.soltaro.cluster.SoltaroCluster.ChannelId.SUB_MASTER_1_COMMUNICATION_FAILURE; +import static io.openems.edge.battery.soltaro.cluster.SoltaroCluster.ChannelId.SUB_MASTER_2_COMMUNICATION_FAILURE; +import static io.openems.edge.battery.soltaro.cluster.SoltaroCluster.ChannelId.SUB_MASTER_3_COMMUNICATION_FAILURE; +import static io.openems.edge.battery.soltaro.cluster.SoltaroCluster.ChannelId.SUB_MASTER_4_COMMUNICATION_FAILURE; +import static io.openems.edge.battery.soltaro.cluster.SoltaroCluster.ChannelId.SUB_MASTER_5_COMMUNICATION_FAILURE; + import org.junit.Test; -import io.openems.common.types.ChannelAddress; -import io.openems.edge.battery.soltaro.cluster.SoltaroCluster; import io.openems.edge.battery.soltaro.common.enums.BatteryState; import io.openems.edge.battery.soltaro.common.enums.ModuleType; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; @@ -14,30 +18,16 @@ public class BatterySoltaroClusterVersionBImplTest { - private static final String BATTERY_ID = "battery0"; - private static final String MODBUS_ID = "modbus0"; - - private static final ChannelAddress SUB_MASTER_1_COMMUNICATION_FAILURE = new ChannelAddress(BATTERY_ID, - SoltaroCluster.ChannelId.SUB_MASTER_1_COMMUNICATION_FAILURE.id()); - private static final ChannelAddress SUB_MASTER_2_COMMUNICATION_FAILURE = new ChannelAddress(BATTERY_ID, - SoltaroCluster.ChannelId.SUB_MASTER_2_COMMUNICATION_FAILURE.id()); - private static final ChannelAddress SUB_MASTER_3_COMMUNICATION_FAILURE = new ChannelAddress(BATTERY_ID, - SoltaroCluster.ChannelId.SUB_MASTER_3_COMMUNICATION_FAILURE.id()); - private static final ChannelAddress SUB_MASTER_4_COMMUNICATION_FAILURE = new ChannelAddress(BATTERY_ID, - SoltaroCluster.ChannelId.SUB_MASTER_4_COMMUNICATION_FAILURE.id()); - private static final ChannelAddress SUB_MASTER_5_COMMUNICATION_FAILURE = new ChannelAddress(BATTERY_ID, - SoltaroCluster.ChannelId.SUB_MASTER_5_COMMUNICATION_FAILURE.id()); - @Test public void test() throws Exception { var sut = new BatterySoltaroClusterVersionBImpl(); new ComponentTest(sut) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(BATTERY_ID) // - .setModbusId(MODBUS_ID) // + .setId("battery0") // + .setModbusId("modbus0") // .setModbusUnitId(0) // .setNumberOfSlaves(0) // .setModuleType(ModuleType.MODULE_3_5_KWH) // diff --git a/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/cluster/versionc/BatterySoltaroClusterVersionCImplTest.java b/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/cluster/versionc/BatterySoltaroClusterVersionCImplTest.java index aaffbc2cc9e..ebcb21122ce 100644 --- a/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/cluster/versionc/BatterySoltaroClusterVersionCImplTest.java +++ b/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/cluster/versionc/BatterySoltaroClusterVersionCImplTest.java @@ -9,17 +9,14 @@ public class BatterySoltaroClusterVersionCImplTest { - private static final String BATTERY_ID = "battery0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new BatterySoltaroClusterVersionCImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(BATTERY_ID) // - .setModbusId(MODBUS_ID) // + .setId("battery0") // + .setModbusId("modbus0") // .setStartStop(StartStopConfig.AUTO) // .build()) // ; diff --git a/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/single/versiona/BatterySoltaroSingleRackVersionAImplTest.java b/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/single/versiona/BatterySoltaroSingleRackVersionAImplTest.java index 443dbce24c9..daccfa35f66 100644 --- a/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/single/versiona/BatterySoltaroSingleRackVersionAImplTest.java +++ b/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/single/versiona/BatterySoltaroSingleRackVersionAImplTest.java @@ -9,17 +9,14 @@ public class BatterySoltaroSingleRackVersionAImplTest { - private static final String BATTERY_ID = "battery0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new BatterySoltaroSingleRackVersionAImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(BATTERY_ID) // - .setModbusId(MODBUS_ID) // + .setId("battery0") // + .setModbusId("modbus0") // .setModbusUnitId(0) // .setErrorLevel2Delay(0) // .setMaxStartTime(0) // diff --git a/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/single/versionb/BatterySoltaroSingleRackVersionBImplTest.java b/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/single/versionb/BatterySoltaroSingleRackVersionBImplTest.java index b2b33ff92fb..3fce33da342 100644 --- a/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/single/versionb/BatterySoltaroSingleRackVersionBImplTest.java +++ b/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/single/versionb/BatterySoltaroSingleRackVersionBImplTest.java @@ -11,18 +11,15 @@ public class BatterySoltaroSingleRackVersionBImplTest { - private static final String BATTERY_ID = "battery0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new BatterySoltaroSingleRackVersionBImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(BATTERY_ID) // - .setModbusId(MODBUS_ID) // + .setId("battery0") // + .setModbusId("modbus0") // .setModbusUnitId(0) // .setErrorLevel2Delay(0) // .setMaxStartTime(0) // diff --git a/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/single/versionc/BatterySoltaroSingleRackVersionCImplTest.java b/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/single/versionc/BatterySoltaroSingleRackVersionCImplTest.java index 82e9a8c75d9..504d8d66b2a 100644 --- a/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/single/versionc/BatterySoltaroSingleRackVersionCImplTest.java +++ b/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/single/versionc/BatterySoltaroSingleRackVersionCImplTest.java @@ -9,17 +9,14 @@ public class BatterySoltaroSingleRackVersionCImplTest { - private static final String BATTERY_ID = "battery0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new BatterySoltaroSingleRackVersionCImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(BATTERY_ID) // - .setModbusId(MODBUS_ID) // + .setId("battery0") // + .setModbusId("modbus0") // .setModbusUnitId(0) // .setStartStop(StartStopConfig.AUTO) // .build()) // diff --git a/io.openems.edge.batteryinverter.api/.classpath b/io.openems.edge.batteryinverter.api/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.batteryinverter.api/.classpath +++ b/io.openems.edge.batteryinverter.api/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/.classpath b/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/.classpath +++ b/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/BatteryInverterKacoBlueplanetGridsave.java b/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/BatteryInverterKacoBlueplanetGridsave.java index d9124765b01..b32ba021cd3 100644 --- a/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/BatteryInverterKacoBlueplanetGridsave.java +++ b/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/BatteryInverterKacoBlueplanetGridsave.java @@ -33,29 +33,40 @@ public interface BatteryInverterKacoBlueplanetGridsave extends ManagedSymmetricB public static final int WATCHDOG_TRIGGER_SECONDS = 10; public enum ChannelId implements io.openems.edge.common.channel.ChannelId { + + /* + * Whenever one of these states would be Level.FAULT, the EssGeneric will stop + * the battery and the inverter. If this is necessary, it must be specifically + * mentioned and the state should have a proper description of the fault. + */ + STATE_MACHINE(Doc.of(State.values()) // .text("Current State of State-Machine")), // - RUN_FAILED(Doc.of(Level.FAULT) // + RUN_FAILED(Doc.of(Level.WARNING) // .text("Running the Logic failed")), // - MAX_START_TIMEOUT(Doc.of(Level.FAULT) // + MAX_START_TIMEOUT(Doc.of(Level.WARNING) // .text("Max start time is exceeded")), // - MAX_STOP_TIMEOUT(Doc.of(Level.FAULT) // + MAX_STOP_TIMEOUT(Doc.of(Level.WARNING) // .text("Max stop time is exceeded")), // - INVERTER_CURRENT_STATE_FAULT(Doc.of(Level.FAULT) // + + /** + * Internal StateMachine from KACO. + */ + INVERTER_CURRENT_STATE_FAULT(Doc.of(Level.WARNING) // .text("The 'CurrentState' is invalid")), // - GRID_DISCONNECTION(Doc.of(Level.FAULT) // + GRID_DISCONNECTION(Doc.of(Level.WARNING) // .text("External grid protection disconnection (17)")), // - GRID_FAILURE_LINE_TO_LINE(Doc.of(Level.FAULT) // + GRID_FAILURE_LINE_TO_LINE(Doc.of(Level.WARNING) // .text("Grid failure phase-to-phase voltage (47)")), // - LINE_FAILURE_UNDER_FREQ(Doc.of(Level.FAULT) // + LINE_FAILURE_UNDER_FREQ(Doc.of(Level.WARNING) // .text("Line failure: Grid frequency is too low (48)")), // - LINE_FAILURE_OVER_FREQ(Doc.of(Level.FAULT) // + LINE_FAILURE_OVER_FREQ(Doc.of(Level.WARNING) // .text("Line failure: Grid frequency is too high (49)")), // - PROTECTION_SHUTDOWN_LINE_1(Doc.of(Level.FAULT) // + PROTECTION_SHUTDOWN_LINE_1(Doc.of(Level.WARNING) // .text("Grid Failure: grid voltage L1 protection (81)")), // - PROTECTION_SHUTDOWN_LINE_2(Doc.of(Level.FAULT) // + PROTECTION_SHUTDOWN_LINE_2(Doc.of(Level.WARNING) // .text("Grid Failure: grid voltage L2 protection (82)")), // - PROTECTION_SHUTDOWN_LINE_3(Doc.of(Level.FAULT) // + PROTECTION_SHUTDOWN_LINE_3(Doc.of(Level.WARNING) // .text("Grid Failure: grid voltage L3 protection (83)")), // ; diff --git a/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/BatteryInverterKacoBlueplanetGridsaveImpl.java b/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/BatteryInverterKacoBlueplanetGridsaveImpl.java index 84ec0153305..9b5a3cefef3 100644 --- a/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/BatteryInverterKacoBlueplanetGridsaveImpl.java +++ b/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/BatteryInverterKacoBlueplanetGridsaveImpl.java @@ -28,6 +28,7 @@ import com.google.common.base.Objects; import com.google.common.collect.ImmutableMap; +import io.openems.common.channel.AccessMode; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.exceptions.OpenemsException; import io.openems.common.types.OptionsEnum; @@ -57,6 +58,9 @@ import io.openems.edge.common.channel.value.Value; import io.openems.edge.common.component.ComponentManager; import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.common.modbusslave.ModbusSlave; +import io.openems.edge.common.modbusslave.ModbusSlaveNatureTable; +import io.openems.edge.common.modbusslave.ModbusSlaveTable; import io.openems.edge.common.startstop.StartStop; import io.openems.edge.common.startstop.StartStoppable; import io.openems.edge.common.taskmanager.Priority; @@ -75,7 +79,7 @@ ) public class BatteryInverterKacoBlueplanetGridsaveImpl extends AbstractSunSpecBatteryInverter implements BatteryInverterKacoBlueplanetGridsave, ManagedSymmetricBatteryInverter, SymmetricBatteryInverter, - ModbusComponent, OpenemsComponent, TimedataProvider, StartStoppable { + ModbusComponent, ModbusSlave, OpenemsComponent, TimedataProvider, StartStoppable { private static final int UNIT_ID = 1; private static final int READ_FROM_MODBUS_BLOCK = 1; @@ -140,7 +144,6 @@ protected void setModbus(BridgeModbus modbus) { // .put(SunSpecModel.S_136, Priority.LOW) // // .put(SunSpecModel.S_160, Priority.LOW) // - @Activate public BatteryInverterKacoBlueplanetGridsaveImpl() { super(// ACTIVE_MODELS, // @@ -158,11 +161,11 @@ public BatteryInverterKacoBlueplanetGridsaveImpl() { @Activate private void activate(ComponentContext context, Config config) throws OpenemsException { + this.config = config; if (super.activate(context, config.id(), config.alias(), config.enabled(), UNIT_ID, this.cm, "Modbus", config.modbus_id(), READ_FROM_MODBUS_BLOCK)) { return; } - this.config = config; } @Override @@ -279,6 +282,16 @@ private void handleGridDisconnection() { }); } + @Override + public ModbusSlaveTable getModbusSlaveTable(AccessMode accessMode) { + return new ModbusSlaveTable(// + OpenemsComponent.getModbusSlaveNatureTable(accessMode), // + SymmetricBatteryInverter.getModbusSlaveNatureTable(accessMode), // + ManagedSymmetricBatteryInverter.getModbusSlaveNatureTable(accessMode), // + ModbusSlaveNatureTable.of(BatteryInverterKacoBlueplanetGridsave.class, accessMode, 100) // + .build()); + } + @Override public BatteryInverterConstraint[] getStaticConstraints() throws OpenemsException { if (this.stateMachine.getCurrentState() == State.RUNNING) { diff --git a/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/KacoSunSpecModel.java b/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/KacoSunSpecModel.java index 2757dbf6616..751aed62757 100644 --- a/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/KacoSunSpecModel.java +++ b/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/KacoSunSpecModel.java @@ -106,7 +106,7 @@ public static enum S64201 implements SunSpecPoint { V_AR(new ScaledValuePoint("S64201_V_AR", "AC Reactive Power", "", // ValuePoint.Type.INT16, true, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "V_AR_SF")), // HZ(new ScaledValuePoint("S64201_HZ", "Line Frequency", "", // - ValuePoint.Type.INT16, true, AccessMode.READ_ONLY, Unit.MILLIHERTZ, "mHZ_SF")), // + ValuePoint.Type.UINT16, true, AccessMode.READ_ONLY, Unit.HERTZ, "Hz_SF")), // RESERVED_36(new ReservedPoint("S64201_RESERVED_36")), // RESERVED_37(new ReservedPoint("S64201_RESERVED_37")), // RESERVED_38(new ReservedPoint("S64201_RESERVED_38")), // @@ -271,6 +271,7 @@ public static enum S64201StVnd implements OptionsEnum { LINE_FAILURE_OVERVOLTAGE_3(46, "Line failure overvoltage L3 The voltage of a grid phase is too low; the grid cannot be fed into. The phase experiencing failure is displayed."), // GRID_FAILURE_PHASETOPHASE(47, "Grid failure phase-to-phase voltage"), // + LINE_FAILURE_UNDERFREQ(48, "Line failure: underfreq. Grid frequency is too low. This fault may be gridrelated."), // LINE_FAILURE_OVERFREQ(49, diff --git a/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/statemachine/ErrorHandler.java b/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/statemachine/ErrorHandler.java index 8dc242a57b6..1424879da21 100644 --- a/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/statemachine/ErrorHandler.java +++ b/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/statemachine/ErrorHandler.java @@ -14,11 +14,18 @@ protected void onEntry(Context context) throws OpenemsNamedException { } @Override - public State runAndGetNextState(Context context) throws OpenemsNamedException { + public State runAndGetNextState(Context context) { final var inverter = context.getParent(); if (!inverter.hasFailure()) { - return State.GO_STOPPED; + return State.UNDEFINED; } return State.ERROR; } + + @Override + protected void onExit(Context context) { + final var inverter = context.getParent(); + inverter._setMaxStartTimeout(false); + inverter._setMaxStopTimeout(false); + } } diff --git a/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/test/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/BatteryInverterKacoBlueplanetGridsaveImplTest.java b/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/test/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/BatteryInverterKacoBlueplanetGridsaveImplTest.java index 4bc316e97c5..d3d8025f5e1 100644 --- a/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/test/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/BatteryInverterKacoBlueplanetGridsaveImplTest.java +++ b/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/test/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/BatteryInverterKacoBlueplanetGridsaveImplTest.java @@ -1,15 +1,19 @@ package io.openems.edge.batteryinverter.kaco.blueplanetgridsave; -import java.time.Instant; -import java.time.ZoneOffset; -import java.time.temporal.ChronoUnit; +import static io.openems.common.test.TestUtils.createDummyClock; +import static io.openems.edge.batteryinverter.api.SymmetricBatteryInverter.ChannelId.MAX_APPARENT_POWER; +import static io.openems.edge.batteryinverter.kaco.blueplanetgridsave.BatteryInverterKacoBlueplanetGridsave.WATCHDOG_TIMEOUT_SECONDS; +import static io.openems.edge.batteryinverter.kaco.blueplanetgridsave.BatteryInverterKacoBlueplanetGridsave.WATCHDOG_TRIGGER_SECONDS; +import static io.openems.edge.batteryinverter.kaco.blueplanetgridsave.BatteryInverterKacoBlueplanetGridsave.ChannelId.STATE_MACHINE; +import static io.openems.edge.batteryinverter.kaco.blueplanetgridsave.KacoSunSpecModel.S64201.CURRENT_STATE; +import static io.openems.edge.batteryinverter.kaco.blueplanetgridsave.KacoSunSpecModel.S64201.WATCHDOG; +import static java.time.temporal.ChronoUnit.SECONDS; import org.junit.Before; import org.junit.Test; import io.openems.common.exceptions.OpenemsException; import io.openems.common.test.TimeLeapClock; -import io.openems.common.types.ChannelAddress; import io.openems.edge.battery.api.Battery; import io.openems.edge.battery.test.DummyBattery; import io.openems.edge.batteryinverter.kaco.blueplanetgridsave.KacoSunSpecModel.S64201.S64201CurrentState; @@ -27,22 +31,9 @@ public class BatteryInverterKacoBlueplanetGridsaveImplTest { - private static final String BATTERY_INVERTER_ID = "batteryInverter0"; - private static final String BATTERY_ID = "battery0"; - private static final String MODBUS_ID = "modbus0"; - - private static final ChannelAddress STATE_MACHINE = new ChannelAddress(BATTERY_INVERTER_ID, "StateMachine"); - - private static final ChannelAddress MAX_APPARENT_POWER = new ChannelAddress(BATTERY_INVERTER_ID, - "MaxApparentPower"); - private static final ChannelAddress CURRENT_STATE = new ChannelAddress(BATTERY_INVERTER_ID, - KacoSunSpecModel.S64201.CURRENT_STATE.getChannelId().id()); - private static final ChannelAddress WATCHDOG = new ChannelAddress(BATTERY_INVERTER_ID, - KacoSunSpecModel.S64201.WATCHDOG.getChannelId().id()); - private static class MyComponentTest extends ComponentTest { - private final Battery battery = new DummyBattery(BATTERY_ID); + private final Battery battery = new DummyBattery("battery0"); public MyComponentTest(OpenemsComponent sut) throws OpenemsException { super(sut); @@ -58,20 +49,17 @@ protected void handleEvent(String topic) throws Exception { } - private static TimeLeapClock clock; + private static final TimeLeapClock CLOCK = createDummyClock(); private static ComponentTest test; @Before public void prepareTest() throws Exception { - final var start = 1577836800L; - clock = new TimeLeapClock(Instant.ofEpochSecond(start) /* starts at 1. January 2020 00:00:00 */, - ZoneOffset.UTC); var sut = new BatteryInverterKacoBlueplanetGridsaveImpl(); test = new MyComponentTest(sut) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("componentManager", new DummyComponentManager(clock)) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)); + .addReference("componentManager", new DummyComponentManager(CLOCK)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")); // TODO implement proper Dummy-Modbus-Bridge with SunSpec support. Till then... test.addReference("isSunSpecInitializationCompleted", true); // @@ -86,16 +74,16 @@ public void prepareTest() throws Exception { addChannel.invoke(sut, KacoSunSpecModel.S64202.CHA_MAX_A_0.getChannelId()); addChannel.invoke(sut, KacoSunSpecModel.S64202.EN_LIMIT_0.getChannelId()); addChannel.invoke(sut, KacoSunSpecModel.S64201.REQUESTED_STATE.getChannelId()); - addChannel.invoke(sut, KacoSunSpecModel.S64201.CURRENT_STATE.getChannelId()); - addChannel.invoke(sut, KacoSunSpecModel.S64201.WATCHDOG.getChannelId()); + addChannel.invoke(sut, CURRENT_STATE.getChannelId()); + addChannel.invoke(sut, WATCHDOG.getChannelId()); addChannel.invoke(sut, KacoSunSpecModel.S64201.W_SET_PCT.getChannelId()); addChannel.invoke(sut, KacoSunSpecModel.S64201.WPARAM_RMP_TMS.getChannelId()); addChannel.invoke(sut, KacoSunSpecModel.S64201.ST_VND.getChannelId()); test.activate(MyConfig.create() // - .setId(BATTERY_INVERTER_ID) // + .setId("batteryInverter0") // .setStartStopConfig(StartStopConfig.START) // - .setModbusId(MODBUS_ID) // + .setModbusId("modbus0") // .setActivateWatchdog(true) // .build()); // } @@ -104,16 +92,16 @@ public void prepareTest() throws Exception { public void testStart() throws Exception { test // .next(new TestCase() // - .input(CURRENT_STATE, S64201CurrentState.STANDBY) // + .input(CURRENT_STATE.getChannelId(), S64201CurrentState.STANDBY) // .input(MAX_APPARENT_POWER, 50_000) // .output(STATE_MACHINE, State.UNDEFINED)) // .next(new TestCase() // - .timeleap(clock, 4, ChronoUnit.SECONDS) // + .timeleap(CLOCK, 4, SECONDS) // .output(STATE_MACHINE, State.GO_RUNNING)) // .next(new TestCase() // - .timeleap(clock, 1, ChronoUnit.SECONDS) // - .input(CURRENT_STATE, S64201CurrentState.GRID_CONNECTED) // - .output(WATCHDOG, BatteryInverterKacoBlueplanetGridsave.WATCHDOG_TIMEOUT_SECONDS)) // + .timeleap(CLOCK, 1, SECONDS) // + .input(CURRENT_STATE.getChannelId(), S64201CurrentState.GRID_CONNECTED) // + .output(WATCHDOG.getChannelId(), WATCHDOG_TIMEOUT_SECONDS)) // .next(new TestCase() // .output(STATE_MACHINE, State.RUNNING)) // ; @@ -123,14 +111,13 @@ public void testStart() throws Exception { public void testWatchdog() throws Exception { test // .next(new TestCase() // - .output(WATCHDOG, BatteryInverterKacoBlueplanetGridsave.WATCHDOG_TIMEOUT_SECONDS)) // + .output(WATCHDOG.getChannelId(), WATCHDOG_TIMEOUT_SECONDS)) // .next(new TestCase() // - .timeleap(clock, BatteryInverterKacoBlueplanetGridsave.WATCHDOG_TRIGGER_SECONDS - 1, - ChronoUnit.SECONDS) // - .output(WATCHDOG, null /* waiting till next watchdog trigger */)) // + .timeleap(CLOCK, WATCHDOG_TRIGGER_SECONDS - 1, SECONDS) // + .output(WATCHDOG.getChannelId(), null /* waiting till next watchdog trigger */)) // .next(new TestCase() // - .timeleap(clock, 1, ChronoUnit.SECONDS) // - .output(WATCHDOG, BatteryInverterKacoBlueplanetGridsave.WATCHDOG_TIMEOUT_SECONDS)) // + .timeleap(CLOCK, 1, SECONDS) // + .output(WATCHDOG.getChannelId(), WATCHDOG_TIMEOUT_SECONDS)) // ; } } diff --git a/io.openems.edge.batteryinverter.refu88k/.classpath b/io.openems.edge.batteryinverter.refu88k/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.batteryinverter.refu88k/.classpath +++ b/io.openems.edge.batteryinverter.refu88k/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.batteryinverter.refu88k/src/io/openems/edge/batteryinverter/refu88k/BatteryInverterRefuStore88k.java b/io.openems.edge.batteryinverter.refu88k/src/io/openems/edge/batteryinverter/refu88k/BatteryInverterRefuStore88k.java index 2a9e69ceb53..64a0473325d 100644 --- a/io.openems.edge.batteryinverter.refu88k/src/io/openems/edge/batteryinverter/refu88k/BatteryInverterRefuStore88k.java +++ b/io.openems.edge.batteryinverter.refu88k/src/io/openems/edge/batteryinverter/refu88k/BatteryInverterRefuStore88k.java @@ -43,16 +43,22 @@ public interface BatteryInverterRefuStore88k */ public static int RETRY_COMMAND_MAX_ATTEMPTS = 30; + /* + * Whenever one of these states would be Level.FAULT, the EssGeneric will stop + * the battery and the inverter. If this is necessary, it must be specifically + * mentioned and the state should have a proper description of the fault. + */ + public enum ChannelId implements io.openems.edge.common.channel.ChannelId { STATE_MACHINE(Doc.of(State.values()) // .text("Current State of State-Machine")), // - RUN_FAILED(Doc.of(Level.FAULT) // + RUN_FAILED(Doc.of(Level.WARNING) // .text("Running the Logic failed")), // - MAX_START_ATTEMPTS(Doc.of(Level.FAULT) // + MAX_START_ATTEMPTS(Doc.of(Level.WARNING) // .text("The maximum number of start attempts failed")), // - MAX_STOP_ATTEMPTS(Doc.of(Level.FAULT) // + MAX_STOP_ATTEMPTS(Doc.of(Level.WARNING) // .text("The maximum number of stop attempts failed")), // - INVERTER_CURRENT_STATE_FAULT(Doc.of(Level.FAULT) // + INVERTER_CURRENT_STATE_FAULT(Doc.of(Level.WARNING) // .text("The 'CurrentState' is invalid")), // /* @@ -106,41 +112,41 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { ST(Doc.of(OperatingState.values())), // ST_VND(Doc.of(VendorOperatingState.values())), // // Evt1 Alarms and Warnings - GROUND_FAULT(Doc.of(Level.FAULT) // + GROUND_FAULT(Doc.of(Level.WARNING) // .text("Ground fault")), // - DC_OVER_VOLTAGE(Doc.of(Level.FAULT) // + DC_OVER_VOLTAGE(Doc.of(Level.WARNING) // .text("Dc over voltage")), // - AC_DISCONNECT(Doc.of(Level.FAULT) // + AC_DISCONNECT(Doc.of(Level.WARNING) // .text("AC disconnect open")), // - DC_DISCONNECT(Doc.of(Level.FAULT) // + DC_DISCONNECT(Doc.of(Level.WARNING) // .text("DC disconnect open")), // - GRID_DISCONNECT(Doc.of(Level.FAULT) // + GRID_DISCONNECT(Doc.of(Level.WARNING) // .text("Grid shutdown")), // - CABINET_OPEN(Doc.of(Level.FAULT) // + CABINET_OPEN(Doc.of(Level.WARNING) // .text("Cabinet open")), // - MANUAL_SHUTDOWN(Doc.of(Level.FAULT) // + MANUAL_SHUTDOWN(Doc.of(Level.WARNING) // .text("Manual shutdown")), // - OVER_TEMP(Doc.of(Level.FAULT) // + OVER_TEMP(Doc.of(Level.WARNING) // .text("Over temperature")), // - OVER_FREQUENCY(Doc.of(Level.FAULT) // + OVER_FREQUENCY(Doc.of(Level.WARNING) // .text("Frequency above limit")), // - UNDER_FREQUENCY(Doc.of(Level.FAULT) // + UNDER_FREQUENCY(Doc.of(Level.WARNING) // .text("Frequency under limit")), // - AC_OVER_VOLT(Doc.of(Level.FAULT) // + AC_OVER_VOLT(Doc.of(Level.WARNING) // .text("AC Voltage above limit")), // - AC_UNDER_VOLT(Doc.of(Level.FAULT) // + AC_UNDER_VOLT(Doc.of(Level.WARNING) // .text("AC Voltage under limit")), // - BLOWN_STRING_FUSE(Doc.of(Level.FAULT) // + BLOWN_STRING_FUSE(Doc.of(Level.WARNING) // .text("Blown String fuse on input")), // - UNDER_TEMP(Doc.of(Level.FAULT) // + UNDER_TEMP(Doc.of(Level.WARNING) // .text("Under temperature")), // - MEMORY_LOSS(Doc.of(Level.FAULT) // + MEMORY_LOSS(Doc.of(Level.WARNING) // .text("Generic Memory or Communication error (internal)")), // - HW_TEST_FAILURE(Doc.of(Level.FAULT) // + HW_TEST_FAILURE(Doc.of(Level.WARNING) // .text("Hardware test failure")), // - OTHER_ALARM(Doc.of(Level.FAULT) // + OTHER_ALARM(Doc.of(Level.WARNING) // .text("Other alarm")), // - OTHER_WARNING(Doc.of(Level.FAULT) // + OTHER_WARNING(Doc.of(Level.WARNING) // .text("Other warning")), // EVT_2(Doc.of(OpenemsType.INTEGER).unit(Unit.NONE).accessMode(AccessMode.READ_ONLY)), // EVT_VND_1(Doc.of(OpenemsType.INTEGER).unit(Unit.NONE).accessMode(AccessMode.READ_ONLY)), // diff --git a/io.openems.edge.batteryinverter.refu88k/test/io/openems/edge/batteryinverter/refu88k/BatteryInverterRefuStore88kImplTest.java b/io.openems.edge.batteryinverter.refu88k/test/io/openems/edge/batteryinverter/refu88k/BatteryInverterRefuStore88kImplTest.java index 08316f856cf..560aa3bb4f3 100644 --- a/io.openems.edge.batteryinverter.refu88k/test/io/openems/edge/batteryinverter/refu88k/BatteryInverterRefuStore88kImplTest.java +++ b/io.openems.edge.batteryinverter.refu88k/test/io/openems/edge/batteryinverter/refu88k/BatteryInverterRefuStore88kImplTest.java @@ -9,17 +9,14 @@ public class BatteryInverterRefuStore88kImplTest { - private static final String BATTERY_INVERTER_ID = "batteryInverter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new BatteryInverterRefuStore88kImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(BATTERY_INVERTER_ID) // - .setModbusId(MODBUS_ID) // + .setId("batteryInverter0") // + .setModbusId("modbus0") // .setStartStop(StartStopConfig.AUTO) // .setTimeLimitNoPower(0) // .setWatchdoginterval(0) // diff --git a/io.openems.edge.batteryinverter.sinexcel/.classpath b/io.openems.edge.batteryinverter.sinexcel/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.batteryinverter.sinexcel/.classpath +++ b/io.openems.edge.batteryinverter.sinexcel/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/BatteryInverterSinexcel.java b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/BatteryInverterSinexcel.java index daa20ac5029..d54e8bbe2d1 100644 --- a/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/BatteryInverterSinexcel.java +++ b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/BatteryInverterSinexcel.java @@ -44,7 +44,7 @@ public interface BatteryInverterSinexcel extends OffGridBatteryInverter, Managed public enum ChannelId implements io.openems.edge.common.channel.ChannelId { STATE_MACHINE(Doc.of(State.values()) // .text("Current State of State-Machine")), // - RUN_FAILED(Doc.of(Level.FAULT) // + RUN_FAILED(Doc.of(Level.WARNING) // .text("Running the Logic failed")), // SET_ACTIVE_POWER(Doc.of(OpenemsType.INTEGER) // .accessMode(AccessMode.READ_WRITE)// @@ -96,7 +96,7 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { SERIAL_NUMBER(Doc.of(OpenemsType.STRING) // .persistencePriority(PersistencePriority.HIGH) // .accessMode(AccessMode.READ_ONLY)), // - FAULT_STATUS(Doc.of(Level.FAULT) // + FAULT_STATUS(Doc.of(Level.WARNING) // .accessMode(AccessMode.READ_ONLY)), // ALERT_STATUS(Doc.of(Level.WARNING) // .accessMode(AccessMode.READ_ONLY)), // diff --git a/io.openems.edge.batteryinverter.sinexcel/test/io/openems/edge/batteryinverter/sinexcel/BatteryInverterSinexcelImplTest.java b/io.openems.edge.batteryinverter.sinexcel/test/io/openems/edge/batteryinverter/sinexcel/BatteryInverterSinexcelImplTest.java index 6092ebde7d6..abf144c927e 100644 --- a/io.openems.edge.batteryinverter.sinexcel/test/io/openems/edge/batteryinverter/sinexcel/BatteryInverterSinexcelImplTest.java +++ b/io.openems.edge.batteryinverter.sinexcel/test/io/openems/edge/batteryinverter/sinexcel/BatteryInverterSinexcelImplTest.java @@ -1,5 +1,11 @@ package io.openems.edge.batteryinverter.sinexcel; +import static io.openems.edge.batteryinverter.api.OffGridBatteryInverter.ChannelId.INVERTER_STATE; +import static io.openems.edge.batteryinverter.api.SymmetricBatteryInverter.ChannelId.MAX_APPARENT_POWER; +import static io.openems.edge.batteryinverter.sinexcel.BatteryInverterSinexcel.ChannelId.SET_OFF_GRID_MODE; +import static io.openems.edge.batteryinverter.sinexcel.BatteryInverterSinexcel.ChannelId.SET_ON_GRID_MODE; +import static io.openems.edge.batteryinverter.sinexcel.BatteryInverterSinexcel.ChannelId.STATE_MACHINE; + import java.time.Instant; import java.time.ZoneOffset; import java.time.temporal.ChronoUnit; @@ -8,10 +14,8 @@ import io.openems.common.exceptions.OpenemsException; import io.openems.common.test.TimeLeapClock; -import io.openems.common.types.ChannelAddress; import io.openems.edge.battery.api.Battery; import io.openems.edge.battery.test.DummyBattery; -import io.openems.edge.batteryinverter.api.OffGridBatteryInverter; import io.openems.edge.batteryinverter.api.OffGridBatteryInverter.TargetGridMode; import io.openems.edge.batteryinverter.sinexcel.enums.CountryCode; import io.openems.edge.batteryinverter.sinexcel.enums.EnableDisable; @@ -27,21 +31,9 @@ public class BatteryInverterSinexcelImplTest { - private static final String BATTERY_INVERTER_ID = "batteryInverter0"; - private static final String BATTERY_ID = "battery0"; - private static final String MODBUS_ID = "modbus0"; - private static final ChannelAddress STATE_MACHINE = new ChannelAddress(BATTERY_INVERTER_ID, "StateMachine"); - private static final ChannelAddress SET_ON_GRID_MODE = new ChannelAddress(BATTERY_INVERTER_ID, "SetOnGridMode"); - private static final ChannelAddress SET_OFF_GRID_MODE = new ChannelAddress(BATTERY_INVERTER_ID, "SetOffGridMode"); - private static final ChannelAddress MAX_APPARENT_POWER = new ChannelAddress(BATTERY_INVERTER_ID, // - "MaxApparentPower"); - - private static final ChannelAddress INVERTER_STATE = new ChannelAddress(BATTERY_INVERTER_ID, // - OffGridBatteryInverter.ChannelId.INVERTER_STATE.id()); - private static class MyComponentTest extends ComponentTest { - private final Battery battery = new DummyBattery(BATTERY_ID); + private final Battery battery = new DummyBattery("battery0"); public MyComponentTest(OpenemsComponent sut) throws OpenemsException { super(sut); @@ -64,11 +56,11 @@ public void testStart() throws Exception { new MyComponentTest(new BatteryInverterSinexcelImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager(clock)) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(BATTERY_INVERTER_ID) // + .setId("batteryInverter0") // .setStartStopConfig(StartStopConfig.START) // - .setModbusId(MODBUS_ID) // + .setModbusId("modbus0") // .setCountryCode(CountryCode.GERMANY)// .setEmergencyPower(EnableDisable.DISABLE)// .build()) // @@ -100,11 +92,11 @@ public void testOffGrid() throws Exception { new MyComponentTest(sut) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager(clock)) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(BATTERY_INVERTER_ID) // + .setId("batteryInverter0") // .setStartStopConfig(StartStopConfig.START) // - .setModbusId(MODBUS_ID) // + .setModbusId("modbus0") // .setCountryCode(CountryCode.GERMANY)// .setEmergencyPower(EnableDisable.DISABLE)// .build()) // diff --git a/io.openems.edge.batteryinverter.sunspec/.classpath b/io.openems.edge.batteryinverter.sunspec/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.batteryinverter.sunspec/.classpath +++ b/io.openems.edge.batteryinverter.sunspec/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.bosch.bpts5hybrid/.classpath b/io.openems.edge.bosch.bpts5hybrid/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.bosch.bpts5hybrid/.classpath +++ b/io.openems.edge.bosch.bpts5hybrid/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.bosch.bpts5hybrid/src/io/openems/edge/bosch/bpts5hybrid/meter/BoschBpts5HybridMeterImpl.java b/io.openems.edge.bosch.bpts5hybrid/src/io/openems/edge/bosch/bpts5hybrid/meter/BoschBpts5HybridMeterImpl.java index 74c10e22aad..03f72961afa 100644 --- a/io.openems.edge.bosch.bpts5hybrid/src/io/openems/edge/bosch/bpts5hybrid/meter/BoschBpts5HybridMeterImpl.java +++ b/io.openems.edge.bosch.bpts5hybrid/src/io/openems/edge/bosch/bpts5hybrid/meter/BoschBpts5HybridMeterImpl.java @@ -17,13 +17,13 @@ import org.osgi.service.metatype.annotations.Designate; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.types.MeterType; import io.openems.edge.bosch.bpts5hybrid.core.BoschBpts5HybridCore; import io.openems.edge.common.channel.Doc; import io.openems.edge.common.component.AbstractOpenemsComponent; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.event.EdgeEventConstants; import io.openems.edge.meter.api.ElectricityMeter; -import io.openems.edge.meter.api.MeterType; @Designate(ocd = Config.class, factory = true) @Component(// diff --git a/io.openems.edge.bosch.bpts5hybrid/test/io/openems/edge/bosch/bpts5hybrid/core/BoschBpts5HybridCoreImplTest.java b/io.openems.edge.bosch.bpts5hybrid/test/io/openems/edge/bosch/bpts5hybrid/core/BoschBpts5HybridCoreImplTest.java index b7f597fbc35..e13cd08a7ab 100644 --- a/io.openems.edge.bosch.bpts5hybrid/test/io/openems/edge/bosch/bpts5hybrid/core/BoschBpts5HybridCoreImplTest.java +++ b/io.openems.edge.bosch.bpts5hybrid/test/io/openems/edge/bosch/bpts5hybrid/core/BoschBpts5HybridCoreImplTest.java @@ -2,6 +2,7 @@ import org.junit.Test; +import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; @@ -21,6 +22,7 @@ public void test() throws Exception { .setIpaddress("127.0.0.1") // .setInterval(2) // .build()) // - ; + .next(new TestCase()) // + .deactivate(); } } \ No newline at end of file diff --git a/io.openems.edge.bosch.bpts5hybrid/test/io/openems/edge/bosch/bpts5hybrid/ess/BoschBpts5HybridEssTest.java b/io.openems.edge.bosch.bpts5hybrid/test/io/openems/edge/bosch/bpts5hybrid/ess/BoschBpts5HybridEssTest.java index c45058b22e5..0a2d9d77811 100644 --- a/io.openems.edge.bosch.bpts5hybrid/test/io/openems/edge/bosch/bpts5hybrid/ess/BoschBpts5HybridEssTest.java +++ b/io.openems.edge.bosch.bpts5hybrid/test/io/openems/edge/bosch/bpts5hybrid/ess/BoschBpts5HybridEssTest.java @@ -3,6 +3,7 @@ import org.junit.Test; import io.openems.edge.bosch.bpts5hybrid.core.BoschBpts5HybridCoreImpl; +import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; @@ -30,6 +31,7 @@ public void test() throws Exception { .setId(ESS_ID) // .setCoreId(CORE_ID) // .build()) // - ; + .next(new TestCase()) // + .deactivate(); } } \ No newline at end of file diff --git a/io.openems.edge.bosch.bpts5hybrid/test/io/openems/edge/bosch/bpts5hybrid/meter/BoschBpts5HybridMeterTest.java b/io.openems.edge.bosch.bpts5hybrid/test/io/openems/edge/bosch/bpts5hybrid/meter/BoschBpts5HybridMeterTest.java index 476489b3ef7..1d3af580ac8 100644 --- a/io.openems.edge.bosch.bpts5hybrid/test/io/openems/edge/bosch/bpts5hybrid/meter/BoschBpts5HybridMeterTest.java +++ b/io.openems.edge.bosch.bpts5hybrid/test/io/openems/edge/bosch/bpts5hybrid/meter/BoschBpts5HybridMeterTest.java @@ -3,6 +3,7 @@ import org.junit.Test; import io.openems.edge.bosch.bpts5hybrid.core.BoschBpts5HybridCoreImpl; +import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; @@ -30,6 +31,7 @@ public void test() throws Exception { .setId(METER_ID) // .setCoreId(CORE_ID) // .build()) // - ; + .next(new TestCase()) // + .deactivate(); } } \ No newline at end of file diff --git a/io.openems.edge.bosch.bpts5hybrid/test/io/openems/edge/bosch/bpts5hybrid/pv/BoschBpts5HybridPvTest.java b/io.openems.edge.bosch.bpts5hybrid/test/io/openems/edge/bosch/bpts5hybrid/pv/BoschBpts5HybridPvTest.java index 5d0041227f6..8a411cf25e0 100644 --- a/io.openems.edge.bosch.bpts5hybrid/test/io/openems/edge/bosch/bpts5hybrid/pv/BoschBpts5HybridPvTest.java +++ b/io.openems.edge.bosch.bpts5hybrid/test/io/openems/edge/bosch/bpts5hybrid/pv/BoschBpts5HybridPvTest.java @@ -3,6 +3,7 @@ import org.junit.Test; import io.openems.edge.bosch.bpts5hybrid.core.BoschBpts5HybridCoreImpl; +import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; @@ -30,6 +31,7 @@ public void test() throws Exception { .setId(CHARGER_ID) // .setCoreId(CORE_ID) // .build()) // - ; + .next(new TestCase()) // + .deactivate(); } } \ No newline at end of file diff --git a/io.openems.edge.bridge.http/.classpath b/io.openems.edge.bridge.http/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.bridge.http/.classpath +++ b/io.openems.edge.bridge.http/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.bridge.http/src/io/openems/edge/bridge/http/api/UrlBuilder.java b/io.openems.edge.bridge.http/src/io/openems/edge/bridge/http/api/UrlBuilder.java new file mode 100644 index 00000000000..095b1fe2c24 --- /dev/null +++ b/io.openems.edge.bridge.http/src/io/openems/edge/bridge/http/api/UrlBuilder.java @@ -0,0 +1,246 @@ +package io.openems.edge.bridge.http.api; + +import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.toUnmodifiableMap; + +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Stream; + +/** + * Simple URL Builder class to build URLs with correctly encoded query + * parameter. This class is immutable. + * + *

+ * Example Usage: + * + *

+ * 
+ * final var url = UrlBuilder.create() //
+		.withScheme("https") //
+		.withHost("openems.io") //
+		.withPort(443) //
+		.withPath("/path/to") //
+		.withQueryParam("key", "value") //
+		.withFragment("fragment") //
+		// get final result
+		.toUri() // to get a URI Object
+		.toUrl() // to get a URL Object
+		.toEncodedString() // to get the URL as a encoded String
+ * 
+ * or parse from a string
+ * 
+ * final var url = UrlBuilder.from("https://openems.io:443/path?key=value#fragment");
+ * 
+ * 
+ * + *

+ * URL-Schema: + * scheme://host:port/path?queryParams#fragment + * + */ +public final class UrlBuilder { + + /** + * Parses a raw uri string to the url parts. + * + * @param uriString the raw string to parse + * @return the {@link UrlBuilder} with the parts of th uri + */ + public static UrlBuilder parse(String uriString) { + final var uri = URI.create(uriString); + + final var query = uri.getQuery(); + final var queryParams = query == null ? Collections.emptyMap() + : Stream.of(uri.getQuery().split("&")) // + .map(t -> t.split("=")) // + .collect(toUnmodifiableMap(t -> t[0], t -> t[1])); + return new UrlBuilder(// + uri.getScheme(), // + uri.getHost(), // + uri.getPort(), // + uri.getPath(), // + queryParams, // + uri.getFragment() // + ); + } + + /** + * Creates a new {@link UrlBuilder}. + * + * @return the new {@link UrlBuilder} instance + */ + public static UrlBuilder create() { + return new UrlBuilder(null, null, null, null, Collections.emptyMap(), null); + } + + private final String scheme; + private final String host; + private final Integer port; + private final String path; + private final Map queryParams; + private final String fragment; + + private UrlBuilder(// + String scheme, // + String host, // + Integer port, // + String path, // + Map queryParams, // + String fragment // + ) { + this.scheme = scheme; + this.host = host; + this.port = port; + this.path = path; + this.queryParams = queryParams; + this.fragment = fragment; + } + + /** + * Creates a copy of the current {@link UrlBuilder} with the new scheme. + * + * @param scheme the new scheme + * @return the copy of the {@link UrlBuilder} with the new scheme + */ + public UrlBuilder withScheme(String scheme) { + return new UrlBuilder(scheme, this.host, this.port, this.path, this.queryParams, this.fragment); + } + + /** + * Creates a copy of the current {@link UrlBuilder} with the new host. + * + * @param host the new host + * @return the copy of the {@link UrlBuilder} with the new host + */ + public UrlBuilder withHost(String host) { + return new UrlBuilder(this.scheme, host, this.port, this.path, this.queryParams, this.fragment); + } + + /** + * Creates a copy of the current {@link UrlBuilder} with the new port. + * + * @param port the new port + * @return the copy of the {@link UrlBuilder} with the new port + */ + public UrlBuilder withPort(int port) { + if (port < 0) { + throw new IllegalArgumentException("Property 'port' must not be smaller than '0'."); + } + return new UrlBuilder(this.scheme, this.host, port, this.path, this.queryParams, this.fragment); + } + + /** + * Creates a copy of the current {@link UrlBuilder} with the new path. + * + * @param path the new path + * @return the copy of the {@link UrlBuilder} with the new path + */ + public UrlBuilder withPath(String path) { + return new UrlBuilder(this.scheme, this.host, this.port, path, this.queryParams, this.fragment); + } + + /** + * Creates a copy of the current {@link UrlBuilder} with the new query parameter + * added. + * + * @param key the key of the new query parameter + * @param value the value of the new query parameter + * @return the copy of the {@link UrlBuilder} with the new query parameter added + */ + public UrlBuilder withQueryParam(String key, String value) { + Map newQueryParams = new HashMap<>(this.queryParams); + newQueryParams.put(key, value); + return new UrlBuilder(this.scheme, this.host, this.port, this.path, Collections.unmodifiableMap(newQueryParams), + this.fragment); + } + + /** + * Creates a copy of the current {@link UrlBuilder} with the new fragment. + * + * @param fragment the new fragment + * @return the copy of the {@link UrlBuilder} with the new fragment + */ + public UrlBuilder withFragment(String fragment) { + return new UrlBuilder(this.scheme, this.host, this.port, this.path, this.queryParams, fragment); + } + + /** + * Creates a {@link URI} from this object. + * + * @return the {@link URI} + */ + public URI toUri() { + return URI.create(this.toEncodedString()); + } + + /** + * Creates a {@link URI} from this object. + * + * @return the {@link URI} + * @throws MalformedURLException If a protocol handler for the URL could not be + * found, or if some other error occurred while + * constructing the URL + */ + public URL toUrl() throws MalformedURLException { + return this.toUri().toURL(); + } + + /** + * Creates an encoded string url from this object. + * + *

+ * Note: does not check if the url is valid. To Check if it is valid use + * {@link #toUrl()} + * + * @return the encoded url + */ + public String toEncodedString() { + final var url = new StringBuilder(); + + url.append(this.scheme); + url.append("://"); + url.append(this.host); + + if (this.port != null) { + url.append(":"); + url.append(this.port); + } + + if (this.path != null && !this.path.isEmpty()) { + if (!this.path.startsWith("/")) { + url.append("/"); + } + url.append(this.path); + } + + if (!this.queryParams.isEmpty()) { + var query = this.queryParams.entrySet().stream() // + .map(t -> encode(t.getKey()) + "=" + encode(t.getValue())) // + .collect(joining("&", "?", "")); + url.append(query); + } + + if (this.fragment != null && !this.fragment.isEmpty()) { + if (!this.fragment.startsWith("#")) { + url.append("#"); + } + url.append(this.fragment); + } + + return url.toString(); + } + + // Helper method to URL-encode values + private static String encode(String value) { + return URLEncoder.encode(value, StandardCharsets.UTF_8) // + .replace("+", "%20") // " " => "+" => "%20" + ; + } +} diff --git a/io.openems.edge.bridge.http/src/io/openems/edge/bridge/http/dummy/DummyBridgeHttpBundle.java b/io.openems.edge.bridge.http/src/io/openems/edge/bridge/http/dummy/DummyBridgeHttpBundle.java index befb0e1ebca..fcd0d24b56d 100644 --- a/io.openems.edge.bridge.http/src/io/openems/edge/bridge/http/dummy/DummyBridgeHttpBundle.java +++ b/io.openems.edge.bridge.http/src/io/openems/edge/bridge/http/dummy/DummyBridgeHttpBundle.java @@ -1,5 +1,6 @@ package io.openems.edge.bridge.http.dummy; +import static io.openems.edge.bridge.http.dummy.DummyBridgeHttpFactory.dummyEndpointFetcher; import static java.util.Collections.emptyMap; import java.util.concurrent.CompletableFuture; @@ -7,7 +8,6 @@ import org.osgi.service.event.Event; -import io.openems.common.test.TimeLeapClock; import io.openems.edge.bridge.http.api.BridgeHttp.Endpoint; import io.openems.edge.bridge.http.api.BridgeHttpFactory; import io.openems.edge.bridge.http.api.CycleSubscriber; @@ -17,9 +17,8 @@ public class DummyBridgeHttpBundle { - private final DummyEndpointFetcher fetcher = DummyBridgeHttpFactory.dummyEndpointFetcher(); - private final DummyBridgeHttpExecutor pool = DummyBridgeHttpFactory.dummyBridgeHttpExecutor(new TimeLeapClock(), - true); + private final DummyEndpointFetcher fetcher = dummyEndpointFetcher(); + private final DummyBridgeHttpExecutor pool = DummyBridgeHttpFactory.dummyBridgeHttpExecutor(true); private final CycleSubscriber cycleSubscriber = DummyBridgeHttpFactory.cycleSubscriber(); private final DummyBridgeHttpFactory bridgeFactory = DummyBridgeHttpFactory.ofBridgeImpl(() -> this.cycleSubscriber, () -> this.fetcher, () -> this.pool); diff --git a/io.openems.edge.bridge.http/src/io/openems/edge/bridge/http/dummy/DummyBridgeHttpExecutor.java b/io.openems.edge.bridge.http/src/io/openems/edge/bridge/http/dummy/DummyBridgeHttpExecutor.java index 2a74f1c7ea0..71b7370b2fb 100644 --- a/io.openems.edge.bridge.http/src/io/openems/edge/bridge/http/dummy/DummyBridgeHttpExecutor.java +++ b/io.openems.edge.bridge.http/src/io/openems/edge/bridge/http/dummy/DummyBridgeHttpExecutor.java @@ -1,5 +1,7 @@ package io.openems.edge.bridge.http.dummy; +import static io.openems.common.test.TestUtils.createDummyClock; + import java.time.Clock; import java.time.Duration; import java.time.Instant; @@ -110,10 +112,18 @@ public DummyBridgeHttpExecutor(Clock clock, boolean handleTasksImmediately) { this.taskExecutor = handleTasksImmediately ? new ImmediateTaskExecutor() : new DelayedTaskExecutor(); } + public DummyBridgeHttpExecutor(boolean handleTasksImmediately) { + this(createDummyClock(), handleTasksImmediately); + } + public DummyBridgeHttpExecutor(Clock clock) { this(clock, false); } + public DummyBridgeHttpExecutor() { + this(false); + } + @Override public ScheduledFuture schedule(Runnable task, Delay.DurationDelay durationDelay) { if (this.isShutdown()) { diff --git a/io.openems.edge.bridge.http/src/io/openems/edge/bridge/http/dummy/DummyBridgeHttpFactory.java b/io.openems.edge.bridge.http/src/io/openems/edge/bridge/http/dummy/DummyBridgeHttpFactory.java index 635b56ad885..01f1f169e83 100644 --- a/io.openems.edge.bridge.http/src/io/openems/edge/bridge/http/dummy/DummyBridgeHttpFactory.java +++ b/io.openems.edge.bridge.http/src/io/openems/edge/bridge/http/dummy/DummyBridgeHttpFactory.java @@ -124,6 +124,21 @@ public static DummyBridgeHttpExecutor dummyBridgeHttpExecutor(// return new DummyBridgeHttpExecutor(clock, handleTasksImmediately); } + /** + * Creates a {@link DummyBridgeHttpExecutor} to handle the execution of the + * requests to fetch an {@link Endpoint}. + * + * @param handleTasksImmediately true if all tasks which are not scheduled + * should be executed immediately in the same + * thread; false if only executed during the + * {@link DummyBridgeHttpExecutor#update()} + * method. + * @return the created {@link DummyBridgeHttpExecutor} + */ + public static DummyBridgeHttpExecutor dummyBridgeHttpExecutor(boolean handleTasksImmediately) { + return new DummyBridgeHttpExecutor(handleTasksImmediately); + } + /** * Creates a {@link DummyBridgeHttpExecutor} to handle the execution of the * requests to fetch an {@link Endpoint}. @@ -136,6 +151,16 @@ public static DummyBridgeHttpExecutor dummyBridgeHttpExecutor(Clock clock) { return new DummyBridgeHttpExecutor(clock); } + /** + * Creates a {@link DummyBridgeHttpExecutor} to handle the execution of the + * requests to fetch an {@link Endpoint}. + * + * @return the created {@link DummyBridgeHttpExecutor} + */ + public static DummyBridgeHttpExecutor dummyBridgeHttpExecutor() { + return new DummyBridgeHttpExecutor(); + } + private DummyBridgeHttpFactory(Supplier supplier) { super(new DummyBridgeHttpCso(supplier)); } diff --git a/io.openems.edge.bridge.http/test/io/openems/edge/bridge/http/api/BridgeHttpCycleTest.java b/io.openems.edge.bridge.http/test/io/openems/edge/bridge/http/api/BridgeHttpCycleTest.java index 0e4ca875adc..ca2a7d82130 100644 --- a/io.openems.edge.bridge.http/test/io/openems/edge/bridge/http/api/BridgeHttpCycleTest.java +++ b/io.openems.edge.bridge.http/test/io/openems/edge/bridge/http/api/BridgeHttpCycleTest.java @@ -13,7 +13,6 @@ import org.junit.Test; import org.osgi.service.event.Event; -import io.openems.common.test.TimeLeapClock; import io.openems.common.utils.FunctionUtils; import io.openems.edge.bridge.http.BridgeHttpImpl; import io.openems.edge.bridge.http.api.BridgeHttp.Endpoint; @@ -33,7 +32,7 @@ public class BridgeHttpCycleTest { public void before() throws Exception { this.cycleSubscriber = new CycleSubscriber(); this.fetcher = new DummyEndpointFetcher(); - this.pool = new DummyBridgeHttpExecutor(new TimeLeapClock()); + this.pool = new DummyBridgeHttpExecutor(); this.bridgeHttp = new BridgeHttpImpl(// this.cycleSubscriber, // this.fetcher, // diff --git a/io.openems.edge.bridge.http/test/io/openems/edge/bridge/http/api/BridgeHttpTest.java b/io.openems.edge.bridge.http/test/io/openems/edge/bridge/http/api/BridgeHttpTest.java index 013e2ddc067..253668672e1 100644 --- a/io.openems.edge.bridge.http/test/io/openems/edge/bridge/http/api/BridgeHttpTest.java +++ b/io.openems.edge.bridge.http/test/io/openems/edge/bridge/http/api/BridgeHttpTest.java @@ -1,5 +1,6 @@ package io.openems.edge.bridge.http.api; +import static io.openems.edge.bridge.http.dummy.DummyBridgeHttpFactory.dummyBridgeHttpExecutor; import static java.util.Collections.emptyMap; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -14,7 +15,6 @@ import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.function.ThrowingFunction; -import io.openems.common.test.TimeLeapClock; import io.openems.common.utils.JsonUtils; import io.openems.edge.bridge.http.BridgeHttpImpl; import io.openems.edge.bridge.http.api.BridgeHttp.Endpoint; @@ -31,8 +31,7 @@ public class BridgeHttpTest { public void before() throws Exception { this.cycleSubscriber = DummyBridgeHttpFactory.cycleSubscriber(); this.fetcher = DummyBridgeHttpFactory.dummyEndpointFetcher(); - this.bridgeHttp = new BridgeHttpImpl(this.cycleSubscriber, this.fetcher, - DummyBridgeHttpFactory.dummyBridgeHttpExecutor(new TimeLeapClock(), true)); + this.bridgeHttp = new BridgeHttpImpl(this.cycleSubscriber, this.fetcher, dummyBridgeHttpExecutor(true)); } @After diff --git a/io.openems.edge.bridge.http/test/io/openems/edge/bridge/http/api/BridgeHttpTimeTest.java b/io.openems.edge.bridge.http/test/io/openems/edge/bridge/http/api/BridgeHttpTimeTest.java index 74f89ade961..203f1f8ceb7 100644 --- a/io.openems.edge.bridge.http/test/io/openems/edge/bridge/http/api/BridgeHttpTimeTest.java +++ b/io.openems.edge.bridge.http/test/io/openems/edge/bridge/http/api/BridgeHttpTimeTest.java @@ -1,5 +1,6 @@ package io.openems.edge.bridge.http.api; +import static io.openems.common.test.TestUtils.createDummyClock; import static io.openems.edge.bridge.http.time.DelayTimeProviderChain.fixedDelay; import static org.junit.Assert.assertEquals; @@ -35,7 +36,7 @@ public void before() throws Exception { }; }); - this.pool = new DummyBridgeHttpExecutor(this.clock = new TimeLeapClock()); + this.pool = new DummyBridgeHttpExecutor(this.clock = createDummyClock()); this.bridgeHttp = new BridgeHttpImpl(cycleSubscriber, fetcher, this.pool); } diff --git a/io.openems.edge.bridge.http/test/io/openems/edge/bridge/http/api/UrlBuilderTest.java b/io.openems.edge.bridge.http/test/io/openems/edge/bridge/http/api/UrlBuilderTest.java new file mode 100644 index 00000000000..7bfbc193476 --- /dev/null +++ b/io.openems.edge.bridge.http/test/io/openems/edge/bridge/http/api/UrlBuilderTest.java @@ -0,0 +1,117 @@ +package io.openems.edge.bridge.http.api; + +import static org.junit.Assert.assertEquals; + +import java.net.URI; + +import org.junit.Test; + +public class UrlBuilderTest { + + @Test + public void testParse() { + final var rawUrl = "https://openems.io:443/path?key=value#fragment"; + final var parsedUrl = UrlBuilder.parse(rawUrl); + assertEquals(rawUrl, parsedUrl.toEncodedString()); + } + + @Test + public void testParseNoQueryParams() { + final var rawUrl = "https://openems.io:443/path#fragment"; + final var parsedUrl = UrlBuilder.parse(rawUrl); + assertEquals(rawUrl, parsedUrl.toEncodedString()); + } + + @Test + public void testScheme() { + final var url = UrlBuilder.create() // + .withScheme("https") // + .withHost("openems.io"); + + assertEquals("https://openems.io", url.toEncodedString()); + assertEquals("http://openems.io", url.withScheme("http").toEncodedString()); + } + + @Test + public void testHost() { + final var url = UrlBuilder.create() // + .withScheme("https") // + .withHost("openems.io"); + + assertEquals("https://openems.io", url.toEncodedString()); + assertEquals("https://better.openems.io", url.withHost("better.openems.io").toEncodedString()); + } + + @Test + public void testPort() { + final var url = UrlBuilder.create() // + .withScheme("https") // + .withHost("openems.io") // + .withPort(443); + + assertEquals("https://openems.io:443", url.toEncodedString()); + assertEquals("https://openems.io:445", url.withPort(445).toEncodedString()); + } + + @Test + public void testPath() { + final var url = UrlBuilder.create() // + .withScheme("https") // + .withHost("openems.io") // + .withPath("/path"); + + assertEquals("https://openems.io/path", url.toEncodedString()); + assertEquals("https://openems.io/path/abc", url.withPath("/path/abc").toEncodedString()); + assertEquals("https://openems.io/withoutslash", url.withPath("withoutslash").toEncodedString()); + } + + @Test + public void testQueryParameter() { + final var url = UrlBuilder.create() // + .withScheme("https") // + .withHost("openems.io") // + .withQueryParam("key", "value"); + + assertEquals("https://openems.io?key=value", url.toEncodedString()); + assertEquals("https://openems.io?key=otherValue", url.withQueryParam("key", "otherValue").toEncodedString()); + } + + @Test + public void testFragment() { + final var url = UrlBuilder.create() // + .withScheme("https") // + .withHost("openems.io") // + .withFragment("myFragment"); + + assertEquals("https://openems.io#myFragment", url.toEncodedString()); + assertEquals("https://openems.io#myOtherFragment", url.withFragment("myOtherFragment").toEncodedString()); + assertEquals("https://openems.io#with", url.withFragment("#with").toEncodedString()); + } + + @Test + public void testToUri() { + final var url = UrlBuilder.create() // + .withScheme("https") // + .withHost("openems.io") // + .withPort(443) // + .withPath("/path") // + .withQueryParam("key", "value") // + .withFragment("fragment"); + + assertEquals(URI.create("https://openems.io:443/path?key=value#fragment"), url.toUri()); + } + + @Test + public void testToEncodedString() { + final var url = UrlBuilder.create() // + .withScheme("https") // + .withHost("openems.io") // + .withPort(443) // + .withPath("/path") // + .withQueryParam("key", "va lu+e") // + .withFragment("fragment"); + + assertEquals("https://openems.io:443/path?key=va%20lu%2Be#fragment", url.toEncodedString()); + } + +} diff --git a/io.openems.edge.bridge.mbus/.classpath b/io.openems.edge.bridge.mbus/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.bridge.mbus/.classpath +++ b/io.openems.edge.bridge.mbus/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.bridge.mbus/test/io/openems/edge/bridge/mbus/BridgeMbusImplTest.java b/io.openems.edge.bridge.mbus/test/io/openems/edge/bridge/mbus/BridgeMbusImplTest.java index 799f89f4971..79e1f813886 100644 --- a/io.openems.edge.bridge.mbus/test/io/openems/edge/bridge/mbus/BridgeMbusImplTest.java +++ b/io.openems.edge.bridge.mbus/test/io/openems/edge/bridge/mbus/BridgeMbusImplTest.java @@ -2,22 +2,18 @@ import org.junit.Test; -import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; public class BridgeMbusImplTest { - private static final String COMPONENT_ID = "mbus0"; - @Test public void test() throws Exception { new ComponentTest(new BridgeMbusImpl()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("mbus0") // .setPortName("/dev/ttyUSB0") // .setBaudrate(2400) // - .build()) // - .next(new TestCase()); // + .build()); // } } diff --git a/io.openems.edge.bridge.modbus/.classpath b/io.openems.edge.bridge.modbus/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.bridge.modbus/.classpath +++ b/io.openems.edge.bridge.modbus/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/BridgeModbusSerialImpl.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/BridgeModbusSerialImpl.java index d36835f41f8..3c5c5d64c19 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/BridgeModbusSerialImpl.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/BridgeModbusSerialImpl.java @@ -24,6 +24,7 @@ import io.openems.edge.bridge.modbus.api.AbstractModbusBridge; import io.openems.edge.bridge.modbus.api.BridgeModbus; import io.openems.edge.bridge.modbus.api.BridgeModbusSerial; +import io.openems.edge.bridge.modbus.api.Config; import io.openems.edge.bridge.modbus.api.Parity; import io.openems.edge.bridge.modbus.api.Stopbit; import io.openems.edge.common.component.OpenemsComponent; @@ -86,15 +87,15 @@ public BridgeModbusSerialImpl() { @Activate private void activate(ComponentContext context, ConfigSerial config) { - super.activate(context, config.id(), config.alias(), config.enabled(), config.logVerbosity(), - config.invalidateElementsAfterReadErrors()); + super.activate(context, new Config(config.id(), config.alias(), config.enabled(), config.logVerbosity(), + config.invalidateElementsAfterReadErrors())); this.applyConfig(config); } @Modified private void modified(ComponentContext context, ConfigSerial config) { - super.modified(context, config.id(), config.alias(), config.enabled(), config.logVerbosity(), - config.invalidateElementsAfterReadErrors()); + super.modified(context, new Config(config.id(), config.alias(), config.enabled(), config.logVerbosity(), + config.invalidateElementsAfterReadErrors())); this.applyConfig(config); this.closeModbusConnection(); } diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/BridgeModbusTcpImpl.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/BridgeModbusTcpImpl.java index 9cd9cd3dff3..aacd72d89fd 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/BridgeModbusTcpImpl.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/BridgeModbusTcpImpl.java @@ -22,6 +22,7 @@ import io.openems.edge.bridge.modbus.api.AbstractModbusBridge; import io.openems.edge.bridge.modbus.api.BridgeModbus; import io.openems.edge.bridge.modbus.api.BridgeModbusTcp; +import io.openems.edge.bridge.modbus.api.Config; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.event.EdgeEventConstants; @@ -56,15 +57,15 @@ public BridgeModbusTcpImpl() { @Activate private void activate(ComponentContext context, ConfigTcp config) throws UnknownHostException { - super.activate(context, config.id(), config.alias(), config.enabled(), config.logVerbosity(), - config.invalidateElementsAfterReadErrors()); + super.activate(context, new Config(config.id(), config.alias(), config.enabled(), config.logVerbosity(), + config.invalidateElementsAfterReadErrors())); this.applyConfig(config); } @Modified private void modified(ComponentContext context, ConfigTcp config) throws UnknownHostException { - super.modified(context, config.id(), config.alias(), config.enabled(), config.logVerbosity(), - config.invalidateElementsAfterReadErrors()); + super.modified(context, new Config(config.id(), config.alias(), config.enabled(), config.logVerbosity(), + config.invalidateElementsAfterReadErrors())); this.applyConfig(config); this.closeModbusConnection(); } diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/AbstractModbusBridge.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/AbstractModbusBridge.java index 52087943576..2410fbaf055 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/AbstractModbusBridge.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/AbstractModbusBridge.java @@ -1,6 +1,5 @@ package io.openems.edge.bridge.modbus.api; -import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Stream; import org.osgi.service.component.ComponentContext; @@ -35,8 +34,7 @@ public abstract class AbstractModbusBridge extends AbstractOpenemsComponent impl */ protected static final int DEFAULT_RETRIES = 1; - private final AtomicReference logVerbosity = new AtomicReference<>(LogVerbosity.NONE); - private int invalidateElementsAfterReadErrors = 1; + private Config config = null; protected final ModbusWorker worker = new ModbusWorker( // Execute Task @@ -47,8 +45,8 @@ public abstract class AbstractModbusBridge extends AbstractOpenemsComponent impl state -> this._setCycleTimeIsTooShort(state), // Set ChannelId.CYCLE_DELAY cycleDelay -> this._setCycleDelay(cycleDelay), - // LogVerbosity - this.logVerbosity // + // LogHandler + () -> this.config.log // ); protected AbstractModbusBridge(io.openems.edge.common.channel.ChannelId[] firstInitialChannelIds, @@ -62,12 +60,11 @@ protected void activate(ComponentContext context, String id, String alias, boole throw new IllegalArgumentException("Use the other activate() method."); } - protected void activate(ComponentContext context, String id, String alias, boolean enabled, - LogVerbosity logVerbosity, int invalidateElementsAfterReadErrors) { - super.activate(context, id, alias, enabled); - this.applyConfig(logVerbosity, invalidateElementsAfterReadErrors); - if (enabled) { - this.worker.activate(id); + protected void activate(ComponentContext context, Config config) { + super.activate(context, config.id, config.alias, config.enabled); + this.applyConfig(config); + if (config.enabled) { + this.worker.activate(config.id); } } @@ -84,20 +81,18 @@ protected void modified(ComponentContext context, String id, String alias, boole throw new IllegalArgumentException("Use the other modified() method."); } - protected void modified(ComponentContext context, String id, String alias, boolean enabled, - LogVerbosity logVerbosity, int invalidateElementsAfterReadErrors) { - super.modified(context, id, alias, enabled); - this.applyConfig(logVerbosity, invalidateElementsAfterReadErrors); - if (enabled) { - this.worker.modified(id); + protected void modified(ComponentContext context, Config config) { + super.modified(context, config.id, config.alias, config.enabled); + this.applyConfig(config); + if (config.enabled) { + this.worker.modified(config.id); } else { this.worker.deactivate(); } } - private void applyConfig(LogVerbosity logVerbosity, int invalidateElementsAfterReadErrors) { - this.logVerbosity.set(logVerbosity); - this.invalidateElementsAfterReadErrors = invalidateElementsAfterReadErrors; + private void applyConfig(Config config) { + this.config = config; } /** @@ -124,22 +119,24 @@ public void removeProtocol(String sourceId) { @Override public void handleEvent(Event event) { - if (!this.isEnabled()) { + if (this.config == null || !this.isEnabled()) { return; } switch (event.getTopic()) { - case EdgeEventConstants.TOPIC_CYCLE_BEFORE_PROCESS_IMAGE: - this.worker.onBeforeProcessImage(); - break; - case EdgeEventConstants.TOPIC_CYCLE_EXECUTE_WRITE: - this.worker.onExecuteWrite(); - break; + case EdgeEventConstants.TOPIC_CYCLE_BEFORE_PROCESS_IMAGE // + -> this.worker.onBeforeProcessImage(); + + case EdgeEventConstants.TOPIC_CYCLE_EXECUTE_WRITE // + -> this.worker.onExecuteWrite(); } } @Override public String debugLog() { - return switch (this.logVerbosity.get()) { + if (this.config == null) { + return null; + } + return switch (this.config.log.verbosity) { case NONE -> // null; case DEBUG_LOG, READS_AND_WRITES, READS_AND_WRITES_DURATION, READS_AND_WRITES_VERBOSE, @@ -167,7 +164,7 @@ public String debugLog() { * @return {@link LogVerbosity} */ public LogVerbosity getLogVerbosity() { - return this.logVerbosity.get(); + return this.config.log.verbosity; } /** @@ -177,7 +174,7 @@ public LogVerbosity getLogVerbosity() { * @return value */ public int invalidateElementsAfterReadErrors() { - return this.invalidateElementsAfterReadErrors; + return this.config.invalidateElementsAfterReadErrors; } @Override diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/AbstractOpenemsModbusComponent.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/AbstractOpenemsModbusComponent.java index 7bf6e9e249d..80c29c5f234 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/AbstractOpenemsModbusComponent.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/AbstractOpenemsModbusComponent.java @@ -110,17 +110,7 @@ protected void activate(String id) { protected boolean activate(ComponentContext context, String id, String alias, boolean enabled, int unitId, ConfigurationAdmin cm, String modbusReference, String modbusId) throws OpenemsException { super.activate(context, id, alias, enabled); - // update filter for 'Modbus' - if (OpenemsComponent.updateReferenceFilter(cm, this.servicePid(), "Modbus", modbusId)) { - return true; - } - this.unitId = unitId; - var modbus = this.modbus.get(); - if (this.isEnabled() && modbus != null) { - modbus.addProtocol(this.id(), this.getModbusProtocol()); - modbus.retryModbusCommunication(this.id()); - } - return false; + return this.activateOrModified(unitId, cm, modbusReference, modbusId); } @Override @@ -147,19 +137,41 @@ protected void activate(ComponentContext context, String id, String alias, boole * @param modbusId The ID of the Modbus bridge. Typically * 'config.modbus_id()' * @return true if the target filter was updated. You may use it to abort the - * activate() method. + * modified() method. * @throws OpenemsException on error */ protected boolean modified(ComponentContext context, String id, String alias, boolean enabled, int unitId, ConfigurationAdmin cm, String modbusReference, String modbusId) throws OpenemsException { super.modified(context, id, alias, enabled); + return this.activateOrModified(unitId, cm, modbusReference, modbusId); + } + + @Override + protected void modified(ComponentContext context, String id, String alias, boolean enabled) { + throw new IllegalArgumentException("Use the other modified() for Modbus components!"); + } + + /** + * Common tasks for @Activate and @Modified. + * + * @param unitId Unit-ID of the Modbus target + * @param cm An instance of ConfigurationAdmin. Receive it + * using @Reference + * @param modbusReference The name of the @Reference setter method for the + * Modbus bridge - e.g. 'Modbus' if you have a + * setModbus()-method + * @param modbusId The ID of the Modbus bridge. Typically + * 'config.modbus_id()' + * @return true if the target filter was updated. You may use it to abort the + * activate() or modified() method. + */ + private boolean activateOrModified(int unitId, ConfigurationAdmin cm, String modbusReference, String modbusId) { // update filter for 'Modbus' - if (OpenemsComponent.updateReferenceFilter(cm, this.servicePid(), "Modbus", modbusId)) { + if (OpenemsComponent.updateReferenceFilter(cm, this.servicePid(), modbusReference, modbusId)) { return true; } this.unitId = unitId; var modbus = this.modbus.get(); - modbus.removeProtocol(this.id()); if (this.isEnabled() && modbus != null) { modbus.addProtocol(this.id(), this.getModbusProtocol()); modbus.retryModbusCommunication(this.id()); @@ -167,11 +179,6 @@ protected boolean modified(ComponentContext context, String id, String alias, bo return false; } - @Override - protected void modified(ComponentContext context, String id, String alias, boolean enabled) { - throw new IllegalArgumentException("Use the other activate() for Modbus components!"); - } - @Override protected void deactivate() { super.deactivate(); diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/Config.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/Config.java new file mode 100644 index 00000000000..9f6e98b6d45 --- /dev/null +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/Config.java @@ -0,0 +1,60 @@ +package io.openems.edge.bridge.modbus.api; + +import java.util.function.Supplier; + +import org.slf4j.Logger; + +public class Config { + + public final String id; + public final String alias; + public final boolean enabled; + public final int invalidateElementsAfterReadErrors; + public final LogHandler log; + + public Config(String id, String alias, boolean enabled, LogVerbosity logVerbosity, + int invalidateElementsAfterReadErrors) { + this.id = id; + this.alias = alias; + this.enabled = enabled; + this.invalidateElementsAfterReadErrors = invalidateElementsAfterReadErrors; + this.log = new LogHandler(this, logVerbosity); + } + + public static class LogHandler { + public final LogVerbosity verbosity; + + private final Config config; + + private LogHandler(Config config, LogVerbosity logVerbosity) { + this.config = config; + this.verbosity = logVerbosity; + } + + /** + * Logs messages for + * {@link LogVerbosity#READS_AND_WRITES_DURATION_TRACE_EVENTS}. + * + * @param logger the {@link Logger} + * @param message the String message + */ + public void trace(Logger logger, Supplier message) { + if (this.isTrace()) { + logger.info("[" + this.config.id + "] " + message.get()); + } + } + + /** + * Return true if {@link LogVerbosity#READS_AND_WRITES_DURATION_TRACE_EVENTS} is + * active. + * + * @return true for trace-log + */ + public boolean isTrace() { + return switch (this.verbosity) { + case NONE, DEBUG_LOG, READS_AND_WRITES, READS_AND_WRITES_DURATION, READS_AND_WRITES_VERBOSE -> false; + case READS_AND_WRITES_DURATION_TRACE_EVENTS -> true; + }; + } + } +} \ No newline at end of file diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ElementToChannelConverter.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ElementToChannelConverter.java index 4ab8f28fb73..87a9c0eb0bb 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ElementToChannelConverter.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ElementToChannelConverter.java @@ -292,6 +292,26 @@ public static final ElementToChannelConverter SET_NULL_FOR_DEFAULT(int defaultVa }); } + /** + * Sets the null value for given {@link Long} value. + * + * @param defaultValue to ignore {@link Long} + * @return null if actual value is equal to default value. + */ + // CHECKSTYLE:OFF + public static final ElementToChannelConverter SET_NULL_FOR_DEFAULT(long defaultValue) { + // CHECKSTYLE:ON + return new ElementToChannelConverter(value -> { + var v = TypeUtils.getAsType(OpenemsType.LONG, value); + + if (v == null || v == defaultValue) { + return null; + } + + return v; + }); + } + /** * Sets the chain with given {@link ElementToChannelConverter * ElementToChannelConverters}. diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ModbusComponent.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ModbusComponent.java index ace3a2c4da4..c2b284dd80f 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ModbusComponent.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ModbusComponent.java @@ -20,7 +20,13 @@ public interface ModbusComponent extends OpenemsComponent { public enum ChannelId implements io.openems.edge.common.channel.ChannelId { - MODBUS_COMMUNICATION_FAILED(Doc.of(Level.FAULT) // + + /* + * If ModbusCommunicationFault would be a FaultState, check it explicitly in + * Generic Ess ErrorHandler, as the battery could still have a communication + * fault while starting the battery + */ + MODBUS_COMMUNICATION_FAILED(Doc.of(Level.WARNING) // .debounce(10, Debounce.SAME_VALUES_IN_A_ROW_TO_CHANGE) // .text("Modbus Communication failed")) // ; diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ModbusUtils.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ModbusUtils.java index 48dc3a52b99..4fdd3142d3e 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ModbusUtils.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ModbusUtils.java @@ -18,12 +18,23 @@ import io.openems.edge.bridge.modbus.api.element.ModbusRegisterElement; import io.openems.edge.bridge.modbus.api.task.FC3ReadRegistersTask; +import io.openems.edge.bridge.modbus.api.task.FC4ReadInputRegistersTask; import io.openems.edge.bridge.modbus.api.task.Task; import io.openems.edge.bridge.modbus.api.task.Task.ExecuteState; import io.openems.edge.common.taskmanager.Priority; public class ModbusUtils { + /** + * Enum representing Modbus function codes used to select the appropriate task. + * + *

+ * This enum defines the available function codes for use in modbus tasks. + */ + public static enum FunctionCode { + FC3, FC4; + } + /** * Predefined `retryPredicate` that triggers a retry whenever `value` is null, * i.e. on any error. @@ -53,6 +64,7 @@ public static boolean doNotRetry(ExecuteState executeState, T value) { * Reads given Element once from Modbus. * * @param the Type of the element + * @param functionCode the {@link FunctionCode} * @param modbusProtocol the {@link ModbusProtocol}, that is linked with a * {@link BridgeModbus} * @param retryPredicate yield true to retry reading values; false otherwise. @@ -62,9 +74,9 @@ public static boolean doNotRetry(ExecuteState executeState, T value) { * @return a future value, e.g. a Integer or null (if tryAgainOnError is false) */ @SuppressWarnings("unchecked") - public static CompletableFuture readElementOnce(ModbusProtocol modbusProtocol, + public static CompletableFuture readElementOnce(FunctionCode functionCode, ModbusProtocol modbusProtocol, BiPredicate retryPredicate, ModbusRegisterElement element) { - return readElementsOnce(modbusProtocol, retryPredicate, // + return readElementsOnce(functionCode, modbusProtocol, retryPredicate, // new ModbusRegisterElement[] { element }) // .thenApply(rsr -> ((ReadElementsResult) rsr).values().get(0)); } @@ -73,6 +85,7 @@ public static CompletableFuture readElementOnce(ModbusProtocol modbusProt * Reads given Elements once from Modbus. * * @param the Type of the elements + * @param functionCode the {@link FunctionCode} * @param modbusProtocol the {@link ModbusProtocol}, that is linked with a * {@link BridgeModbus} * @param retryPredicate yield true to retry reading values. Parameters are the @@ -82,22 +95,26 @@ public static CompletableFuture readElementOnce(ModbusProtocol modbusProt * returned, it is guaranteed to have the same length as `elements` */ @SafeVarargs - public static CompletableFuture> readElementsOnce(ModbusProtocol modbusProtocol, - BiPredicate retryPredicate, ModbusRegisterElement... elements) { + public static CompletableFuture> readElementsOnce(FunctionCode functionCode, + ModbusProtocol modbusProtocol, BiPredicate retryPredicate, + ModbusRegisterElement... elements) { if (elements.length == 0) { return completedFuture(new ReadElementsResult<>(NO_OP, emptyList())); } // Register listener for each element - final var executeState = new AtomicReference(ExecuteState.NO_OP); + final var executeState = new AtomicReference(NO_OP); - // Activate task - final Task task = new FC3ReadRegistersTask(executeState::set, // - elements[0].startAddress, Priority.HIGH, elements); + // Activate task based on functionCode + Task task = switch (functionCode) { + case FC4 -> new FC4ReadInputRegistersTask(executeState::set, elements[0].startAddress, Priority.HIGH, elements); + case FC3 -> new FC3ReadRegistersTask(executeState::set, elements[0].startAddress, Priority.HIGH, elements); + }; modbusProtocol.addTask(task); @SuppressWarnings("unchecked") final var subResults = (CompletableFuture[]) new CompletableFuture[elements.length]; + for (var i = 0; i < elements.length; i++) { var subResult = new CompletableFuture(); subResults[i] = subResult; @@ -127,7 +144,6 @@ public static CompletableFuture> readElementsOnce(Modb } public static record ReadElementsResult(ExecuteState executeState, List values) { - } /** @@ -165,4 +181,4 @@ private static String registersToHexString(T[] registers, Function intToHexString(fnct.apply(r))) // .collect(Collectors.joining(" ")); } -} +} \ No newline at end of file diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/WordOrder.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/WordOrder.java index 0a3d508f1bd..a666988038a 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/WordOrder.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/WordOrder.java @@ -3,7 +3,6 @@ /** * Defines the word order. * - *

*

    *
  • LSWMSW = Least significant word, most significant word *
  • MSWLSW = Most significant word, least significant word diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/ModbusWorker.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/ModbusWorker.java index 68aed664b5d..2558852fc4d 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/ModbusWorker.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/ModbusWorker.java @@ -1,12 +1,12 @@ package io.openems.edge.bridge.modbus.api.worker; -import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import java.util.function.Function; +import java.util.function.Supplier; import io.openems.common.worker.AbstractImmediateWorker; import io.openems.edge.bridge.modbus.api.BridgeModbus; -import io.openems.edge.bridge.modbus.api.LogVerbosity; +import io.openems.edge.bridge.modbus.api.Config.LogHandler; import io.openems.edge.bridge.modbus.api.ModbusComponent; import io.openems.edge.bridge.modbus.api.ModbusProtocol; import io.openems.edge.bridge.modbus.api.element.ModbusElement; @@ -52,18 +52,19 @@ public class ModbusWorker extends AbstractImmediateWorker { * @param cycleDelayChannel sets the * {@link BridgeModbus.ChannelId#CYCLE_DELAY} * channel - * @param logVerbosity the configured {@link LogVerbosity} + * @param logHandler a {@link Supplier} for the + * {@link LogHandler} */ public ModbusWorker(Function execute, Consumer invalidate, Consumer cycleTimeIsTooShortChannel, Consumer cycleDelayChannel, - AtomicReference logVerbosity) { + Supplier logHandler) { this.execute = execute; this.invalidate = invalidate; - this.defectiveComponents = new DefectiveComponents(logVerbosity); - this.tasksSupplier = new TasksSupplierImpl(); + this.defectiveComponents = new DefectiveComponents(logHandler); + this.tasksSupplier = new TasksSupplierImpl(logHandler); this.cycleTasksManager = new CycleTasksManager(this.tasksSupplier, this.defectiveComponents, - cycleTimeIsTooShortChannel, cycleDelayChannel, logVerbosity); + cycleTimeIsTooShortChannel, cycleDelayChannel, logHandler); } @Override @@ -123,7 +124,7 @@ private void markComponentAsDefective(ModbusComponent component, boolean isDefec * @param protocol the ModbusProtocol */ public void addProtocol(String sourceId, ModbusProtocol protocol) { - this.tasksSupplier.addProtocol(sourceId, protocol); + this.tasksSupplier.addProtocol(sourceId, protocol, this.invalidate); this.defectiveComponents.remove(sourceId); // Cleanup } @@ -133,7 +134,7 @@ public void addProtocol(String sourceId, ModbusProtocol protocol) { * @param sourceId Component-ID of the source */ public void removeProtocol(String sourceId) { - this.tasksSupplier.removeProtocol(sourceId); + this.tasksSupplier.removeProtocol(sourceId, this.invalidate); this.defectiveComponents.remove(sourceId); // Cleanup } diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/CycleTasksManager.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/CycleTasksManager.java index fbee14394ac..03222a82a04 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/CycleTasksManager.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/CycleTasksManager.java @@ -1,12 +1,12 @@ package io.openems.edge.bridge.modbus.api.worker.internal; -import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; +import java.util.function.Supplier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import io.openems.edge.bridge.modbus.api.LogVerbosity; +import io.openems.edge.bridge.modbus.api.Config.LogHandler; import io.openems.edge.bridge.modbus.api.task.Task; import io.openems.edge.bridge.modbus.api.task.WaitTask; import io.openems.edge.bridge.modbus.api.worker.ModbusWorker; @@ -26,7 +26,7 @@ public class CycleTasksManager { private final TasksSupplier tasksSupplier; private final DefectiveComponents defectiveComponents; private final Consumer cycleTimeIsTooShortChannel; - private final AtomicReference logVerbosity; + private final Supplier logHandler; private final WaitDelayHandler waitDelayHandler; private final WaitTask.Mutex waitMutexTask = new WaitTask.Mutex(); @@ -35,22 +35,15 @@ public class CycleTasksManager { public CycleTasksManager(TasksSupplier tasksSupplier, DefectiveComponents defectiveComponents, Consumer cycleTimeIsTooShortChannel, Consumer cycleDelayChannel, - AtomicReference logVerbosity) { + Supplier logHandler) { this.tasksSupplier = tasksSupplier; this.defectiveComponents = defectiveComponents; this.cycleTimeIsTooShortChannel = cycleTimeIsTooShortChannel; - this.logVerbosity = logVerbosity; - + this.logHandler = logHandler; this.waitDelayHandler = new WaitDelayHandler(() -> this.onWaitDelayTaskFinished(), cycleDelayChannel); } - protected CycleTasksManager(TasksSupplier tasksSupplier, DefectiveComponents defectiveComponents, - Consumer cycleTimeIsTooShortChannel, Consumer cycleDelayChannel) { - this(tasksSupplier, defectiveComponents, cycleTimeIsTooShortChannel, cycleDelayChannel, - new AtomicReference<>(LogVerbosity.NONE)); - } - - private static enum StateMachine { + protected static enum StateMachine { INITIAL_WAIT, // READ_BEFORE_WRITE, // WAIT_FOR_WRITE, // @@ -62,24 +55,31 @@ private static enum StateMachine { private StateMachine state = StateMachine.FINISHED; + /** + * Gets the current state. + * + * @return the {@link StateMachine} + */ + protected StateMachine getState() { + return this.state; + } + /** * Called on BEFORE_PROCESS_IMAGE event. */ public synchronized void onBeforeProcessImage() { // Calculate Delay - var waitDelayHandlerLog = this.waitDelayHandler.onBeforeProcessImage(this.isTraceLog()); + final var waitDelayHandlerLog = this.waitDelayHandler.onBeforeProcessImage(this.logHandler.get().isTrace()); // Evaluate Cycle-Time-Is-Too-Short, invalidate time measurement and stop early var cycleTimeIsTooShort = this.state != StateMachine.FINISHED; this.cycleTimeIsTooShortChannel.accept(cycleTimeIsTooShort); if (cycleTimeIsTooShort) { this.waitDelayHandler.timeIsInvalid(); - if (this.isTraceLog()) { - this.log.info("State: " + this.state + " unchanged" // - + " (in onBeforeProcessImage)" // - + " Delay [" + this.waitDelayHandler.getWaitDelayTask().initialDelay + "] " // - + waitDelayHandlerLog); - } + this.traceLog(() -> "State: " + this.state + " unchanged" // + + " (in onBeforeProcessImage)" // + + " Delay [" + this.waitDelayHandler.getWaitDelayTask().initialDelay + "] " // + + waitDelayHandlerLog); return; } @@ -90,18 +90,19 @@ public synchronized void onBeforeProcessImage() { this.cycleTasks = this.tasksSupplier.getCycleTasks(this.defectiveComponents); // On defectiveComponents invalidate time measurement - if (this.cycleTasks.containsDefectiveComponent(this.defectiveComponents)) { + final var containsDefectiveComponents = this.cycleTasks.containsDefectiveComponent(this.defectiveComponents); + if (containsDefectiveComponents) { this.waitDelayHandler.timeIsInvalid(); - waitDelayHandlerLog += " DEFECTIVE_COMPONENT"; } // Initialize next Cycle - if (this.isTraceLog()) { - this.log.info("State: " + this.state + " -> " + StateMachine.INITIAL_WAIT // - + " (in onBeforeProcessImage)" // - + " Delay [" + this.waitDelayHandler.getWaitDelayTask().initialDelay + "] " // - + waitDelayHandlerLog); - } + this.traceLog(() -> "State: " + this.state + " -> " + StateMachine.INITIAL_WAIT // + + " (in onBeforeProcessImage)" // + + " Delay [" + this.waitDelayHandler.getWaitDelayTask().initialDelay + "] " // + + waitDelayHandlerLog // + + (containsDefectiveComponents // + ? " DEFECTIVE_COMPONENT" + : "")); this.state = StateMachine.INITIAL_WAIT; // Interrupt wait @@ -112,9 +113,7 @@ public synchronized void onBeforeProcessImage() { * Called on EXECUTE_WRITE event. */ public synchronized void onExecuteWrite() { - if (this.isTraceLog()) { - this.log.info("State: " + this.state + " -> " + StateMachine.WRITE + " (onExecuteWrite)"); - } + this.traceLog(() -> "State: " + this.state + " -> " + StateMachine.WRITE + " (onExecuteWrite)"); this.state = StateMachine.WRITE; this.waitMutexTask.release(); @@ -128,7 +127,8 @@ public synchronized void onExecuteWrite() { */ public Task getNextTask() { if (this.cycleTasks == null) { - return this.waitMutexTask; + // Fallback to avoid NPE on race condition + this.cycleTasks = this.tasksSupplier.getCycleTasks(this.defectiveComponents); } var previousState = this.state; // drop before release @@ -187,8 +187,8 @@ public Task getNextTask() { } }; - if (this.state != previousState && this.isTraceLog()) { - this.log.info("State: " + previousState + " -> " + this.state + " (getNextTask)"); + if (this.state != previousState) { + this.traceLog(() -> "State: " + previousState + " -> " + this.state + " (getNextTask)"); } return nextTask; } @@ -207,16 +207,11 @@ private synchronized void onWaitDelayTaskFinished() { }; if (this.state != previousState) { - if (this.isTraceLog()) { - this.log.info("State: " + previousState + " -> " + this.state + " (onWaitDelayTaskFinished)"); - } + this.traceLog(() -> "State: " + previousState + " -> " + this.state + " (onWaitDelayTaskFinished)"); } } - private boolean isTraceLog() { - return switch (this.logVerbosity.get()) { - case READS_AND_WRITES_DURATION_TRACE_EVENTS -> true; - case NONE, DEBUG_LOG, READS_AND_WRITES, READS_AND_WRITES_DURATION, READS_AND_WRITES_VERBOSE -> false; - }; + private void traceLog(Supplier message) { + this.logHandler.get().trace(this.log, message); } } \ No newline at end of file diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/DefectiveComponents.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/DefectiveComponents.java index 6fa218f805f..80cb95937df 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/DefectiveComponents.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/DefectiveComponents.java @@ -4,12 +4,12 @@ import java.time.Instant; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import io.openems.edge.bridge.modbus.api.LogVerbosity; +import io.openems.edge.bridge.modbus.api.Config.LogHandler; import io.openems.edge.common.type.TypeUtils; public class DefectiveComponents { @@ -23,24 +23,16 @@ private static record NextTry(Instant timestamp, int count) { } private final Clock clock; - private final AtomicReference logVerbosity; + private final Supplier logHandler; private final Map nextTries = new HashMap<>(); - public DefectiveComponents(AtomicReference logVerbosity) { - this(Clock.systemDefaultZone(), logVerbosity); + public DefectiveComponents(Supplier logHandler) { + this(Clock.systemDefaultZone(), logHandler); } - protected DefectiveComponents() { - this(Clock.systemDefaultZone(), new AtomicReference<>(LogVerbosity.READS_AND_WRITES_DURATION_TRACE_EVENTS)); - } - - protected DefectiveComponents(Clock clock) { - this(clock, new AtomicReference<>(LogVerbosity.READS_AND_WRITES_DURATION_TRACE_EVENTS)); - } - - protected DefectiveComponents(Clock clock, AtomicReference logVerbosity) { + protected DefectiveComponents(Clock clock, Supplier logHandler) { this.clock = clock; - this.logVerbosity = logVerbosity; + this.logHandler = logHandler; } /** @@ -54,15 +46,11 @@ public synchronized void add(String componentId) { this.nextTries.compute(componentId, (k, v) -> { var count = (v == null) ? 1 : v.count + 1; var wait = Math.min(INCREASE_WAIT_SECONDS * count, MAX_WAIT_SECONDS); - if (this.isTraceLog()) { - final String log; - if (count == 1) { - log = "Add [" + componentId + "] to defective Components."; - } else { - log = "Increase wait for defective Component [" + componentId + "]."; - } - this.log.info(log + " Wait [" + wait + "s]" + " Count [" + count + "]"); - } + this.traceLog(() -> // + (count == 1 // + ? "Add [" + componentId + "] to defective Components." // + : "Increase wait for defective Component [" + componentId + "].") + " Wait [" + wait + "s]" + + " Count [" + count + "]"); return new NextTry(Instant.now(this.clock).plusSeconds(wait), count); }); } @@ -74,8 +62,8 @@ public synchronized void add(String componentId) { */ public synchronized void remove(String componentId) { TypeUtils.assertNull("DefectiveComponents remove() takes no null values", componentId); - if (this.nextTries.remove(componentId) != null && this.isTraceLog()) { - this.log.info("Remove [" + componentId + "] from defective Components."); + if (this.nextTries.remove(componentId) != null) { + this.traceLog(() -> "Remove [" + componentId + "] from defective Components."); } } @@ -105,12 +93,7 @@ public synchronized Boolean isDueForNextTry(String componentId) { return now.isAfter(nextTry.timestamp); } - private boolean isTraceLog() { - return switch (this.logVerbosity.get()) { - case READS_AND_WRITES, READS_AND_WRITES_DURATION, READS_AND_WRITES_VERBOSE, - READS_AND_WRITES_DURATION_TRACE_EVENTS -> - true; - case NONE, DEBUG_LOG -> false; - }; + private void traceLog(Supplier message) { + this.logHandler.get().trace(this.log, message); } } \ No newline at end of file diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/TasksSupplierImpl.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/TasksSupplierImpl.java index e7c6a4bcb02..2f836ee0c14 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/TasksSupplierImpl.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/TasksSupplierImpl.java @@ -5,9 +5,16 @@ import java.util.LinkedList; import java.util.Map; import java.util.Queue; +import java.util.function.Consumer; +import java.util.function.Supplier; import java.util.stream.Collectors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.openems.edge.bridge.modbus.api.Config.LogHandler; import io.openems.edge.bridge.modbus.api.ModbusProtocol; +import io.openems.edge.bridge.modbus.api.element.ModbusElement; import io.openems.edge.bridge.modbus.api.task.ReadTask; import io.openems.edge.bridge.modbus.api.task.Task; import io.openems.edge.bridge.modbus.api.task.WriteTask; @@ -20,6 +27,13 @@ */ public class TasksSupplierImpl implements TasksSupplier { + private final Logger log = LoggerFactory.getLogger(TasksSupplierImpl.class); + private final Supplier logHandler; + + public TasksSupplierImpl(Supplier logHandler) { + this.logHandler = logHandler; + } + /** * Source-ID -> TasksManager for {@link Task}s. */ @@ -31,22 +45,45 @@ public class TasksSupplierImpl implements TasksSupplier { private final Queue> nextLowPriorityTasks = new LinkedList<>(); /** - * Adds the protocol. - * - * @param sourceId Component-ID of the source - * @param protocol the ModbusProtocol + * Adds (or replaces) the protocol identified by its sourceId. + * + *

    + * If a protocol with the same sourceId existed before, + * {@link #removeProtocol(String, Consumer)} is called internally first. + * + * @param sourceId Component-ID of the source + * @param protocol the ModbusProtocol + * @param invalidate invalidates the given {@link ModbusElement}s after read + * errors */ - public synchronized void addProtocol(String sourceId, ModbusProtocol protocol) { + public synchronized void addProtocol(String sourceId, ModbusProtocol protocol, + Consumer invalidate) { + this.removeProtocol(sourceId, invalidate); // remove if sourceId exists + + this.traceLog(() -> "Add Protocol for " // + + "[" + sourceId + "] with " // + + "[" + protocol.getTaskManager().countTasks() + "] tasks"); this.taskManagers.put(sourceId, protocol.getTaskManager()); } /** - * Removes the protocol. + * Removes the protocol and invalidates all {@link ModbusElement}s. * - * @param sourceId Component-ID of the source + * @param sourceId Component-ID of the source + * @param invalidate invalidates the given {@link ModbusElement}s after read + * errors */ - public synchronized void removeProtocol(String sourceId) { - this.taskManagers.remove(sourceId); + public synchronized void removeProtocol(String sourceId, Consumer invalidate) { + var taskManager = this.taskManagers.remove(sourceId); + if (taskManager == null) { + return; + } + + this.traceLog(() -> "Remove Protocol for " // + + "[" + sourceId + "] with " // + + "[" + taskManager.countTasks() + "] tasks"); + taskManager.getTasks() // + .forEach(t -> invalidate.accept(t.getElements())); this.nextLowPriorityTasks.removeIf(t -> t.a() == sourceId); } @@ -84,7 +121,7 @@ public synchronized CycleTasks getCycleTasks(DefectiveComponents defectiveCompon componentTasks.clear(); } }); - return new CycleTasks(// + var result = new CycleTasks(// tasks.values().stream().flatMap(LinkedList::stream) // .filter(ReadTask.class::isInstance).map(ReadTask.class::cast) // // Sort HIGH priority to the end @@ -93,6 +130,11 @@ public synchronized CycleTasks getCycleTasks(DefectiveComponents defectiveCompon tasks.values().stream().flatMap(LinkedList::stream) // .filter(WriteTask.class::isInstance).map(WriteTask.class::cast) // .collect(Collectors.toCollection(LinkedList::new))); + + this.traceLog(() -> "Getting " // + + "[" + result.reads().size() + "] read and " // + + "[" + result.writes().size() + "] write tasks for this Cycle"); + return result; } /** @@ -128,4 +170,8 @@ public synchronized int getTotalNumberOfTasks() { .mapToInt(m -> m.countTasks()) // .sum(); } + + private void traceLog(Supplier message) { + this.logHandler.get().trace(this.log, message); + } } diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/sunspec/AbstractOpenemsSunSpecComponent.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/sunspec/AbstractOpenemsSunSpecComponent.java index 3b01f8ce0da..42e57c330b4 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/sunspec/AbstractOpenemsSunSpecComponent.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/sunspec/AbstractOpenemsSunSpecComponent.java @@ -3,6 +3,7 @@ import static com.ghgande.j2mod.modbus.Modbus.ILLEGAL_ADDRESS_EXCEPTION; import static io.openems.edge.bridge.modbus.api.ModbusUtils.readElementOnce; import static io.openems.edge.bridge.modbus.api.ModbusUtils.readElementsOnce; +import static io.openems.edge.bridge.modbus.api.ModbusUtils.FunctionCode.FC3; import static io.openems.edge.bridge.modbus.sunspec.Utils.toUpperUnderscore; import static java.util.concurrent.CompletableFuture.completedFuture; @@ -132,7 +133,8 @@ protected final ModbusProtocol defineModbusProtocol() { * @throws OpenemsException on error */ private CompletableFuture isSunSpec() throws OpenemsException { - return readElementOnce(this.modbusProtocol, ModbusUtils::retryOnNull, new UnsignedDoublewordElement(40_000)) // + return readElementOnce(FC3, this.modbusProtocol, ModbusUtils::retryOnNull, + new UnsignedDoublewordElement(40_000)) // .thenApply(v -> v == 0x53756e53); } @@ -158,7 +160,7 @@ private CompletableFuture readNextBlock(int startAddress, Set rem * and that some blocks are not read - especially when one component is used for * multiple devices like single and three phase inverter. */ - return readElementsOnce(this.modbusProtocol, // + return readElementsOnce(FC3, this.modbusProtocol, // // Retry if value is null and error is not "Illegal Data Address". // Background: some SMA inverters do not provide an END_OF_MAP register. (executeState, value) -> { diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/sunspec/DefaultSunSpecModel.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/sunspec/DefaultSunSpecModel.java index 8911c6ee34e..7d5a31e4c8d 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/sunspec/DefaultSunSpecModel.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/sunspec/DefaultSunSpecModel.java @@ -2,7 +2,29 @@ package io.openems.edge.bridge.modbus.sunspec; -import io.openems.common.channel.AccessMode; +import static io.openems.common.channel.AccessMode.READ_ONLY; +import static io.openems.common.channel.AccessMode.READ_WRITE; +import static io.openems.edge.bridge.modbus.sunspec.Point.BitFieldPoint.Type.BITFIELD16; +import static io.openems.edge.bridge.modbus.sunspec.Point.BitFieldPoint.Type.BITFIELD32; +import static io.openems.edge.bridge.modbus.sunspec.Point.EnumPoint.Type.ENUM16; +import static io.openems.edge.bridge.modbus.sunspec.Point.EnumPoint.Type.ENUM32; +import static io.openems.edge.bridge.modbus.sunspec.Point.ValuePoint.Type.ACC32; +import static io.openems.edge.bridge.modbus.sunspec.Point.ValuePoint.Type.ACC64; +import static io.openems.edge.bridge.modbus.sunspec.Point.ValuePoint.Type.FLOAT32; +import static io.openems.edge.bridge.modbus.sunspec.Point.ValuePoint.Type.INT16; +import static io.openems.edge.bridge.modbus.sunspec.Point.ValuePoint.Type.INT32; +import static io.openems.edge.bridge.modbus.sunspec.Point.ValuePoint.Type.STRING16; +import static io.openems.edge.bridge.modbus.sunspec.Point.ValuePoint.Type.STRING2; +import static io.openems.edge.bridge.modbus.sunspec.Point.ValuePoint.Type.STRING20; +import static io.openems.edge.bridge.modbus.sunspec.Point.ValuePoint.Type.STRING32; +import static io.openems.edge.bridge.modbus.sunspec.Point.ValuePoint.Type.STRING4; +import static io.openems.edge.bridge.modbus.sunspec.Point.ValuePoint.Type.STRING5; +import static io.openems.edge.bridge.modbus.sunspec.Point.ValuePoint.Type.STRING6; +import static io.openems.edge.bridge.modbus.sunspec.Point.ValuePoint.Type.STRING8; +import static io.openems.edge.bridge.modbus.sunspec.Point.ValuePoint.Type.UINT16; +import static io.openems.edge.bridge.modbus.sunspec.Point.ValuePoint.Type.UINT32; +import static io.openems.edge.bridge.modbus.sunspec.Point.ValuePoint.Type.UINT64; + import io.openems.common.channel.Level; import io.openems.common.channel.Unit; import io.openems.common.types.OptionsEnum; @@ -166,25 +188,25 @@ public enum DefaultSunSpecModel implements SunSpecModel { public static enum S1 implements SunSpecPoint { MN(new ValuePoint("S1_MN", "Manufacturer", // "Well known value registered with SunSpec for compliance", // - ValuePoint.Type.STRING16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + STRING16, true /* mandatory? */, READ_ONLY, Unit.NONE)), // MD(new ValuePoint("S1_MD", "Model", // "Manufacturer specific value (32 chars)", // - ValuePoint.Type.STRING16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + STRING16, true /* mandatory? */, READ_ONLY, Unit.NONE)), // OPT(new ValuePoint("S1_OPT", "Options", // "Manufacturer specific value (16 chars)", // - ValuePoint.Type.STRING8, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + STRING8, false /* mandatory? */, READ_ONLY, Unit.NONE)), // VR(new ValuePoint("S1_VR", "Version", // "Manufacturer specific value (16 chars)", // - ValuePoint.Type.STRING8, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + STRING8, false /* mandatory? */, READ_ONLY, Unit.NONE)), // SN(new ValuePoint("S1_SN", "Serial Number", // "Manufacturer specific value (32 chars)", // - ValuePoint.Type.STRING16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + STRING16, true /* mandatory? */, READ_ONLY, Unit.NONE)), // DA(new ValuePoint("S1_DA", "Device Address", // "Modbus device address", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.NONE)), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.NONE)), // PAD(new ValuePoint("S1_PAD", "", // "Force even alignment", // - ValuePoint.Type.PAD, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)); + ValuePoint.Type.PAD, false /* mandatory? */, READ_ONLY, Unit.NONE)); private final Point point; @@ -201,34 +223,34 @@ public Point get() { public static enum S2 implements SunSpecPoint { AID(new ValuePoint("S2_AID", "AID", // "Aggregated model id", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.NONE)), // N(new ValuePoint("S2_N", "N", // "Number of aggregated models", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.NONE)), // UN(new ValuePoint("S2_UN", "UN", // "Update Number. Incrementing number each time the mapping is changed. If the number is not changed from the last reading the direct access to a specific offset will result in reading the same logical model as before. Otherwise the entire model must be read to refresh the changes", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.NONE)), // ST(new EnumPoint("S2_ST", "Status", // "Enumerated status code", // - EnumPoint.Type.ENUM16, true /* mandatory? */, AccessMode.READ_ONLY, S2_St.values())), // + ENUM16, true /* mandatory? */, READ_ONLY, S2_St.values())), // ST_VND(new EnumPoint("S2_ST_VND", "Vendor Status", // "Vendor specific status code", // - EnumPoint.Type.ENUM16, false /* mandatory? */, AccessMode.READ_ONLY, new OptionsEnum[0])), // + ENUM16, false /* mandatory? */, READ_ONLY, new OptionsEnum[0])), // EVT(new BitFieldPoint("S2_EVT", "Event Code", // "Bitmask event code", // - BitFieldPoint.Type.BITFIELD32, true /* mandatory? */, AccessMode.READ_ONLY, S2_Evt.values())), // + BITFIELD32, true /* mandatory? */, READ_ONLY, S2_Evt.values())), // EVT_VND(new BitFieldPoint("S2_EVT_VND", "Vendor Event Code", // "Vendor specific event code", // - BitFieldPoint.Type.BITFIELD32, false /* mandatory? */, AccessMode.READ_ONLY, new SunSpecBitPoint[0])), // + BITFIELD32, false /* mandatory? */, READ_ONLY, new SunSpecBitPoint[0])), // CTL(new EnumPoint("S2_CTL", "Control", // "Control register for all aggregated devices", // - EnumPoint.Type.ENUM16, false /* mandatory? */, AccessMode.READ_ONLY, S2_Ctl.values())), // + ENUM16, false /* mandatory? */, READ_ONLY, S2_Ctl.values())), // CTL_VND(new EnumPoint("S2_CTL_VND", "Vendor Control", // "Vendor control register for all aggregated devices", // - EnumPoint.Type.ENUM32, false /* mandatory? */, AccessMode.READ_ONLY, new OptionsEnum[0])), // + ENUM32, false /* mandatory? */, READ_ONLY, new OptionsEnum[0])), // CTL_VL(new EnumPoint("S2_CTL_VL", "Control Value", // "Numerical value used as a parameter to the control", // - EnumPoint.Type.ENUM32, false /* mandatory? */, AccessMode.READ_ONLY, new OptionsEnum[0])); + ENUM32, false /* mandatory? */, READ_ONLY, new OptionsEnum[0])); private final Point point; @@ -343,42 +365,42 @@ public OptionsEnum getUndefined() { public static enum S15 implements SunSpecPoint { CLR(new ValuePoint("S15_CLR", "Clear", // "Write a \"1\" to clear all counters", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.NONE)), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.NONE)), // IN_CNT(new ValuePoint("S15_IN_CNT", "Input Count", // "Number of bytes received", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.NONE)), // IN_UC_CNT(new ValuePoint("S15_IN_UC_CNT", "Input Unicast Count", // "Number of Unicast packets received", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.NONE)), // IN_N_UC_CNT(new ValuePoint("S15_IN_N_UC_CNT", "Input Non-Unicast Count", // "Number of non-Unicast packets received", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.NONE)), // IN_DSC_CNT(new ValuePoint("S15_IN_DSC_CNT", "Input Discarded Count", // "Number of inbound packets received on the interface but discarded", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.NONE)), // IN_ERR_CNT(new ValuePoint("S15_IN_ERR_CNT", "Input Error Count", // "Number of inbound packets that contain errors (excluding discards)", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.NONE)), // IN_UNK_CNT(new ValuePoint("S15_IN_UNK_CNT", "Input Unknown Count", // "Number of inbound packets with unknown protocol", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.NONE)), // OUT_CNT(new ValuePoint("S15_OUT_CNT", "Output Count", // "Total number of bytes transmitted on this interface", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.NONE)), // OUT_UC_CNT(new ValuePoint("S15_OUT_UC_CNT", "Output Unicast Count", // "Number of Unicast packets transmitted", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.NONE)), // OUT_N_UC_CNT(new ValuePoint("S15_OUT_N_UC_CNT", "Output Non-Unicast Count", // "Number of Non-Unicast packets transmitted", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.NONE)), // OUT_DSC_CNT(new ValuePoint("S15_OUT_DSC_CNT", "Output Discarded Count", // "Number of Discarded output packets", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.NONE)), // OUT_ERR_CNT(new ValuePoint("S15_OUT_ERR_CNT", "Output Error Count", // "Number of outbound error packets", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.NONE)), // PAD(new ValuePoint("S15_PAD", "", "", // - ValuePoint.Type.PAD, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)); + ValuePoint.Type.PAD, false /* mandatory? */, READ_ONLY, Unit.NONE)); private final Point point; @@ -395,19 +417,19 @@ public Point get() { public static enum S18 implements SunSpecPoint { NAM(new ValuePoint("S18_NAM", "Name", // "Interface name", // - ValuePoint.Type.STRING4, false /* mandatory? */, AccessMode.READ_WRITE, Unit.NONE)), // + STRING4, false /* mandatory? */, READ_WRITE, Unit.NONE)), // IMEI(new ValuePoint("S18_IMEI", "IMEI", // "International Mobile Equipment Identifier for the interface", // - ValuePoint.Type.UINT32, false /* mandatory? */, AccessMode.READ_WRITE, Unit.NONE)), // + UINT32, false /* mandatory? */, READ_WRITE, Unit.NONE)), // APN(new ValuePoint("S18_APN", "APN", // "Access Point Name for the interface", // - ValuePoint.Type.STRING4, false /* mandatory? */, AccessMode.READ_WRITE, Unit.NONE)), // + STRING4, false /* mandatory? */, READ_WRITE, Unit.NONE)), // NUM(new ValuePoint("S18_NUM", "Number", // "Phone number for the interface", // - ValuePoint.Type.STRING6, false /* mandatory? */, AccessMode.READ_WRITE, Unit.NONE)), // + STRING6, false /* mandatory? */, READ_WRITE, Unit.NONE)), // PIN(new ValuePoint("S18_PIN", "PIN", // "Personal Identification Number for the interface", // - ValuePoint.Type.STRING6, false /* mandatory? */, AccessMode.READ_WRITE, Unit.NONE)); + STRING6, false /* mandatory? */, READ_WRITE, Unit.NONE)); private final Point point; @@ -424,109 +446,109 @@ public Point get() { public static enum S101 implements SunSpecPoint { A(new ScaledValuePoint("S101_A", "Amps", // "AC Current", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE, "A_SF")), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.AMPERE, "A_SF")), // APH_A(new ScaledValuePoint("S101_APH_A", "Amps PhaseA", // "Phase A Current", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE, "A_SF")), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.AMPERE, "A_SF")), // APH_B(new ScaledValuePoint("S101_APH_B", "Amps PhaseB", // "Phase B Current", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE, "A_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.AMPERE, "A_SF")), // APH_C(new ScaledValuePoint("S101_APH_C", "Amps PhaseC", // "Phase C Current", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE, "A_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.AMPERE, "A_SF")), // A_SF(new ScaleFactorPoint("S101_A_SF", "", "")), // P_P_VPH_A_B(new ScaledValuePoint("S101_P_P_VPH_A_B", "Phase Voltage AB", // "Phase Voltage AB", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // P_P_VPH_B_C(new ScaledValuePoint("S101_P_P_VPH_B_C", "Phase Voltage BC", // "Phase Voltage BC", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // P_P_VPH_C_A(new ScaledValuePoint("S101_P_P_VPH_C_A", "Phase Voltage CA", // "Phase Voltage CA", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // PH_VPH_A(new ScaledValuePoint("S101_PH_VPH_A", "Phase Voltage AN", // "Phase Voltage AN", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // PH_VPH_B(new ScaledValuePoint("S101_PH_VPH_B", "Phase Voltage BN", // "Phase Voltage BN", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // PH_VPH_C(new ScaledValuePoint("S101_PH_VPH_C", "Phase Voltage CN", // "Phase Voltage CN", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // V_SF(new ScaleFactorPoint("S101_V_SF", "", "")), // W(new ScaledValuePoint("S101_W", "Watts", // "AC Power", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.WATT, "W_SF")), // + INT16, true /* mandatory? */, READ_ONLY, Unit.WATT, "W_SF")), // W_SF(new ScaleFactorPoint("S101_W_SF", "", "")), // HZ(new ScaledValuePoint("S101_HZ", "Hz", // "Line Frequency", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.HERTZ, "Hz_SF")), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.HERTZ, "Hz_SF")), // HZ_S_F(new ScaleFactorPoint("S101_HZ_S_F", "", "")), // VA(new ScaledValuePoint("S101_VA", "VA", // "AC Apparent Power", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE, "VA_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE, "VA_SF")), // VA_SF(new ScaleFactorPoint("S101_VA_SF", "", "")), // V_AR(new ScaledValuePoint("S101_V_AR", "VAr", // "AC Reactive Power", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "VAr_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "VAr_SF")), // V_AR_S_F(new ScaleFactorPoint("S101_V_AR_S_F", "", "")), // PF(new ScaledValuePoint("S101_PF", "PF", // "AC Power Factor", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE, "PF_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.NONE, "PF_SF")), // PF_SF(new ScaleFactorPoint("S101_PF_SF", "", "")), // WH(new ScaledValuePoint("S101_WH", "WattHours", // "AC Energy", // - ValuePoint.Type.ACC32, true /* mandatory? */, AccessMode.READ_ONLY, Unit.CUMULATED_WATT_HOURS, "WH_SF")), // + ACC32, true /* mandatory? */, READ_ONLY, Unit.CUMULATED_WATT_HOURS, "WH_SF")), // WH_SF(new ScaleFactorPoint("S101_WH_SF", "", "")), // DCA(new ScaledValuePoint("S101_DCA", "DC Amps", // "DC Current", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE, "DCA_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.AMPERE, "DCA_SF")), // DCA_SF(new ScaleFactorPoint("S101_DCA_SF", "", "")), // DCV(new ScaledValuePoint("S101_DCV", "DC Voltage", // "DC Voltage", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "DCV_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.VOLT, "DCV_SF")), // DCV_SF(new ScaleFactorPoint("S101_DCV_SF", "", "")), // DCW(new ScaledValuePoint("S101_DCW", "DC Watts", // "DC Power", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.WATT, "DCW_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.WATT, "DCW_SF")), // DCW_SF(new ScaleFactorPoint("S101_DCW_SF", "", "")), // TMP_CAB(new ScaledValuePoint("S101_TMP_CAB", "Cabinet Temperature", // "Cabinet Temperature", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.DEGREE_CELSIUS, "Tmp_SF")), // + INT16, true /* mandatory? */, READ_ONLY, Unit.DEGREE_CELSIUS, "Tmp_SF")), // TMP_SNK(new ScaledValuePoint("S101_TMP_SNK", "Heat Sink Temperature", // "Heat Sink Temperature", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.DEGREE_CELSIUS, "Tmp_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.DEGREE_CELSIUS, "Tmp_SF")), // TMP_TRNS(new ScaledValuePoint("S101_TMP_TRNS", "Transformer Temperature", // "Transformer Temperature", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.DEGREE_CELSIUS, "Tmp_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.DEGREE_CELSIUS, "Tmp_SF")), // TMP_OT(new ScaledValuePoint("S101_TMP_OT", "Other Temperature", // "Other Temperature", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.DEGREE_CELSIUS, "Tmp_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.DEGREE_CELSIUS, "Tmp_SF")), // TMP_S_F(new ScaleFactorPoint("S101_TMP_S_F", "", "")), // ST(new EnumPoint("S101_ST", "Operating State", // "Enumerated value. Operating state", // - EnumPoint.Type.ENUM16, true /* mandatory? */, AccessMode.READ_ONLY, S101_St.values())), // + ENUM16, true /* mandatory? */, READ_ONLY, S101_St.values())), // ST_VND(new EnumPoint("S101_ST_VND", "Vendor Operating State", // "Vendor specific operating state code", // - EnumPoint.Type.ENUM16, false /* mandatory? */, AccessMode.READ_ONLY, new OptionsEnum[0])), // + ENUM16, false /* mandatory? */, READ_ONLY, new OptionsEnum[0])), // EVT1(new BitFieldPoint("S101_EVT1", "Event1", // "Bitmask value. Event fields", // - BitFieldPoint.Type.BITFIELD32, true /* mandatory? */, AccessMode.READ_ONLY, S101_Evt1.values())), // + BITFIELD32, true /* mandatory? */, READ_ONLY, S101_Evt1.values())), // EVT2(new BitFieldPoint("S101_EVT2", "Event Bitfield 2", // "Reserved for future use", // - BitFieldPoint.Type.BITFIELD32, true /* mandatory? */, AccessMode.READ_ONLY, new SunSpecBitPoint[0])), // + BITFIELD32, true /* mandatory? */, READ_ONLY, new SunSpecBitPoint[0])), // EVT_VND1(new BitFieldPoint("S101_EVT_VND1", "Vendor Event Bitfield 1", // "Vendor defined events", // - BitFieldPoint.Type.BITFIELD32, false /* mandatory? */, AccessMode.READ_ONLY, new SunSpecBitPoint[0])), // + BITFIELD32, false /* mandatory? */, READ_ONLY, new SunSpecBitPoint[0])), // EVT_VND2(new BitFieldPoint("S101_EVT_VND2", "Vendor Event Bitfield 2", // "Vendor defined events", // - BitFieldPoint.Type.BITFIELD32, false /* mandatory? */, AccessMode.READ_ONLY, new SunSpecBitPoint[0])), // + BITFIELD32, false /* mandatory? */, READ_ONLY, new SunSpecBitPoint[0])), // EVT_VND3(new BitFieldPoint("S101_EVT_VND3", "Vendor Event Bitfield 3", // "Vendor defined events", // - BitFieldPoint.Type.BITFIELD32, false /* mandatory? */, AccessMode.READ_ONLY, new SunSpecBitPoint[0])), // + BITFIELD32, false /* mandatory? */, READ_ONLY, new SunSpecBitPoint[0])), // EVT_VND4(new BitFieldPoint("S101_EVT_VND4", "Vendor Event Bitfield 4", // "Vendor defined events", // - BitFieldPoint.Type.BITFIELD32, false /* mandatory? */, AccessMode.READ_ONLY, new SunSpecBitPoint[0])); + BITFIELD32, false /* mandatory? */, READ_ONLY, new SunSpecBitPoint[0])); private final Point point; @@ -608,109 +630,109 @@ public BitPoint get() { public static enum S102 implements SunSpecPoint { A(new ScaledValuePoint("S102_A", "Amps", // "AC Current", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE, "A_SF")), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.AMPERE, "A_SF")), // APH_A(new ScaledValuePoint("S102_APH_A", "Amps PhaseA", // "Phase A Current", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE, "A_SF")), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.AMPERE, "A_SF")), // APH_B(new ScaledValuePoint("S102_APH_B", "Amps PhaseB", // "Phase B Current", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE, "A_SF")), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.AMPERE, "A_SF")), // APH_C(new ScaledValuePoint("S102_APH_C", "Amps PhaseC", // "Phase C Current", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE, "A_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.AMPERE, "A_SF")), // A_SF(new ScaleFactorPoint("S102_A_SF", "", "")), // P_P_VPH_A_B(new ScaledValuePoint("S102_P_P_VPH_A_B", "Phase Voltage AB", // "Phase Voltage AB", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // P_P_VPH_B_C(new ScaledValuePoint("S102_P_P_VPH_B_C", "Phase Voltage BC", // "Phase Voltage BC", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // P_P_VPH_C_A(new ScaledValuePoint("S102_P_P_VPH_C_A", "Phase Voltage CA", // "Phase Voltage CA", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // PH_VPH_A(new ScaledValuePoint("S102_PH_VPH_A", "Phase Voltage AN", // "Phase Voltage AN", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // PH_VPH_B(new ScaledValuePoint("S102_PH_VPH_B", "Phase Voltage BN", // "Phase Voltage BN", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // PH_VPH_C(new ScaledValuePoint("S102_PH_VPH_C", "Phase Voltage CN", // "Phase Voltage CN", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // V_SF(new ScaleFactorPoint("S102_V_SF", "", "")), // W(new ScaledValuePoint("S102_W", "Watts", // "AC Power", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.WATT, "W_SF")), // + INT16, true /* mandatory? */, READ_ONLY, Unit.WATT, "W_SF")), // W_SF(new ScaleFactorPoint("S102_W_SF", "", "")), // HZ(new ScaledValuePoint("S102_HZ", "Hz", // "Line Frequency", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.HERTZ, "Hz_SF")), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.HERTZ, "Hz_SF")), // HZ_S_F(new ScaleFactorPoint("S102_HZ_S_F", "", "")), // VA(new ScaledValuePoint("S102_VA", "VA", // "AC Apparent Power", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE, "VA_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE, "VA_SF")), // VA_SF(new ScaleFactorPoint("S102_VA_SF", "", "")), // V_AR(new ScaledValuePoint("S102_V_AR", "VAr", // "AC Reactive Power", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "VAr_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "VAr_SF")), // V_AR_S_F(new ScaleFactorPoint("S102_V_AR_S_F", "", "")), // PF(new ScaledValuePoint("S102_PF", "PF", // "AC Power Factor", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE, "PF_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.NONE, "PF_SF")), // PF_SF(new ScaleFactorPoint("S102_PF_SF", "", "")), // WH(new ScaledValuePoint("S102_WH", "WattHours", // "AC Energy", // - ValuePoint.Type.ACC32, true /* mandatory? */, AccessMode.READ_ONLY, Unit.CUMULATED_WATT_HOURS, "WH_SF")), // + ACC32, true /* mandatory? */, READ_ONLY, Unit.CUMULATED_WATT_HOURS, "WH_SF")), // WH_SF(new ScaleFactorPoint("S102_WH_SF", "", "")), // DCA(new ScaledValuePoint("S102_DCA", "DC Amps", // "DC Current", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE, "DCA_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.AMPERE, "DCA_SF")), // DCA_SF(new ScaleFactorPoint("S102_DCA_SF", "", "")), // DCV(new ScaledValuePoint("S102_DCV", "DC Voltage", // "DC Voltage", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "DCV_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.VOLT, "DCV_SF")), // DCV_SF(new ScaleFactorPoint("S102_DCV_SF", "", "")), // DCW(new ScaledValuePoint("S102_DCW", "DC Watts", // "DC Power", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.WATT, "DCW_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.WATT, "DCW_SF")), // DCW_SF(new ScaleFactorPoint("S102_DCW_SF", "", "")), // TMP_CAB(new ScaledValuePoint("S102_TMP_CAB", "Cabinet Temperature", // "Cabinet Temperature", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.DEGREE_CELSIUS, "Tmp_SF")), // + INT16, true /* mandatory? */, READ_ONLY, Unit.DEGREE_CELSIUS, "Tmp_SF")), // TMP_SNK(new ScaledValuePoint("S102_TMP_SNK", "Heat Sink Temperature", // "Heat Sink Temperature", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.DEGREE_CELSIUS, "Tmp_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.DEGREE_CELSIUS, "Tmp_SF")), // TMP_TRNS(new ScaledValuePoint("S102_TMP_TRNS", "Transformer Temperature", // "Transformer Temperature", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.DEGREE_CELSIUS, "Tmp_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.DEGREE_CELSIUS, "Tmp_SF")), // TMP_OT(new ScaledValuePoint("S102_TMP_OT", "Other Temperature", // "Other Temperature", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.DEGREE_CELSIUS, "Tmp_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.DEGREE_CELSIUS, "Tmp_SF")), // TMP_S_F(new ScaleFactorPoint("S102_TMP_S_F", "", "")), // ST(new EnumPoint("S102_ST", "Operating State", // "Enumerated value. Operating state", // - EnumPoint.Type.ENUM16, true /* mandatory? */, AccessMode.READ_ONLY, S102_St.values())), // + ENUM16, true /* mandatory? */, READ_ONLY, S102_St.values())), // ST_VND(new EnumPoint("S102_ST_VND", "Vendor Operating State", // "Vendor specific operating state code", // - EnumPoint.Type.ENUM16, false /* mandatory? */, AccessMode.READ_ONLY, new OptionsEnum[0])), // + ENUM16, false /* mandatory? */, READ_ONLY, new OptionsEnum[0])), // EVT1(new BitFieldPoint("S102_EVT1", "Event1", // "Bitmask value. Event fields", // - BitFieldPoint.Type.BITFIELD32, true /* mandatory? */, AccessMode.READ_ONLY, S102_Evt1.values())), // + BITFIELD32, true /* mandatory? */, READ_ONLY, S102_Evt1.values())), // EVT2(new BitFieldPoint("S102_EVT2", "Event Bitfield 2", // "Reserved for future use", // - BitFieldPoint.Type.BITFIELD32, true /* mandatory? */, AccessMode.READ_ONLY, new SunSpecBitPoint[0])), // + BITFIELD32, true /* mandatory? */, READ_ONLY, new SunSpecBitPoint[0])), // EVT_VND1(new BitFieldPoint("S102_EVT_VND1", "Vendor Event Bitfield 1", // "Vendor defined events", // - BitFieldPoint.Type.BITFIELD32, false /* mandatory? */, AccessMode.READ_ONLY, new SunSpecBitPoint[0])), // + BITFIELD32, false /* mandatory? */, READ_ONLY, new SunSpecBitPoint[0])), // EVT_VND2(new BitFieldPoint("S102_EVT_VND2", "Vendor Event Bitfield 2", // "Vendor defined events", // - BitFieldPoint.Type.BITFIELD32, false /* mandatory? */, AccessMode.READ_ONLY, new SunSpecBitPoint[0])), // + BITFIELD32, false /* mandatory? */, READ_ONLY, new SunSpecBitPoint[0])), // EVT_VND3(new BitFieldPoint("S102_EVT_VND3", "Vendor Event Bitfield 3", // "Vendor defined events", // - BitFieldPoint.Type.BITFIELD32, false /* mandatory? */, AccessMode.READ_ONLY, new SunSpecBitPoint[0])), // + BITFIELD32, false /* mandatory? */, READ_ONLY, new SunSpecBitPoint[0])), // EVT_VND4(new BitFieldPoint("S102_EVT_VND4", "Vendor Event Bitfield 4", // "Vendor defined events", // - BitFieldPoint.Type.BITFIELD32, false /* mandatory? */, AccessMode.READ_ONLY, new SunSpecBitPoint[0])); + BITFIELD32, false /* mandatory? */, READ_ONLY, new SunSpecBitPoint[0])); private final Point point; @@ -792,109 +814,109 @@ public BitPoint get() { public static enum S103 implements SunSpecPoint { A(new ScaledValuePoint("S103_A", "Amps", // "AC Current", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE, "A_SF")), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.AMPERE, "A_SF")), // APH_A(new ScaledValuePoint("S103_APH_A", "Amps PhaseA", // "Phase A Current", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE, "A_SF")), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.AMPERE, "A_SF")), // APH_B(new ScaledValuePoint("S103_APH_B", "Amps PhaseB", // "Phase B Current", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE, "A_SF")), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.AMPERE, "A_SF")), // APH_C(new ScaledValuePoint("S103_APH_C", "Amps PhaseC", // "Phase C Current", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE, "A_SF")), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.AMPERE, "A_SF")), // A_SF(new ScaleFactorPoint("S103_A_SF", "", "")), // P_P_VPH_A_B(new ScaledValuePoint("S103_P_P_VPH_A_B", "Phase Voltage AB", // "Phase Voltage AB", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // P_P_VPH_B_C(new ScaledValuePoint("S103_P_P_VPH_B_C", "Phase Voltage BC", // "Phase Voltage BC", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // P_P_VPH_C_A(new ScaledValuePoint("S103_P_P_VPH_C_A", "Phase Voltage CA", // "Phase Voltage CA", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // PH_VPH_A(new ScaledValuePoint("S103_PH_VPH_A", "Phase Voltage AN", // "Phase Voltage AN", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // PH_VPH_B(new ScaledValuePoint("S103_PH_VPH_B", "Phase Voltage BN", // "Phase Voltage BN", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // PH_VPH_C(new ScaledValuePoint("S103_PH_VPH_C", "Phase Voltage CN", // "Phase Voltage CN", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // V_SF(new ScaleFactorPoint("S103_V_SF", "", "")), // W(new ScaledValuePoint("S103_W", "Watts", // "AC Power", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.WATT, "W_SF")), // + INT16, true /* mandatory? */, READ_ONLY, Unit.WATT, "W_SF")), // W_SF(new ScaleFactorPoint("S103_W_SF", "", "")), // HZ(new ScaledValuePoint("S103_HZ", "Hz", // "Line Frequency", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.HERTZ, "Hz_SF")), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.HERTZ, "Hz_SF")), // HZ_S_F(new ScaleFactorPoint("S103_HZ_S_F", "", "")), // VA(new ScaledValuePoint("S103_VA", "VA", // "AC Apparent Power", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE, "VA_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE, "VA_SF")), // VA_SF(new ScaleFactorPoint("S103_VA_SF", "", "")), // V_AR(new ScaledValuePoint("S103_V_AR", "VAr", // "AC Reactive Power", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "VAr_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "VAr_SF")), // V_AR_S_F(new ScaleFactorPoint("S103_V_AR_S_F", "", "")), // PF(new ScaledValuePoint("S103_PF", "PF", // "AC Power Factor", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE, "PF_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.NONE, "PF_SF")), // PF_SF(new ScaleFactorPoint("S103_PF_SF", "", "")), // WH(new ScaledValuePoint("S103_WH", "WattHours", // "AC Energy", // - ValuePoint.Type.ACC32, true /* mandatory? */, AccessMode.READ_ONLY, Unit.CUMULATED_WATT_HOURS, "WH_SF")), // + ACC32, true /* mandatory? */, READ_ONLY, Unit.CUMULATED_WATT_HOURS, "WH_SF")), // WH_SF(new ScaleFactorPoint("S103_WH_SF", "", "")), // DCA(new ScaledValuePoint("S103_DCA", "DC Amps", // "DC Current", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE, "DCA_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.AMPERE, "DCA_SF")), // DCA_SF(new ScaleFactorPoint("S103_DCA_SF", "", "")), // DCV(new ScaledValuePoint("S103_DCV", "DC Voltage", // "DC Voltage", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "DCV_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.VOLT, "DCV_SF")), // DCV_SF(new ScaleFactorPoint("S103_DCV_SF", "", "")), // DCW(new ScaledValuePoint("S103_DCW", "DC Watts", // "DC Power", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.WATT, "DCW_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.WATT, "DCW_SF")), // DCW_SF(new ScaleFactorPoint("S103_DCW_SF", "", "")), // TMP_CAB(new ScaledValuePoint("S103_TMP_CAB", "Cabinet Temperature", // "Cabinet Temperature", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.DEGREE_CELSIUS, "Tmp_SF")), // + INT16, true /* mandatory? */, READ_ONLY, Unit.DEGREE_CELSIUS, "Tmp_SF")), // TMP_SNK(new ScaledValuePoint("S103_TMP_SNK", "Heat Sink Temperature", // "Heat Sink Temperature", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.DEGREE_CELSIUS, "Tmp_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.DEGREE_CELSIUS, "Tmp_SF")), // TMP_TRNS(new ScaledValuePoint("S103_TMP_TRNS", "Transformer Temperature", // "Transformer Temperature", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.DEGREE_CELSIUS, "Tmp_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.DEGREE_CELSIUS, "Tmp_SF")), // TMP_OT(new ScaledValuePoint("S103_TMP_OT", "Other Temperature", // "Other Temperature", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.DEGREE_CELSIUS, "Tmp_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.DEGREE_CELSIUS, "Tmp_SF")), // TMP_S_F(new ScaleFactorPoint("S103_TMP_S_F", "", "")), // ST(new EnumPoint("S103_ST", "Operating State", // "Enumerated value. Operating state", // - EnumPoint.Type.ENUM16, true /* mandatory? */, AccessMode.READ_ONLY, S103_St.values())), // + ENUM16, true /* mandatory? */, READ_ONLY, S103_St.values())), // ST_VND(new EnumPoint("S103_ST_VND", "Vendor Operating State", // "Vendor specific operating state code", // - EnumPoint.Type.ENUM16, false /* mandatory? */, AccessMode.READ_ONLY, new OptionsEnum[0])), // + ENUM16, false /* mandatory? */, READ_ONLY, new OptionsEnum[0])), // EVT1(new BitFieldPoint("S103_EVT1", "Event1", // "Bitmask value. Event fields", // - BitFieldPoint.Type.BITFIELD32, true /* mandatory? */, AccessMode.READ_ONLY, S103_Evt1.values())), // + BITFIELD32, true /* mandatory? */, READ_ONLY, S103_Evt1.values())), // EVT2(new BitFieldPoint("S103_EVT2", "Event Bitfield 2", // "Reserved for future use", // - BitFieldPoint.Type.BITFIELD32, true /* mandatory? */, AccessMode.READ_ONLY, new SunSpecBitPoint[0])), // + BITFIELD32, true /* mandatory? */, READ_ONLY, new SunSpecBitPoint[0])), // EVT_VND1(new BitFieldPoint("S103_EVT_VND1", "Vendor Event Bitfield 1", // "Vendor defined events", // - BitFieldPoint.Type.BITFIELD32, false /* mandatory? */, AccessMode.READ_ONLY, new SunSpecBitPoint[0])), // + BITFIELD32, false /* mandatory? */, READ_ONLY, new SunSpecBitPoint[0])), // EVT_VND2(new BitFieldPoint("S103_EVT_VND2", "Vendor Event Bitfield 2", // "Vendor defined events", // - BitFieldPoint.Type.BITFIELD32, false /* mandatory? */, AccessMode.READ_ONLY, new SunSpecBitPoint[0])), // + BITFIELD32, false /* mandatory? */, READ_ONLY, new SunSpecBitPoint[0])), // EVT_VND3(new BitFieldPoint("S103_EVT_VND3", "Vendor Event Bitfield 3", // "Vendor defined events", // - BitFieldPoint.Type.BITFIELD32, false /* mandatory? */, AccessMode.READ_ONLY, new SunSpecBitPoint[0])), // + BITFIELD32, false /* mandatory? */, READ_ONLY, new SunSpecBitPoint[0])), // EVT_VND4(new BitFieldPoint("S103_EVT_VND4", "Vendor Event Bitfield 4", // "Vendor defined events", // - BitFieldPoint.Type.BITFIELD32, false /* mandatory? */, AccessMode.READ_ONLY, new SunSpecBitPoint[0])); + BITFIELD32, false /* mandatory? */, READ_ONLY, new SunSpecBitPoint[0])); private final Point point; @@ -976,97 +998,97 @@ public BitPoint get() { public static enum S111 implements SunSpecPoint { A(new ValuePoint("S111_A", "Amps", // "AC Current", // - ValuePoint.Type.FLOAT32, true /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE)), // + FLOAT32, true /* mandatory? */, READ_ONLY, Unit.AMPERE)), // APH_A(new ValuePoint("S111_APH_A", "Amps PhaseA", // "Phase A Current", // - ValuePoint.Type.FLOAT32, true /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE)), // + FLOAT32, true /* mandatory? */, READ_ONLY, Unit.AMPERE)), // APH_B(new ValuePoint("S111_APH_B", "Amps PhaseB", // "Phase B Current", // - ValuePoint.Type.FLOAT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE)), // + FLOAT32, false /* mandatory? */, READ_ONLY, Unit.AMPERE)), // APH_C(new ValuePoint("S111_APH_C", "Amps PhaseC", // "Phase C Current", // - ValuePoint.Type.FLOAT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE)), // + FLOAT32, false /* mandatory? */, READ_ONLY, Unit.AMPERE)), // P_P_VPH_A_B(new ValuePoint("S111_P_P_VPH_A_B", "Phase Voltage AB", // "Phase Voltage AB", // - ValuePoint.Type.FLOAT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT)), // + FLOAT32, false /* mandatory? */, READ_ONLY, Unit.VOLT)), // P_P_VPH_B_C(new ValuePoint("S111_P_P_VPH_B_C", "Phase Voltage BC", // "Phase Voltage BC", // - ValuePoint.Type.FLOAT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT)), // + FLOAT32, false /* mandatory? */, READ_ONLY, Unit.VOLT)), // P_P_VPH_C_A(new ValuePoint("S111_P_P_VPH_C_A", "Phase Voltage CA", // "Phase Voltage CA", // - ValuePoint.Type.FLOAT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT)), // + FLOAT32, false /* mandatory? */, READ_ONLY, Unit.VOLT)), // PH_VPH_A(new ValuePoint("S111_PH_VPH_A", "Phase Voltage AN", // "Phase Voltage AN", // - ValuePoint.Type.FLOAT32, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT)), // + FLOAT32, true /* mandatory? */, READ_ONLY, Unit.VOLT)), // PH_VPH_B(new ValuePoint("S111_PH_VPH_B", "Phase Voltage BN", // "Phase Voltage BN", // - ValuePoint.Type.FLOAT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT)), // + FLOAT32, false /* mandatory? */, READ_ONLY, Unit.VOLT)), // PH_VPH_C(new ValuePoint("S111_PH_VPH_C", "Phase Voltage CN", // "Phase Voltage CN", // - ValuePoint.Type.FLOAT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT)), // + FLOAT32, false /* mandatory? */, READ_ONLY, Unit.VOLT)), // W(new ValuePoint("S111_W", "Watts", // "AC Power", // - ValuePoint.Type.FLOAT32, true /* mandatory? */, AccessMode.READ_ONLY, Unit.WATT)), // + FLOAT32, true /* mandatory? */, READ_ONLY, Unit.WATT)), // HZ(new ValuePoint("S111_HZ", "Hz", // "Line Frequency", // - ValuePoint.Type.FLOAT32, true /* mandatory? */, AccessMode.READ_ONLY, Unit.HERTZ)), // + FLOAT32, true /* mandatory? */, READ_ONLY, Unit.HERTZ)), // VA(new ValuePoint("S111_VA", "VA", // "AC Apparent Power", // - ValuePoint.Type.FLOAT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE)), // + FLOAT32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE)), // V_AR(new ValuePoint("S111_V_AR", "VAr", // "AC Reactive Power", // - ValuePoint.Type.FLOAT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE)), // + FLOAT32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE)), // PF(new ValuePoint("S111_PF", "PF", // "AC Power Factor", // - ValuePoint.Type.FLOAT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + FLOAT32, false /* mandatory? */, READ_ONLY, Unit.NONE)), // WH(new ValuePoint("S111_WH", "WattHours", // "AC Energy", // - ValuePoint.Type.FLOAT32, true /* mandatory? */, AccessMode.READ_ONLY, Unit.CUMULATED_WATT_HOURS)), // + FLOAT32, true /* mandatory? */, READ_ONLY, Unit.CUMULATED_WATT_HOURS)), // DCA(new ValuePoint("S111_DCA", "DC Amps", // "DC Current", // - ValuePoint.Type.FLOAT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE)), // + FLOAT32, false /* mandatory? */, READ_ONLY, Unit.AMPERE)), // DCV(new ValuePoint("S111_DCV", "DC Voltage", // "DC Voltage", // - ValuePoint.Type.FLOAT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT)), // + FLOAT32, false /* mandatory? */, READ_ONLY, Unit.VOLT)), // DCW(new ValuePoint("S111_DCW", "DC Watts", // "DC Power", // - ValuePoint.Type.FLOAT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.WATT)), // + FLOAT32, false /* mandatory? */, READ_ONLY, Unit.WATT)), // TMP_CAB(new ValuePoint("S111_TMP_CAB", "Cabinet Temperature", // "Cabinet Temperature", // - ValuePoint.Type.FLOAT32, true /* mandatory? */, AccessMode.READ_ONLY, Unit.DEGREE_CELSIUS)), // + FLOAT32, true /* mandatory? */, READ_ONLY, Unit.DEGREE_CELSIUS)), // TMP_SNK(new ValuePoint("S111_TMP_SNK", "Heat Sink Temperature", // "Heat Sink Temperature", // - ValuePoint.Type.FLOAT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.DEGREE_CELSIUS)), // + FLOAT32, false /* mandatory? */, READ_ONLY, Unit.DEGREE_CELSIUS)), // TMP_TRNS(new ValuePoint("S111_TMP_TRNS", "Transformer Temperature", // "Transformer Temperature", // - ValuePoint.Type.FLOAT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.DEGREE_CELSIUS)), // + FLOAT32, false /* mandatory? */, READ_ONLY, Unit.DEGREE_CELSIUS)), // TMP_OT(new ValuePoint("S111_TMP_OT", "Other Temperature", // "Other Temperature", // - ValuePoint.Type.FLOAT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.DEGREE_CELSIUS)), // + FLOAT32, false /* mandatory? */, READ_ONLY, Unit.DEGREE_CELSIUS)), // ST(new EnumPoint("S111_ST", "Operating State", // "Enumerated value. Operating state", // - EnumPoint.Type.ENUM16, true /* mandatory? */, AccessMode.READ_ONLY, S111_St.values())), // + ENUM16, true /* mandatory? */, READ_ONLY, S111_St.values())), // ST_VND(new EnumPoint("S111_ST_VND", "Vendor Operating State", // "Vendor specific operating state code", // - EnumPoint.Type.ENUM16, false /* mandatory? */, AccessMode.READ_ONLY, new OptionsEnum[0])), // + ENUM16, false /* mandatory? */, READ_ONLY, new OptionsEnum[0])), // EVT1(new BitFieldPoint("S111_EVT1", "Event1", // "Bitmask value. Event fields", // - BitFieldPoint.Type.BITFIELD32, true /* mandatory? */, AccessMode.READ_ONLY, S111_Evt1.values())), // + BITFIELD32, true /* mandatory? */, READ_ONLY, S111_Evt1.values())), // EVT2(new BitFieldPoint("S111_EVT2", "Event Bitfield 2", // "Reserved for future use", // - BitFieldPoint.Type.BITFIELD32, true /* mandatory? */, AccessMode.READ_ONLY, new SunSpecBitPoint[0])), // + BITFIELD32, true /* mandatory? */, READ_ONLY, new SunSpecBitPoint[0])), // EVT_VND1(new BitFieldPoint("S111_EVT_VND1", "Vendor Event Bitfield 1", // "Vendor defined events", // - BitFieldPoint.Type.BITFIELD32, false /* mandatory? */, AccessMode.READ_ONLY, new SunSpecBitPoint[0])), // + BITFIELD32, false /* mandatory? */, READ_ONLY, new SunSpecBitPoint[0])), // EVT_VND2(new BitFieldPoint("S111_EVT_VND2", "Vendor Event Bitfield 2", // "Vendor defined events", // - BitFieldPoint.Type.BITFIELD32, false /* mandatory? */, AccessMode.READ_ONLY, new SunSpecBitPoint[0])), // + BITFIELD32, false /* mandatory? */, READ_ONLY, new SunSpecBitPoint[0])), // EVT_VND3(new BitFieldPoint("S111_EVT_VND3", "Vendor Event Bitfield 3", // "Vendor defined events", // - BitFieldPoint.Type.BITFIELD32, false /* mandatory? */, AccessMode.READ_ONLY, new SunSpecBitPoint[0])), // + BITFIELD32, false /* mandatory? */, READ_ONLY, new SunSpecBitPoint[0])), // EVT_VND4(new BitFieldPoint("S111_EVT_VND4", "Vendor Event Bitfield 4", // "Vendor defined events", // - BitFieldPoint.Type.BITFIELD32, false /* mandatory? */, AccessMode.READ_ONLY, new SunSpecBitPoint[0])); + BITFIELD32, false /* mandatory? */, READ_ONLY, new SunSpecBitPoint[0])); private final Point point; @@ -1148,97 +1170,97 @@ public BitPoint get() { public static enum S112 implements SunSpecPoint { A(new ValuePoint("S112_A", "Amps", // "AC Current", // - ValuePoint.Type.FLOAT32, true /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE)), // + FLOAT32, true /* mandatory? */, READ_ONLY, Unit.AMPERE)), // APH_A(new ValuePoint("S112_APH_A", "Amps PhaseA", // "Phase A Current", // - ValuePoint.Type.FLOAT32, true /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE)), // + FLOAT32, true /* mandatory? */, READ_ONLY, Unit.AMPERE)), // APH_B(new ValuePoint("S112_APH_B", "Amps PhaseB", // "Phase B Current", // - ValuePoint.Type.FLOAT32, true /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE)), // + FLOAT32, true /* mandatory? */, READ_ONLY, Unit.AMPERE)), // APH_C(new ValuePoint("S112_APH_C", "Amps PhaseC", // "Phase C Current", // - ValuePoint.Type.FLOAT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE)), // + FLOAT32, false /* mandatory? */, READ_ONLY, Unit.AMPERE)), // P_P_VPH_A_B(new ValuePoint("S112_P_P_VPH_A_B", "Phase Voltage AB", // "Phase Voltage AB", // - ValuePoint.Type.FLOAT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT)), // + FLOAT32, false /* mandatory? */, READ_ONLY, Unit.VOLT)), // P_P_VPH_B_C(new ValuePoint("S112_P_P_VPH_B_C", "Phase Voltage BC", // "Phase Voltage BC", // - ValuePoint.Type.FLOAT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT)), // + FLOAT32, false /* mandatory? */, READ_ONLY, Unit.VOLT)), // P_P_VPH_C_A(new ValuePoint("S112_P_P_VPH_C_A", "Phase Voltage CA", // "Phase Voltage CA", // - ValuePoint.Type.FLOAT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT)), // + FLOAT32, false /* mandatory? */, READ_ONLY, Unit.VOLT)), // PH_VPH_A(new ValuePoint("S112_PH_VPH_A", "Phase Voltage AN", // "Phase Voltage AN", // - ValuePoint.Type.FLOAT32, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT)), // + FLOAT32, true /* mandatory? */, READ_ONLY, Unit.VOLT)), // PH_VPH_B(new ValuePoint("S112_PH_VPH_B", "Phase Voltage BN", // "Phase Voltage BN", // - ValuePoint.Type.FLOAT32, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT)), // + FLOAT32, true /* mandatory? */, READ_ONLY, Unit.VOLT)), // PH_VPH_C(new ValuePoint("S112_PH_VPH_C", "Phase Voltage CN", // "Phase Voltage CN", // - ValuePoint.Type.FLOAT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT)), // + FLOAT32, false /* mandatory? */, READ_ONLY, Unit.VOLT)), // W(new ValuePoint("S112_W", "Watts", // "AC Power", // - ValuePoint.Type.FLOAT32, true /* mandatory? */, AccessMode.READ_ONLY, Unit.WATT)), // + FLOAT32, true /* mandatory? */, READ_ONLY, Unit.WATT)), // HZ(new ValuePoint("S112_HZ", "Hz", // "Line Frequency", // - ValuePoint.Type.FLOAT32, true /* mandatory? */, AccessMode.READ_ONLY, Unit.HERTZ)), // + FLOAT32, true /* mandatory? */, READ_ONLY, Unit.HERTZ)), // VA(new ValuePoint("S112_VA", "VA", // "AC Apparent Power", // - ValuePoint.Type.FLOAT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE)), // + FLOAT32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE)), // V_AR(new ValuePoint("S112_V_AR", "VAr", // "AC Reactive Power", // - ValuePoint.Type.FLOAT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE)), // + FLOAT32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE)), // PF(new ValuePoint("S112_PF", "PF", // "AC Power Factor", // - ValuePoint.Type.FLOAT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + FLOAT32, false /* mandatory? */, READ_ONLY, Unit.NONE)), // WH(new ValuePoint("S112_WH", "WattHours", // "AC Energy", // - ValuePoint.Type.FLOAT32, true /* mandatory? */, AccessMode.READ_ONLY, Unit.CUMULATED_WATT_HOURS)), // + FLOAT32, true /* mandatory? */, READ_ONLY, Unit.CUMULATED_WATT_HOURS)), // DCA(new ValuePoint("S112_DCA", "DC Amps", // "DC Current", // - ValuePoint.Type.FLOAT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE)), // + FLOAT32, false /* mandatory? */, READ_ONLY, Unit.AMPERE)), // DCV(new ValuePoint("S112_DCV", "DC Voltage", // "DC Voltage", // - ValuePoint.Type.FLOAT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT)), // + FLOAT32, false /* mandatory? */, READ_ONLY, Unit.VOLT)), // DCW(new ValuePoint("S112_DCW", "DC Watts", // "DC Power", // - ValuePoint.Type.FLOAT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.WATT)), // + FLOAT32, false /* mandatory? */, READ_ONLY, Unit.WATT)), // TMP_CAB(new ValuePoint("S112_TMP_CAB", "Cabinet Temperature", // "Cabinet Temperature", // - ValuePoint.Type.FLOAT32, true /* mandatory? */, AccessMode.READ_ONLY, Unit.DEGREE_CELSIUS)), // + FLOAT32, true /* mandatory? */, READ_ONLY, Unit.DEGREE_CELSIUS)), // TMP_SNK(new ValuePoint("S112_TMP_SNK", "Heat Sink Temperature", // "Heat Sink Temperature", // - ValuePoint.Type.FLOAT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.DEGREE_CELSIUS)), // + FLOAT32, false /* mandatory? */, READ_ONLY, Unit.DEGREE_CELSIUS)), // TMP_TRNS(new ValuePoint("S112_TMP_TRNS", "Transformer Temperature", // "Transformer Temperature", // - ValuePoint.Type.FLOAT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.DEGREE_CELSIUS)), // + FLOAT32, false /* mandatory? */, READ_ONLY, Unit.DEGREE_CELSIUS)), // TMP_OT(new ValuePoint("S112_TMP_OT", "Other Temperature", // "Other Temperature", // - ValuePoint.Type.FLOAT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.DEGREE_CELSIUS)), // + FLOAT32, false /* mandatory? */, READ_ONLY, Unit.DEGREE_CELSIUS)), // ST(new EnumPoint("S112_ST", "Operating State", // "Enumerated value. Operating state", // - EnumPoint.Type.ENUM16, true /* mandatory? */, AccessMode.READ_ONLY, S112_St.values())), // + ENUM16, true /* mandatory? */, READ_ONLY, S112_St.values())), // ST_VND(new EnumPoint("S112_ST_VND", "Vendor Operating State", // "Vendor specific operating state code", // - EnumPoint.Type.ENUM16, false /* mandatory? */, AccessMode.READ_ONLY, new OptionsEnum[0])), // + ENUM16, false /* mandatory? */, READ_ONLY, new OptionsEnum[0])), // EVT1(new BitFieldPoint("S112_EVT1", "Event1", // "Bitmask value. Event fields", // - BitFieldPoint.Type.BITFIELD32, true /* mandatory? */, AccessMode.READ_ONLY, S112_Evt1.values())), // + BITFIELD32, true /* mandatory? */, READ_ONLY, S112_Evt1.values())), // EVT2(new BitFieldPoint("S112_EVT2", "Event Bitfield 2", // "Reserved for future use", // - BitFieldPoint.Type.BITFIELD32, true /* mandatory? */, AccessMode.READ_ONLY, new SunSpecBitPoint[0])), // + BITFIELD32, true /* mandatory? */, READ_ONLY, new SunSpecBitPoint[0])), // EVT_VND1(new BitFieldPoint("S112_EVT_VND1", "Vendor Event Bitfield 1", // "Vendor defined events", // - BitFieldPoint.Type.BITFIELD32, false /* mandatory? */, AccessMode.READ_ONLY, new SunSpecBitPoint[0])), // + BITFIELD32, false /* mandatory? */, READ_ONLY, new SunSpecBitPoint[0])), // EVT_VND2(new BitFieldPoint("S112_EVT_VND2", "Vendor Event Bitfield 2", // "Vendor defined events", // - BitFieldPoint.Type.BITFIELD32, false /* mandatory? */, AccessMode.READ_ONLY, new SunSpecBitPoint[0])), // + BITFIELD32, false /* mandatory? */, READ_ONLY, new SunSpecBitPoint[0])), // EVT_VND3(new BitFieldPoint("S112_EVT_VND3", "Vendor Event Bitfield 3", // "Vendor defined events", // - BitFieldPoint.Type.BITFIELD32, false /* mandatory? */, AccessMode.READ_ONLY, new SunSpecBitPoint[0])), // + BITFIELD32, false /* mandatory? */, READ_ONLY, new SunSpecBitPoint[0])), // EVT_VND4(new BitFieldPoint("S112_EVT_VND4", "Vendor Event Bitfield 4", // "Vendor defined events", // - BitFieldPoint.Type.BITFIELD32, false /* mandatory? */, AccessMode.READ_ONLY, new SunSpecBitPoint[0])); + BITFIELD32, false /* mandatory? */, READ_ONLY, new SunSpecBitPoint[0])); private final Point point; @@ -1320,97 +1342,97 @@ public BitPoint get() { public static enum S113 implements SunSpecPoint { A(new ValuePoint("S113_A", "Amps", // "AC Current", // - ValuePoint.Type.FLOAT32, true /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE)), // + FLOAT32, true /* mandatory? */, READ_ONLY, Unit.AMPERE)), // APH_A(new ValuePoint("S113_APH_A", "Amps PhaseA", // "Phase A Current", // - ValuePoint.Type.FLOAT32, true /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE)), // + FLOAT32, true /* mandatory? */, READ_ONLY, Unit.AMPERE)), // APH_B(new ValuePoint("S113_APH_B", "Amps PhaseB", // "Phase B Current", // - ValuePoint.Type.FLOAT32, true /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE)), // + FLOAT32, true /* mandatory? */, READ_ONLY, Unit.AMPERE)), // APH_C(new ValuePoint("S113_APH_C", "Amps PhaseC", // "Phase C Current", // - ValuePoint.Type.FLOAT32, true /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE)), // + FLOAT32, true /* mandatory? */, READ_ONLY, Unit.AMPERE)), // P_P_VPH_A_B(new ValuePoint("S113_P_P_VPH_A_B", "Phase Voltage AB", // "Phase Voltage AB", // - ValuePoint.Type.FLOAT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT)), // + FLOAT32, false /* mandatory? */, READ_ONLY, Unit.VOLT)), // P_P_VPH_B_C(new ValuePoint("S113_P_P_VPH_B_C", "Phase Voltage BC", // "Phase Voltage BC", // - ValuePoint.Type.FLOAT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT)), // + FLOAT32, false /* mandatory? */, READ_ONLY, Unit.VOLT)), // P_P_VPH_C_A(new ValuePoint("S113_P_P_VPH_C_A", "Phase Voltage CA", // "Phase Voltage CA", // - ValuePoint.Type.FLOAT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT)), // + FLOAT32, false /* mandatory? */, READ_ONLY, Unit.VOLT)), // PH_VPH_A(new ValuePoint("S113_PH_VPH_A", "Phase Voltage AN", // "Phase Voltage AN", // - ValuePoint.Type.FLOAT32, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT)), // + FLOAT32, true /* mandatory? */, READ_ONLY, Unit.VOLT)), // PH_VPH_B(new ValuePoint("S113_PH_VPH_B", "Phase Voltage BN", // "Phase Voltage BN", // - ValuePoint.Type.FLOAT32, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT)), // + FLOAT32, true /* mandatory? */, READ_ONLY, Unit.VOLT)), // PH_VPH_C(new ValuePoint("S113_PH_VPH_C", "Phase Voltage CN", // "Phase Voltage CN", // - ValuePoint.Type.FLOAT32, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT)), // + FLOAT32, true /* mandatory? */, READ_ONLY, Unit.VOLT)), // W(new ValuePoint("S113_W", "Watts", // "AC Power", // - ValuePoint.Type.FLOAT32, true /* mandatory? */, AccessMode.READ_ONLY, Unit.WATT)), // + FLOAT32, true /* mandatory? */, READ_ONLY, Unit.WATT)), // HZ(new ValuePoint("S113_HZ", "Hz", // "Line Frequency", // - ValuePoint.Type.FLOAT32, true /* mandatory? */, AccessMode.READ_ONLY, Unit.HERTZ)), // + FLOAT32, true /* mandatory? */, READ_ONLY, Unit.HERTZ)), // VA(new ValuePoint("S113_VA", "VA", // "AC Apparent Power", // - ValuePoint.Type.FLOAT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE)), // + FLOAT32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE)), // V_AR(new ValuePoint("S113_V_AR", "VAr", // "AC Reactive Power", // - ValuePoint.Type.FLOAT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE)), // + FLOAT32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE)), // PF(new ValuePoint("S113_PF", "PF", // "AC Power Factor", // - ValuePoint.Type.FLOAT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + FLOAT32, false /* mandatory? */, READ_ONLY, Unit.NONE)), // WH(new ValuePoint("S113_WH", "WattHours", // "AC Energy", // - ValuePoint.Type.FLOAT32, true /* mandatory? */, AccessMode.READ_ONLY, Unit.CUMULATED_WATT_HOURS)), // + FLOAT32, true /* mandatory? */, READ_ONLY, Unit.CUMULATED_WATT_HOURS)), // DCA(new ValuePoint("S113_DCA", "DC Amps", // "DC Current", // - ValuePoint.Type.FLOAT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE)), // + FLOAT32, false /* mandatory? */, READ_ONLY, Unit.AMPERE)), // DCV(new ValuePoint("S113_DCV", "DC Voltage", // "DC Voltage", // - ValuePoint.Type.FLOAT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT)), // + FLOAT32, false /* mandatory? */, READ_ONLY, Unit.VOLT)), // DCW(new ValuePoint("S113_DCW", "DC Watts", // "DC Power", // - ValuePoint.Type.FLOAT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.WATT)), // + FLOAT32, false /* mandatory? */, READ_ONLY, Unit.WATT)), // TMP_CAB(new ValuePoint("S113_TMP_CAB", "Cabinet Temperature", // "Cabinet Temperature", // - ValuePoint.Type.FLOAT32, true /* mandatory? */, AccessMode.READ_ONLY, Unit.DEGREE_CELSIUS)), // + FLOAT32, true /* mandatory? */, READ_ONLY, Unit.DEGREE_CELSIUS)), // TMP_SNK(new ValuePoint("S113_TMP_SNK", "Heat Sink Temperature", // "Heat Sink Temperature", // - ValuePoint.Type.FLOAT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.DEGREE_CELSIUS)), // + FLOAT32, false /* mandatory? */, READ_ONLY, Unit.DEGREE_CELSIUS)), // TMP_TRNS(new ValuePoint("S113_TMP_TRNS", "Transformer Temperature", // "Transformer Temperature", // - ValuePoint.Type.FLOAT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.DEGREE_CELSIUS)), // + FLOAT32, false /* mandatory? */, READ_ONLY, Unit.DEGREE_CELSIUS)), // TMP_OT(new ValuePoint("S113_TMP_OT", "Other Temperature", // "Other Temperature", // - ValuePoint.Type.FLOAT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.DEGREE_CELSIUS)), // + FLOAT32, false /* mandatory? */, READ_ONLY, Unit.DEGREE_CELSIUS)), // ST(new EnumPoint("S113_ST", "Operating State", // "Enumerated value. Operating state", // - EnumPoint.Type.ENUM16, true /* mandatory? */, AccessMode.READ_ONLY, S113_St.values())), // + ENUM16, true /* mandatory? */, READ_ONLY, S113_St.values())), // ST_VND(new EnumPoint("S113_ST_VND", "Vendor Operating State", // "Vendor specific operating state code", // - EnumPoint.Type.ENUM16, false /* mandatory? */, AccessMode.READ_ONLY, new OptionsEnum[0])), // + ENUM16, false /* mandatory? */, READ_ONLY, new OptionsEnum[0])), // EVT1(new BitFieldPoint("S113_EVT1", "Event1", // "Bitmask value. Event fields", // - BitFieldPoint.Type.BITFIELD32, true /* mandatory? */, AccessMode.READ_ONLY, S113_Evt1.values())), // + BITFIELD32, true /* mandatory? */, READ_ONLY, S113_Evt1.values())), // EVT2(new BitFieldPoint("S113_EVT2", "Event Bitfield 2", // "Reserved for future use", // - BitFieldPoint.Type.BITFIELD32, true /* mandatory? */, AccessMode.READ_ONLY, new SunSpecBitPoint[0])), // + BITFIELD32, true /* mandatory? */, READ_ONLY, new SunSpecBitPoint[0])), // EVT_VND1(new BitFieldPoint("S113_EVT_VND1", "Vendor Event Bitfield 1", // "Vendor defined events", // - BitFieldPoint.Type.BITFIELD32, false /* mandatory? */, AccessMode.READ_ONLY, new SunSpecBitPoint[0])), // + BITFIELD32, false /* mandatory? */, READ_ONLY, new SunSpecBitPoint[0])), // EVT_VND2(new BitFieldPoint("S113_EVT_VND2", "Vendor Event Bitfield 2", // "Vendor defined events", // - BitFieldPoint.Type.BITFIELD32, false /* mandatory? */, AccessMode.READ_ONLY, new SunSpecBitPoint[0])), // + BITFIELD32, false /* mandatory? */, READ_ONLY, new SunSpecBitPoint[0])), // EVT_VND3(new BitFieldPoint("S113_EVT_VND3", "Vendor Event Bitfield 3", // "Vendor defined events", // - BitFieldPoint.Type.BITFIELD32, false /* mandatory? */, AccessMode.READ_ONLY, new SunSpecBitPoint[0])), // + BITFIELD32, false /* mandatory? */, READ_ONLY, new SunSpecBitPoint[0])), // EVT_VND4(new BitFieldPoint("S113_EVT_VND4", "Vendor Event Bitfield 4", // "Vendor defined events", // - BitFieldPoint.Type.BITFIELD32, false /* mandatory? */, AccessMode.READ_ONLY, new SunSpecBitPoint[0])); + BITFIELD32, false /* mandatory? */, READ_ONLY, new SunSpecBitPoint[0])); private final Point point; @@ -1492,73 +1514,73 @@ public BitPoint get() { public static enum S120 implements SunSpecPoint { D_E_R_TYP(new EnumPoint("S120_D_E_R_TYP", "DERTyp", // "Type of DER device. Default value is 4 to indicate PV device.", // - EnumPoint.Type.ENUM16, true /* mandatory? */, AccessMode.READ_ONLY, S120_DERTyp.values())), // + ENUM16, true /* mandatory? */, READ_ONLY, S120_DERTyp.values())), // W_RTG(new ScaledValuePoint("S120_W_RTG", "WRtg", // "Continuous power output capability of the inverter.", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.WATT, "WRtg_SF")), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.WATT, "WRtg_SF")), // W_RTG_S_F(new ScaleFactorPoint("S120_W_RTG_S_F", "WRtg_SF", // "Scale factor")), // V_A_RTG(new ScaledValuePoint("S120_V_A_RTG", "VARtg", // "Continuous Volt-Ampere capability of the inverter.", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE, "VARtg_SF")), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE, "VARtg_SF")), // V_A_RTG_S_F(new ScaleFactorPoint("S120_V_A_RTG_S_F", "VARtg_SF", // "Scale factor")), // V_AR_RTG_Q1(new ScaledValuePoint("S120_V_AR_RTG_Q1", "VArRtgQ1", // "Continuous VAR capability of the inverter in quadrant 1.", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "VArRtg_SF")), // + INT16, true /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "VArRtg_SF")), // V_AR_RTG_Q2(new ScaledValuePoint("S120_V_AR_RTG_Q2", "VArRtgQ2", // "Continuous VAR capability of the inverter in quadrant 2.", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "VArRtg_SF")), // + INT16, true /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "VArRtg_SF")), // V_AR_RTG_Q3(new ScaledValuePoint("S120_V_AR_RTG_Q3", "VArRtgQ3", // "Continuous VAR capability of the inverter in quadrant 3.", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "VArRtg_SF")), // + INT16, true /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "VArRtg_SF")), // V_AR_RTG_Q4(new ScaledValuePoint("S120_V_AR_RTG_Q4", "VArRtgQ4", // "Continuous VAR capability of the inverter in quadrant 4.", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "VArRtg_SF")), // + INT16, true /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "VArRtg_SF")), // V_AR_RTG_S_F(new ScaleFactorPoint("S120_V_AR_RTG_S_F", "VArRtg_SF", // "Scale factor")), // A_RTG(new ScaledValuePoint("S120_A_RTG", "ARtg", // "Maximum RMS AC current level capability of the inverter.", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE, "ARtg_SF")), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.AMPERE, "ARtg_SF")), // A_RTG_S_F(new ScaleFactorPoint("S120_A_RTG_S_F", "ARtg_SF", // "Scale factor")), // P_F_RTG_Q1(new ScaledValuePoint("S120_P_F_RTG_Q1", "PFRtgQ1", // "Minimum power factor capability of the inverter in quadrant 1.", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE, "PFRtg_SF")), // + INT16, true /* mandatory? */, READ_ONLY, Unit.NONE, "PFRtg_SF")), // P_F_RTG_Q2(new ScaledValuePoint("S120_P_F_RTG_Q2", "PFRtgQ2", // "Minimum power factor capability of the inverter in quadrant 2.", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE, "PFRtg_SF")), // + INT16, true /* mandatory? */, READ_ONLY, Unit.NONE, "PFRtg_SF")), // P_F_RTG_Q3(new ScaledValuePoint("S120_P_F_RTG_Q3", "PFRtgQ3", // "Minimum power factor capability of the inverter in quadrant 3.", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE, "PFRtg_SF")), // + INT16, true /* mandatory? */, READ_ONLY, Unit.NONE, "PFRtg_SF")), // P_F_RTG_Q4(new ScaledValuePoint("S120_P_F_RTG_Q4", "PFRtgQ4", // "Minimum power factor capability of the inverter in quadrant 4.", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE, "PFRtg_SF")), // + INT16, true /* mandatory? */, READ_ONLY, Unit.NONE, "PFRtg_SF")), // P_F_RTG_S_F(new ScaleFactorPoint("S120_P_F_RTG_S_F", "PFRtg_SF", // "Scale factor")), // W_H_RTG(new ScaledValuePoint("S120_W_H_RTG", "WHRtg", // "Nominal energy rating of storage device.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.CUMULATED_WATT_HOURS, "WHRtg_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.CUMULATED_WATT_HOURS, "WHRtg_SF")), // W_H_RTG_S_F(new ScaleFactorPoint("S120_W_H_RTG_S_F", "WHRtg_SF", // "Scale factor")), // AHR_RTG(new ScaledValuePoint("S120_AHR_RTG", "AhrRtg", // "The usable capacity of the battery. Maximum charge minus minimum charge from a technology capability perspective (Amp-hour capacity rating).", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE_HOURS, "AhrRtg_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.AMPERE_HOURS, "AhrRtg_SF")), // AHR_RTG_S_F(new ScaleFactorPoint("S120_AHR_RTG_S_F", "AhrRtg_SF", // "Scale factor for amp-hour rating.")), // MAX_CHA_RTE(new ScaledValuePoint("S120_MAX_CHA_RTE", "MaxChaRte", // "Maximum rate of energy transfer into the storage device.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.WATT, "MaxChaRte_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.WATT, "MaxChaRte_SF")), // MAX_CHA_RTE_S_F(new ScaleFactorPoint("S120_MAX_CHA_RTE_S_F", "MaxChaRte_SF", // "Scale factor")), // MAX_DIS_CHA_RTE(new ScaledValuePoint("S120_MAX_DIS_CHA_RTE", "MaxDisChaRte", // "Maximum rate of energy transfer out of the storage device.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.WATT, "MaxDisChaRte_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.WATT, "MaxDisChaRte_SF")), // MAX_DIS_CHA_RTE_S_F(new ScaleFactorPoint("S120_MAX_DIS_CHA_RTE_S_F", "MaxDisChaRte_SF", // "Scale factor")), // PAD(new ValuePoint("S120_PAD", "Pad", // "Pad register.", // - ValuePoint.Type.PAD, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)); + ValuePoint.Type.PAD, false /* mandatory? */, READ_ONLY, Unit.NONE)); private final Point point; @@ -1604,64 +1626,64 @@ public OptionsEnum getUndefined() { public static enum S121 implements SunSpecPoint { W_MAX(new ScaledValuePoint("S121_W_MAX", "WMax", // "Setting for maximum power output. Default to WRtg.", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_WRITE, Unit.WATT, "WMax_SF")), // + UINT16, true /* mandatory? */, READ_WRITE, Unit.WATT, "WMax_SF")), // V_REF(new ScaledValuePoint("S121_V_REF", "VRef", // "Voltage at the PCC.", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_WRITE, Unit.VOLT, "VRef_SF")), // + UINT16, true /* mandatory? */, READ_WRITE, Unit.VOLT, "VRef_SF")), // V_REF_OFS(new ScaledValuePoint("S121_V_REF_OFS", "VRefOfs", // "Offset from PCC to inverter.", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_WRITE, Unit.VOLT, "VRefOfs_SF")), // + INT16, true /* mandatory? */, READ_WRITE, Unit.VOLT, "VRefOfs_SF")), // V_MAX(new ScaledValuePoint("S121_V_MAX", "VMax", // "Setpoint for maximum voltage.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.VOLT, "VMinMax_SF")), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.VOLT, "VMinMax_SF")), // V_MIN(new ScaledValuePoint("S121_V_MIN", "VMin", // "Setpoint for minimum voltage.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.VOLT, "VMinMax_SF")), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.VOLT, "VMinMax_SF")), // V_A_MAX(new ScaledValuePoint("S121_V_A_MAX", "VAMax", // "Setpoint for maximum apparent power. Default to VARtg.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.VOLT_AMPERE, "VAMax_SF")), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.VOLT_AMPERE, "VAMax_SF")), // V_AR_MAX_Q1(new ScaledValuePoint("S121_V_AR_MAX_Q1", "VArMaxQ1", // "Setting for maximum reactive power in quadrant 1. Default to VArRtgQ1.", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.VOLT_AMPERE_REACTIVE, "VArMax_SF")), // + INT16, false /* mandatory? */, READ_WRITE, Unit.VOLT_AMPERE_REACTIVE, "VArMax_SF")), // V_AR_MAX_Q2(new ScaledValuePoint("S121_V_AR_MAX_Q2", "VArMaxQ2", // "Setting for maximum reactive power in quadrant 2. Default to VArRtgQ2.", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.VOLT_AMPERE_REACTIVE, "VArMax_SF")), // + INT16, false /* mandatory? */, READ_WRITE, Unit.VOLT_AMPERE_REACTIVE, "VArMax_SF")), // V_AR_MAX_Q3(new ScaledValuePoint("S121_V_AR_MAX_Q3", "VArMaxQ3", // "Setting for maximum reactive power in quadrant 3. Default to VArRtgQ3.", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.VOLT_AMPERE_REACTIVE, "VArMax_SF")), // + INT16, false /* mandatory? */, READ_WRITE, Unit.VOLT_AMPERE_REACTIVE, "VArMax_SF")), // V_AR_MAX_Q4(new ScaledValuePoint("S121_V_AR_MAX_Q4", "VArMaxQ4", // "Setting for maximum reactive power in quadrant 4. Default to VArRtgQ4.", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.VOLT_AMPERE_REACTIVE, "VArMax_SF")), // + INT16, false /* mandatory? */, READ_WRITE, Unit.VOLT_AMPERE_REACTIVE, "VArMax_SF")), // W_GRA(new ScaledValuePoint("S121_W_GRA", "WGra", // "Default ramp rate of change of active power due to command or internal action.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.PERCENT, "WGra_SF")), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.PERCENT, "WGra_SF")), // P_F_MIN_Q1(new ScaledValuePoint("S121_P_F_MIN_Q1", "PFMinQ1", // "Setpoint for minimum power factor value in quadrant 1. Default to PFRtgQ1.", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.NONE, "PFMin_SF")), // + INT16, false /* mandatory? */, READ_WRITE, Unit.NONE, "PFMin_SF")), // P_F_MIN_Q2(new ScaledValuePoint("S121_P_F_MIN_Q2", "PFMinQ2", // "Setpoint for minimum power factor value in quadrant 2. Default to PFRtgQ2.", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.NONE, "PFMin_SF")), // + INT16, false /* mandatory? */, READ_WRITE, Unit.NONE, "PFMin_SF")), // P_F_MIN_Q3(new ScaledValuePoint("S121_P_F_MIN_Q3", "PFMinQ3", // "Setpoint for minimum power factor value in quadrant 3. Default to PFRtgQ3.", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.NONE, "PFMin_SF")), // + INT16, false /* mandatory? */, READ_WRITE, Unit.NONE, "PFMin_SF")), // P_F_MIN_Q4(new ScaledValuePoint("S121_P_F_MIN_Q4", "PFMinQ4", // "Setpoint for minimum power factor value in quadrant 4. Default to PFRtgQ4.", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.NONE, "PFMin_SF")), // + INT16, false /* mandatory? */, READ_WRITE, Unit.NONE, "PFMin_SF")), // V_AR_ACT(new EnumPoint("S121_V_AR_ACT", "VArAct", // "VAR action on change between charging and discharging: 1=switch 2=maintain VAR characterization.", // - EnumPoint.Type.ENUM16, false /* mandatory? */, AccessMode.READ_WRITE, S121_VArAct.values())), // + ENUM16, false /* mandatory? */, READ_WRITE, S121_VArAct.values())), // CLC_TOT_V_A(new EnumPoint("S121_CLC_TOT_V_A", "ClcTotVA", // "Calculation method for total apparent power. 1=vector 2=arithmetic.", // - EnumPoint.Type.ENUM16, false /* mandatory? */, AccessMode.READ_WRITE, S121_ClcTotVA.values())), // + ENUM16, false /* mandatory? */, READ_WRITE, S121_ClcTotVA.values())), // MAX_RMP_RTE(new ScaledValuePoint("S121_MAX_RMP_RTE", "MaxRmpRte", // "Setpoint for maximum ramp rate as percentage of nominal maximum ramp rate. This setting will limit the rate that watts delivery to the grid can increase or decrease in response to intermittent PV generation.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.PERCENT, "MaxRmpRte_SF")), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.PERCENT, "MaxRmpRte_SF")), // E_C_P_NOM_HZ(new ScaledValuePoint("S121_E_C_P_NOM_HZ", "ECPNomHz", // "Setpoint for nominal frequency at the ECP.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.HERTZ, "ECPNomHz_SF")), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.HERTZ, "ECPNomHz_SF")), // CONN_PH(new EnumPoint("S121_CONN_PH", "ConnPh", // "Identity of connected phase for single phase inverters. A=1 B=2 C=3.", // - EnumPoint.Type.ENUM16, false /* mandatory? */, AccessMode.READ_WRITE, S121_ConnPh.values())), // + ENUM16, false /* mandatory? */, READ_WRITE, S121_ConnPh.values())), // W_MAX_S_F(new ScaleFactorPoint("S121_W_MAX_S_F", "WMax_SF", // "Scale factor for real power.")), // V_REF_S_F(new ScaleFactorPoint("S121_V_REF_S_F", "VRef_SF", // @@ -1786,59 +1808,59 @@ public OptionsEnum getUndefined() { public static enum S122 implements SunSpecPoint { P_V_CONN(new BitFieldPoint("S122_P_V_CONN", "PVConn", // "PV inverter present/available status. Enumerated value.", // - BitFieldPoint.Type.BITFIELD16, true /* mandatory? */, AccessMode.READ_ONLY, S122_PVConn.values())), // + BITFIELD16, true /* mandatory? */, READ_ONLY, S122_PVConn.values())), // STOR_CONN(new BitFieldPoint("S122_STOR_CONN", "StorConn", // "Storage inverter present/available status. Enumerated value.", // - BitFieldPoint.Type.BITFIELD16, true /* mandatory? */, AccessMode.READ_ONLY, S122_StorConn.values())), // + BITFIELD16, true /* mandatory? */, READ_ONLY, S122_StorConn.values())), // E_C_P_CONN(new BitFieldPoint("S122_E_C_P_CONN", "ECPConn", // "ECP connection status: disconnected=0 connected=1.", // - BitFieldPoint.Type.BITFIELD16, true /* mandatory? */, AccessMode.READ_ONLY, S122_ECPConn.values())), // + BITFIELD16, true /* mandatory? */, READ_ONLY, S122_ECPConn.values())), // ACT_WH(new ValuePoint("S122_ACT_WH", "ActWh", // "AC lifetime active (real) energy output.", // - ValuePoint.Type.ACC64, false /* mandatory? */, AccessMode.READ_ONLY, Unit.CUMULATED_WATT_HOURS)), // + ACC64, false /* mandatory? */, READ_ONLY, Unit.CUMULATED_WATT_HOURS)), // ACT_V_AH(new ValuePoint("S122_ACT_V_AH", "ActVAh", // "AC lifetime apparent energy output.", // - ValuePoint.Type.ACC64, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_HOURS)), // + ACC64, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_HOURS)), // ACT_V_ARH_Q1(new ValuePoint("S122_ACT_V_ARH_Q1", "ActVArhQ1", // "AC lifetime reactive energy output in quadrant 1.", // - ValuePoint.Type.ACC64, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS)), // + ACC64, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS)), // ACT_V_ARH_Q2(new ValuePoint("S122_ACT_V_ARH_Q2", "ActVArhQ2", // "AC lifetime reactive energy output in quadrant 2.", // - ValuePoint.Type.ACC64, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS)), // + ACC64, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS)), // ACT_V_ARH_Q3(new ValuePoint("S122_ACT_V_ARH_Q3", "ActVArhQ3", // "AC lifetime negative energy output in quadrant 3.", // - ValuePoint.Type.ACC64, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS)), // + ACC64, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS)), // ACT_V_ARH_Q4(new ValuePoint("S122_ACT_V_ARH_Q4", "ActVArhQ4", // "AC lifetime reactive energy output in quadrant 4.", // - ValuePoint.Type.ACC64, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS)), // + ACC64, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS)), // V_AR_AVAL(new ScaledValuePoint("S122_V_AR_AVAL", "VArAval", // "Amount of VARs available without impacting watts output.", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "VArAval_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "VArAval_SF")), // V_AR_AVAL_S_F(new ScaleFactorPoint("S122_V_AR_AVAL_S_F", "VArAval_SF", // "Scale factor for available VARs.")), // W_AVAL(new ScaledValuePoint("S122_W_AVAL", "WAval", // "Amount of Watts available.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "WAval_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "WAval_SF")), // W_AVAL_S_F(new ScaleFactorPoint("S122_W_AVAL_S_F", "WAval_SF", // "Scale factor for available Watts.")), // ST_SET_LIM_MSK(new BitFieldPoint("S122_ST_SET_LIM_MSK", "StSetLimMsk", // "Bit Mask indicating setpoint limit(s) reached.", // - BitFieldPoint.Type.BITFIELD32, false /* mandatory? */, AccessMode.READ_ONLY, S122_StSetLimMsk.values())), // + BITFIELD32, false /* mandatory? */, READ_ONLY, S122_StSetLimMsk.values())), // ST_ACT_CTL(new BitFieldPoint("S122_ST_ACT_CTL", "StActCtl", // "Bit Mask indicating which inverter controls are currently active.", // - BitFieldPoint.Type.BITFIELD32, false /* mandatory? */, AccessMode.READ_ONLY, S122_StActCtl.values())), // + BITFIELD32, false /* mandatory? */, READ_ONLY, S122_StActCtl.values())), // TM_SRC(new ValuePoint("S122_TM_SRC", "TmSrc", // "Source of time synchronization.", // - ValuePoint.Type.STRING4, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + STRING4, false /* mandatory? */, READ_ONLY, Unit.NONE)), // TMS(new ValuePoint("S122_TMS", "Tms", // "Seconds since 01-01-2000 00:00 UTC", // - ValuePoint.Type.UINT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.SECONDS)), // + UINT32, false /* mandatory? */, READ_ONLY, Unit.SECONDS)), // RT_ST(new BitFieldPoint("S122_RT_ST", "RtSt", // "Bit Mask indicating active ride-through status.", // - BitFieldPoint.Type.BITFIELD16, false /* mandatory? */, AccessMode.READ_ONLY, S122_RtSt.values())), // + BITFIELD16, false /* mandatory? */, READ_ONLY, S122_RtSt.values())), // RIS(new ScaledValuePoint("S122_RIS", "Ris", // "Isolation resistance.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE, "Ris_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.NONE, "Ris_SF")), // RIS_S_F(new ScaleFactorPoint("S122_RIS_S_F", "Ris_SF", // "Scale factor for isolation resistance.")); @@ -1979,67 +2001,67 @@ public BitPoint get() { public static enum S123 implements SunSpecPoint { CONN_WIN_TMS(new ValuePoint("S123_CONN_WIN_TMS", "Conn_WinTms", // "Time window for connect/disconnect.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.SECONDS)), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.SECONDS)), // CONN_RVRT_TMS(new ValuePoint("S123_CONN_RVRT_TMS", "Conn_RvrtTms", // "Timeout period for connect/disconnect.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.SECONDS)), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.SECONDS)), // CONN(new EnumPoint("S123_CONN", "Conn", // "Enumerated valued. Connection control.", // - EnumPoint.Type.ENUM16, true /* mandatory? */, AccessMode.READ_WRITE, S123_Conn.values())), // + ENUM16, true /* mandatory? */, READ_WRITE, S123_Conn.values())), // W_MAX_LIM_PCT(new ScaledValuePoint("S123_W_MAX_LIM_PCT", "WMaxLimPct", // "Set power output to specified level.", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_WRITE, Unit.PERCENT, "WMaxLimPct_SF")), // + UINT16, true /* mandatory? */, READ_WRITE, Unit.PERCENT, "WMaxLimPct_SF")), // W_MAX_LIM_PCT_WIN_TMS(new ValuePoint("S123_W_MAX_LIM_PCT_WIN_TMS", "WMaxLimPct_WinTms", // "Time window for power limit change.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.SECONDS)), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.SECONDS)), // W_MAX_LIM_PCT_RVRT_TMS(new ValuePoint("S123_W_MAX_LIM_PCT_RVRT_TMS", "WMaxLimPct_RvrtTms", // "Timeout period for power limit.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.SECONDS)), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.SECONDS)), // W_MAX_LIM_PCT_RMP_TMS(new ValuePoint("S123_W_MAX_LIM_PCT_RMP_TMS", "WMaxLimPct_RmpTms", // "Ramp time for moving from current setpoint to new setpoint.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.SECONDS)), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.SECONDS)), // W_MAX_LIM_ENA(new EnumPoint("S123_W_MAX_LIM_ENA", "WMaxLim_Ena", // "Enumerated valued. Throttle enable/disable control.", // - EnumPoint.Type.ENUM16, true /* mandatory? */, AccessMode.READ_WRITE, S123_WMaxLim_Ena.values())), // + ENUM16, true /* mandatory? */, READ_WRITE, S123_WMaxLim_Ena.values())), // OUT_P_F_SET(new ScaledValuePoint("S123_OUT_P_F_SET", "OutPFSet", // "Set power factor to specific value - cosine of angle.", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_WRITE, Unit.NONE, "OutPFSet_SF")), // + INT16, true /* mandatory? */, READ_WRITE, Unit.NONE, "OutPFSet_SF")), // OUT_P_F_SET_WIN_TMS(new ValuePoint("S123_OUT_P_F_SET_WIN_TMS", "OutPFSet_WinTms", // "Time window for power factor change.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.SECONDS)), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.SECONDS)), // OUT_P_F_SET_RVRT_TMS(new ValuePoint("S123_OUT_P_F_SET_RVRT_TMS", "OutPFSet_RvrtTms", // "Timeout period for power factor.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.SECONDS)), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.SECONDS)), // OUT_P_F_SET_RMP_TMS(new ValuePoint("S123_OUT_P_F_SET_RMP_TMS", "OutPFSet_RmpTms", // "Ramp time for moving from current setpoint to new setpoint.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.SECONDS)), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.SECONDS)), // OUT_P_F_SET_ENA(new EnumPoint("S123_OUT_P_F_SET_ENA", "OutPFSet_Ena", // "Enumerated valued. Fixed power factor enable/disable control.", // - EnumPoint.Type.ENUM16, true /* mandatory? */, AccessMode.READ_WRITE, S123_OutPFSet_Ena.values())), // + ENUM16, true /* mandatory? */, READ_WRITE, S123_OutPFSet_Ena.values())), // V_AR_W_MAX_PCT(new ScaledValuePoint("S123_V_AR_W_MAX_PCT", "VArWMaxPct", // "Reactive power in percent of WMax.", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.PERCENT, "VArPct_SF")), // + INT16, false /* mandatory? */, READ_WRITE, Unit.PERCENT, "VArPct_SF")), // V_AR_MAX_PCT(new ScaledValuePoint("S123_V_AR_MAX_PCT", "VArMaxPct", // "Reactive power in percent of VArMax.", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.PERCENT, "VArPct_SF")), // + INT16, false /* mandatory? */, READ_WRITE, Unit.PERCENT, "VArPct_SF")), // V_AR_AVAL_PCT(new ScaledValuePoint("S123_V_AR_AVAL_PCT", "VArAvalPct", // "Reactive power in percent of VArAval.", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.PERCENT, "VArPct_SF")), // + INT16, false /* mandatory? */, READ_WRITE, Unit.PERCENT, "VArPct_SF")), // V_AR_PCT_WIN_TMS(new ValuePoint("S123_V_AR_PCT_WIN_TMS", "VArPct_WinTms", // "Time window for VAR limit change.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.SECONDS)), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.SECONDS)), // V_AR_PCT_RVRT_TMS(new ValuePoint("S123_V_AR_PCT_RVRT_TMS", "VArPct_RvrtTms", // "Timeout period for VAR limit.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.SECONDS)), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.SECONDS)), // V_AR_PCT_RMP_TMS(new ValuePoint("S123_V_AR_PCT_RMP_TMS", "VArPct_RmpTms", // "Ramp time for moving from current setpoint to new setpoint.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.SECONDS)), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.SECONDS)), // V_AR_PCT_MOD(new EnumPoint("S123_V_AR_PCT_MOD", "VArPct_Mod", // "Enumerated value. VAR percent limit mode.", // - EnumPoint.Type.ENUM16, false /* mandatory? */, AccessMode.READ_WRITE, S123_VArPct_Mod.values())), // + ENUM16, false /* mandatory? */, READ_WRITE, S123_VArPct_Mod.values())), // V_AR_PCT_ENA(new EnumPoint("S123_V_AR_PCT_ENA", "VArPct_Ena", // "Enumerated valued. Percent limit VAr enable/disable control.", // - EnumPoint.Type.ENUM16, true /* mandatory? */, AccessMode.READ_WRITE, S123_VArPct_Ena.values())), // + ENUM16, true /* mandatory? */, READ_WRITE, S123_VArPct_Ena.values())), // W_MAX_LIM_PCT_S_F(new ScaleFactorPoint("S123_W_MAX_LIM_PCT_S_F", "WMaxLimPct_SF", // "Scale factor for power output percent.")), // OUT_P_F_SET_S_F(new ScaleFactorPoint("S123_OUT_P_F_SET_S_F", "OutPFSet_SF", // @@ -2209,51 +2231,51 @@ public OptionsEnum getUndefined() { public static enum S124 implements SunSpecPoint { W_CHA_MAX(new ScaledValuePoint("S124_W_CHA_MAX", "WChaMax", // "Setpoint for maximum charge.", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_WRITE, Unit.WATT, "WChaMax_SF")), // + UINT16, true /* mandatory? */, READ_WRITE, Unit.WATT, "WChaMax_SF")), // W_CHA_GRA(new ScaledValuePoint("S124_W_CHA_GRA", "WChaGra", // "Setpoint for maximum charging rate. Default is MaxChaRte.", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_WRITE, Unit.PERCENT, "WChaDisChaGra_SF")), // + UINT16, true /* mandatory? */, READ_WRITE, Unit.PERCENT, "WChaDisChaGra_SF")), // W_DIS_CHA_GRA(new ScaledValuePoint("S124_W_DIS_CHA_GRA", "WDisChaGra", // "Setpoint for maximum discharge rate. Default is MaxDisChaRte.", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_WRITE, Unit.PERCENT, "WChaDisChaGra_SF")), // + UINT16, true /* mandatory? */, READ_WRITE, Unit.PERCENT, "WChaDisChaGra_SF")), // STOR_CTL_MOD(new BitFieldPoint("S124_STOR_CTL_MOD", "StorCtl_Mod", // "Activate hold/discharge/charge storage control mode. Bitfield value.", // - BitFieldPoint.Type.BITFIELD16, true /* mandatory? */, AccessMode.READ_WRITE, S124_StorCtl_Mod.values())), // + BITFIELD16, true /* mandatory? */, READ_WRITE, S124_StorCtl_Mod.values())), // V_A_CHA_MAX(new ScaledValuePoint("S124_V_A_CHA_MAX", "VAChaMax", // "Setpoint for maximum charging VA.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.VOLT_AMPERE, "VAChaMax_SF")), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.VOLT_AMPERE, "VAChaMax_SF")), // MIN_RSV_PCT(new ScaledValuePoint("S124_MIN_RSV_PCT", "MinRsvPct", // "Setpoint for minimum reserve for storage as a percentage of the nominal maximum storage.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.PERCENT, "MinRsvPct_SF")), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.PERCENT, "MinRsvPct_SF")), // CHA_STATE(new ScaledValuePoint("S124_CHA_STATE", "ChaState", // "Currently available energy as a percent of the capacity rating.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.PERCENT, "ChaState_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.PERCENT, "ChaState_SF")), // STOR_AVAL(new ScaledValuePoint("S124_STOR_AVAL", "StorAval", // "State of charge (ChaState) minus storage reserve (MinRsvPct) times capacity rating (AhrRtg).", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE_HOURS, "StorAval_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.AMPERE_HOURS, "StorAval_SF")), // IN_BAT_V(new ScaledValuePoint("S124_IN_BAT_V", "InBatV", // "Internal battery voltage.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "InBatV_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.VOLT, "InBatV_SF")), // CHA_ST(new EnumPoint("S124_CHA_ST", "ChaSt", // "Charge status of storage device. Enumerated value.", // - EnumPoint.Type.ENUM16, false /* mandatory? */, AccessMode.READ_ONLY, S124_ChaSt.values())), // + ENUM16, false /* mandatory? */, READ_ONLY, S124_ChaSt.values())), // OUT_W_RTE(new ScaledValuePoint("S124_OUT_W_RTE", "OutWRte", // "Percent of max discharge rate.", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.PERCENT, "InOutWRte_SF")), // + INT16, false /* mandatory? */, READ_WRITE, Unit.PERCENT, "InOutWRte_SF")), // IN_W_RTE(new ScaledValuePoint("S124_IN_W_RTE", "InWRte", // "Percent of max charging rate.", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.PERCENT, "InOutWRte_SF")), // + INT16, false /* mandatory? */, READ_WRITE, Unit.PERCENT, "InOutWRte_SF")), // IN_OUT_W_RTE_WIN_TMS(new ValuePoint("S124_IN_OUT_W_RTE_WIN_TMS", "InOutWRte_WinTms", // "Time window for charge/discharge rate change.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.SECONDS)), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.SECONDS)), // IN_OUT_W_RTE_RVRT_TMS(new ValuePoint("S124_IN_OUT_W_RTE_RVRT_TMS", "InOutWRte_RvrtTms", // "Timeout period for charge/discharge rate.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.SECONDS)), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.SECONDS)), // IN_OUT_W_RTE_RMP_TMS(new ValuePoint("S124_IN_OUT_W_RTE_RMP_TMS", "InOutWRte_RmpTms", // "Ramp time for moving from current setpoint to new setpoint.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.SECONDS)), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.SECONDS)), // CHA_GRI_SET(new EnumPoint("S124_CHA_GRI_SET", "", "", // - EnumPoint.Type.ENUM16, false /* mandatory? */, AccessMode.READ_WRITE, S124_ChaGriSet.values())), // + ENUM16, false /* mandatory? */, READ_WRITE, S124_ChaGriSet.values())), // W_CHA_MAX_S_F(new ScaleFactorPoint("S124_W_CHA_MAX_S_F", "WChaMax_SF", // "Scale factor for maximum charge.")), // W_CHA_DIS_CHA_GRA_S_F(new ScaleFactorPoint("S124_W_CHA_DIS_CHA_GRA_S_F", "WChaDisChaGra_SF", // @@ -2365,26 +2387,26 @@ public OptionsEnum getUndefined() { public static enum S125 implements SunSpecPoint { MOD_ENA(new BitFieldPoint("S125_MOD_ENA", "ModEna", // "Is price-based charge/discharge mode active?", // - BitFieldPoint.Type.BITFIELD16, true /* mandatory? */, AccessMode.READ_WRITE, S125_ModEna.values())), // + BITFIELD16, true /* mandatory? */, READ_WRITE, S125_ModEna.values())), // SIG_TYPE(new EnumPoint("S125_SIG_TYPE", "SigType", // "Meaning of the pricing signal. When a Price schedule is used, type must match the schedule range variable description.", // - EnumPoint.Type.ENUM16, false /* mandatory? */, AccessMode.READ_WRITE, S125_SigType.values())), // + ENUM16, false /* mandatory? */, READ_WRITE, S125_SigType.values())), // SIG(new ScaledValuePoint("S125_SIG", "Sig", // "Utility/ESP specific pricing signal. Content depends on pricing signal type. When H/M/L type is specified. Low=0; Med=1; High=2.", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_WRITE, Unit.NONE, "Sig_SF")), // + INT16, true /* mandatory? */, READ_WRITE, Unit.NONE, "Sig_SF")), // WIN_TMS(new ValuePoint("S125_WIN_TMS", "WinTms", // "Time window for charge/discharge pricing change.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.SECONDS)), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.SECONDS)), // RVT_TMS(new ValuePoint("S125_RVT_TMS", "RvtTms", // "Timeout period for charge/discharge pricing change.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.SECONDS)), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.SECONDS)), // RMP_TMS(new ValuePoint("S125_RMP_TMS", "RmpTms", // "Ramp time for moving from current charge or discharge level to new level.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.SECONDS)), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.SECONDS)), // SIG_S_F(new ScaleFactorPoint("S125_SIG_S_F", "Sig_SF", // "Pricing signal scale factor.")), // PAD(new ValuePoint("S125_PAD", "", "", // - ValuePoint.Type.PAD, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)); + ValuePoint.Type.PAD, false /* mandatory? */, READ_ONLY, Unit.NONE)); private final Point point; @@ -2448,22 +2470,22 @@ public OptionsEnum getUndefined() { public static enum S127 implements SunSpecPoint { W_GRA(new ScaledValuePoint("S127_W_GRA", "WGra", // "The slope of the reduction in the maximum allowed watts output as a function of frequency.", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_WRITE, Unit.PERCENT, "WGra_SF")), // + UINT16, true /* mandatory? */, READ_WRITE, Unit.PERCENT, "WGra_SF")), // HZ_STR(new ScaledValuePoint("S127_HZ_STR", "HzStr", // "The frequency deviation from nominal frequency (ECPNomHz) at which a snapshot of the instantaneous power output is taken to act as the CAPPED power level (PM) and above which reduction in power output occurs.", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_WRITE, Unit.HERTZ, "HzStrStop_SF")), // + INT16, true /* mandatory? */, READ_WRITE, Unit.HERTZ, "HzStrStop_SF")), // HZ_STOP(new ScaledValuePoint("S127_HZ_STOP", "HzStop", // "The frequency deviation from nominal frequency (ECPNomHz) at which curtailed power output may return to normal and the cap on the power level value is removed.", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_WRITE, Unit.HERTZ, "HzStrStop_SF")), // + INT16, true /* mandatory? */, READ_WRITE, Unit.HERTZ, "HzStrStop_SF")), // HYS_ENA(new BitFieldPoint("S127_HYS_ENA", "HysEna", // "Enable hysteresis", // - BitFieldPoint.Type.BITFIELD16, true /* mandatory? */, AccessMode.READ_WRITE, S127_HysEna.values())), // + BITFIELD16, true /* mandatory? */, READ_WRITE, S127_HysEna.values())), // MOD_ENA(new BitFieldPoint("S127_MOD_ENA", "ModEna", // "Is Parameterized Frequency-Watt control active.", // - BitFieldPoint.Type.BITFIELD16, true /* mandatory? */, AccessMode.READ_WRITE, S127_ModEna.values())), // + BITFIELD16, true /* mandatory? */, READ_WRITE, S127_ModEna.values())), // HZ_STOP_W_GRA(new ScaledValuePoint("S127_HZ_STOP_W_GRA", "HzStopWGra", // "The maximum time-based rate of change at which power output returns to normal after having been capped by an over frequency event.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.PERCENT, "RmpIncDec_SF")), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.PERCENT, "RmpIncDec_SF")), // W_GRA_S_F(new ScaleFactorPoint("S127_W_GRA_S_F", "WGra_SF", // "Scale factor for output gradient.")), // HZ_STR_STOP_S_F(new ScaleFactorPoint("S127_HZ_STR_STOP_S_F", "HzStrStop_SF", // @@ -2471,7 +2493,7 @@ public static enum S127 implements SunSpecPoint { RMP_INC_DEC_S_F(new ScaleFactorPoint("S127_RMP_INC_DEC_S_F", "RmpIncDec_SF", // "Scale factor for increment and decrement ramps.")), // PAD(new ValuePoint("S127_PAD", "", "", // - ValuePoint.Type.PAD, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)); + ValuePoint.Type.PAD, false /* mandatory? */, READ_ONLY, Unit.NONE)); private final Point point; @@ -2518,43 +2540,43 @@ public BitPoint get() { public static enum S128 implements SunSpecPoint { AR_GRA_MOD(new EnumPoint("S128_AR_GRA_MOD", "ArGraMod", // "Indicates if gradients trend toward zero at the edges of the deadband or trend toward zero at the center of the deadband.", // - EnumPoint.Type.ENUM16, true /* mandatory? */, AccessMode.READ_WRITE, S128_ArGraMod.values())), // + ENUM16, true /* mandatory? */, READ_WRITE, S128_ArGraMod.values())), // AR_GRA_SAG(new ScaledValuePoint("S128_AR_GRA_SAG", "ArGraSag", // "The gradient used to increase capacitive dynamic current. A value of 0 indicates no additional reactive current support.", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_WRITE, Unit.NONE, "ArGra_SF")), // + UINT16, true /* mandatory? */, READ_WRITE, Unit.NONE, "ArGra_SF")), // AR_GRA_SWELL(new ScaledValuePoint("S128_AR_GRA_SWELL", "ArGraSwell", // "The gradient used to increase inductive dynamic current. A value of 0 indicates no additional reactive current support.", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_WRITE, Unit.NONE, "ArGra_SF")), // + UINT16, true /* mandatory? */, READ_WRITE, Unit.NONE, "ArGra_SF")), // MOD_ENA(new BitFieldPoint("S128_MOD_ENA", "ModEna", // "Activate dynamic reactive current model", // - BitFieldPoint.Type.BITFIELD16, true /* mandatory? */, AccessMode.READ_WRITE, S128_ModEna.values())), // + BITFIELD16, true /* mandatory? */, READ_WRITE, S128_ModEna.values())), // FIL_TMS(new ValuePoint("S128_FIL_TMS", "FilTms", // "The time window used to calculate the moving average voltage.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.SECONDS)), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.SECONDS)), // DB_V_MIN(new ScaledValuePoint("S128_DB_V_MIN", "DbVMin", // "The lower delta voltage limit for which negative voltage deviations less than this value no dynamic vars are produced.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.PERCENT, "VRefPct_SF")), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.PERCENT, "VRefPct_SF")), // DB_V_MAX(new ScaledValuePoint("S128_DB_V_MAX", "DbVMax", // "The upper delta voltage limit for which positive voltage deviations less than this value no dynamic current produced.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.PERCENT, "VRefPct_SF")), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.PERCENT, "VRefPct_SF")), // BLK_ZN_V(new ScaledValuePoint("S128_BLK_ZN_V", "BlkZnV", // "Block zone voltage which defines a lower voltage boundary below which no dynamic current is produced.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.PERCENT, "VRefPct_SF")), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.PERCENT, "VRefPct_SF")), // HYS_BLK_ZN_V(new ScaledValuePoint("S128_HYS_BLK_ZN_V", "HysBlkZnV", // "Hysteresis voltage used with BlkZnV.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.PERCENT, "VRefPct_SF")), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.PERCENT, "VRefPct_SF")), // BLK_ZN_TMMS(new ValuePoint("S128_BLK_ZN_TMMS", "BlkZnTmms", // "Block zone time the time before which reactive current support remains active regardless of how low the voltage drops.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.MILLISECONDS)), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.MILLISECONDS)), // HOLD_TMMS(new ValuePoint("S128_HOLD_TMMS", "HoldTmms", // "Hold time during which reactive current support continues after the average voltage has entered the dead zone.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.MILLISECONDS)), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.MILLISECONDS)), // AR_GRA_S_F(new ScaleFactorPoint("S128_AR_GRA_S_F", "ArGra_SF", // "Scale factor for the gradients.")), // V_REF_PCT_S_F(new ScaleFactorPoint("S128_V_REF_PCT_S_F", "VRefPct_SF", // "Scale factor for the voltage zone and limit settings.")), // PAD(new ValuePoint("S128_PAD", "", "", // - ValuePoint.Type.PAD, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)); + ValuePoint.Type.PAD, false /* mandatory? */, READ_ONLY, Unit.NONE)); private final Point point; @@ -2615,25 +2637,25 @@ public BitPoint get() { public static enum S145 implements SunSpecPoint { NOM_RMP_UP_RTE(new ScaledValuePoint("S145_NOM_RMP_UP_RTE", "Ramp Up Rate", // "Ramp up rate as a percentage of max current.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.NONE, "Rmp_SF")), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.NONE, "Rmp_SF")), // NOM_RMP_DN_RTE(new ScaledValuePoint("S145_NOM_RMP_DN_RTE", "NomRmpDnRte", // "Ramp down rate as a percentage of max current.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.NONE, "Rmp_SF")), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.NONE, "Rmp_SF")), // EMG_RMP_UP_RTE(new ScaledValuePoint("S145_EMG_RMP_UP_RTE", "Emergency Ramp Up Rate", // "Emergency ramp up rate as a percentage of max current.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.NONE, "Rmp_SF")), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.NONE, "Rmp_SF")), // EMG_RMP_DN_RTE(new ScaledValuePoint("S145_EMG_RMP_DN_RTE", "Emergency Ramp Down Rate", // "Emergency ramp down rate as a percentage of max current.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.NONE, "Rmp_SF")), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.NONE, "Rmp_SF")), // CONN_RMP_UP_RTE(new ScaledValuePoint("S145_CONN_RMP_UP_RTE", "Connect Ramp Up Rate", // "Connect ramp up rate as a percentage of max current.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.NONE, "Rmp_SF")), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.NONE, "Rmp_SF")), // CONN_RMP_DN_RTE(new ScaledValuePoint("S145_CONN_RMP_DN_RTE", "Connect Ramp Down Rate", // "Connect ramp down rate as a percentage of max current.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.NONE, "Rmp_SF")), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.NONE, "Rmp_SF")), // A_GRA(new ScaledValuePoint("S145_A_GRA", "Default Ramp Rate", // "Ramp rate specified in percent of max current.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.NONE, "Rmp_SF")), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.NONE, "Rmp_SF")), // RMP_S_F(new ScaleFactorPoint("S145_RMP_S_F", "Ramp Rate Scale Factor", // "Ramp Rate Scale Factor")); @@ -2652,174 +2674,186 @@ public Point get() { public static enum S201 implements SunSpecPoint { A(new ScaledValuePoint("S201_A", "Amps", // "Total AC Current", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE, "A_SF")), // + INT16, true /* mandatory? */, READ_ONLY, Unit.AMPERE, "A_SF")), // APH_A(new ScaledValuePoint("S201_APH_A", "Amps PhaseA", // "Phase A Current", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE, "A_SF")), // + INT16, true /* mandatory? */, READ_ONLY, Unit.AMPERE, "A_SF")), // APH_B(new ScaledValuePoint("S201_APH_B", "Amps PhaseB", // "Phase B Current", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE, "A_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.AMPERE, "A_SF")), // APH_C(new ScaledValuePoint("S201_APH_C", "Amps PhaseC", // "Phase C Current", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE, "A_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.AMPERE, "A_SF")), // A_SF(new ScaleFactorPoint("S201_A_SF", "", // "Current scale factor")), // PH_V(new ScaledValuePoint("S201_PH_V", "Voltage LN", // "Line to Neutral AC Voltage (average of active phases)", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // PH_VPH_A(new ScaledValuePoint("S201_PH_VPH_A", "Phase Voltage AN", // "Phase Voltage AN", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // PH_VPH_B(new ScaledValuePoint("S201_PH_VPH_B", "Phase Voltage BN", // "Phase Voltage BN", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // PH_VPH_C(new ScaledValuePoint("S201_PH_VPH_C", "Phase Voltage CN", // "Phase Voltage CN", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // PPV(new ScaledValuePoint("S201_PPV", "Voltage LL", // "Line to Line AC Voltage (average of active phases)", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // P_P_VPH_A_B(new ScaledValuePoint("S201_P_P_VPH_A_B", "Phase Voltage AB", // "Phase Voltage AB", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // P_P_VPH_B_C(new ScaledValuePoint("S201_P_P_VPH_B_C", "Phase Voltage BC", // "Phase Voltage BC", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // P_P_VPH_C_A(new ScaledValuePoint("S201_P_P_VPH_C_A", "Phase Voltage CA", // "Phase Voltage CA", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // V_SF(new ScaleFactorPoint("S201_V_SF", "", // "Voltage scale factor")), // HZ(new ScaledValuePoint("S201_HZ", "Hz", // "Frequency", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.HERTZ, "Hz_SF")), // + INT16, true /* mandatory? */, READ_ONLY, Unit.HERTZ, "Hz_SF")), // HZ_S_F(new ScaleFactorPoint("S201_HZ_S_F", "", // "Frequency scale factor")), // W(new ScaledValuePoint("S201_W", "Watts", // "Total Real Power", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.WATT, "W_SF")), // + INT16, true /* mandatory? */, READ_ONLY, Unit.WATT, "W_SF")), // WPH_A(new ScaledValuePoint("S201_WPH_A", "Watts phase A", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.WATT, "W_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.WATT, "W_SF")), // WPH_B(new ScaledValuePoint("S201_WPH_B", "Watts phase B", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.WATT, "W_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.WATT, "W_SF")), // WPH_C(new ScaledValuePoint("S201_WPH_C", "Watts phase C", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.WATT, "W_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.WATT, "W_SF")), // W_SF(new ScaleFactorPoint("S201_W_SF", "", // "Real Power scale factor")), // VA(new ScaledValuePoint("S201_VA", "VA", // "AC Apparent Power", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE, "VA_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE, "VA_SF")), // V_APH_A(new ScaledValuePoint("S201_V_APH_A", "VA phase A", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE, "VA_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE, "VA_SF")), // V_APH_B(new ScaledValuePoint("S201_V_APH_B", "VA phase B", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE, "VA_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE, "VA_SF")), // V_APH_C(new ScaledValuePoint("S201_V_APH_C", "VA phase C", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE, "VA_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE, "VA_SF")), // VA_SF(new ScaleFactorPoint("S201_VA_SF", "", // "Apparent Power scale factor")), // VAR(new ScaledValuePoint("S201_VAR", "VAR", // "Reactive Power", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "VAR_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "VAR_SF")), // V_A_RPH_A(new ScaledValuePoint("S201_V_A_RPH_A", "VAR phase A", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "VAR_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "VAR_SF")), // V_A_RPH_B(new ScaledValuePoint("S201_V_A_RPH_B", "VAR phase B", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "VAR_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "VAR_SF")), // V_A_RPH_C(new ScaledValuePoint("S201_V_A_RPH_C", "VAR phase C", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "VAR_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "VAR_SF")), // VAR_SF(new ScaleFactorPoint("S201_VAR_SF", "", // "Reactive Power scale factor")), // PF(new ScaledValuePoint("S201_PF", "PF", // "Power Factor", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE, "PF_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.NONE, "PF_SF")), // P_FPH_A(new ScaledValuePoint("S201_P_FPH_A", "PF phase A", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE, "PF_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.NONE, "PF_SF")), // P_FPH_B(new ScaledValuePoint("S201_P_FPH_B", "PF phase B", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE, "PF_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.NONE, "PF_SF")), // P_FPH_C(new ScaledValuePoint("S201_P_FPH_C", "PF phase C", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE, "PF_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.NONE, "PF_SF")), // PF_SF(new ScaleFactorPoint("S201_PF_SF", "", // "Power Factor scale factor")), // TOT_WH_EXP(new ScaledValuePoint("S201_TOT_WH_EXP", "Total Watt-hours Exported", // "Total Real Energy Exported", // - ValuePoint.Type.ACC32, true /* mandatory? */, AccessMode.READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // + ACC32, true /* mandatory? */, READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // TOT_WH_EXP_PH_A(new ScaledValuePoint("S201_TOT_WH_EXP_PH_A", "Total Watt-hours Exported phase A", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // TOT_WH_EXP_PH_B(new ScaledValuePoint("S201_TOT_WH_EXP_PH_B", "Total Watt-hours Exported phase B", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // TOT_WH_EXP_PH_C(new ScaledValuePoint("S201_TOT_WH_EXP_PH_C", "Total Watt-hours Exported phase C", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // TOT_WH_IMP(new ScaledValuePoint("S201_TOT_WH_IMP", "Total Watt-hours Imported", // "Total Real Energy Imported", // - ValuePoint.Type.ACC32, true /* mandatory? */, AccessMode.READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // + ACC32, true /* mandatory? */, READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // TOT_WH_IMP_PH_A(new ScaledValuePoint("S201_TOT_WH_IMP_PH_A", "Total Watt-hours Imported phase A", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // TOT_WH_IMP_PH_B(new ScaledValuePoint("S201_TOT_WH_IMP_PH_B", "Total Watt-hours Imported phase B", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // TOT_WH_IMP_PH_C(new ScaledValuePoint("S201_TOT_WH_IMP_PH_C", "Total Watt-hours Imported phase C", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // TOT_WH_S_F(new ScaleFactorPoint("S201_TOT_WH_S_F", "", // "Real Energy scale factor")), // TOT_V_AH_EXP(new ScaledValuePoint("S201_TOT_V_AH_EXP", "Total VA-hours Exported", // "Total Apparent Energy Exported", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // TOT_V_AH_EXP_PH_A(new ScaledValuePoint("S201_TOT_V_AH_EXP_PH_A", "Total VA-hours Exported phase A", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // TOT_V_AH_EXP_PH_B(new ScaledValuePoint("S201_TOT_V_AH_EXP_PH_B", "Total VA-hours Exported phase B", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // TOT_V_AH_EXP_PH_C(new ScaledValuePoint("S201_TOT_V_AH_EXP_PH_C", "Total VA-hours Exported phase C", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // TOT_V_AH_IMP(new ScaledValuePoint("S201_TOT_V_AH_IMP", "Total VA-hours Imported", // "Total Apparent Energy Imported", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // TOT_V_AH_IMP_PH_A(new ScaledValuePoint("S201_TOT_V_AH_IMP_PH_A", "Total VA-hours Imported phase A", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // TOT_V_AH_IMP_PH_B(new ScaledValuePoint("S201_TOT_V_AH_IMP_PH_B", "Total VA-hours Imported phase B", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // TOT_V_AH_IMP_PH_C(new ScaledValuePoint("S201_TOT_V_AH_IMP_PH_C", "Total VA-hours Imported phase C", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // TOT_V_AH_S_F(new ScaleFactorPoint("S201_TOT_V_AH_S_F", "", // "Apparent Energy scale factor")), // TOT_V_ARH_IMP_Q1(new ScaledValuePoint("S201_TOT_V_ARH_IMP_Q1", "Total VAR-hours Imported Q1", // "Total Reactive Energy Imported Quadrant 1", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // - TOT_V_ARH_IMP_Q1_PH_A(new ScaledValuePoint("S201_TOT_V_ARH_IMP_Q1_PH_A", "Total VAr-hours Imported Q1 phase A", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // - TOT_V_ARH_IMP_Q1_PH_B(new ScaledValuePoint("S201_TOT_V_ARH_IMP_Q1_PH_B", "Total VAr-hours Imported Q1 phase B", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // - TOT_V_ARH_IMP_Q1_PH_C(new ScaledValuePoint("S201_TOT_V_ARH_IMP_Q1_PH_C", "Total VAr-hours Imported Q1 phase C", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + TOT_V_ARH_IMP_Q1_PH_A( + new ScaledValuePoint("S201_TOT_V_ARH_IMP_Q1_PH_A", "Total VAr-hours Imported Q1 phase A", "", // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + TOT_V_ARH_IMP_Q1_PH_B( + new ScaledValuePoint("S201_TOT_V_ARH_IMP_Q1_PH_B", "Total VAr-hours Imported Q1 phase B", "", // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + TOT_V_ARH_IMP_Q1_PH_C( + new ScaledValuePoint("S201_TOT_V_ARH_IMP_Q1_PH_C", "Total VAr-hours Imported Q1 phase C", "", // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // TOT_V_ARH_IMP_Q2(new ScaledValuePoint("S201_TOT_V_ARH_IMP_Q2", "Total VAr-hours Imported Q2", // "Total Reactive Power Imported Quadrant 2", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // - TOT_V_ARH_IMP_Q2_PH_A(new ScaledValuePoint("S201_TOT_V_ARH_IMP_Q2_PH_A", "Total VAr-hours Imported Q2 phase A", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // - TOT_V_ARH_IMP_Q2_PH_B(new ScaledValuePoint("S201_TOT_V_ARH_IMP_Q2_PH_B", "Total VAr-hours Imported Q2 phase B", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // - TOT_V_ARH_IMP_Q2_PH_C(new ScaledValuePoint("S201_TOT_V_ARH_IMP_Q2_PH_C", "Total VAr-hours Imported Q2 phase C", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + TOT_V_ARH_IMP_Q2_PH_A( + new ScaledValuePoint("S201_TOT_V_ARH_IMP_Q2_PH_A", "Total VAr-hours Imported Q2 phase A", "", // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + TOT_V_ARH_IMP_Q2_PH_B( + new ScaledValuePoint("S201_TOT_V_ARH_IMP_Q2_PH_B", "Total VAr-hours Imported Q2 phase B", "", // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + TOT_V_ARH_IMP_Q2_PH_C( + new ScaledValuePoint("S201_TOT_V_ARH_IMP_Q2_PH_C", "Total VAr-hours Imported Q2 phase C", "", // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // TOT_V_ARH_EXP_Q3(new ScaledValuePoint("S201_TOT_V_ARH_EXP_Q3", "Total VAr-hours Exported Q3", // "Total Reactive Power Exported Quadrant 3", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // - TOT_V_ARH_EXP_Q3_PH_A(new ScaledValuePoint("S201_TOT_V_ARH_EXP_Q3_PH_A", "Total VAr-hours Exported Q3 phase A", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // - TOT_V_ARH_EXP_Q3_PH_B(new ScaledValuePoint("S201_TOT_V_ARH_EXP_Q3_PH_B", "Total VAr-hours Exported Q3 phase B", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // - TOT_V_ARH_EXP_Q3_PH_C(new ScaledValuePoint("S201_TOT_V_ARH_EXP_Q3_PH_C", "Total VAr-hours Exported Q3 phase C", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + TOT_V_ARH_EXP_Q3_PH_A( + new ScaledValuePoint("S201_TOT_V_ARH_EXP_Q3_PH_A", "Total VAr-hours Exported Q3 phase A", "", // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + TOT_V_ARH_EXP_Q3_PH_B( + new ScaledValuePoint("S201_TOT_V_ARH_EXP_Q3_PH_B", "Total VAr-hours Exported Q3 phase B", "", // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + TOT_V_ARH_EXP_Q3_PH_C( + new ScaledValuePoint("S201_TOT_V_ARH_EXP_Q3_PH_C", "Total VAr-hours Exported Q3 phase C", "", // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // TOT_V_ARH_EXP_Q4(new ScaledValuePoint("S201_TOT_V_ARH_EXP_Q4", "Total VAr-hours Exported Q4", // "Total Reactive Power Exported Quadrant 4", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // - TOT_V_ARH_EXP_Q4_PH_A(new ScaledValuePoint("S201_TOT_V_ARH_EXP_Q4_PH_A", "Total VAr-hours Exported Q4 Imported phase A", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // - TOT_V_ARH_EXP_Q4_PH_B(new ScaledValuePoint("S201_TOT_V_ARH_EXP_Q4_PH_B", "Total VAr-hours Exported Q4 Imported phase B", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // - TOT_V_ARH_EXP_Q4_PH_C(new ScaledValuePoint("S201_TOT_V_ARH_EXP_Q4_PH_C", "Total VAr-hours Exported Q4 Imported phase C", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + TOT_V_ARH_EXP_Q4_PH_A( + new ScaledValuePoint("S201_TOT_V_ARH_EXP_Q4_PH_A", "Total VAr-hours Exported Q4 Imported phase A", "", // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + TOT_V_ARH_EXP_Q4_PH_B( + new ScaledValuePoint("S201_TOT_V_ARH_EXP_Q4_PH_B", "Total VAr-hours Exported Q4 Imported phase B", "", // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + TOT_V_ARH_EXP_Q4_PH_C( + new ScaledValuePoint("S201_TOT_V_ARH_EXP_Q4_PH_C", "Total VAr-hours Exported Q4 Imported phase C", "", // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // TOT_V_ARH_S_F(new ScaleFactorPoint("S201_TOT_V_ARH_S_F", "", // "Reactive Energy scale factor")), // EVT(new BitFieldPoint("S201_EVT", "Events", // "Meter Event Flags", // - BitFieldPoint.Type.BITFIELD32, true /* mandatory? */, AccessMode.READ_ONLY, S201_Evt.values())); + BITFIELD32, true /* mandatory? */, READ_ONLY, S201_Evt.values())); private final Point point; @@ -2871,174 +2905,186 @@ public BitPoint get() { public static enum S202 implements SunSpecPoint { A(new ScaledValuePoint("S202_A", "Amps", // "Total AC Current", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE, "A_SF")), // + INT16, true /* mandatory? */, READ_ONLY, Unit.AMPERE, "A_SF")), // APH_A(new ScaledValuePoint("S202_APH_A", "Amps PhaseA", // "Phase A Current", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE, "A_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.AMPERE, "A_SF")), // APH_B(new ScaledValuePoint("S202_APH_B", "Amps PhaseB", // "Phase B Current", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE, "A_SF")), // + INT16, true /* mandatory? */, READ_ONLY, Unit.AMPERE, "A_SF")), // APH_C(new ScaledValuePoint("S202_APH_C", "Amps PhaseC", // "Phase C Current", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE, "A_SF")), // + INT16, true /* mandatory? */, READ_ONLY, Unit.AMPERE, "A_SF")), // A_SF(new ScaleFactorPoint("S202_A_SF", "", // "Current scale factor")), // PH_V(new ScaledValuePoint("S202_PH_V", "Voltage LN", // "Line to Neutral AC Voltage (average of active phases)", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + INT16, true /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // PH_VPH_A(new ScaledValuePoint("S202_PH_VPH_A", "Phase Voltage AN", // "Phase Voltage AN", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + INT16, true /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // PH_VPH_B(new ScaledValuePoint("S202_PH_VPH_B", "Phase Voltage BN", // "Phase Voltage BN", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + INT16, true /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // PH_VPH_C(new ScaledValuePoint("S202_PH_VPH_C", "Phase Voltage CN", // "Phase Voltage CN", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // PPV(new ScaledValuePoint("S202_PPV", "Voltage LL", // "Line to Line AC Voltage (average of active phases)", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + INT16, true /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // PH_VPH_A_B(new ScaledValuePoint("S202_PH_VPH_A_B", "Phase Voltage AB", // "Phase Voltage AB", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + INT16, true /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // PH_VPH_B_C(new ScaledValuePoint("S202_PH_VPH_B_C", "Phase Voltage BC", // "Phase Voltage BC", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // PH_VPH_C_A(new ScaledValuePoint("S202_PH_VPH_C_A", "Phase Voltage CA", // "Phase Voltage CA", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // V_SF(new ScaleFactorPoint("S202_V_SF", "", // "Voltage scale factor")), // HZ(new ScaledValuePoint("S202_HZ", "Hz", // "Frequency", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.HERTZ, "Hz_SF")), // + INT16, true /* mandatory? */, READ_ONLY, Unit.HERTZ, "Hz_SF")), // HZ_S_F(new ScaleFactorPoint("S202_HZ_S_F", "", // "Frequency scale factor")), // W(new ScaledValuePoint("S202_W", "Watts", // "Total Real Power", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.WATT, "W_SF")), // + INT16, true /* mandatory? */, READ_ONLY, Unit.WATT, "W_SF")), // WPH_A(new ScaledValuePoint("S202_WPH_A", "Watts phase A", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.WATT, "W_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.WATT, "W_SF")), // WPH_B(new ScaledValuePoint("S202_WPH_B", "Watts phase B", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.WATT, "W_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.WATT, "W_SF")), // WPH_C(new ScaledValuePoint("S202_WPH_C", "Watts phase C", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.WATT, "W_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.WATT, "W_SF")), // W_SF(new ScaleFactorPoint("S202_W_SF", "", // "Real Power scale factor")), // VA(new ScaledValuePoint("S202_VA", "VA", // "AC Apparent Power", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE, "VA_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE, "VA_SF")), // V_APH_A(new ScaledValuePoint("S202_V_APH_A", "VA phase A", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE, "VA_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE, "VA_SF")), // V_APH_B(new ScaledValuePoint("S202_V_APH_B", "VA phase B", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE, "VA_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE, "VA_SF")), // V_APH_C(new ScaledValuePoint("S202_V_APH_C", "VA phase C", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE, "VA_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE, "VA_SF")), // VA_SF(new ScaleFactorPoint("S202_VA_SF", "", // "Apparent Power scale factor")), // VAR(new ScaledValuePoint("S202_VAR", "VAR", // "Reactive Power", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "VAR_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "VAR_SF")), // V_A_RPH_A(new ScaledValuePoint("S202_V_A_RPH_A", "VAR phase A", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "VAR_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "VAR_SF")), // V_A_RPH_B(new ScaledValuePoint("S202_V_A_RPH_B", "VAR phase B", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "VAR_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "VAR_SF")), // V_A_RPH_C(new ScaledValuePoint("S202_V_A_RPH_C", "VAR phase C", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "VAR_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "VAR_SF")), // VAR_SF(new ScaleFactorPoint("S202_VAR_SF", "", // "Reactive Power scale factor")), // PF(new ScaledValuePoint("S202_PF", "PF", // "Power Factor", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE, "PF_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.NONE, "PF_SF")), // P_FPH_A(new ScaledValuePoint("S202_P_FPH_A", "PF phase A", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE, "PF_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.NONE, "PF_SF")), // P_FPH_B(new ScaledValuePoint("S202_P_FPH_B", "PF phase B", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE, "PF_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.NONE, "PF_SF")), // P_FPH_C(new ScaledValuePoint("S202_P_FPH_C", "PF phase C", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE, "PF_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.NONE, "PF_SF")), // PF_SF(new ScaleFactorPoint("S202_PF_SF", "", // "Power Factor scale factor")), // TOT_WH_EXP(new ScaledValuePoint("S202_TOT_WH_EXP", "Total Watt-hours Exported", // "Total Real Energy Exported", // - ValuePoint.Type.ACC32, true /* mandatory? */, AccessMode.READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // + ACC32, true /* mandatory? */, READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // TOT_WH_EXP_PH_A(new ScaledValuePoint("S202_TOT_WH_EXP_PH_A", "Total Watt-hours Exported phase A", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // TOT_WH_EXP_PH_B(new ScaledValuePoint("S202_TOT_WH_EXP_PH_B", "Total Watt-hours Exported phase B", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // TOT_WH_EXP_PH_C(new ScaledValuePoint("S202_TOT_WH_EXP_PH_C", "Total Watt-hours Exported phase C", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // TOT_WH_IMP(new ScaledValuePoint("S202_TOT_WH_IMP", "Total Watt-hours Imported", // "Total Real Energy Imported", // - ValuePoint.Type.ACC32, true /* mandatory? */, AccessMode.READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // + ACC32, true /* mandatory? */, READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // TOT_WH_IMP_PH_A(new ScaledValuePoint("S202_TOT_WH_IMP_PH_A", "Total Watt-hours Imported phase A", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // TOT_WH_IMP_PH_B(new ScaledValuePoint("S202_TOT_WH_IMP_PH_B", "Total Watt-hours Imported phase B", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // TOT_WH_IMP_PH_C(new ScaledValuePoint("S202_TOT_WH_IMP_PH_C", "Total Watt-hours Imported phase C", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // TOT_WH_S_F(new ScaleFactorPoint("S202_TOT_WH_S_F", "", // "Real Energy scale factor")), // TOT_V_AH_EXP(new ScaledValuePoint("S202_TOT_V_AH_EXP", "Total VA-hours Exported", // "Total Apparent Energy Exported", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // TOT_V_AH_EXP_PH_A(new ScaledValuePoint("S202_TOT_V_AH_EXP_PH_A", "Total VA-hours Exported phase A", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // TOT_V_AH_EXP_PH_B(new ScaledValuePoint("S202_TOT_V_AH_EXP_PH_B", "Total VA-hours Exported phase B", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // TOT_V_AH_EXP_PH_C(new ScaledValuePoint("S202_TOT_V_AH_EXP_PH_C", "Total VA-hours Exported phase C", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // TOT_V_AH_IMP(new ScaledValuePoint("S202_TOT_V_AH_IMP", "Total VA-hours Imported", // "Total Apparent Energy Imported", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // TOT_V_AH_IMP_PH_A(new ScaledValuePoint("S202_TOT_V_AH_IMP_PH_A", "Total VA-hours Imported phase A", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // TOT_V_AH_IMP_PH_B(new ScaledValuePoint("S202_TOT_V_AH_IMP_PH_B", "Total VA-hours Imported phase B", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // TOT_V_AH_IMP_PH_C(new ScaledValuePoint("S202_TOT_V_AH_IMP_PH_C", "Total VA-hours Imported phase C", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // TOT_V_AH_S_F(new ScaleFactorPoint("S202_TOT_V_AH_S_F", "", // "Apparent Energy scale factor")), // TOT_V_ARH_IMP_Q1(new ScaledValuePoint("S202_TOT_V_ARH_IMP_Q1", "Total VAR-hours Imported Q1", // "Total Reactive Energy Imported Quadrant 1", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // - TOT_V_ARH_IMP_Q1_PH_A(new ScaledValuePoint("S202_TOT_V_ARH_IMP_Q1_PH_A", "Total VAr-hours Imported Q1 phase A", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // - TOT_V_ARH_IMP_Q1_PH_B(new ScaledValuePoint("S202_TOT_V_ARH_IMP_Q1_PH_B", "Total VAr-hours Imported Q1 phase B", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // - TOT_V_ARH_IMP_Q1_PH_C(new ScaledValuePoint("S202_TOT_V_ARH_IMP_Q1_PH_C", "Total VAr-hours Imported Q1 phase C", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + TOT_V_ARH_IMP_Q1_PH_A( + new ScaledValuePoint("S202_TOT_V_ARH_IMP_Q1_PH_A", "Total VAr-hours Imported Q1 phase A", "", // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + TOT_V_ARH_IMP_Q1_PH_B( + new ScaledValuePoint("S202_TOT_V_ARH_IMP_Q1_PH_B", "Total VAr-hours Imported Q1 phase B", "", // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + TOT_V_ARH_IMP_Q1_PH_C( + new ScaledValuePoint("S202_TOT_V_ARH_IMP_Q1_PH_C", "Total VAr-hours Imported Q1 phase C", "", // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // TOT_V_ARH_IMP_Q2(new ScaledValuePoint("S202_TOT_V_ARH_IMP_Q2", "Total VAr-hours Imported Q2", // "Total Reactive Power Imported Quadrant 2", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // - TOT_V_ARH_IMP_Q2_PH_A(new ScaledValuePoint("S202_TOT_V_ARH_IMP_Q2_PH_A", "Total VAr-hours Imported Q2 phase A", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // - TOT_V_ARH_IMP_Q2_PH_B(new ScaledValuePoint("S202_TOT_V_ARH_IMP_Q2_PH_B", "Total VAr-hours Imported Q2 phase B", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // - TOT_V_ARH_IMP_Q2_PH_C(new ScaledValuePoint("S202_TOT_V_ARH_IMP_Q2_PH_C", "Total VAr-hours Imported Q2 phase C", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + TOT_V_ARH_IMP_Q2_PH_A( + new ScaledValuePoint("S202_TOT_V_ARH_IMP_Q2_PH_A", "Total VAr-hours Imported Q2 phase A", "", // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + TOT_V_ARH_IMP_Q2_PH_B( + new ScaledValuePoint("S202_TOT_V_ARH_IMP_Q2_PH_B", "Total VAr-hours Imported Q2 phase B", "", // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + TOT_V_ARH_IMP_Q2_PH_C( + new ScaledValuePoint("S202_TOT_V_ARH_IMP_Q2_PH_C", "Total VAr-hours Imported Q2 phase C", "", // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // TOT_V_ARH_EXP_Q3(new ScaledValuePoint("S202_TOT_V_ARH_EXP_Q3", "Total VAr-hours Exported Q3", // "Total Reactive Power Exported Quadrant 3", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // - TOT_V_ARH_EXP_Q3_PH_A(new ScaledValuePoint("S202_TOT_V_ARH_EXP_Q3_PH_A", "Total VAr-hours Exported Q3 phase A", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // - TOT_V_ARH_EXP_Q3_PH_B(new ScaledValuePoint("S202_TOT_V_ARH_EXP_Q3_PH_B", "Total VAr-hours Exported Q3 phase B", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // - TOT_V_ARH_EXP_Q3_PH_C(new ScaledValuePoint("S202_TOT_V_ARH_EXP_Q3_PH_C", "Total VAr-hours Exported Q3 phase C", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + TOT_V_ARH_EXP_Q3_PH_A( + new ScaledValuePoint("S202_TOT_V_ARH_EXP_Q3_PH_A", "Total VAr-hours Exported Q3 phase A", "", // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + TOT_V_ARH_EXP_Q3_PH_B( + new ScaledValuePoint("S202_TOT_V_ARH_EXP_Q3_PH_B", "Total VAr-hours Exported Q3 phase B", "", // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + TOT_V_ARH_EXP_Q3_PH_C( + new ScaledValuePoint("S202_TOT_V_ARH_EXP_Q3_PH_C", "Total VAr-hours Exported Q3 phase C", "", // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // TOT_V_ARH_EXP_Q4(new ScaledValuePoint("S202_TOT_V_ARH_EXP_Q4", "Total VAr-hours Exported Q4", // "Total Reactive Power Exported Quadrant 4", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // - TOT_V_ARH_EXP_Q4_PH_A(new ScaledValuePoint("S202_TOT_V_ARH_EXP_Q4_PH_A", "Total VAr-hours Exported Q4 Imported phase A", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // - TOT_V_ARH_EXP_Q4_PH_B(new ScaledValuePoint("S202_TOT_V_ARH_EXP_Q4_PH_B", "Total VAr-hours Exported Q4 Imported phase B", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // - TOT_V_ARH_EXP_Q4_PH_C(new ScaledValuePoint("S202_TOT_V_ARH_EXP_Q4_PH_C", "Total VAr-hours Exported Q4 Imported phase C", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + TOT_V_ARH_EXP_Q4_PH_A( + new ScaledValuePoint("S202_TOT_V_ARH_EXP_Q4_PH_A", "Total VAr-hours Exported Q4 Imported phase A", "", // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + TOT_V_ARH_EXP_Q4_PH_B( + new ScaledValuePoint("S202_TOT_V_ARH_EXP_Q4_PH_B", "Total VAr-hours Exported Q4 Imported phase B", "", // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + TOT_V_ARH_EXP_Q4_PH_C( + new ScaledValuePoint("S202_TOT_V_ARH_EXP_Q4_PH_C", "Total VAr-hours Exported Q4 Imported phase C", "", // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // TOT_V_ARH_S_F(new ScaleFactorPoint("S202_TOT_V_ARH_S_F", "", // "Reactive Energy scale factor")), // EVT(new BitFieldPoint("S202_EVT", "Events", // "Meter Event Flags", // - BitFieldPoint.Type.BITFIELD32, true /* mandatory? */, AccessMode.READ_ONLY, S202_Evt.values())); + BITFIELD32, true /* mandatory? */, READ_ONLY, S202_Evt.values())); private final Point point; @@ -3098,174 +3144,186 @@ public BitPoint get() { public static enum S203 implements SunSpecPoint { A(new ScaledValuePoint("S203_A", "Amps", // "Total AC Current", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE, "A_SF")), // + INT16, true /* mandatory? */, READ_ONLY, Unit.AMPERE, "A_SF")), // APH_A(new ScaledValuePoint("S203_APH_A", "Amps PhaseA", // "Phase A Current", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE, "A_SF")), // + INT16, true /* mandatory? */, READ_ONLY, Unit.AMPERE, "A_SF")), // APH_B(new ScaledValuePoint("S203_APH_B", "Amps PhaseB", // "Phase B Current", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE, "A_SF")), // + INT16, true /* mandatory? */, READ_ONLY, Unit.AMPERE, "A_SF")), // APH_C(new ScaledValuePoint("S203_APH_C", "Amps PhaseC", // "Phase C Current", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE, "A_SF")), // + INT16, true /* mandatory? */, READ_ONLY, Unit.AMPERE, "A_SF")), // A_SF(new ScaleFactorPoint("S203_A_SF", "", // "Current scale factor")), // PH_V(new ScaledValuePoint("S203_PH_V", "Voltage LN", // "Line to Neutral AC Voltage (average of active phases)", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + INT16, true /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // PH_VPH_A(new ScaledValuePoint("S203_PH_VPH_A", "Phase Voltage AN", // "Phase Voltage AN", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + INT16, true /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // PH_VPH_B(new ScaledValuePoint("S203_PH_VPH_B", "Phase Voltage BN", // "Phase Voltage BN", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + INT16, true /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // PH_VPH_C(new ScaledValuePoint("S203_PH_VPH_C", "Phase Voltage CN", // "Phase Voltage CN", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + INT16, true /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // PPV(new ScaledValuePoint("S203_PPV", "Voltage LL", // "Line to Line AC Voltage (average of active phases)", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + INT16, true /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // PH_VPH_A_B(new ScaledValuePoint("S203_PH_VPH_A_B", "Phase Voltage AB", // "Phase Voltage AB", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + INT16, true /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // PH_VPH_B_C(new ScaledValuePoint("S203_PH_VPH_B_C", "Phase Voltage BC", // "Phase Voltage BC", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + INT16, true /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // PH_VPH_C_A(new ScaledValuePoint("S203_PH_VPH_C_A", "Phase Voltage CA", // "Phase Voltage CA", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + INT16, true /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // V_SF(new ScaleFactorPoint("S203_V_SF", "", // "Voltage scale factor")), // HZ(new ScaledValuePoint("S203_HZ", "Hz", // "Frequency", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.HERTZ, "Hz_SF")), // + INT16, true /* mandatory? */, READ_ONLY, Unit.HERTZ, "Hz_SF")), // HZ_S_F(new ScaleFactorPoint("S203_HZ_S_F", "", // "Frequency scale factor")), // W(new ScaledValuePoint("S203_W", "Watts", // "Total Real Power", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.WATT, "W_SF")), // + INT16, true /* mandatory? */, READ_ONLY, Unit.WATT, "W_SF")), // WPH_A(new ScaledValuePoint("S203_WPH_A", "Watts phase A", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.WATT, "W_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.WATT, "W_SF")), // WPH_B(new ScaledValuePoint("S203_WPH_B", "Watts phase B", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.WATT, "W_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.WATT, "W_SF")), // WPH_C(new ScaledValuePoint("S203_WPH_C", "Watts phase C", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.WATT, "W_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.WATT, "W_SF")), // W_SF(new ScaleFactorPoint("S203_W_SF", "", // "Real Power scale factor")), // VA(new ScaledValuePoint("S203_VA", "VA", // "AC Apparent Power", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE, "VA_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE, "VA_SF")), // V_APH_A(new ScaledValuePoint("S203_V_APH_A", "VA phase A", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE, "VA_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE, "VA_SF")), // V_APH_B(new ScaledValuePoint("S203_V_APH_B", "VA phase B", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE, "VA_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE, "VA_SF")), // V_APH_C(new ScaledValuePoint("S203_V_APH_C", "VA phase C", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE, "VA_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE, "VA_SF")), // VA_SF(new ScaleFactorPoint("S203_VA_SF", "", // "Apparent Power scale factor")), // VAR(new ScaledValuePoint("S203_VAR", "VAR", // "Reactive Power", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "VAR_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "VAR_SF")), // V_A_RPH_A(new ScaledValuePoint("S203_V_A_RPH_A", "VAR phase A", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "VAR_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "VAR_SF")), // V_A_RPH_B(new ScaledValuePoint("S203_V_A_RPH_B", "VAR phase B", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "VAR_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "VAR_SF")), // V_A_RPH_C(new ScaledValuePoint("S203_V_A_RPH_C", "VAR phase C", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "VAR_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "VAR_SF")), // VAR_SF(new ScaleFactorPoint("S203_VAR_SF", "", // "Reactive Power scale factor")), // PF(new ScaledValuePoint("S203_PF", "PF", // "Power Factor", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE, "PF_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.NONE, "PF_SF")), // P_FPH_A(new ScaledValuePoint("S203_P_FPH_A", "PF phase A", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE, "PF_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.NONE, "PF_SF")), // P_FPH_B(new ScaledValuePoint("S203_P_FPH_B", "PF phase B", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE, "PF_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.NONE, "PF_SF")), // P_FPH_C(new ScaledValuePoint("S203_P_FPH_C", "PF phase C", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE, "PF_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.NONE, "PF_SF")), // PF_SF(new ScaleFactorPoint("S203_PF_SF", "", // "Power Factor scale factor")), // TOT_WH_EXP(new ScaledValuePoint("S203_TOT_WH_EXP", "Total Watt-hours Exported", // "Total Real Energy Exported", // - ValuePoint.Type.ACC32, true /* mandatory? */, AccessMode.READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // + ACC32, true /* mandatory? */, READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // TOT_WH_EXP_PH_A(new ScaledValuePoint("S203_TOT_WH_EXP_PH_A", "Total Watt-hours Exported phase A", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // TOT_WH_EXP_PH_B(new ScaledValuePoint("S203_TOT_WH_EXP_PH_B", "Total Watt-hours Exported phase B", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // TOT_WH_EXP_PH_C(new ScaledValuePoint("S203_TOT_WH_EXP_PH_C", "Total Watt-hours Exported phase C", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // TOT_WH_IMP(new ScaledValuePoint("S203_TOT_WH_IMP", "Total Watt-hours Imported", // "Total Real Energy Imported", // - ValuePoint.Type.ACC32, true /* mandatory? */, AccessMode.READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // + ACC32, true /* mandatory? */, READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // TOT_WH_IMP_PH_A(new ScaledValuePoint("S203_TOT_WH_IMP_PH_A", "Total Watt-hours Imported phase A", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // TOT_WH_IMP_PH_B(new ScaledValuePoint("S203_TOT_WH_IMP_PH_B", "Total Watt-hours Imported phase B", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // TOT_WH_IMP_PH_C(new ScaledValuePoint("S203_TOT_WH_IMP_PH_C", "Total Watt-hours Imported phase C", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // TOT_WH_S_F(new ScaleFactorPoint("S203_TOT_WH_S_F", "", // "Real Energy scale factor")), // TOT_V_AH_EXP(new ScaledValuePoint("S203_TOT_V_AH_EXP", "Total VA-hours Exported", // "Total Apparent Energy Exported", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // TOT_V_AH_EXP_PH_A(new ScaledValuePoint("S203_TOT_V_AH_EXP_PH_A", "Total VA-hours Exported phase A", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // TOT_V_AH_EXP_PH_B(new ScaledValuePoint("S203_TOT_V_AH_EXP_PH_B", "Total VA-hours Exported phase B", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // TOT_V_AH_EXP_PH_C(new ScaledValuePoint("S203_TOT_V_AH_EXP_PH_C", "Total VA-hours Exported phase C", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // TOT_V_AH_IMP(new ScaledValuePoint("S203_TOT_V_AH_IMP", "Total VA-hours Imported", // "Total Apparent Energy Imported", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // TOT_V_AH_IMP_PH_A(new ScaledValuePoint("S203_TOT_V_AH_IMP_PH_A", "Total VA-hours Imported phase A", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // TOT_V_AH_IMP_PH_B(new ScaledValuePoint("S203_TOT_V_AH_IMP_PH_B", "Total VA-hours Imported phase B", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // TOT_V_AH_IMP_PH_C(new ScaledValuePoint("S203_TOT_V_AH_IMP_PH_C", "Total VA-hours Imported phase C", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // TOT_V_AH_S_F(new ScaleFactorPoint("S203_TOT_V_AH_S_F", "", // "Apparent Energy scale factor")), // TOT_V_ARH_IMP_Q1(new ScaledValuePoint("S203_TOT_V_ARH_IMP_Q1", "Total VAR-hours Imported Q1", // "Total Reactive Energy Imported Quadrant 1", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // - TOT_V_ARH_IMP_Q1_PH_A(new ScaledValuePoint("S203_TOT_V_ARH_IMP_Q1_PH_A", "Total VAr-hours Imported Q1 phase A", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // - TOT_V_ARH_IMP_Q1_PH_B(new ScaledValuePoint("S203_TOT_V_ARH_IMP_Q1_PH_B", "Total VAr-hours Imported Q1 phase B", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // - TOT_V_ARH_IMP_Q1_PH_C(new ScaledValuePoint("S203_TOT_V_ARH_IMP_Q1_PH_C", "Total VAr-hours Imported Q1 phase C", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + TOT_V_ARH_IMP_Q1_PH_A( + new ScaledValuePoint("S203_TOT_V_ARH_IMP_Q1_PH_A", "Total VAr-hours Imported Q1 phase A", "", // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + TOT_V_ARH_IMP_Q1_PH_B( + new ScaledValuePoint("S203_TOT_V_ARH_IMP_Q1_PH_B", "Total VAr-hours Imported Q1 phase B", "", // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + TOT_V_ARH_IMP_Q1_PH_C( + new ScaledValuePoint("S203_TOT_V_ARH_IMP_Q1_PH_C", "Total VAr-hours Imported Q1 phase C", "", // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // TOT_V_ARH_IMP_Q2(new ScaledValuePoint("S203_TOT_V_ARH_IMP_Q2", "Total VAr-hours Imported Q2", // "Total Reactive Power Imported Quadrant 2", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // - TOT_V_ARH_IMP_Q2_PH_A(new ScaledValuePoint("S203_TOT_V_ARH_IMP_Q2_PH_A", "Total VAr-hours Imported Q2 phase A", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // - TOT_V_ARH_IMP_Q2_PH_B(new ScaledValuePoint("S203_TOT_V_ARH_IMP_Q2_PH_B", "Total VAr-hours Imported Q2 phase B", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // - TOT_V_ARH_IMP_Q2_PH_C(new ScaledValuePoint("S203_TOT_V_ARH_IMP_Q2_PH_C", "Total VAr-hours Imported Q2 phase C", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + TOT_V_ARH_IMP_Q2_PH_A( + new ScaledValuePoint("S203_TOT_V_ARH_IMP_Q2_PH_A", "Total VAr-hours Imported Q2 phase A", "", // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + TOT_V_ARH_IMP_Q2_PH_B( + new ScaledValuePoint("S203_TOT_V_ARH_IMP_Q2_PH_B", "Total VAr-hours Imported Q2 phase B", "", // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + TOT_V_ARH_IMP_Q2_PH_C( + new ScaledValuePoint("S203_TOT_V_ARH_IMP_Q2_PH_C", "Total VAr-hours Imported Q2 phase C", "", // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // TOT_V_ARH_EXP_Q3(new ScaledValuePoint("S203_TOT_V_ARH_EXP_Q3", "Total VAr-hours Exported Q3", // "Total Reactive Power Exported Quadrant 3", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // - TOT_V_ARH_EXP_Q3_PH_A(new ScaledValuePoint("S203_TOT_V_ARH_EXP_Q3_PH_A", "Total VAr-hours Exported Q3 phase A", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // - TOT_V_ARH_EXP_Q3_PH_B(new ScaledValuePoint("S203_TOT_V_ARH_EXP_Q3_PH_B", "Total VAr-hours Exported Q3 phase B", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // - TOT_V_ARH_EXP_Q3_PH_C(new ScaledValuePoint("S203_TOT_V_ARH_EXP_Q3_PH_C", "Total VAr-hours Exported Q3 phase C", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + TOT_V_ARH_EXP_Q3_PH_A( + new ScaledValuePoint("S203_TOT_V_ARH_EXP_Q3_PH_A", "Total VAr-hours Exported Q3 phase A", "", // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + TOT_V_ARH_EXP_Q3_PH_B( + new ScaledValuePoint("S203_TOT_V_ARH_EXP_Q3_PH_B", "Total VAr-hours Exported Q3 phase B", "", // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + TOT_V_ARH_EXP_Q3_PH_C( + new ScaledValuePoint("S203_TOT_V_ARH_EXP_Q3_PH_C", "Total VAr-hours Exported Q3 phase C", "", // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // TOT_V_ARH_EXP_Q4(new ScaledValuePoint("S203_TOT_V_ARH_EXP_Q4", "Total VAr-hours Exported Q4", // "Total Reactive Power Exported Quadrant 4", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // - TOT_V_ARH_EXP_Q4_PH_A(new ScaledValuePoint("S203_TOT_V_ARH_EXP_Q4_PH_A", "Total VAr-hours Exported Q4 Imported phase A", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // - TOT_V_ARH_EXP_Q4_PH_B(new ScaledValuePoint("S203_TOT_V_ARH_EXP_Q4_PH_B", "Total VAr-hours Exported Q4 Imported phase B", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // - TOT_V_ARH_EXP_Q4_PH_C(new ScaledValuePoint("S203_TOT_V_ARH_EXP_Q4_PH_C", "Total VAr-hours Exported Q4 Imported phase C", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + TOT_V_ARH_EXP_Q4_PH_A( + new ScaledValuePoint("S203_TOT_V_ARH_EXP_Q4_PH_A", "Total VAr-hours Exported Q4 Imported phase A", "", // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + TOT_V_ARH_EXP_Q4_PH_B( + new ScaledValuePoint("S203_TOT_V_ARH_EXP_Q4_PH_B", "Total VAr-hours Exported Q4 Imported phase B", "", // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + TOT_V_ARH_EXP_Q4_PH_C( + new ScaledValuePoint("S203_TOT_V_ARH_EXP_Q4_PH_C", "Total VAr-hours Exported Q4 Imported phase C", "", // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // TOT_V_ARH_S_F(new ScaleFactorPoint("S203_TOT_V_ARH_S_F", "", // "Reactive Energy scale factor")), // EVT(new BitFieldPoint("S203_EVT", "Events", // "Meter Event Flags", // - BitFieldPoint.Type.BITFIELD32, true /* mandatory? */, AccessMode.READ_ONLY, S203_Evt.values())); + BITFIELD32, true /* mandatory? */, READ_ONLY, S203_Evt.values())); private final Point point; @@ -3325,174 +3383,186 @@ public BitPoint get() { public static enum S204 implements SunSpecPoint { A(new ScaledValuePoint("S204_A", "Amps", // "Total AC Current", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE, "A_SF")), // + INT16, true /* mandatory? */, READ_ONLY, Unit.AMPERE, "A_SF")), // APH_A(new ScaledValuePoint("S204_APH_A", "Amps PhaseA", // "Phase A Current", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE, "A_SF")), // + INT16, true /* mandatory? */, READ_ONLY, Unit.AMPERE, "A_SF")), // APH_B(new ScaledValuePoint("S204_APH_B", "Amps PhaseB", // "Phase B Current", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE, "A_SF")), // + INT16, true /* mandatory? */, READ_ONLY, Unit.AMPERE, "A_SF")), // APH_C(new ScaledValuePoint("S204_APH_C", "Amps PhaseC", // "Phase C Current", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE, "A_SF")), // + INT16, true /* mandatory? */, READ_ONLY, Unit.AMPERE, "A_SF")), // A_SF(new ScaleFactorPoint("S204_A_SF", "", // "Current scale factor")), // PH_V(new ScaledValuePoint("S204_PH_V", "Voltage LN", // "Line to Neutral AC Voltage (average of active phases)", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // PH_VPH_A(new ScaledValuePoint("S204_PH_VPH_A", "Phase Voltage AN", // "Phase Voltage AN", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // PH_VPH_B(new ScaledValuePoint("S204_PH_VPH_B", "Phase Voltage BN", // "Phase Voltage BN", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // PH_VPH_C(new ScaledValuePoint("S204_PH_VPH_C", "Phase Voltage CN", // "Phase Voltage CN", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // PPV(new ScaledValuePoint("S204_PPV", "Voltage LL", // "Line to Line AC Voltage (average of active phases)", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + INT16, true /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // PH_VPH_A_B(new ScaledValuePoint("S204_PH_VPH_A_B", "Phase Voltage AB", // "Phase Voltage AB", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + INT16, true /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // PH_VPH_B_C(new ScaledValuePoint("S204_PH_VPH_B_C", "Phase Voltage BC", // "Phase Voltage BC", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + INT16, true /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // PH_VPH_C_A(new ScaledValuePoint("S204_PH_VPH_C_A", "Phase Voltage CA", // "Phase Voltage CA", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + INT16, true /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // V_SF(new ScaleFactorPoint("S204_V_SF", "", // "Voltage scale factor")), // HZ(new ScaledValuePoint("S204_HZ", "Hz", // "Frequency", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.HERTZ, "Hz_SF")), // + INT16, true /* mandatory? */, READ_ONLY, Unit.HERTZ, "Hz_SF")), // HZ_S_F(new ScaleFactorPoint("S204_HZ_S_F", "", // "Frequency scale factor")), // W(new ScaledValuePoint("S204_W", "Watts", // "Total Real Power", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.WATT, "W_SF")), // + INT16, true /* mandatory? */, READ_ONLY, Unit.WATT, "W_SF")), // WPH_A(new ScaledValuePoint("S204_WPH_A", "Watts phase A", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.WATT, "W_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.WATT, "W_SF")), // WPH_B(new ScaledValuePoint("S204_WPH_B", "Watts phase B", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.WATT, "W_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.WATT, "W_SF")), // WPH_C(new ScaledValuePoint("S204_WPH_C", "Watts phase C", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.WATT, "W_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.WATT, "W_SF")), // W_SF(new ScaleFactorPoint("S204_W_SF", "", // "Real Power scale factor")), // VA(new ScaledValuePoint("S204_VA", "VA", // "AC Apparent Power", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE, "VA_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE, "VA_SF")), // V_APH_A(new ScaledValuePoint("S204_V_APH_A", "VA phase A", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE, "VA_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE, "VA_SF")), // V_APH_B(new ScaledValuePoint("S204_V_APH_B", "VA phase B", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE, "VA_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE, "VA_SF")), // V_APH_C(new ScaledValuePoint("S204_V_APH_C", "VA phase C", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE, "VA_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE, "VA_SF")), // VA_SF(new ScaleFactorPoint("S204_VA_SF", "", // "Apparent Power scale factor")), // VAR(new ScaledValuePoint("S204_VAR", "VAR", // "Reactive Power", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "VAR_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "VAR_SF")), // V_A_RPH_A(new ScaledValuePoint("S204_V_A_RPH_A", "VAR phase A", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "VAR_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "VAR_SF")), // V_A_RPH_B(new ScaledValuePoint("S204_V_A_RPH_B", "VAR phase B", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "VAR_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "VAR_SF")), // V_A_RPH_C(new ScaledValuePoint("S204_V_A_RPH_C", "VAR phase C", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "VAR_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "VAR_SF")), // VAR_SF(new ScaleFactorPoint("S204_VAR_SF", "", // "Reactive Power scale factor")), // PF(new ScaledValuePoint("S204_PF", "PF", // "Power Factor", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE, "PF_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.NONE, "PF_SF")), // P_FPH_A(new ScaledValuePoint("S204_P_FPH_A", "PF phase A", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE, "PF_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.NONE, "PF_SF")), // P_FPH_B(new ScaledValuePoint("S204_P_FPH_B", "PF phase B", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE, "PF_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.NONE, "PF_SF")), // P_FPH_C(new ScaledValuePoint("S204_P_FPH_C", "PF phase C", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE, "PF_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.NONE, "PF_SF")), // PF_SF(new ScaleFactorPoint("S204_PF_SF", "", // "Power Factor scale factor")), // TOT_WH_EXP(new ScaledValuePoint("S204_TOT_WH_EXP", "Total Watt-hours Exported", // "Total Real Energy Exported", // - ValuePoint.Type.ACC32, true /* mandatory? */, AccessMode.READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // + ACC32, true /* mandatory? */, READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // TOT_WH_EXP_PH_A(new ScaledValuePoint("S204_TOT_WH_EXP_PH_A", "Total Watt-hours Exported phase A", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // TOT_WH_EXP_PH_B(new ScaledValuePoint("S204_TOT_WH_EXP_PH_B", "Total Watt-hours Exported phase B", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // TOT_WH_EXP_PH_C(new ScaledValuePoint("S204_TOT_WH_EXP_PH_C", "Total Watt-hours Exported phase C", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // TOT_WH_IMP(new ScaledValuePoint("S204_TOT_WH_IMP", "Total Watt-hours Imported", // "Total Real Energy Imported", // - ValuePoint.Type.ACC32, true /* mandatory? */, AccessMode.READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // + ACC32, true /* mandatory? */, READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // TOT_WH_IMP_PH_A(new ScaledValuePoint("S204_TOT_WH_IMP_PH_A", "Total Watt-hours Imported phase A", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // TOT_WH_IMP_PH_B(new ScaledValuePoint("S204_TOT_WH_IMP_PH_B", "Total Watt-hours Imported phase B", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // TOT_WH_IMP_PH_C(new ScaledValuePoint("S204_TOT_WH_IMP_PH_C", "Total Watt-hours Imported phase C", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // TOT_WH_S_F(new ScaleFactorPoint("S204_TOT_WH_S_F", "", // "Real Energy scale factor")), // TOT_V_AH_EXP(new ScaledValuePoint("S204_TOT_V_AH_EXP", "Total VA-hours Exported", // "Total Apparent Energy Exported", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // TOT_V_AH_EXP_PH_A(new ScaledValuePoint("S204_TOT_V_AH_EXP_PH_A", "Total VA-hours Exported phase A", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // TOT_V_AH_EXP_PH_B(new ScaledValuePoint("S204_TOT_V_AH_EXP_PH_B", "Total VA-hours Exported phase B", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // TOT_V_AH_EXP_PH_C(new ScaledValuePoint("S204_TOT_V_AH_EXP_PH_C", "Total VA-hours Exported phase C", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // TOT_V_AH_IMP(new ScaledValuePoint("S204_TOT_V_AH_IMP", "Total VA-hours Imported", // "Total Apparent Energy Imported", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // TOT_V_AH_IMP_PH_A(new ScaledValuePoint("S204_TOT_V_AH_IMP_PH_A", "Total VA-hours Imported phase A", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // TOT_V_AH_IMP_PH_B(new ScaledValuePoint("S204_TOT_V_AH_IMP_PH_B", "Total VA-hours Imported phase B", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // TOT_V_AH_IMP_PH_C(new ScaledValuePoint("S204_TOT_V_AH_IMP_PH_C", "Total VA-hours Imported phase C", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_HOURS, "TotVAh_SF")), // TOT_V_AH_S_F(new ScaleFactorPoint("S204_TOT_V_AH_S_F", "", // "Apparent Energy scale factor")), // TOT_V_ARH_IMP_Q1(new ScaledValuePoint("S204_TOT_V_ARH_IMP_Q1", "Total VAR-hours Imported Q1", // "Total Reactive Energy Imported Quadrant 1", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // - TOT_V_ARH_IMP_Q1_PH_A(new ScaledValuePoint("S204_TOT_V_ARH_IMP_Q1_PH_A", "Total VAr-hours Imported Q1 phase A", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // - TOT_V_ARH_IMP_Q1_PH_B(new ScaledValuePoint("S204_TOT_V_ARH_IMP_Q1_PH_B", "Total VAr-hours Imported Q1 phase B", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // - TOT_V_ARH_IMP_Q1_PH_C(new ScaledValuePoint("S204_TOT_V_ARH_IMP_Q1_PH_C", "Total VAr-hours Imported Q1 phase C", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + TOT_V_ARH_IMP_Q1_PH_A( + new ScaledValuePoint("S204_TOT_V_ARH_IMP_Q1_PH_A", "Total VAr-hours Imported Q1 phase A", "", // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + TOT_V_ARH_IMP_Q1_PH_B( + new ScaledValuePoint("S204_TOT_V_ARH_IMP_Q1_PH_B", "Total VAr-hours Imported Q1 phase B", "", // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + TOT_V_ARH_IMP_Q1_PH_C( + new ScaledValuePoint("S204_TOT_V_ARH_IMP_Q1_PH_C", "Total VAr-hours Imported Q1 phase C", "", // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // TOT_V_ARH_IMP_Q2(new ScaledValuePoint("S204_TOT_V_ARH_IMP_Q2", "Total VAr-hours Imported Q2", // "Total Reactive Power Imported Quadrant 2", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // - TOT_V_ARH_IMP_Q2_PH_A(new ScaledValuePoint("S204_TOT_V_ARH_IMP_Q2_PH_A", "Total VAr-hours Imported Q2 phase A", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // - TOT_V_ARH_IMP_Q2_PH_B(new ScaledValuePoint("S204_TOT_V_ARH_IMP_Q2_PH_B", "Total VAr-hours Imported Q2 phase B", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // - TOT_V_ARH_IMP_Q2_PH_C(new ScaledValuePoint("S204_TOT_V_ARH_IMP_Q2_PH_C", "Total VAr-hours Imported Q2 phase C", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + TOT_V_ARH_IMP_Q2_PH_A( + new ScaledValuePoint("S204_TOT_V_ARH_IMP_Q2_PH_A", "Total VAr-hours Imported Q2 phase A", "", // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + TOT_V_ARH_IMP_Q2_PH_B( + new ScaledValuePoint("S204_TOT_V_ARH_IMP_Q2_PH_B", "Total VAr-hours Imported Q2 phase B", "", // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + TOT_V_ARH_IMP_Q2_PH_C( + new ScaledValuePoint("S204_TOT_V_ARH_IMP_Q2_PH_C", "Total VAr-hours Imported Q2 phase C", "", // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // TOT_V_ARH_EXP_Q3(new ScaledValuePoint("S204_TOT_V_ARH_EXP_Q3", "Total VAr-hours Exported Q3", // "Total Reactive Power Exported Quadrant 3", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // - TOT_V_ARH_EXP_Q3_PH_A(new ScaledValuePoint("S204_TOT_V_ARH_EXP_Q3_PH_A", "Total VAr-hours Exported Q3 phase A", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // - TOT_V_ARH_EXP_Q3_PH_B(new ScaledValuePoint("S204_TOT_V_ARH_EXP_Q3_PH_B", "Total VAr-hours Exported Q3 phase B", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // - TOT_V_ARH_EXP_Q3_PH_C(new ScaledValuePoint("S204_TOT_V_ARH_EXP_Q3_PH_C", "Total VAr-hours Exported Q3 phase C", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + TOT_V_ARH_EXP_Q3_PH_A( + new ScaledValuePoint("S204_TOT_V_ARH_EXP_Q3_PH_A", "Total VAr-hours Exported Q3 phase A", "", // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + TOT_V_ARH_EXP_Q3_PH_B( + new ScaledValuePoint("S204_TOT_V_ARH_EXP_Q3_PH_B", "Total VAr-hours Exported Q3 phase B", "", // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + TOT_V_ARH_EXP_Q3_PH_C( + new ScaledValuePoint("S204_TOT_V_ARH_EXP_Q3_PH_C", "Total VAr-hours Exported Q3 phase C", "", // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // TOT_V_ARH_EXP_Q4(new ScaledValuePoint("S204_TOT_V_ARH_EXP_Q4", "Total VAr-hours Exported Q4", // "Total Reactive Power Exported Quadrant 4", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // - TOT_V_ARH_EXP_Q4_PH_A(new ScaledValuePoint("S204_TOT_V_ARH_EXP_Q4_PH_A", "Total VAr-hours Exported Q4 Imported phase A", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // - TOT_V_ARH_EXP_Q4_PH_B(new ScaledValuePoint("S204_TOT_V_ARH_EXP_Q4_PH_B", "Total VAr-hours Exported Q4 Imported phase B", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // - TOT_V_ARH_EXP_Q4_PH_C(new ScaledValuePoint("S204_TOT_V_ARH_EXP_Q4_PH_C", "Total VAr-hours Exported Q4 Imported phase C", "", // - ValuePoint.Type.ACC32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + TOT_V_ARH_EXP_Q4_PH_A( + new ScaledValuePoint("S204_TOT_V_ARH_EXP_Q4_PH_A", "Total VAr-hours Exported Q4 Imported phase A", "", // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + TOT_V_ARH_EXP_Q4_PH_B( + new ScaledValuePoint("S204_TOT_V_ARH_EXP_Q4_PH_B", "Total VAr-hours Exported Q4 Imported phase B", "", // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // + TOT_V_ARH_EXP_Q4_PH_C( + new ScaledValuePoint("S204_TOT_V_ARH_EXP_Q4_PH_C", "Total VAr-hours Exported Q4 Imported phase C", "", // + ACC32, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVArh_SF")), // TOT_V_ARH_S_F(new ScaleFactorPoint("S204_TOT_V_ARH_S_F", "", // "Reactive Energy scale factor")), // EVT(new BitFieldPoint("S204_EVT", "Events", // "Meter Event Flags", // - BitFieldPoint.Type.BITFIELD32, true /* mandatory? */, AccessMode.READ_ONLY, S204_Evt.values())); + BITFIELD32, true /* mandatory? */, READ_ONLY, S204_Evt.values())); private final Point point; @@ -3552,22 +3622,22 @@ public BitPoint get() { public static enum S305 implements SunSpecPoint { TM(new ValuePoint("S305_TM", "Tm", // "UTC 24 hour time stamp to millisecond hhmmss.sssZ format", // - ValuePoint.Type.STRING6, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + STRING6, false /* mandatory? */, READ_ONLY, Unit.NONE)), // DATE(new ValuePoint("S305_DATE", "Date", // "UTC Date string YYYYMMDD format", // - ValuePoint.Type.STRING4, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + STRING4, false /* mandatory? */, READ_ONLY, Unit.NONE)), // LOC(new ValuePoint("S305_LOC", "Location", // "Location string (40 chars max)", // - ValuePoint.Type.STRING20, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + STRING20, false /* mandatory? */, READ_ONLY, Unit.NONE)), // LAT(new ScaledValuePoint("S305_LAT", "Lat", // "Latitude with seven degrees of precision", // - ValuePoint.Type.INT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE, "-7")), // + INT32, false /* mandatory? */, READ_ONLY, Unit.NONE, "-7")), // LONG(new ScaledValuePoint("S305_LONG", "Long", // "Longitude with seven degrees of precision", // - ValuePoint.Type.INT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE, "-7")), // + INT32, false /* mandatory? */, READ_ONLY, Unit.NONE, "-7")), // ALT(new ValuePoint("S305_ALT", "Altitude", // "Altitude measurement in meters", // - ValuePoint.Type.INT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)); + INT32, false /* mandatory? */, READ_ONLY, Unit.NONE)); private final Point point; @@ -3584,16 +3654,16 @@ public Point get() { public static enum S306 implements SunSpecPoint { GHI(new ValuePoint("S306_GHI", "GHI", // "Global Horizontal Irradiance", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.NONE)), // A(new ValuePoint("S306_A", "Amps", // "Current measurement at reference point", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.NONE)), // V(new ValuePoint("S306_V", "Voltage", // "Voltage measurement at reference point", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.NONE)), // TMP(new ValuePoint("S306_TMP", "Temperature", // "Temperature measurement at reference point", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)); + UINT16, false /* mandatory? */, READ_ONLY, Unit.NONE)); private final Point point; @@ -3609,28 +3679,28 @@ public Point get() { public static enum S307 implements SunSpecPoint { TMP_AMB(new ScaledValuePoint("S307_TMP_AMB", "Ambient Temperature", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.DEGREE_CELSIUS, "-1")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.DEGREE_CELSIUS, "-1")), // RH(new ValuePoint("S307_RH", "Relative Humidity", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + INT16, false /* mandatory? */, READ_ONLY, Unit.NONE)), // PRES(new ValuePoint("S307_PRES", "Barometric Pressure", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + INT16, false /* mandatory? */, READ_ONLY, Unit.NONE)), // WND_SPD(new ValuePoint("S307_WND_SPD", "Wind Speed", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + INT16, false /* mandatory? */, READ_ONLY, Unit.NONE)), // WND_DIR(new ValuePoint("S307_WND_DIR", "Wind Direction", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + INT16, false /* mandatory? */, READ_ONLY, Unit.NONE)), // RAIN(new ValuePoint("S307_RAIN", "Rainfall", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + INT16, false /* mandatory? */, READ_ONLY, Unit.NONE)), // SNW(new ValuePoint("S307_SNW", "Snow Depth", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + INT16, false /* mandatory? */, READ_ONLY, Unit.NONE)), // PPT(new ValuePoint("S307_PPT", "Precipitation Type", // "Precipitation Type (WMO 4680 SYNOP code reference)", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + INT16, false /* mandatory? */, READ_ONLY, Unit.NONE)), // ELEC_FLD(new ValuePoint("S307_ELEC_FLD", "Electric Field", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + INT16, false /* mandatory? */, READ_ONLY, Unit.NONE)), // SUR_WET(new ValuePoint("S307_SUR_WET", "Surface Wetness", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + INT16, false /* mandatory? */, READ_ONLY, Unit.NONE)), // SOIL_WET(new ValuePoint("S307_SOIL_WET", "Soil Wetness", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)); + INT16, false /* mandatory? */, READ_ONLY, Unit.NONE)); private final Point point; @@ -3647,14 +3717,14 @@ public Point get() { public static enum S308 implements SunSpecPoint { GHI(new ValuePoint("S308_GHI", "GHI", // "Global Horizontal Irradiance", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.NONE)), // TMP_B_O_M(new ScaledValuePoint("S308_TMP_B_O_M", "Temp", // "Back of module temperature measurement", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.DEGREE_CELSIUS, "-1")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.DEGREE_CELSIUS, "-1")), // TMP_AMB(new ScaledValuePoint("S308_TMP_AMB", "Ambient Temperature", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.DEGREE_CELSIUS, "-1")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.DEGREE_CELSIUS, "-1")), // WND_SPD(new ValuePoint("S308_WND_SPD", "Wind Speed", "", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)); + UINT16, false /* mandatory? */, READ_ONLY, Unit.NONE)); private final Point point; @@ -3671,181 +3741,181 @@ public Point get() { public static enum S701 implements SunSpecPoint { A_C_TYPE(new EnumPoint("S701_A_C_TYPE", "AC Wiring Type", // "AC wiring type.", // - EnumPoint.Type.ENUM16, true /* mandatory? */, AccessMode.READ_ONLY, S701_ACType.values())), // + ENUM16, true /* mandatory? */, READ_ONLY, S701_ACType.values())), // ST(new EnumPoint("S701_ST", "Operating State", // "Operating state of the DER.", // - EnumPoint.Type.ENUM16, false /* mandatory? */, AccessMode.READ_ONLY, S701_St.values())), // + ENUM16, false /* mandatory? */, READ_ONLY, S701_St.values())), // INV_ST(new EnumPoint("S701_INV_ST", "Inverter State", // "Enumerated value. Inverter state.", // - EnumPoint.Type.ENUM16, false /* mandatory? */, AccessMode.READ_ONLY, S701_InvSt.values())), // + ENUM16, false /* mandatory? */, READ_ONLY, S701_InvSt.values())), // CONN_ST(new EnumPoint("S701_CONN_ST", "Grid Connection State", // "Grid connection state of the DER.", // - EnumPoint.Type.ENUM16, false /* mandatory? */, AccessMode.READ_ONLY, S701_ConnSt.values())), // + ENUM16, false /* mandatory? */, READ_ONLY, S701_ConnSt.values())), // ALRM(new BitFieldPoint("S701_ALRM", "Alarm Bitfield", // "Active alarms for the DER.", // - BitFieldPoint.Type.BITFIELD32, false /* mandatory? */, AccessMode.READ_ONLY, S701_Alrm.values())), // + BITFIELD32, false /* mandatory? */, READ_ONLY, S701_Alrm.values())), // D_E_R_MODE(new BitFieldPoint("S701_D_E_R_MODE", "DER Operational Characteristics", // "Current operational characteristics of the DER.", // - BitFieldPoint.Type.BITFIELD32, false /* mandatory? */, AccessMode.READ_ONLY, S701_DERMode.values())), // + BITFIELD32, false /* mandatory? */, READ_ONLY, S701_DERMode.values())), // W(new ScaledValuePoint("S701_W", "Active Power", // "Total active power. Active power is positive for DER generation and negative for absorption.", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.WATT, "W_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.WATT, "W_SF")), // VA(new ScaledValuePoint("S701_VA", "Apparent Power", // "Total apparent power.", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE, "VA_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE, "VA_SF")), // VAR(new ScaledValuePoint("S701_VAR", "Reactive Power", // "Total reactive power.", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "Var_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "Var_SF")), // PF(new ScaledValuePoint("S701_PF", "Power Factor", // "Power factor. The sign of power factor should be the sign of active power.", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE, "PF_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.NONE, "PF_SF")), // A(new ScaledValuePoint("S701_A", "Total AC Current", // "Total AC current.", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE, "A_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.AMPERE, "A_SF")), // LLV(new ScaledValuePoint("S701_LLV", "Voltage LL", // "Line to line AC voltage as an average of active phases.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // LNV(new ScaledValuePoint("S701_LNV", "Voltage LN", // "Line to neutral AC voltage as an average of active phases.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // HZ(new ScaledValuePoint("S701_HZ", "Frequency", // "AC frequency.", // - ValuePoint.Type.UINT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.HERTZ, "Hz_SF")), // + UINT32, false /* mandatory? */, READ_ONLY, Unit.HERTZ, "Hz_SF")), // TOT_WH_INJ(new ScaledValuePoint("S701_TOT_WH_INJ", "Total Energy Injected", // "Total active energy injected (Quadrants 1 & 4).", // - ValuePoint.Type.UINT64, false /* mandatory? */, AccessMode.READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // + UINT64, false /* mandatory? */, READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // TOT_WH_ABS(new ScaledValuePoint("S701_TOT_WH_ABS", "Total Energy Absorbed", // "Total active energy absorbed (Quadrants 2 & 3).", // - ValuePoint.Type.UINT64, false /* mandatory? */, AccessMode.READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // + UINT64, false /* mandatory? */, READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // TOT_VARH_INJ(new ScaledValuePoint("S701_TOT_VARH_INJ", "Total Reactive Energy Inj", // "Total reactive energy injected (Quadrants 1 & 2).", // - ValuePoint.Type.UINT64, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVarh_SF")), // + UINT64, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVarh_SF")), // TOT_VARH_ABS(new ScaledValuePoint("S701_TOT_VARH_ABS", "Total Reactive Energy Abs", // "Total reactive energy absorbed (Quadrants 3 & 4).", // - ValuePoint.Type.UINT64, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVarh_SF")), // + UINT64, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVarh_SF")), // TMP_AMB(new ScaledValuePoint("S701_TMP_AMB", "Ambient Temperature", // "Ambient temperature.", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.DEGREE_CELSIUS, "Tmp_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.DEGREE_CELSIUS, "Tmp_SF")), // TMP_CAB(new ScaledValuePoint("S701_TMP_CAB", "Cabinet Temperature", // "Cabinet temperature.", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.DEGREE_CELSIUS, "Tmp_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.DEGREE_CELSIUS, "Tmp_SF")), // TMP_SNK(new ScaledValuePoint("S701_TMP_SNK", "Heat Sink Temperature", // "Heat sink temperature.", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.DEGREE_CELSIUS, "Tmp_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.DEGREE_CELSIUS, "Tmp_SF")), // TMP_TRNS(new ScaledValuePoint("S701_TMP_TRNS", "Transformer Temperature", // "Transformer temperature.", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.DEGREE_CELSIUS, "Tmp_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.DEGREE_CELSIUS, "Tmp_SF")), // TMP_SW(new ScaledValuePoint("S701_TMP_SW", "IGBT/MOSFET Temperature", // "IGBT/MOSFET temperature.", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.DEGREE_CELSIUS, "Tmp_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.DEGREE_CELSIUS, "Tmp_SF")), // TMP_OT(new ScaledValuePoint("S701_TMP_OT", "Other Temperature", // "Other temperature.", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.DEGREE_CELSIUS, "Tmp_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.DEGREE_CELSIUS, "Tmp_SF")), // WL1(new ScaledValuePoint("S701_WL1", "Watts L1", // "Active power L1.", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.WATT, "W_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.WATT, "W_SF")), // VAL1(new ScaledValuePoint("S701_VAL1", "VA L1", // "Apparent power L1.", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE, "VA_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE, "VA_SF")), // VAR_L1(new ScaledValuePoint("S701_VAR_L1", "Var L1", // "Reactive power L1.", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "Var_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "Var_SF")), // PFL1(new ScaledValuePoint("S701_PFL1", "PF L1", // "Power factor phase L1.", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE, "PF_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.NONE, "PF_SF")), // AL1(new ScaledValuePoint("S701_AL1", "Amps L1", // "Current phase L1.", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE, "A_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.AMPERE, "A_SF")), // VL1L2(new ScaledValuePoint("S701_VL1L2", "Phase Voltage L1-L2", // "Phase voltage L1-L2.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // VL1(new ScaledValuePoint("S701_VL1", "Phase Voltage L1-N", // "Phase voltage L1-N.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // TOT_WH_INJ_L1(new ScaledValuePoint("S701_TOT_WH_INJ_L1", "Total Watt-Hours Inj L1", // "Total active energy injected L1.", // - ValuePoint.Type.UINT64, false /* mandatory? */, AccessMode.READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // + UINT64, false /* mandatory? */, READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // TOT_WH_ABS_L1(new ScaledValuePoint("S701_TOT_WH_ABS_L1", "Total Watt-Hours Abs L1", // "Total active energy absorbed L1.", // - ValuePoint.Type.UINT64, false /* mandatory? */, AccessMode.READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // + UINT64, false /* mandatory? */, READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // TOT_VARH_INJ_L1(new ScaledValuePoint("S701_TOT_VARH_INJ_L1", "Total Var-Hours Inj L1", // "Total reactive energy injected L1.", // - ValuePoint.Type.UINT64, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVarh_SF")), // + UINT64, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVarh_SF")), // TOT_VARH_ABS_L1(new ScaledValuePoint("S701_TOT_VARH_ABS_L1", "Total Var-Hours Abs L1", // "Total reactive energy absorbed L1.", // - ValuePoint.Type.UINT64, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVarh_SF")), // + UINT64, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVarh_SF")), // WL2(new ScaledValuePoint("S701_WL2", "Watts L2", // "Active power L2.", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.WATT, "W_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.WATT, "W_SF")), // VAL2(new ScaledValuePoint("S701_VAL2", "VA L2", // "Apparent power L2.", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE, "VA_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE, "VA_SF")), // VAR_L2(new ScaledValuePoint("S701_VAR_L2", "Var L2", // "Reactive power L2.", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "Var_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "Var_SF")), // PFL2(new ScaledValuePoint("S701_PFL2", "PF L2", // "Power factor L2.", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE, "PF_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.NONE, "PF_SF")), // AL2(new ScaledValuePoint("S701_AL2", "Amps L2", // "Current L2.", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE, "A_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.AMPERE, "A_SF")), // VL2L3(new ScaledValuePoint("S701_VL2L3", "Phase Voltage L2-L3", // "Phase voltage L2-L3.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // VL2(new ScaledValuePoint("S701_VL2", "Phase Voltage L2-N", // "Phase voltage L2-N.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // TOT_WH_INJ_L2(new ScaledValuePoint("S701_TOT_WH_INJ_L2", "Total Watt-Hours Inj L2", // "Total active energy injected L2.", // - ValuePoint.Type.UINT64, false /* mandatory? */, AccessMode.READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // + UINT64, false /* mandatory? */, READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // TOT_WH_ABS_L2(new ScaledValuePoint("S701_TOT_WH_ABS_L2", "Total Watt-Hours Abs L2", // "Total active energy absorbed L2.", // - ValuePoint.Type.UINT64, false /* mandatory? */, AccessMode.READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // + UINT64, false /* mandatory? */, READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // TOT_VARH_INJ_L2(new ScaledValuePoint("S701_TOT_VARH_INJ_L2", "Total Var-Hours Inj L2", // "Total reactive energy injected L2.", // - ValuePoint.Type.UINT64, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVarh_SF")), // + UINT64, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVarh_SF")), // TOT_VARH_ABS_L2(new ScaledValuePoint("S701_TOT_VARH_ABS_L2", "Total Var-Hours Abs L2", // "Total reactive energy absorbed L2.", // - ValuePoint.Type.UINT64, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVarh_SF")), // + UINT64, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVarh_SF")), // WL3(new ScaledValuePoint("S701_WL3", "Watts L3", // "Active power L3.", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.WATT, "W_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.WATT, "W_SF")), // VAL3(new ScaledValuePoint("S701_VAL3", "VA L3", // "Apparent power L3.", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE, "VA_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE, "VA_SF")), // VAR_L3(new ScaledValuePoint("S701_VAR_L3", "Var L3", // "Reactive power L3.", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "Var_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "Var_SF")), // PFL3(new ScaledValuePoint("S701_PFL3", "PF L3", // "Power factor L3.", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE, "PF_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.NONE, "PF_SF")), // AL3(new ScaledValuePoint("S701_AL3", "Amps L3", // "Current L3.", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE, "A_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.AMPERE, "A_SF")), // VL3L1(new ScaledValuePoint("S701_VL3L1", "Phase Voltage L3-L1", // "Phase voltage L3-L1.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // VL3(new ScaledValuePoint("S701_VL3", "Phase Voltage L3-N", // "Phase voltage L3-N.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // TOT_WH_INJ_L3(new ScaledValuePoint("S701_TOT_WH_INJ_L3", "Total Watt-Hours Inj L3", // "Total active energy injected L3.", // - ValuePoint.Type.UINT64, false /* mandatory? */, AccessMode.READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // + UINT64, false /* mandatory? */, READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // TOT_WH_ABS_L3(new ScaledValuePoint("S701_TOT_WH_ABS_L3", "Total Watt-Hours Abs L3", // "Total active energy absorbed L3.", // - ValuePoint.Type.UINT64, false /* mandatory? */, AccessMode.READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // + UINT64, false /* mandatory? */, READ_ONLY, Unit.CUMULATED_WATT_HOURS, "TotWh_SF")), // TOT_VARH_INJ_L3(new ScaledValuePoint("S701_TOT_VARH_INJ_L3", "Total Var-Hours Inj L3", // "Total reactive energy injected L3.", // - ValuePoint.Type.UINT64, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVarh_SF")), // + UINT64, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVarh_SF")), // TOT_VARH_ABS_L3(new ScaledValuePoint("S701_TOT_VARH_ABS_L3", "Total Var-Hours Abs L3", // "Total reactive energy absorbed L3.", // - ValuePoint.Type.UINT64, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVarh_SF")), // + UINT64, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE_HOURS, "TotVarh_SF")), // THROT_PCT(new ValuePoint("S701_THROT_PCT", "Throttling In Pct", // "Throttling in pct of maximum active power.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.NONE)), // THROT_SRC(new BitFieldPoint("S701_THROT_SRC", "Throttle Source Information", // "Active throttling source.", // - BitFieldPoint.Type.BITFIELD32, false /* mandatory? */, AccessMode.READ_ONLY, S701_ThrotSrc.values())), // + BITFIELD32, false /* mandatory? */, READ_ONLY, S701_ThrotSrc.values())), // A_SF(new ScaleFactorPoint("S701_A_SF", "Current Scale Factor", // "Current scale factor.")), // V_SF(new ScaleFactorPoint("S701_V_SF", "Voltage Scale Factor", // @@ -3868,7 +3938,7 @@ public static enum S701 implements SunSpecPoint { "Temperature scale factor.")), // MN_ALRM_INFO(new ValuePoint("S701_MN_ALRM_INFO", "Manufacturer Alarm Info", // "Manufacturer alarm information. Valid if MANUFACTURER_ALRM indication is active.", // - ValuePoint.Type.STRING32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)); + STRING32, false /* mandatory? */, READ_ONLY, Unit.NONE)); private final Point point; @@ -4085,130 +4155,130 @@ public BitPoint get() { public static enum S702 implements SunSpecPoint { W_MAX_RTG(new ScaledValuePoint("S702_W_MAX_RTG", "Active Power Max Rating", // "Maximum active power rating at unity power factor in watts.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.WATT, "W_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.WATT, "W_SF")), // W_OVR_EXT_RTG(new ScaledValuePoint("S702_W_OVR_EXT_RTG", "Active Power (Over-Excited) Rating", // "Active power rating at specified over-excited power factor in watts.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.WATT, "W_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.WATT, "W_SF")), // W_OVR_EXT_RTG_P_F(new ScaledValuePoint("S702_W_OVR_EXT_RTG_P_F", "Specified Over-Excited PF", // "Specified over-excited power factor.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE, "PF_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.NONE, "PF_SF")), // W_UND_EXT_RTG(new ScaledValuePoint("S702_W_UND_EXT_RTG", "Active Power (Under-Excited) Rating", // "Active power rating at specified under-excited power factor in watts.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.WATT, "W_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.WATT, "W_SF")), // W_UND_EXT_RTG_P_F(new ScaledValuePoint("S702_W_UND_EXT_RTG_P_F", "Specified Under-Excited PF", // "Specified under-excited power factor.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE, "PF_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.NONE, "PF_SF")), // V_A_MAX_RTG(new ScaledValuePoint("S702_V_A_MAX_RTG", "Apparent Power Max Rating", // "Maximum apparent power rating in voltamperes.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE, "VA_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE, "VA_SF")), // VAR_MAX_INJ_RTG(new ScaledValuePoint("S702_VAR_MAX_INJ_RTG", "Reactive Power Injected Rating", // "Maximum injected reactive power rating in vars.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "Var_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "Var_SF")), // VAR_MAX_ABS_RTG(new ScaledValuePoint("S702_VAR_MAX_ABS_RTG", "Reactive Power Absorbed Rating", // "Maximum absorbed reactive power rating in vars.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "Var_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "Var_SF")), // W_CHA_RTE_MAX_RTG(new ScaledValuePoint("S702_W_CHA_RTE_MAX_RTG", "Charge Rate Max Rating", // "Maximum active power charge rate in watts.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.WATT, "W_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.WATT, "W_SF")), // W_DIS_CHA_RTE_MAX_RTG(new ScaledValuePoint("S702_W_DIS_CHA_RTE_MAX_RTG", "Discharge Rate Max Rating", // "Maximum active power discharge rate in watts.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.WATT, "W_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.WATT, "W_SF")), // V_A_CHA_RTE_MAX_RTG(new ScaledValuePoint("S702_V_A_CHA_RTE_MAX_RTG", "Charge Rate Max VA Rating", // "Maximum apparent power charge rate in voltamperes.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE, "VA_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE, "VA_SF")), // V_A_DIS_CHA_RTE_MAX_RTG(new ScaledValuePoint("S702_V_A_DIS_CHA_RTE_MAX_RTG", "Discharge Rate Max VA Rating", // "Maximum apparent power discharge rate in voltamperes.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT_AMPERE, "VA_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.VOLT_AMPERE, "VA_SF")), // V_NOM_RTG(new ScaledValuePoint("S702_V_NOM_RTG", "AC Voltage Nominal Rating", // "AC voltage nominal rating.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // V_MAX_RTG(new ScaledValuePoint("S702_V_MAX_RTG", "AC Voltage Max Rating", // "AC voltage maximum rating.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // V_MIN_RTG(new ScaledValuePoint("S702_V_MIN_RTG", "AC Voltage Min Rating", // "AC voltage minimum rating.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // A_MAX_RTG(new ScaledValuePoint("S702_A_MAX_RTG", "AC Current Max Rating", // "AC current maximum rating in amps.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE, "A_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.AMPERE, "A_SF")), // P_F_OVR_EXT_RTG(new ScaledValuePoint("S702_P_F_OVR_EXT_RTG", "PF Over-Excited Rating", // "Power factor over-excited rating.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE, "PF_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.NONE, "PF_SF")), // P_F_UND_EXT_RTG(new ScaledValuePoint("S702_P_F_UND_EXT_RTG", "PF Under-Excited Rating", // "Power factor under-excited rating.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE, "PF_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.NONE, "PF_SF")), // REACT_SUSCEPT_RTG(new ScaledValuePoint("S702_REACT_SUSCEPT_RTG", "Reactive Susceptance", // "Reactive susceptance that remains connected to the Area EPS in the cease to energize and trip state.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE, "S_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.NONE, "S_SF")), // NOR_OP_CAT_RTG(new EnumPoint("S702_NOR_OP_CAT_RTG", "Normal Operating Category", // "Normal operating performance category as specified in IEEE 1547-2018.", // - EnumPoint.Type.ENUM16, false /* mandatory? */, AccessMode.READ_ONLY, S702_NorOpCatRtg.values())), // + ENUM16, false /* mandatory? */, READ_ONLY, S702_NorOpCatRtg.values())), // ABN_OP_CAT_RTG(new EnumPoint("S702_ABN_OP_CAT_RTG", "Abnormal Operating Category", // "Abnormal operating performance category as specified in IEEE 1547-2018.", // - EnumPoint.Type.ENUM16, false /* mandatory? */, AccessMode.READ_ONLY, S702_AbnOpCatRtg.values())), // + ENUM16, false /* mandatory? */, READ_ONLY, S702_AbnOpCatRtg.values())), // CTRL_MODES(new BitFieldPoint("S702_CTRL_MODES", "Supported Control Modes", // "Supported control mode functions.", // - BitFieldPoint.Type.BITFIELD32, false /* mandatory? */, AccessMode.READ_ONLY, S702_CtrlModes.values())), // + BITFIELD32, false /* mandatory? */, READ_ONLY, S702_CtrlModes.values())), // INT_ISLAND_CAT_RTG(new BitFieldPoint("S702_INT_ISLAND_CAT_RTG", "Intentional Island Categories", // "Intentional island categories.", // - BitFieldPoint.Type.BITFIELD16, false /* mandatory? */, AccessMode.READ_ONLY, S702_IntIslandCatRtg.values())), // + BITFIELD16, false /* mandatory? */, READ_ONLY, S702_IntIslandCatRtg.values())), // W_MAX(new ScaledValuePoint("S702_W_MAX", "Active Power Max Setting", // "Maximum active power setting used to adjust maximum active power setting.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.WATT, "W_SF")), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.WATT, "W_SF")), // W_MAX_OVR_EXT(new ScaledValuePoint("S702_W_MAX_OVR_EXT", "Active Power (Over-Excited) Setting", // "Active power setting at specified over-excited power factor in watts.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.WATT, "W_SF")), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.WATT, "W_SF")), // W_OVR_EXT_P_F(new ScaledValuePoint("S702_W_OVR_EXT_P_F", "Specified Over-Excited PF", // "Specified over-excited power factor.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.NONE, "PF_SF")), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.NONE, "PF_SF")), // W_MAX_UND_EXT(new ScaledValuePoint("S702_W_MAX_UND_EXT", "Active Power (Under-Excited) Setting", // "Active power setting at specified under-excited power factor in watts.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.WATT, "W_SF")), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.WATT, "W_SF")), // W_UND_EXT_P_F(new ScaledValuePoint("S702_W_UND_EXT_P_F", "Specified Under-Excited PF", // "Specified under-excited power factor.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.NONE, "PF_SF")), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.NONE, "PF_SF")), // V_A_MAX(new ScaledValuePoint("S702_V_A_MAX", "Apparent Power Max Setting", // "Maximum apparent power setting used to adjust maximum apparent power rating.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.VOLT_AMPERE, "VA_SF")), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.VOLT_AMPERE, "VA_SF")), // VAR_MAX_INJ(new ScaledValuePoint("S702_VAR_MAX_INJ", "Reactive Power Injected Setting", // "Maximum injected reactive power setting used to adjust maximum injected reactive power rating.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.VOLT_AMPERE_REACTIVE, "Var_SF")), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.VOLT_AMPERE_REACTIVE, "Var_SF")), // VAR_MAX_ABS(new ScaledValuePoint("S702_VAR_MAX_ABS", "Reactive Power Absorbed Setting", // "Maximum absorbed reactive power setting used to adjust maximum absorbed reactive power rating.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.VOLT_AMPERE_REACTIVE, "Var_SF")), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.VOLT_AMPERE_REACTIVE, "Var_SF")), // W_CHA_RTE_MAX(new ScaledValuePoint("S702_W_CHA_RTE_MAX", "Charge Rate Max Setting", // "Maximum active power charge rate setting used to adjust maximum active power charge rate rating.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.WATT, "W_SF")), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.WATT, "W_SF")), // W_DIS_CHA_RTE_MAX(new ScaledValuePoint("S702_W_DIS_CHA_RTE_MAX", "Discharge Rate Max Setting", // "Maximum active power discharge rate setting used to adjust maximum active power discharge rate rating.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.WATT, "W_SF")), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.WATT, "W_SF")), // V_A_CHA_RTE_MAX(new ScaledValuePoint("S702_V_A_CHA_RTE_MAX", "Charge Rate Max VA Setting", // "Maximum apparent power charge rate setting used to adjust maximum apparent power charge rate rating.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.VOLT_AMPERE, "VA_SF")), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.VOLT_AMPERE, "VA_SF")), // V_A_DIS_CHA_RTE_MAX(new ScaledValuePoint("S702_V_A_DIS_CHA_RTE_MAX", "Discharge Rate Max VA Setting", // "Maximum apparent power discharge rate setting used to adjust maximum apparent power discharge rate rating.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.VOLT_AMPERE, "VA_SF")), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.VOLT_AMPERE, "VA_SF")), // V_NOM(new ScaledValuePoint("S702_V_NOM", "Nominal AC Voltage Setting", // "Nominal AC voltage setting.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.VOLT, "V_SF")), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.VOLT, "V_SF")), // V_MAX(new ScaledValuePoint("S702_V_MAX", "AC Voltage Max Setting", // "AC voltage maximum setting used to adjust AC voltage maximum rating.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.VOLT, "V_SF")), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.VOLT, "V_SF")), // V_MIN(new ScaledValuePoint("S702_V_MIN", "AC Voltage Min Setting", // "AC voltage minimum setting used to adjust AC voltage minimum rating.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.VOLT, "V_SF")), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.VOLT, "V_SF")), // A_MAX(new ScaledValuePoint("S702_A_MAX", "AC Current Max Setting", // "Maximum AC current setting used to adjust maximum AC current rating.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.AMPERE, "A_SF")), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.AMPERE, "A_SF")), // P_F_OVR_EXT(new ScaledValuePoint("S702_P_F_OVR_EXT", "PF Over-Excited Setting", // "Power factor over-excited setting.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.NONE, "PF_SF")), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.NONE, "PF_SF")), // P_F_UND_EXT(new ScaledValuePoint("S702_P_F_UND_EXT", "PF Under-Excited Setting", // "Power factor under-excited setting.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.NONE, "PF_SF")), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.NONE, "PF_SF")), // INT_ISLAND_CAT(new BitFieldPoint("S702_INT_ISLAND_CAT", "Intentional Island Categories", // "Intentional island categories.", // - BitFieldPoint.Type.BITFIELD16, false /* mandatory? */, AccessMode.READ_WRITE, S702_IntIslandCat.values())), // + BITFIELD16, false /* mandatory? */, READ_WRITE, S702_IntIslandCat.values())), // W_SF(new ScaleFactorPoint("S702_W_SF", "Active Power Scale Factor", // "Active power scale factor.")), // PF_SF(new ScaleFactorPoint("S702_PF_SF", "Power Factor Scale Factor", // @@ -4362,31 +4432,31 @@ public BitPoint get() { public static enum S703 implements SunSpecPoint { ES(new EnumPoint("S703_ES", "Permit Enter Service", // "Permit enter service.", // - EnumPoint.Type.ENUM16, false /* mandatory? */, AccessMode.READ_WRITE, S703_ES.values())), // + ENUM16, false /* mandatory? */, READ_WRITE, S703_ES.values())), // E_S_V_HI(new ScaledValuePoint("S703_E_S_V_HI", "Enter Service Voltage High", // "Enter service voltage high threshold as percent of normal voltage.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.NONE, "V_SF")), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.NONE, "V_SF")), // E_S_V_LO(new ScaledValuePoint("S703_E_S_V_LO", "Enter Service Voltage Low", // "Enter service voltage low threshold as percent of normal voltage.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.NONE, "V_SF")), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.NONE, "V_SF")), // E_S_HZ_HI(new ScaledValuePoint("S703_E_S_HZ_HI", "Enter Service Frequency High", // "Enter service frequency high threshold.", // - ValuePoint.Type.UINT32, false /* mandatory? */, AccessMode.READ_WRITE, Unit.HERTZ, "Hz_SF")), // + UINT32, false /* mandatory? */, READ_WRITE, Unit.HERTZ, "Hz_SF")), // E_S_HZ_LO(new ScaledValuePoint("S703_E_S_HZ_LO", "Enter Service Frequency Low", // "Enter service frequency low threshold.", // - ValuePoint.Type.UINT32, false /* mandatory? */, AccessMode.READ_WRITE, Unit.HERTZ, "Hz_SF")), // + UINT32, false /* mandatory? */, READ_WRITE, Unit.HERTZ, "Hz_SF")), // E_S_DLY_TMS(new ValuePoint("S703_E_S_DLY_TMS", "Enter Service Delay Time", // "Enter service delay time in seconds.", // - ValuePoint.Type.UINT32, false /* mandatory? */, AccessMode.READ_WRITE, Unit.SECONDS)), // + UINT32, false /* mandatory? */, READ_WRITE, Unit.SECONDS)), // E_S_RND_TMS(new ValuePoint("S703_E_S_RND_TMS", "Enter Service Random Delay", // "Enter service random delay in seconds.", // - ValuePoint.Type.UINT32, false /* mandatory? */, AccessMode.READ_WRITE, Unit.SECONDS)), // + UINT32, false /* mandatory? */, READ_WRITE, Unit.SECONDS)), // E_S_RMP_TMS(new ValuePoint("S703_E_S_RMP_TMS", "Enter Service Ramp Time", // "Enter service ramp time in seconds.", // - ValuePoint.Type.UINT32, false /* mandatory? */, AccessMode.READ_WRITE, Unit.SECONDS)), // + UINT32, false /* mandatory? */, READ_WRITE, Unit.SECONDS)), // E_S_DLY_REM_TMS(new ValuePoint("S703_E_S_DLY_REM_TMS", "Enter Service Delay Remaining", // "Enter service delay time remaining in seconds.", // - ValuePoint.Type.UINT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.SECONDS)), // + UINT32, false /* mandatory? */, READ_ONLY, Unit.SECONDS)), // V_SF(new ScaleFactorPoint("S703_V_SF", "Voltage Scale Factor", // "Voltage percentage scale factor.")), // HZ_S_F(new ScaleFactorPoint("S703_HZ_S_F", "Frequency Scale Factor", // @@ -4436,115 +4506,115 @@ public OptionsEnum getUndefined() { public static enum S704 implements SunSpecPoint { P_F_W_INJ_ENA(new EnumPoint("S704_P_F_W_INJ_ENA", "Power Factor Enable (W Inj) Enable", // "Power factor enable when injecting active power.", // - EnumPoint.Type.ENUM16, false /* mandatory? */, AccessMode.READ_WRITE, S704_PFWInjEna.values())), // + ENUM16, false /* mandatory? */, READ_WRITE, S704_PFWInjEna.values())), // P_F_W_INJ_ENA_RVRT(new EnumPoint("S704_P_F_W_INJ_ENA_RVRT", "Power Factor Reversion Enable (W Inj)", // "Power factor reversion timer when injecting active power enable.", // - EnumPoint.Type.ENUM16, false /* mandatory? */, AccessMode.READ_WRITE, S704_PFWInjEnaRvrt.values())), // + ENUM16, false /* mandatory? */, READ_WRITE, S704_PFWInjEnaRvrt.values())), // P_F_W_INJ_RVRT_TMS(new ValuePoint("S704_P_F_W_INJ_RVRT_TMS", "PF Reversion Time (W Inj)", // "Power factor reversion timer when injecting active power.", // - ValuePoint.Type.UINT32, false /* mandatory? */, AccessMode.READ_WRITE, Unit.SECONDS)), // + UINT32, false /* mandatory? */, READ_WRITE, Unit.SECONDS)), // P_F_W_INJ_RVRT_REM(new ValuePoint("S704_P_F_W_INJ_RVRT_REM", "PF Reversion Time Rem (W Inj)", // "Power factor reversion time remaining when injecting active power.", // - ValuePoint.Type.UINT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.SECONDS)), // + UINT32, false /* mandatory? */, READ_ONLY, Unit.SECONDS)), // P_F_W_ABS_ENA(new EnumPoint("S704_P_F_W_ABS_ENA", "Power Factor Enable (W Abs) Enable", // "Power factor enable when absorbing active power.", // - EnumPoint.Type.ENUM16, false /* mandatory? */, AccessMode.READ_WRITE, S704_PFWAbsEna.values())), // + ENUM16, false /* mandatory? */, READ_WRITE, S704_PFWAbsEna.values())), // P_F_W_ABS_ENA_RVRT(new EnumPoint("S704_P_F_W_ABS_ENA_RVRT", "Power Factor Reversion Enable (W Abs)", // "Power factor reversion timer when absorbing active power enable.", // - EnumPoint.Type.ENUM16, false /* mandatory? */, AccessMode.READ_WRITE, S704_PFWAbsEnaRvrt.values())), // + ENUM16, false /* mandatory? */, READ_WRITE, S704_PFWAbsEnaRvrt.values())), // P_F_W_ABS_RVRT_TMS(new ValuePoint("S704_P_F_W_ABS_RVRT_TMS", "PF Reversion Time (W Abs)", // "Power factor reversion timer when absorbing active power.", // - ValuePoint.Type.UINT32, false /* mandatory? */, AccessMode.READ_WRITE, Unit.SECONDS)), // + UINT32, false /* mandatory? */, READ_WRITE, Unit.SECONDS)), // P_F_W_ABS_RVRT_REM(new ValuePoint("S704_P_F_W_ABS_RVRT_REM", "PF Reversion Time Rem (W Abs)", // "Power factor reversion time remaining when absorbing active power.", // - ValuePoint.Type.UINT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.SECONDS)), // + UINT32, false /* mandatory? */, READ_ONLY, Unit.SECONDS)), // W_MAX_LIM_PCT_ENA(new EnumPoint("S704_W_MAX_LIM_PCT_ENA", "Limit Max Power Pct Enable", // "Limit maximum active power percent enable.", // - EnumPoint.Type.ENUM16, false /* mandatory? */, AccessMode.READ_WRITE, S704_WMaxLimPctEna.values())), // + ENUM16, false /* mandatory? */, READ_WRITE, S704_WMaxLimPctEna.values())), // W_MAX_LIM_PCT(new ScaledValuePoint("S704_W_MAX_LIM_PCT", "Limit Max Power Pct Setpoint", // "Limit maximum active power percent value.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.NONE, "WMaxLimPct_SF")), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.NONE, "WMaxLimPct_SF")), // W_MAX_LIM_PCT_RVRT(new ScaledValuePoint("S704_W_MAX_LIM_PCT_RVRT", "Reversion Limit Max Power Pct", // "Reversion limit maximum active power percent value.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.NONE, "WMaxLimPct_SF")), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.NONE, "WMaxLimPct_SF")), // W_MAX_LIM_PCT_ENA_RVRT(new EnumPoint("S704_W_MAX_LIM_PCT_ENA_RVRT", "Reversion Limit Max Power Pct Enable", // "Reversion limit maximum active power percent value enable.", // - EnumPoint.Type.ENUM16, false /* mandatory? */, AccessMode.READ_WRITE, S704_WMaxLimPctEnaRvrt.values())), // + ENUM16, false /* mandatory? */, READ_WRITE, S704_WMaxLimPctEnaRvrt.values())), // W_MAX_LIM_PCT_RVRT_TMS(new ValuePoint("S704_W_MAX_LIM_PCT_RVRT_TMS", "Limit Max Power Pct Reversion Time", // "Limit maximum active power percent reversion time.", // - ValuePoint.Type.UINT32, false /* mandatory? */, AccessMode.READ_WRITE, Unit.SECONDS)), // + UINT32, false /* mandatory? */, READ_WRITE, Unit.SECONDS)), // W_MAX_LIM_PCT_RVRT_REM(new ValuePoint("S704_W_MAX_LIM_PCT_RVRT_REM", "Limit Max Power Pct Rev Time Rem", // "Limit maximum active power percent reversion time remaining.", // - ValuePoint.Type.UINT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.SECONDS)), // + UINT32, false /* mandatory? */, READ_ONLY, Unit.SECONDS)), // W_SET_ENA(new EnumPoint("S704_W_SET_ENA", "Set Active Power Enable", // "Set active power enable.", // - EnumPoint.Type.ENUM16, false /* mandatory? */, AccessMode.READ_WRITE, S704_WSetEna.values())), // + ENUM16, false /* mandatory? */, READ_WRITE, S704_WSetEna.values())), // W_SET_MOD(new EnumPoint("S704_W_SET_MOD", "Set Active Power Mode", // "Set active power mode.", // - EnumPoint.Type.ENUM16, false /* mandatory? */, AccessMode.READ_WRITE, S704_WSetMod.values())), // + ENUM16, false /* mandatory? */, READ_WRITE, S704_WSetMod.values())), // W_SET(new ScaledValuePoint("S704_W_SET", "Active Power Setpoint (W)", // "Active power setting value in watts.", // - ValuePoint.Type.INT32, false /* mandatory? */, AccessMode.READ_WRITE, Unit.WATT, "WSet_SF")), // + INT32, false /* mandatory? */, READ_WRITE, Unit.WATT, "WSet_SF")), // W_SET_RVRT(new ScaledValuePoint("S704_W_SET_RVRT", "Reversion Active Power (W)", // "Reversion active power setting value in watts.", // - ValuePoint.Type.INT32, false /* mandatory? */, AccessMode.READ_WRITE, Unit.WATT, "WSet_SF")), // + INT32, false /* mandatory? */, READ_WRITE, Unit.WATT, "WSet_SF")), // W_SET_PCT(new ScaledValuePoint("S704_W_SET_PCT", "Active Power Setpoint (Pct)", // "Active power setting value as percent.", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.NONE, "WSetPct_SF")), // + INT16, false /* mandatory? */, READ_WRITE, Unit.NONE, "WSetPct_SF")), // W_SET_PCT_RVRT(new ScaledValuePoint("S704_W_SET_PCT_RVRT", "Reversion Active Power (Pct)", // "Reversion active power setting value as percent.", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.NONE, "WSetPct_SF")), // + INT16, false /* mandatory? */, READ_WRITE, Unit.NONE, "WSetPct_SF")), // W_SET_ENA_RVRT(new EnumPoint("S704_W_SET_ENA_RVRT", "Reversion Active Power Enable", // "Reversion active power function enable.", // - EnumPoint.Type.ENUM16, false /* mandatory? */, AccessMode.READ_WRITE, S704_WSetEnaRvrt.values())), // + ENUM16, false /* mandatory? */, READ_WRITE, S704_WSetEnaRvrt.values())), // W_SET_RVRT_TMS(new ValuePoint("S704_W_SET_RVRT_TMS", "Active Power Reversion Time", // "Set active power reversion time.", // - ValuePoint.Type.UINT32, false /* mandatory? */, AccessMode.READ_WRITE, Unit.SECONDS)), // + UINT32, false /* mandatory? */, READ_WRITE, Unit.SECONDS)), // W_SET_RVRT_REM(new ValuePoint("S704_W_SET_RVRT_REM", "Active Power Rev Time Rem", // "Set active power reversion time remaining.", // - ValuePoint.Type.UINT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.SECONDS)), // + UINT32, false /* mandatory? */, READ_ONLY, Unit.SECONDS)), // VAR_SET_ENA(new EnumPoint("S704_VAR_SET_ENA", "Set Reactive Power Enable", // "Set reactive power enable.", // - EnumPoint.Type.ENUM16, false /* mandatory? */, AccessMode.READ_WRITE, S704_VarSetEna.values())), // + ENUM16, false /* mandatory? */, READ_WRITE, S704_VarSetEna.values())), // VAR_SET_MOD(new EnumPoint("S704_VAR_SET_MOD", "Set Reactive Power Mode", // "Set reactive power mode.", // - EnumPoint.Type.ENUM16, false /* mandatory? */, AccessMode.READ_WRITE, S704_VarSetMod.values())), // + ENUM16, false /* mandatory? */, READ_WRITE, S704_VarSetMod.values())), // VAR_SET_PRI(new EnumPoint("S704_VAR_SET_PRI", "Reactive Power Priority", // "Reactive power priority.", // - EnumPoint.Type.ENUM16, false /* mandatory? */, AccessMode.READ_WRITE, S704_VarSetPri.values())), // + ENUM16, false /* mandatory? */, READ_WRITE, S704_VarSetPri.values())), // VAR_SET(new ScaledValuePoint("S704_VAR_SET", "Reactive Power Setpoint (Vars)", // "Reactive power setting value in vars.", // - ValuePoint.Type.INT32, false /* mandatory? */, AccessMode.READ_WRITE, Unit.VOLT_AMPERE_REACTIVE, "VarSet_SF")), // + INT32, false /* mandatory? */, READ_WRITE, Unit.VOLT_AMPERE_REACTIVE, "VarSet_SF")), // VAR_SET_RVRT(new ScaledValuePoint("S704_VAR_SET_RVRT", "Reversion Reactive Power (Vars)", // "Reversion reactive power setting value in vars.", // - ValuePoint.Type.INT32, false /* mandatory? */, AccessMode.READ_WRITE, Unit.VOLT_AMPERE_REACTIVE, "VarSet_SF")), // + INT32, false /* mandatory? */, READ_WRITE, Unit.VOLT_AMPERE_REACTIVE, "VarSet_SF")), // VAR_SET_PCT(new ScaledValuePoint("S704_VAR_SET_PCT", "Reactive Power Setpoint (Pct)", // "Reactive power setting value as percent.", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.NONE, "VarSetPct_SF")), // + INT16, false /* mandatory? */, READ_WRITE, Unit.NONE, "VarSetPct_SF")), // VAR_SET_PCT_RVRT(new ScaledValuePoint("S704_VAR_SET_PCT_RVRT", "Reversion Reactive Power (Pct)", // "Reversion reactive power setting value as percent.", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.NONE, "VarSetPct_SF")), // + INT16, false /* mandatory? */, READ_WRITE, Unit.NONE, "VarSetPct_SF")), // VAR_SET_ENA_RVRT(new EnumPoint("S704_VAR_SET_ENA_RVRT", "Reversion Reactive Power Enable", // "Reversion reactive power function enable.", // - EnumPoint.Type.ENUM16, false /* mandatory? */, AccessMode.READ_WRITE, S704_VarSetEnaRvrt.values())), // + ENUM16, false /* mandatory? */, READ_WRITE, S704_VarSetEnaRvrt.values())), // VAR_SET_RVRT_TMS(new ValuePoint("S704_VAR_SET_RVRT_TMS", "Reactive Power Reversion Time", // "Set reactive power reversion time.", // - ValuePoint.Type.UINT32, false /* mandatory? */, AccessMode.READ_WRITE, Unit.SECONDS)), // + UINT32, false /* mandatory? */, READ_WRITE, Unit.SECONDS)), // VAR_SET_RVRT_REM(new ValuePoint("S704_VAR_SET_RVRT_REM", "Reactive Power Rev Time Rem", // "Set reactive power reversion time remaining.", // - ValuePoint.Type.UINT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.SECONDS)), // + UINT32, false /* mandatory? */, READ_ONLY, Unit.SECONDS)), // W_RMP(new ValuePoint("S704_W_RMP", "Normal Ramp Rate", // "Ramp rate for increases in active power during normal generation.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.NONE)), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.NONE)), // W_RMP_REF(new EnumPoint("S704_W_RMP_REF", "Normal Ramp Rate Reference", // "Ramp rate reference unit for increases in active power or current during normal generation.", // - EnumPoint.Type.ENUM16, false /* mandatory? */, AccessMode.READ_WRITE, S704_WRmpRef.values())), // + ENUM16, false /* mandatory? */, READ_WRITE, S704_WRmpRef.values())), // VAR_RMP(new ValuePoint("S704_VAR_RMP", "Reactive Power Ramp Rate", // "Ramp rate based on max reactive power per second.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.NONE)), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.NONE)), // ANTI_ISL_ENA(new EnumPoint("S704_ANTI_ISL_ENA", "Anti-Islanding Enable", // "Anti-islanding enable.", // - EnumPoint.Type.ENUM16, false /* mandatory? */, AccessMode.READ_WRITE, S704_AntiIslEna.values())), // + ENUM16, false /* mandatory? */, READ_WRITE, S704_AntiIslEna.values())), // PF_SF(new ScaleFactorPoint("S704_PF_SF", "Power Factor Scale Factor", // "Power factor scale factor.")), // W_MAX_LIM_PCT_S_F(new ScaleFactorPoint("S704_W_MAX_LIM_PCT_S_F", "Limit Max Power Scale Factor", // @@ -5012,28 +5082,28 @@ public OptionsEnum getUndefined() { public static enum S705 implements SunSpecPoint { ENA(new EnumPoint("S705_ENA", "DER Volt-Var Module Enable", // "Volt-Var control enable.", // - EnumPoint.Type.ENUM16, true /* mandatory? */, AccessMode.READ_WRITE, S705_Ena.values())), // + ENUM16, true /* mandatory? */, READ_WRITE, S705_Ena.values())), // ADPT_CRV_REQ(new ValuePoint("S705_ADPT_CRV_REQ", "Adopt Curve Request", // "Index of curve points to adopt. First curve index is 1.", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_WRITE, Unit.NONE)), // + UINT16, true /* mandatory? */, READ_WRITE, Unit.NONE)), // ADPT_CRV_RSLT(new EnumPoint("S705_ADPT_CRV_RSLT", "Adopt Curve Result", // "Result of last adopt curve operation.", // - EnumPoint.Type.ENUM16, true /* mandatory? */, AccessMode.READ_ONLY, S705_AdptCrvRslt.values())), // + ENUM16, true /* mandatory? */, READ_ONLY, S705_AdptCrvRslt.values())), // N_PT(new ValuePoint("S705_N_PT", "Number Of Points", // "Number of curve points supported.", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.NONE)), // N_CRV(new ValuePoint("S705_N_CRV", "Stored Curve Count", // "Number of stored curves supported.", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.NONE)), // RVRT_TMS(new ValuePoint("S705_RVRT_TMS", "Reversion Timeout", // "Reversion time in seconds. 0 = No reversion time.", // - ValuePoint.Type.UINT32, false /* mandatory? */, AccessMode.READ_WRITE, Unit.SECONDS)), // + UINT32, false /* mandatory? */, READ_WRITE, Unit.SECONDS)), // RVRT_REM(new ValuePoint("S705_RVRT_REM", "Reversion Time Remaining", // "Reversion time remaining in seconds.", // - ValuePoint.Type.UINT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.SECONDS)), // + UINT32, false /* mandatory? */, READ_ONLY, Unit.SECONDS)), // RVRT_CRV(new ValuePoint("S705_RVRT_CRV", "Reversion Curve", // "Default curve after reversion timeout.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.NONE)), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.NONE)), // V_SF(new ScaleFactorPoint("S705_V_SF", "Voltage Scale Factor", // "Scale factor for curve voltage points.")), // DEPT_REF_S_F(new ScaleFactorPoint("S705_DEPT_REF_S_F", "Var Scale Factor", // @@ -5115,28 +5185,28 @@ public OptionsEnum getUndefined() { public static enum S706 implements SunSpecPoint { ENA(new EnumPoint("S706_ENA", "DER Volt-Watt Module Enable", // "Volt-Watt control enable.", // - EnumPoint.Type.ENUM16, true /* mandatory? */, AccessMode.READ_WRITE, S706_Ena.values())), // + ENUM16, true /* mandatory? */, READ_WRITE, S706_Ena.values())), // ADPT_CRV_REQ(new ValuePoint("S706_ADPT_CRV_REQ", "Adopt Curve Request", // "Index of curve points to adopt. First curve index is 1.", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_WRITE, Unit.NONE)), // + UINT16, true /* mandatory? */, READ_WRITE, Unit.NONE)), // ADPT_CRV_RSLT(new EnumPoint("S706_ADPT_CRV_RSLT", "Adopt Curve Result", // "Result of last adopt curve operation.", // - EnumPoint.Type.ENUM16, true /* mandatory? */, AccessMode.READ_ONLY, S706_AdptCrvRslt.values())), // + ENUM16, true /* mandatory? */, READ_ONLY, S706_AdptCrvRslt.values())), // N_PT(new ValuePoint("S706_N_PT", "Number Of Points", // "Number of curve points supported.", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.NONE)), // N_CRV(new ValuePoint("S706_N_CRV", "Stored Curve Count", // "Number of stored curves supported.", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.NONE)), // RVRT_TMS(new ValuePoint("S706_RVRT_TMS", "Reversion Timeout", // "Reversion time in seconds. 0 = No reversion time.", // - ValuePoint.Type.UINT32, false /* mandatory? */, AccessMode.READ_WRITE, Unit.SECONDS)), // + UINT32, false /* mandatory? */, READ_WRITE, Unit.SECONDS)), // RVRT_REM(new ValuePoint("S706_RVRT_REM", "Reversion Time Remaining", // "Reversion time remaining in seconds.", // - ValuePoint.Type.UINT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.SECONDS)), // + UINT32, false /* mandatory? */, READ_ONLY, Unit.SECONDS)), // RVRT_CRV(new ValuePoint("S706_RVRT_CRV", "Reversion Curve", // "Default curve after reversion timeout.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.NONE)), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.NONE)), // V_SF(new ScaleFactorPoint("S706_V_SF", "Voltage Scale Factor", // "Scale factor for curve voltage points.")), // DEPT_REF_S_F(new ScaleFactorPoint("S706_DEPT_REF_S_F", "Watt Scale Factor", // @@ -5218,19 +5288,19 @@ public OptionsEnum getUndefined() { public static enum S707 implements SunSpecPoint { ENA(new EnumPoint("S707_ENA", "DER Trip LV Module Enable", // "DER low voltage trip control enable.", // - EnumPoint.Type.ENUM16, true /* mandatory? */, AccessMode.READ_WRITE, S707_Ena.values())), // + ENUM16, true /* mandatory? */, READ_WRITE, S707_Ena.values())), // ADPT_CRV_REQ(new ValuePoint("S707_ADPT_CRV_REQ", "Adopt Curve Request", // "Index of curve points to adopt. First curve index is 1.", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_WRITE, Unit.NONE)), // + UINT16, true /* mandatory? */, READ_WRITE, Unit.NONE)), // ADPT_CRV_RSLT(new EnumPoint("S707_ADPT_CRV_RSLT", "Adopt Curve Result", // "Result of last adopt curve operation.", // - EnumPoint.Type.ENUM16, true /* mandatory? */, AccessMode.READ_ONLY, S707_AdptCrvRslt.values())), // + ENUM16, true /* mandatory? */, READ_ONLY, S707_AdptCrvRslt.values())), // N_PT(new ValuePoint("S707_N_PT", "Number Of Points", // "Number of curve points supported.", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.NONE)), // N_CRV_SET(new ValuePoint("S707_N_CRV_SET", "Stored Curve Count", // "Number of stored curves supported.", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.NONE)), // V_SF(new ScaleFactorPoint("S707_V_SF", "Voltage Scale Factor", // "Scale factor for curve voltage points.")), // TMS_S_F(new ScaleFactorPoint("S707_TMS_S_F", "Time Point Scale Factor", // @@ -5310,19 +5380,19 @@ public OptionsEnum getUndefined() { public static enum S708 implements SunSpecPoint { ENA(new EnumPoint("S708_ENA", "DER Trip HV Module Enable", // "DER high voltage trip control enable.", // - EnumPoint.Type.ENUM16, true /* mandatory? */, AccessMode.READ_WRITE, S708_Ena.values())), // + ENUM16, true /* mandatory? */, READ_WRITE, S708_Ena.values())), // ADPT_CRV_REQ(new ValuePoint("S708_ADPT_CRV_REQ", "Adopt Curve Request", // "Index of curve points to adopt. First curve index is 1.", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_WRITE, Unit.NONE)), // + UINT16, true /* mandatory? */, READ_WRITE, Unit.NONE)), // ADPT_CRV_RSLT(new EnumPoint("S708_ADPT_CRV_RSLT", "Adopt Curve Result", // "Result of last adopt curve operation.", // - EnumPoint.Type.ENUM16, true /* mandatory? */, AccessMode.READ_ONLY, S708_AdptCrvRslt.values())), // + ENUM16, true /* mandatory? */, READ_ONLY, S708_AdptCrvRslt.values())), // N_PT(new ValuePoint("S708_N_PT", "Number Of Points", // "Number of curve points supported.", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.NONE)), // N_CRV_SET(new ValuePoint("S708_N_CRV_SET", "Stored Curve Count", // "Number of stored curves supported.", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.NONE)), // V_SF(new ScaleFactorPoint("S708_V_SF", "Voltage Scale Factor", // "Scale factor for curve voltage points.")), // TMS_S_F(new ScaleFactorPoint("S708_TMS_S_F", "Time Point Scale Factor", // @@ -5402,19 +5472,19 @@ public OptionsEnum getUndefined() { public static enum S709 implements SunSpecPoint { ENA(new EnumPoint("S709_ENA", "DER Trip LF Module Enable", // "DER low frequency trip control enable.", // - EnumPoint.Type.ENUM16, true /* mandatory? */, AccessMode.READ_WRITE, S709_Ena.values())), // + ENUM16, true /* mandatory? */, READ_WRITE, S709_Ena.values())), // ADPT_CRV_REQ(new ValuePoint("S709_ADPT_CRV_REQ", "Adopt Curve Request", // "Index of curve points to adopt. First curve index is 1.", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_WRITE, Unit.NONE)), // + UINT16, true /* mandatory? */, READ_WRITE, Unit.NONE)), // ADPT_CRV_RSLT(new EnumPoint("S709_ADPT_CRV_RSLT", "Adopt Curve Result", // "Result of last adopt curve operation.", // - EnumPoint.Type.ENUM16, true /* mandatory? */, AccessMode.READ_ONLY, S709_AdptCrvRslt.values())), // + ENUM16, true /* mandatory? */, READ_ONLY, S709_AdptCrvRslt.values())), // N_PT(new ValuePoint("S709_N_PT", "Number Of Points", // "Number of curve points supported.", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.NONE)), // N_CRV_SET(new ValuePoint("S709_N_CRV_SET", "Stored Curve Count", // "Number of stored curves supported.", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.NONE)), // HZ_S_F(new ScaleFactorPoint("S709_HZ_S_F", "Frequency Scale Factor", // "Scale factor for curve frequency points.")), // TMS_S_F(new ScaleFactorPoint("S709_TMS_S_F", "Time Point Scale Factor", // @@ -5494,19 +5564,19 @@ public OptionsEnum getUndefined() { public static enum S710 implements SunSpecPoint { ENA(new EnumPoint("S710_ENA", "DER Trip HF Module Enable", // "DER high frequency trip control enable.", // - EnumPoint.Type.ENUM16, true /* mandatory? */, AccessMode.READ_WRITE, S710_Ena.values())), // + ENUM16, true /* mandatory? */, READ_WRITE, S710_Ena.values())), // ADPT_CRV_REQ(new ValuePoint("S710_ADPT_CRV_REQ", "Adopt Curve Request", // "Index of curve points to adopt. First curve index is 1.", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_WRITE, Unit.NONE)), // + UINT16, true /* mandatory? */, READ_WRITE, Unit.NONE)), // ADPT_CRV_RSLT(new EnumPoint("S710_ADPT_CRV_RSLT", "Adopt Curve Result", // "Result of last adopt curve operation.", // - EnumPoint.Type.ENUM16, true /* mandatory? */, AccessMode.READ_ONLY, S710_AdptCrvRslt.values())), // + ENUM16, true /* mandatory? */, READ_ONLY, S710_AdptCrvRslt.values())), // N_PT(new ValuePoint("S710_N_PT", "Number Of Points", // "Number of curve points supported.", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.NONE)), // N_CRV_SET(new ValuePoint("S710_N_CRV_SET", "Stored Curve Count", // "Number of stored curves supported.", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.NONE)), // HZ_S_F(new ScaleFactorPoint("S710_HZ_S_F", "Frequency Scale Factor", // "Scale factor for curve frequency points.")), // TMS_S_F(new ScaleFactorPoint("S710_TMS_S_F", "Time Point Scale Factor", // @@ -5586,25 +5656,25 @@ public OptionsEnum getUndefined() { public static enum S711 implements SunSpecPoint { ENA(new EnumPoint("S711_ENA", "DER Frequency Droop Module Enable", // "DER Frequency-Watt (Frequency-Droop) control enable.", // - EnumPoint.Type.ENUM16, true /* mandatory? */, AccessMode.READ_WRITE, S711_Ena.values())), // + ENUM16, true /* mandatory? */, READ_WRITE, S711_Ena.values())), // ADPT_CTL_REQ(new ValuePoint("S711_ADPT_CTL_REQ", "Set Active Control Request", // "Set active control. 0 = No active control.", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_WRITE, Unit.NONE)), // + UINT16, true /* mandatory? */, READ_WRITE, Unit.NONE)), // ADPT_CTL_RSLT(new EnumPoint("S711_ADPT_CTL_RSLT", "Set Active Control Result", // "Result of last set active control operation.", // - EnumPoint.Type.ENUM16, true /* mandatory? */, AccessMode.READ_ONLY, S711_AdptCtlRslt.values())), // + ENUM16, true /* mandatory? */, READ_ONLY, S711_AdptCtlRslt.values())), // N_CTL(new ValuePoint("S711_N_CTL", "Stored Control Count", // "Number of stored controls supported.", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.NONE)), // RVRT_TMS(new ValuePoint("S711_RVRT_TMS", "Reversion Timeout", // "Reversion time in seconds. 0 = No reversion time.", // - ValuePoint.Type.UINT32, false /* mandatory? */, AccessMode.READ_WRITE, Unit.SECONDS)), // + UINT32, false /* mandatory? */, READ_WRITE, Unit.SECONDS)), // RVRT_REM(new ValuePoint("S711_RVRT_REM", "Reversion Time Left", // "Reversion time remaining in seconds.", // - ValuePoint.Type.UINT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.SECONDS)), // + UINT32, false /* mandatory? */, READ_ONLY, Unit.SECONDS)), // RVRT_CTL(new ValuePoint("S711_RVRT_CTL", "Reversion Control", // "Default control after reversion timeout.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.NONE)), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.NONE)), // DB_S_F(new ScaleFactorPoint("S711_DB_S_F", "Deadband Scale Factor", // "Deadband scale factor.")), // K_SF(new ScaleFactorPoint("S711_K_SF", "Frequency Change Scale Factor", // @@ -5686,28 +5756,28 @@ public OptionsEnum getUndefined() { public static enum S712 implements SunSpecPoint { ENA(new EnumPoint("S712_ENA", "DER Watt-Var Module Enable", // "DER Watt-Var control enable.", // - EnumPoint.Type.ENUM16, true /* mandatory? */, AccessMode.READ_WRITE, S712_Ena.values())), // + ENUM16, true /* mandatory? */, READ_WRITE, S712_Ena.values())), // ADPT_CRV_REQ(new ValuePoint("S712_ADPT_CRV_REQ", "Set Active Curve Request", // "Set active curve. 0 = No active curve.", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_WRITE, Unit.NONE)), // + UINT16, true /* mandatory? */, READ_WRITE, Unit.NONE)), // ADPT_CRV_RSLT(new EnumPoint("S712_ADPT_CRV_RSLT", "Set Active Curve Result", // "Result of last set active curve operation.", // - EnumPoint.Type.ENUM16, true /* mandatory? */, AccessMode.READ_ONLY, S712_AdptCrvRslt.values())), // + ENUM16, true /* mandatory? */, READ_ONLY, S712_AdptCrvRslt.values())), // N_PT(new ValuePoint("S712_N_PT", "Number Of Points", // "Number of curve points supported.", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.NONE)), // N_CRV(new ValuePoint("S712_N_CRV", "Stored Curve Count", // "Number of stored curves supported.", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.NONE)), // RVRT_TMS(new ValuePoint("S712_RVRT_TMS", "Reversion Timeout", // "Reversion time in seconds. 0 = No reversion time.", // - ValuePoint.Type.UINT32, false /* mandatory? */, AccessMode.READ_WRITE, Unit.SECONDS)), // + UINT32, false /* mandatory? */, READ_WRITE, Unit.SECONDS)), // RVRT_REM(new ValuePoint("S712_RVRT_REM", "Reversion Time Left", // "Reversion time remaining in seconds.", // - ValuePoint.Type.UINT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.SECONDS)), // + UINT32, false /* mandatory? */, READ_ONLY, Unit.SECONDS)), // RVRT_CRV(new ValuePoint("S712_RVRT_CRV", "Reversion Curve", // "Default curve after reversion timeout.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.NONE)), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.NONE)), // W_SF(new ScaleFactorPoint("S712_W_SF", "Active Power Scale Factor", // "Scale factor for curve active power points.")), // DEPT_REF_S_F(new ScaleFactorPoint("S712_DEPT_REF_S_F", "Var Scale Factor", // @@ -5787,19 +5857,19 @@ public OptionsEnum getUndefined() { public static enum S713 implements SunSpecPoint { W_H_RTG(new ScaledValuePoint("S713_W_H_RTG", "Energy Rating", // "Energy rating of the DER storage.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.CUMULATED_WATT_HOURS, "WH_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.CUMULATED_WATT_HOURS, "WH_SF")), // W_H_AVAIL(new ScaledValuePoint("S713_W_H_AVAIL", "Energy Available", // "Energy available of the DER storage (WHAvail = WHRtg * SoC * SoH)", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.CUMULATED_WATT_HOURS, "WH_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.CUMULATED_WATT_HOURS, "WH_SF")), // SO_C(new ScaledValuePoint("S713_SO_C", "State of Charge", // "State of charge of the DER storage.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE, "Pct_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.NONE, "Pct_SF")), // SO_H(new ScaledValuePoint("S713_SO_H", "State of Health", // "State of health of the DER storage.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE, "Pct_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.NONE, "Pct_SF")), // STA(new EnumPoint("S713_STA", "Status", // "Storage status.", // - EnumPoint.Type.ENUM16, false /* mandatory? */, AccessMode.READ_ONLY, S713_Sta.values())), // + ENUM16, false /* mandatory? */, READ_ONLY, S713_Sta.values())), // WH_SF(new ScaleFactorPoint("S713_WH_SF", "Energy Scale Factor", // "Scale factor for energy capacity.")), // PCT_S_F(new ScaleFactorPoint("S713_PCT_S_F", "Percent Scale Factor", // @@ -5850,22 +5920,22 @@ public OptionsEnum getUndefined() { public static enum S714 implements SunSpecPoint { PRT_ALRMS(new BitFieldPoint("S714_PRT_ALRMS", "Port Alarms", // "Bitfield of ports with active alarms. Bit is 1 if port has an active alarm. Bit 0 is first port.", // - BitFieldPoint.Type.BITFIELD32, false /* mandatory? */, AccessMode.READ_ONLY, new SunSpecBitPoint[0])), // + BITFIELD32, false /* mandatory? */, READ_ONLY, new SunSpecBitPoint[0])), // N_PRT(new ValuePoint("S714_N_PRT", "Number Of Ports", // "Number of DC ports.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.NONE)), // DCA(new ScaledValuePoint("S714_DCA", "DC Current", // "Total DC current for all ports.", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE, "DCA_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.AMPERE, "DCA_SF")), // DCW(new ScaledValuePoint("S714_DCW", "DC Power", // "Total DC power for all ports.", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.WATT, "DCW_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.WATT, "DCW_SF")), // D_C_WH_INJ(new ScaledValuePoint("S714_D_C_WH_INJ", "DC Energy Injected", // "Total cumulative DC energy injected for all ports.", // - ValuePoint.Type.UINT64, false /* mandatory? */, AccessMode.READ_ONLY, Unit.CUMULATED_WATT_HOURS, "DCWH_SF")), // + UINT64, false /* mandatory? */, READ_ONLY, Unit.CUMULATED_WATT_HOURS, "DCWH_SF")), // D_C_WH_ABS(new ScaledValuePoint("S714_D_C_WH_ABS", "DC Energy Absorbed", // "Total cumulative DC energy absorbed for all ports.", // - ValuePoint.Type.UINT64, false /* mandatory? */, AccessMode.READ_ONLY, Unit.CUMULATED_WATT_HOURS, "DCWH_SF")), // + UINT64, false /* mandatory? */, READ_ONLY, Unit.CUMULATED_WATT_HOURS, "DCWH_SF")), // DCA_SF(new ScaleFactorPoint("S714_DCA_SF", "DC Current Scale Factor", // "DC current scale factor.")), // DCV_SF(new ScaleFactorPoint("S714_DCV_SF", "DC Voltage Scale Factor", // @@ -5892,19 +5962,19 @@ public Point get() { public static enum S715 implements SunSpecPoint { LOC_REM_CTL(new EnumPoint("S715_LOC_REM_CTL", "Control Mode", // "DER control mode. Enumeration.", // - EnumPoint.Type.ENUM16, false /* mandatory? */, AccessMode.READ_ONLY, S715_LocRemCtl.values())), // + ENUM16, false /* mandatory? */, READ_ONLY, S715_LocRemCtl.values())), // D_E_R_HB(new ValuePoint("S715_D_E_R_HB", "DER Heartbeat", // "Value is incremented every second by the DER with periodic resets to zero.", // - ValuePoint.Type.UINT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT32, false /* mandatory? */, READ_ONLY, Unit.NONE)), // CONTROLLER_HB(new ValuePoint("S715_CONTROLLER_HB", "Controller Heartbeat", // "Value is incremented every second by the controller with periodic resets to zero.", // - ValuePoint.Type.UINT32, false /* mandatory? */, AccessMode.READ_WRITE, Unit.NONE)), // + UINT32, false /* mandatory? */, READ_WRITE, Unit.NONE)), // ALARM_RESET(new ValuePoint("S715_ALARM_RESET", "Alarm Reset", // "Used to reset any latched alarms. 1 = Reset.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.NONE)), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.NONE)), // OP_CTL(new EnumPoint("S715_OP_CTL", "Set Operation", // "Commands to PCS. Enumerated value.", // - EnumPoint.Type.ENUM16, false /* mandatory? */, AccessMode.READ_WRITE, S715_OpCtl.values())); + ENUM16, false /* mandatory? */, READ_WRITE, S715_OpCtl.values())); private final Point point; @@ -5981,7 +6051,7 @@ public OptionsEnum getUndefined() { public static enum S801 implements SunSpecPoint { DEPRECATED(new EnumPoint("S801_DEPRECATED", "Deprecated Model", // "This model has been deprecated.", // - EnumPoint.Type.ENUM16, true /* mandatory? */, AccessMode.READ_ONLY, new OptionsEnum[0])); + ENUM16, true /* mandatory? */, READ_ONLY, new OptionsEnum[0])); private final Point point; @@ -5998,136 +6068,136 @@ public Point get() { public static enum S802 implements SunSpecPoint { A_H_RTG(new ScaledValuePoint("S802_A_H_RTG", "Nameplate Charge Capacity", // "Nameplate charge capacity in amp-hours.", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE_HOURS, "AHRtg_SF")), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.AMPERE_HOURS, "AHRtg_SF")), // W_H_RTG(new ScaledValuePoint("S802_W_H_RTG", "Nameplate Energy Capacity", // "Nameplate energy capacity in DC watt-hours.", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.CUMULATED_WATT_HOURS, "WHRtg_SF")), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.CUMULATED_WATT_HOURS, "WHRtg_SF")), // W_CHA_RTE_MAX(new ScaledValuePoint("S802_W_CHA_RTE_MAX", "Nameplate Max Charge Rate", // "Maximum rate of energy transfer into the storage device in DC watts.", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.WATT, "WChaDisChaMax_SF")), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.WATT, "WChaDisChaMax_SF")), // W_DIS_CHA_RTE_MAX(new ScaledValuePoint("S802_W_DIS_CHA_RTE_MAX", "Nameplate Max Discharge Rate", // "Maximum rate of energy transfer out of the storage device in DC watts.", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.WATT, "WChaDisChaMax_SF")), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.WATT, "WChaDisChaMax_SF")), // DIS_CHA_RTE(new ScaledValuePoint("S802_DIS_CHA_RTE", "Self Discharge Rate", // "Self discharge rate. Percentage of capacity (WHRtg) discharged per day.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.PERCENT, "DisChaRte_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.PERCENT, "DisChaRte_SF")), // SO_C_MAX(new ScaledValuePoint("S802_SO_C_MAX", "Nameplate Max SoC", // "Manufacturer maximum state of charge, expressed as a percentage.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.PERCENT, "SoC_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.PERCENT, "SoC_SF")), // SO_C_MIN(new ScaledValuePoint("S802_SO_C_MIN", "Nameplate Min SoC", // "Manufacturer minimum state of charge, expressed as a percentage.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.PERCENT, "SoC_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.PERCENT, "SoC_SF")), // SOC_RSV_MAX(new ScaledValuePoint("S802_SOC_RSV_MAX", "Max Reserve Percent", // "Setpoint for maximum reserve for storage as a percentage of the nominal maximum storage.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.PERCENT, "SoC_SF")), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.PERCENT, "SoC_SF")), // SO_C_RSV_MIN(new ScaledValuePoint("S802_SO_C_RSV_MIN", "Min Reserve Percent", // "Setpoint for minimum reserve for storage as a percentage of the nominal maximum storage.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.PERCENT, "SoC_SF")), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.PERCENT, "SoC_SF")), // SO_C(new ScaledValuePoint("S802_SO_C", "State of Charge", // "State of charge, expressed as a percentage.", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.PERCENT, "SoC_SF")), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.PERCENT, "SoC_SF")), // DO_D(new ScaledValuePoint("S802_DO_D", "Depth of Discharge", // "Depth of discharge, expressed as a percentage.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.PERCENT, "DoD_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.PERCENT, "DoD_SF")), // SO_H(new ScaledValuePoint("S802_SO_H", "State of Health", // "Percentage of battery life remaining.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.PERCENT, "SoH_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.PERCENT, "SoH_SF")), // N_CYC(new ValuePoint("S802_N_CYC", "Cycle Count", // "Number of cycles executed in the battery.", // - ValuePoint.Type.UINT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT32, false /* mandatory? */, READ_ONLY, Unit.NONE)), // CHA_ST(new EnumPoint("S802_CHA_ST", "Charge Status", // "Charge status of storage device. Enumeration.", // - EnumPoint.Type.ENUM16, false /* mandatory? */, AccessMode.READ_ONLY, S802_ChaSt.values())), // + ENUM16, false /* mandatory? */, READ_ONLY, S802_ChaSt.values())), // LOC_REM_CTL(new EnumPoint("S802_LOC_REM_CTL", "Control Mode", // "Battery control mode. Enumeration.", // - EnumPoint.Type.ENUM16, true /* mandatory? */, AccessMode.READ_ONLY, S802_LocRemCtl.values())), // + ENUM16, true /* mandatory? */, READ_ONLY, S802_LocRemCtl.values())), // HB(new ValuePoint("S802_HB", "Battery Heartbeat", // "Value is incremented every second with periodic resets to zero.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.NONE)), // CTRL_HB(new ValuePoint("S802_CTRL_HB", "Controller Heartbeat", // "Value is incremented every second with periodic resets to zero.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_WRITE, Unit.NONE)), // + UINT16, false /* mandatory? */, READ_WRITE, Unit.NONE)), // ALM_RST(new ValuePoint("S802_ALM_RST", "Alarm Reset", // "Used to reset any latched alarms. 1 = Reset.", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_WRITE, Unit.NONE)), // + UINT16, true /* mandatory? */, READ_WRITE, Unit.NONE)), // TYP(new EnumPoint("S802_TYP", "Battery Type", // "Type of battery. Enumeration.", // - EnumPoint.Type.ENUM16, true /* mandatory? */, AccessMode.READ_ONLY, S802_Typ.values())), // + ENUM16, true /* mandatory? */, READ_ONLY, S802_Typ.values())), // STATE(new EnumPoint("S802_STATE", "State of the Battery Bank", // "State of the battery bank. Enumeration.", // - EnumPoint.Type.ENUM16, true /* mandatory? */, AccessMode.READ_ONLY, S802_State.values())), // + ENUM16, true /* mandatory? */, READ_ONLY, S802_State.values())), // STATE_VND(new EnumPoint("S802_STATE_VND", "Vendor Battery Bank State", // "Vendor specific battery bank state. Enumeration.", // - EnumPoint.Type.ENUM16, false /* mandatory? */, AccessMode.READ_ONLY, new OptionsEnum[0])), // + ENUM16, false /* mandatory? */, READ_ONLY, new OptionsEnum[0])), // WARR_DT(new ValuePoint("S802_WARR_DT", "Warranty Date", // "Date the device warranty expires.", // - ValuePoint.Type.UINT32, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT32, false /* mandatory? */, READ_ONLY, Unit.NONE)), // EVT1(new BitFieldPoint("S802_EVT1", "Battery Event 1 Bitfield", // "Alarms and warnings. Bit flags.", // - BitFieldPoint.Type.BITFIELD32, true /* mandatory? */, AccessMode.READ_ONLY, S802_Evt1.values())), // + BITFIELD32, true /* mandatory? */, READ_ONLY, S802_Evt1.values())), // EVT2(new BitFieldPoint("S802_EVT2", "Battery Event 2 Bitfield", // "Alarms and warnings. Bit flags.", // - BitFieldPoint.Type.BITFIELD32, true /* mandatory? */, AccessMode.READ_ONLY, new SunSpecBitPoint[0])), // + BITFIELD32, true /* mandatory? */, READ_ONLY, new SunSpecBitPoint[0])), // EVT_VND1(new BitFieldPoint("S802_EVT_VND1", "Vendor Event Bitfield 1", // "Vendor defined events.", // - BitFieldPoint.Type.BITFIELD32, true /* mandatory? */, AccessMode.READ_ONLY, new SunSpecBitPoint[0])), // + BITFIELD32, true /* mandatory? */, READ_ONLY, new SunSpecBitPoint[0])), // EVT_VND2(new BitFieldPoint("S802_EVT_VND2", "Vendor Event Bitfield 2", // "Vendor defined events.", // - BitFieldPoint.Type.BITFIELD32, true /* mandatory? */, AccessMode.READ_ONLY, new SunSpecBitPoint[0])), // + BITFIELD32, true /* mandatory? */, READ_ONLY, new SunSpecBitPoint[0])), // V(new ScaledValuePoint("S802_V", "External Battery Voltage", // "DC Bus Voltage.", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // V_MAX(new ScaledValuePoint("S802_V_MAX", "Max Battery Voltage", // "Instantaneous maximum battery voltage.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // V_MIN(new ScaledValuePoint("S802_V_MIN", "Min Battery Voltage", // "Instantaneous minimum battery voltage.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // CELL_V_MAX(new ScaledValuePoint("S802_CELL_V_MAX", "Max Cell Voltage", // "Maximum voltage for all cells in the bank.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "CellV_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.VOLT, "CellV_SF")), // CELL_V_MAX_STR(new ValuePoint("S802_CELL_V_MAX_STR", "Max Cell Voltage String", // "String containing the cell with maximum voltage.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.NONE)), // CELL_V_MAX_MOD(new ValuePoint("S802_CELL_V_MAX_MOD", "Max Cell Voltage Module", // "Module containing the cell with maximum voltage.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.NONE)), // CELL_V_MIN(new ScaledValuePoint("S802_CELL_V_MIN", "Min Cell Voltage", // "Minimum voltage for all cells in the bank.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "CellV_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.VOLT, "CellV_SF")), // CELL_V_MIN_STR(new ValuePoint("S802_CELL_V_MIN_STR", "Min Cell Voltage String", // "String containing the cell with minimum voltage.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.NONE)), // CELL_V_MIN_MOD(new ValuePoint("S802_CELL_V_MIN_MOD", "Min Cell Voltage Module", // "Module containing the cell with minimum voltage.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.NONE)), // CELL_V_AVG(new ScaledValuePoint("S802_CELL_V_AVG", "Average Cell Voltage", // "Average cell voltage for all cells in the bank.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "CellV_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.VOLT, "CellV_SF")), // A(new ScaledValuePoint("S802_A", "Total DC Current", // "Total DC current flowing to/from the battery bank.", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE, "A_SF")), // + INT16, true /* mandatory? */, READ_ONLY, Unit.AMPERE, "A_SF")), // A_CHA_MAX(new ScaledValuePoint("S802_A_CHA_MAX", "Max Charge Current", // "Instantaneous maximum DC charge current.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE, "AMax_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.AMPERE, "AMax_SF")), // A_DIS_CHA_MAX(new ScaledValuePoint("S802_A_DIS_CHA_MAX", "Max Discharge Current", // "Instantaneous maximum DC discharge current.", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE, "AMax_SF")), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.AMPERE, "AMax_SF")), // W(new ScaledValuePoint("S802_W", "Total Power", // "Total power flowing to/from the battery bank.", // - ValuePoint.Type.INT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.WATT, "W_SF")), // + INT16, true /* mandatory? */, READ_ONLY, Unit.WATT, "W_SF")), // REQ_INV_STATE(new EnumPoint("S802_REQ_INV_STATE", "Inverter State Request", // "Request from battery to start or stop the inverter. Enumeration.", // - EnumPoint.Type.ENUM16, false /* mandatory? */, AccessMode.READ_ONLY, S802_ReqInvState.values())), // + ENUM16, false /* mandatory? */, READ_ONLY, S802_ReqInvState.values())), // REQ_W(new ScaledValuePoint("S802_REQ_W", "Battery Power Request", // "AC Power requested by battery.", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.WATT, "W_SF")), // + INT16, false /* mandatory? */, READ_ONLY, Unit.WATT, "W_SF")), // SET_OP(new EnumPoint("S802_SET_OP", "Set Operation", // "Instruct the battery bank to perform an operation such as connecting. Enumeration.", // - EnumPoint.Type.ENUM16, true /* mandatory? */, AccessMode.READ_WRITE, S802_SetOp.values())), // + ENUM16, true /* mandatory? */, READ_WRITE, S802_SetOp.values())), // SET_INV_STATE(new EnumPoint("S802_SET_INV_STATE", "Set Inverter State", // "Set the current state of the inverter.", // - EnumPoint.Type.ENUM16, true /* mandatory? */, AccessMode.READ_WRITE, S802_SetInvState.values())), // + ENUM16, true /* mandatory? */, READ_WRITE, S802_SetInvState.values())), // A_H_RTG_S_F(new ScaleFactorPoint("S802_A_H_RTG_S_F", "", // "Scale factor for charge capacity.")), // W_H_RTG_S_F(new ScaleFactorPoint("S802_W_H_RTG_S_F", "", // @@ -6308,9 +6378,12 @@ public static enum S802_Evt1 implements SunSpecBitPoint { UNDER_TEMP_ALARM(new BitPoint(3, "S802_EVT1_UNDER_TEMP_ALARM", "Under Temp Alarm")), // UNDER_TEMP_WARNING(new BitPoint(4, "S802_EVT1_UNDER_TEMP_WARNING", "Under Temp Warning")), // OVER_CHARGE_CURRENT_ALARM(new BitPoint(5, "S802_EVT1_OVER_CHARGE_CURRENT_ALARM", "Over Charge Current Alarm")), // - OVER_CHARGE_CURRENT_WARNING(new BitPoint(6, "S802_EVT1_OVER_CHARGE_CURRENT_WARNING", "Over Charge Current Warning")), // - OVER_DISCHARGE_CURRENT_ALARM(new BitPoint(7, "S802_EVT1_OVER_DISCHARGE_CURRENT_ALARM", "Over Discharge Current Alarm")), // - OVER_DISCHARGE_CURRENT_WARNING(new BitPoint(8, "S802_EVT1_OVER_DISCHARGE_CURRENT_WARNING", "Over Discharge Current Warning")), // + OVER_CHARGE_CURRENT_WARNING( + new BitPoint(6, "S802_EVT1_OVER_CHARGE_CURRENT_WARNING", "Over Charge Current Warning")), // + OVER_DISCHARGE_CURRENT_ALARM( + new BitPoint(7, "S802_EVT1_OVER_DISCHARGE_CURRENT_ALARM", "Over Discharge Current Alarm")), // + OVER_DISCHARGE_CURRENT_WARNING( + new BitPoint(8, "S802_EVT1_OVER_DISCHARGE_CURRENT_WARNING", "Over Discharge Current Warning")), // OVER_VOLT_ALARM(new BitPoint(9, "S802_EVT1_OVER_VOLT_ALARM", "Over Volt Alarm")), // OVER_VOLT_WARNING(new BitPoint(10, "S802_EVT1_OVER_VOLT_WARNING", "Over Volt Warning")), // UNDER_VOLT_ALARM(new BitPoint(11, "S802_EVT1_UNDER_VOLT_ALARM", "Under Volt Alarm")), // @@ -6320,8 +6393,10 @@ public static enum S802_Evt1 implements SunSpecBitPoint { OVER_SOC_MAX_ALARM(new BitPoint(15, "S802_EVT1_OVER_SOC_MAX_ALARM", "Over Soc Max Alarm")), // OVER_SOC_MAX_WARNING(new BitPoint(16, "S802_EVT1_OVER_SOC_MAX_WARNING", "Over Soc Max Warning")), // VOLTAGE_IMBALANCE_WARNING(new BitPoint(17, "S802_EVT1_VOLTAGE_IMBALANCE_WARNING", "Voltage Imbalance Warning")), // - TEMPERATURE_IMBALANCE_ALARM(new BitPoint(18, "S802_EVT1_TEMPERATURE_IMBALANCE_ALARM", "Temperature Imbalance Alarm")), // - TEMPERATURE_IMBALANCE_WARNING(new BitPoint(19, "S802_EVT1_TEMPERATURE_IMBALANCE_WARNING", "Temperature Imbalance Warning")), // + TEMPERATURE_IMBALANCE_ALARM( + new BitPoint(18, "S802_EVT1_TEMPERATURE_IMBALANCE_ALARM", "Temperature Imbalance Alarm")), // + TEMPERATURE_IMBALANCE_WARNING( + new BitPoint(19, "S802_EVT1_TEMPERATURE_IMBALANCE_WARNING", "Temperature Imbalance Warning")), // CONTACTOR_ERROR(new BitPoint(20, "S802_EVT1_CONTACTOR_ERROR", "Contactor Error")), // FAN_ERROR(new BitPoint(21, "S802_EVT1_FAN_ERROR", "Fan Error")), // GROUND_FAULT(new BitPoint(22, "S802_EVT1_GROUND_FAULT", "Ground Fault")), // @@ -6436,75 +6511,75 @@ public OptionsEnum getUndefined() { public static enum S64001 implements SunSpecPoint { CMD(new EnumPoint("S64001_CMD", "Command Code", "", // - EnumPoint.Type.ENUM16, false /* mandatory? */, AccessMode.READ_WRITE, new OptionsEnum[0])), // + ENUM16, false /* mandatory? */, READ_WRITE, new OptionsEnum[0])), // H_W_REV(new ValuePoint("S64001_H_W_REV", "Hardware Revision", "", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.NONE)), // R_S_F_W_REV(new ValuePoint("S64001_R_S_F_W_REV", "RS FW Revision", "", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.NONE)), // O_S_F_W_REV(new ValuePoint("S64001_O_S_F_W_REV", "OS FW Revision", "", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.NONE)), // PROD_REV(new ValuePoint("S64001_PROD_REV", "Product Revision", "", // - ValuePoint.Type.STRING2, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + STRING2, false /* mandatory? */, READ_ONLY, Unit.NONE)), // BOOTS(new ValuePoint("S64001_BOOTS", "Boot Count", "", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.NONE)), // SWITCH(new BitFieldPoint("S64001_SWITCH", "DIP Switches", "", // - BitFieldPoint.Type.BITFIELD16, false /* mandatory? */, AccessMode.READ_ONLY, new SunSpecBitPoint[0])), // + BITFIELD16, false /* mandatory? */, READ_ONLY, new SunSpecBitPoint[0])), // SENSORS(new ValuePoint("S64001_SENSORS", "Num Detected Sensors", "", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.NONE)), // TALKING(new ValuePoint("S64001_TALKING", "Num Communicating Sensors", "", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.NONE)), // STATUS(new BitFieldPoint("S64001_STATUS", "System Status", "", // - BitFieldPoint.Type.BITFIELD16, false /* mandatory? */, AccessMode.READ_ONLY, new SunSpecBitPoint[0])), // + BITFIELD16, false /* mandatory? */, READ_ONLY, new SunSpecBitPoint[0])), // CONFIG(new BitFieldPoint("S64001_CONFIG", "System Configuration", "", // - BitFieldPoint.Type.BITFIELD16, false /* mandatory? */, AccessMode.READ_ONLY, new SunSpecBitPoint[0])), // + BITFIELD16, false /* mandatory? */, READ_ONLY, new SunSpecBitPoint[0])), // L_E_DBLINK(new ValuePoint("S64001_L_E_DBLINK", "LED Blink Threshold", "", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.NONE)), // L_E_DON(new ValuePoint("S64001_L_E_DON", "LED On Threshold", "", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.NONE)), // RESERVED(new ValuePoint("S64001_RESERVED", "", "", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.NONE)), // LOC(new ValuePoint("S64001_LOC", "Location String", "", // - ValuePoint.Type.STRING16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + STRING16, false /* mandatory? */, READ_ONLY, Unit.NONE)), // S1ID(new EnumPoint("S64001_S1ID", "Sensor 1 Unit ID", "", // - EnumPoint.Type.ENUM16, false /* mandatory? */, AccessMode.READ_ONLY, new OptionsEnum[0])), // + ENUM16, false /* mandatory? */, READ_ONLY, new OptionsEnum[0])), // S1_ADDR(new ValuePoint("S64001_S1_ADDR", "Sensor 1 Address", "", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.NONE)), // S1_O_S_VER(new ValuePoint("S64001_S1_O_S_VER", "Sensor 1 OS Version", "", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.NONE)), // S1_VER(new ValuePoint("S64001_S1_VER", "Sensor 1 Product Version", "", // - ValuePoint.Type.STRING2, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + STRING2, false /* mandatory? */, READ_ONLY, Unit.NONE)), // S1_SERIAL(new ValuePoint("S64001_S1_SERIAL", "Sensor 1 Serial Num", "", // - ValuePoint.Type.STRING5, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + STRING5, false /* mandatory? */, READ_ONLY, Unit.NONE)), // S2ID(new EnumPoint("S64001_S2ID", "Sensor 2 Unit ID", "", // - EnumPoint.Type.ENUM16, false /* mandatory? */, AccessMode.READ_ONLY, new OptionsEnum[0])), // + ENUM16, false /* mandatory? */, READ_ONLY, new OptionsEnum[0])), // S2_ADDR(new ValuePoint("S64001_S2_ADDR", "Sensor 2 Address", "", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.NONE)), // S2_O_S_VER(new ValuePoint("S64001_S2_O_S_VER", "Sensor 2 OS Version", "", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.NONE)), // S2_VER(new ValuePoint("S64001_S2_VER", "Sensor 2 Product Version", "", // - ValuePoint.Type.STRING2, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + STRING2, false /* mandatory? */, READ_ONLY, Unit.NONE)), // S2_SERIAL(new ValuePoint("S64001_S2_SERIAL", "Sensor 2 Serial Num", "", // - ValuePoint.Type.STRING5, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + STRING5, false /* mandatory? */, READ_ONLY, Unit.NONE)), // S3ID(new EnumPoint("S64001_S3ID", "Sensor 3 Unit ID", "", // - EnumPoint.Type.ENUM16, false /* mandatory? */, AccessMode.READ_ONLY, new OptionsEnum[0])), // + ENUM16, false /* mandatory? */, READ_ONLY, new OptionsEnum[0])), // S3_ADDR(new ValuePoint("S64001_S3_ADDR", "Sensor 3 Address", "", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.NONE)), // S3_O_S_VER(new ValuePoint("S64001_S3_O_S_VER", "Sensor 3 OS Version", "", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.NONE)), // S3_VER(new ValuePoint("S64001_S3_VER", "Sensor 3 Product Version", "", // - ValuePoint.Type.STRING2, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + STRING2, false /* mandatory? */, READ_ONLY, Unit.NONE)), // S3_SERIAL(new ValuePoint("S64001_S3_SERIAL", "Sensor 3 Serial Num", "", // - ValuePoint.Type.STRING5, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + STRING5, false /* mandatory? */, READ_ONLY, Unit.NONE)), // S4ID(new EnumPoint("S64001_S4ID", "Sensor 4 Unit ID", "", // - EnumPoint.Type.ENUM16, false /* mandatory? */, AccessMode.READ_ONLY, new OptionsEnum[0])), // + ENUM16, false /* mandatory? */, READ_ONLY, new OptionsEnum[0])), // S4_ADDR(new ValuePoint("S64001_S4_ADDR", "Sensor 4 Address", "", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.NONE)), // S4_O_S_VER(new ValuePoint("S64001_S4_O_S_VER", "Sensor 4 OS Version", "", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.NONE)), // S4_VER(new ValuePoint("S64001_S4_VER", "Sensor 4 Product Version", "", // - ValuePoint.Type.STRING2, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + STRING2, false /* mandatory? */, READ_ONLY, Unit.NONE)), // S4_SERIAL(new ValuePoint("S64001_S4_SERIAL", "Sensor 4 Serial Num", "", // - ValuePoint.Type.STRING5, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)); + STRING5, false /* mandatory? */, READ_ONLY, Unit.NONE)); private final Point point; @@ -6520,19 +6595,19 @@ public Point get() { public static enum S64101 implements SunSpecPoint { ELTEK_COUNTRY_CODE(new ValuePoint("S64101_ELTEK_COUNTRY_CODE", "", "", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.NONE)), // ELTEK_FEEDING_PHASE(new ValuePoint("S64101_ELTEK_FEEDING_PHASE", "", "", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.NONE)), // ELTEK_A_P_D_METHOD(new ValuePoint("S64101_ELTEK_A_P_D_METHOD", "", "", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.NONE)), // ELTEK_A_P_D_POWER_REF(new ValuePoint("S64101_ELTEK_A_P_D_POWER_REF", "", "", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.NONE)), // ELTEK_R_P_S_METHOD(new ValuePoint("S64101_ELTEK_R_P_S_METHOD", "", "", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.NONE)), // ELTEK_R_P_S_Q_REF(new ValuePoint("S64101_ELTEK_R_P_S_Q_REF", "", "", // - ValuePoint.Type.UINT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, false /* mandatory? */, READ_ONLY, Unit.NONE)), // ELTEK_R_P_S_COS_PHI_REF(new ValuePoint("S64101_ELTEK_R_P_S_COS_PHI_REF", "", "", // - ValuePoint.Type.INT16, false /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)); + INT16, false /* mandatory? */, READ_ONLY, Unit.NONE)); private final Point point; @@ -6548,46 +6623,46 @@ public Point get() { public static enum S64111 implements SunSpecPoint { PORT(new ValuePoint("S64111_PORT", "Port Number", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.NONE)), // V_SF(new ScaleFactorPoint("S64111_V_SF", "", "")), // A_SF(new ScaleFactorPoint("S64111_A_SF", "", "")), // P_SF(new ScaleFactorPoint("S64111_P_SF", "", "")), // AH_SF(new ScaleFactorPoint("S64111_AH_SF", "", "")), // KWH_SF(new ScaleFactorPoint("S64111_KWH_SF", "", "")), // BATT_V(new ScaledValuePoint("S64111_BATT_V", "Battery Voltage", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // ARRAY_V(new ScaledValuePoint("S64111_ARRAY_V", "Array Voltage", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // OUTPUT_A(new ScaledValuePoint("S64111_OUTPUT_A", "Output Current", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE, "A_SF")), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.AMPERE, "A_SF")), // INPUT_A(new ScaledValuePoint("S64111_INPUT_A", "Array Current", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE, "P_SF")), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.AMPERE, "P_SF")), // CHARGER_ST(new EnumPoint("S64111_CHARGER_ST", "Operating State", "", // - EnumPoint.Type.ENUM16, true /* mandatory? */, AccessMode.READ_ONLY, S64111_ChargerSt.values())), // + ENUM16, true /* mandatory? */, READ_ONLY, S64111_ChargerSt.values())), // OUTPUT_W(new ScaledValuePoint("S64111_OUTPUT_W", "Output Wattage", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.WATT, "P_SF")), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.WATT, "P_SF")), // TODAY_MIN_BAT_V(new ScaledValuePoint("S64111_TODAY_MIN_BAT_V", "Today's Minimum Battery Voltage", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // TODAY_MAX_BAT_V(new ScaledValuePoint("S64111_TODAY_MAX_BAT_V", "Today's Maximum Battery Voltage", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // VOCV(new ScaledValuePoint("S64111_VOCV", "VOC", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // TODAY_MAX_V_O_C(new ScaledValuePoint("S64111_TODAY_MAX_V_O_C", "Today's Maximum VOC", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // TODAYK_WH_OUTPUT(new ScaledValuePoint("S64111_TODAYK_WH_OUTPUT", "Today's kWh", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.KILOWATT_HOURS, "KWH_SF")), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.KILOWATT_HOURS, "KWH_SF")), // TODAY_A_H_OUTPUT(new ScaledValuePoint("S64111_TODAY_A_H_OUTPUT", "Today's AH", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE_HOURS, "AH_SF")), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.AMPERE_HOURS, "AH_SF")), // LIFE_TIME_K_W_H_OUT(new ScaledValuePoint("S64111_LIFE_TIME_K_W_H_OUT", "Lifetime kWh", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.KILOWATT_HOURS, "P_SF")), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.KILOWATT_HOURS, "P_SF")), // LIFE_TIME_A_H_OUT(new ScaledValuePoint("S64111_LIFE_TIME_A_H_OUT", "Lifetime kAH", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.KILOAMPERE_HOURS, "KWH_SF")), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.KILOAMPERE_HOURS, "KWH_SF")), // LIFE_TIME_MAX_OUT(new ScaledValuePoint("S64111_LIFE_TIME_MAX_OUT", "Lifetime Maximum Output Wattage", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.WATT, "P_SF")), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.WATT, "P_SF")), // LIFE_TIME_MAX_BATT(new ScaledValuePoint("S64111_LIFE_TIME_MAX_BATT", "Lifetime Maximum Battery Voltage", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // LIFE_TIME_MAX_V_O_C(new ScaledValuePoint("S64111_LIFE_TIME_MAX_V_O_C", "Lifetime Maximum VOC Voltage", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")); + UINT16, true /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")); private final Point point; @@ -6635,7 +6710,7 @@ public OptionsEnum getUndefined() { public static enum S64112 implements SunSpecPoint { PORT(new ValuePoint("S64112_PORT", "Port Number", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.NONE)), // V_SF(new ScaleFactorPoint("S64112_V_SF", "", "")), // C_SF(new ScaleFactorPoint("S64112_C_SF", "", "")), // H_SF(new ScaleFactorPoint("S64112_H_SF", "", "")), // @@ -6643,119 +6718,147 @@ public static enum S64112 implements SunSpecPoint { AH_SF(new ScaleFactorPoint("S64112_AH_SF", "", "")), // KWH_SF(new ScaleFactorPoint("S64112_KWH_SF", "", "")), // C_C_CONFIG_FAULT(new BitFieldPoint("S64112_C_C_CONFIG_FAULT", "Faults", "", // - BitFieldPoint.Type.BITFIELD16, true /* mandatory? */, AccessMode.READ_ONLY, new SunSpecBitPoint[0])), // + BITFIELD16, true /* mandatory? */, READ_ONLY, new SunSpecBitPoint[0])), // C_C_CONFIG_ABSORB_V(new ScaledValuePoint("S64112_C_C_CONFIG_ABSORB_V", "Absorb", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // C_C_CONFIG_ABSORB_HR(new ScaledValuePoint("S64112_C_C_CONFIG_ABSORB_HR", "Absorb Time", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE, "H_SF")), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.NONE, "H_SF")), // C_C_CONFIG_ABSORB_END_A(new ScaledValuePoint("S64112_C_C_CONFIG_ABSORB_END_A", "Absorb End", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE, "V_SF")), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.AMPERE, "V_SF")), // C_C_CONFIG_REBULK_V(new ScaledValuePoint("S64112_C_C_CONFIG_REBULK_V", "Rebulk", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // C_C_CONFIG_FLOAT_V(new ScaledValuePoint("S64112_C_C_CONFIG_FLOAT_V", "Float", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // C_C_CONFIG_MAX_CHG_A(new ScaledValuePoint("S64112_C_C_CONFIG_MAX_CHG_A", "Maximum Charge", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE, "V_SF")), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.AMPERE, "V_SF")), // C_C_CONFIG_EQUALIZE_V(new ScaledValuePoint("S64112_C_C_CONFIG_EQUALIZE_V", "Equalize", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // C_C_CONFIG_EQUALIZE_HR(new ValuePoint("S64112_C_C_CONFIG_EQUALIZE_HR", "Equalize Time", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.NONE)), // C_C_CONFIG_AUTO_EQUALIZE(new ValuePoint("S64112_C_C_CONFIG_AUTO_EQUALIZE", "Auto Equalize Interval", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.NONE)), // C_C_CONFIG_M_P_P_T_MODE(new EnumPoint("S64112_C_C_CONFIG_M_P_P_T_MODE", "MPPT mode", "", // - EnumPoint.Type.ENUM16, true /* mandatory? */, AccessMode.READ_ONLY, S64112_CC_Config_MPPT_mode.values())), // + ENUM16, true /* mandatory? */, READ_ONLY, S64112_CC_Config_MPPT_mode.values())), // C_C_CONFIG_SWEEP_WIDTH(new EnumPoint("S64112_C_C_CONFIG_SWEEP_WIDTH", "Sweep Width", "", // - EnumPoint.Type.ENUM16, true /* mandatory? */, AccessMode.READ_ONLY, S64112_CC_Config_sweep_width.values())), // + ENUM16, true /* mandatory? */, READ_ONLY, S64112_CC_Config_sweep_width.values())), // C_C_CONFIG_SWEEP_MAX(new EnumPoint("S64112_C_C_CONFIG_SWEEP_MAX", "Sweep Maximum", "", // - EnumPoint.Type.ENUM16, true /* mandatory? */, AccessMode.READ_ONLY, S64112_CC_Config_sweep_max.values())), // - C_C_CONFIG_U_PICK_DUTY_CYC(new ScaledValuePoint("S64112_C_C_CONFIG_U_PICK_DUTY_CYC", "U-Pick PWM Duty Cycle", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE, "V_SF")), // + ENUM16, true /* mandatory? */, READ_ONLY, S64112_CC_Config_sweep_max.values())), // + C_C_CONFIG_U_PICK_DUTY_CYC( + new ScaledValuePoint("S64112_C_C_CONFIG_U_PICK_DUTY_CYC", "U-Pick PWM Duty Cycle", "", // + UINT16, true /* mandatory? */, READ_ONLY, Unit.NONE, "V_SF")), // C_C_CONFIG_GRID_TIE(new EnumPoint("S64112_C_C_CONFIG_GRID_TIE", "Grid Tie Mode", "", // - EnumPoint.Type.ENUM16, true /* mandatory? */, AccessMode.READ_ONLY, S64112_CC_Config_grid_tie.values())), // + ENUM16, true /* mandatory? */, READ_ONLY, S64112_CC_Config_grid_tie.values())), // C_C_CONFIG_TEMP_COMP(new EnumPoint("S64112_C_C_CONFIG_TEMP_COMP", "Temp Comp Mode", "", // - EnumPoint.Type.ENUM16, true /* mandatory? */, AccessMode.READ_ONLY, S64112_CC_Config_temp_comp.values())), // - C_C_CONFIG_TEMP_COMP_LLIMT(new ScaledValuePoint("S64112_C_C_CONFIG_TEMP_COMP_LLIMT", "Temp Comp Lower Limit", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // - C_C_CONFIG_TEMP_COMP_HLIMT(new ScaledValuePoint("S64112_C_C_CONFIG_TEMP_COMP_HLIMT", "Temp Comp Upper Limit", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + ENUM16, true /* mandatory? */, READ_ONLY, S64112_CC_Config_temp_comp.values())), // + C_C_CONFIG_TEMP_COMP_LLIMT( + new ScaledValuePoint("S64112_C_C_CONFIG_TEMP_COMP_LLIMT", "Temp Comp Lower Limit", "", // + UINT16, true /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // + C_C_CONFIG_TEMP_COMP_HLIMT( + new ScaledValuePoint("S64112_C_C_CONFIG_TEMP_COMP_HLIMT", "Temp Comp Upper Limit", "", // + UINT16, true /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // C_C_CONFIG_AUTO_RESTART(new EnumPoint("S64112_C_C_CONFIG_AUTO_RESTART", "Auto Restart Mode", "", // - EnumPoint.Type.ENUM16, true /* mandatory? */, AccessMode.READ_ONLY, S64112_CC_Config_auto_restart.values())), // + ENUM16, true /* mandatory? */, READ_ONLY, S64112_CC_Config_auto_restart.values())), // C_C_CONFIG_WAKEUP_V_O_C(new ScaledValuePoint("S64112_C_C_CONFIG_WAKEUP_V_O_C", "Wakeup VOC Change", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // C_C_CONFIG_SNOOZE_MODE_A(new ScaledValuePoint("S64112_C_C_CONFIG_SNOOZE_MODE_A", "Snooze Mode", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE, "V_SF")), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.AMPERE, "V_SF")), // C_C_CONFIG_WAKEUP_INTERVAL(new ValuePoint("S64112_C_C_CONFIG_WAKEUP_INTERVAL", "Wakeup Interval", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.NONE)), // C_C_CONFIG_A_U_X_MODE(new EnumPoint("S64112_C_C_CONFIG_A_U_X_MODE", "AUX Output Mode", "", // - EnumPoint.Type.ENUM16, true /* mandatory? */, AccessMode.READ_ONLY, S64112_CC_Config_AUX_mode.values())), // + ENUM16, true /* mandatory? */, READ_ONLY, S64112_CC_Config_AUX_mode.values())), // C_C_CONFIG_A_U_X_CONTROL(new EnumPoint("S64112_C_C_CONFIG_A_U_X_CONTROL", "AUX Output Control", "", // - EnumPoint.Type.ENUM16, true /* mandatory? */, AccessMode.READ_ONLY, S64112_CC_Config_AUX_control.values())), // + ENUM16, true /* mandatory? */, READ_ONLY, S64112_CC_Config_AUX_control.values())), // C_C_CONFIG_A_U_X_STATE(new EnumPoint("S64112_C_C_CONFIG_A_U_X_STATE", "AUX Output State", "", // - EnumPoint.Type.ENUM16, true /* mandatory? */, AccessMode.READ_ONLY, S64112_CC_Config_AUX_state.values())), // + ENUM16, true /* mandatory? */, READ_ONLY, S64112_CC_Config_AUX_state.values())), // C_C_CONFIG_A_U_X_POLARITY(new EnumPoint("S64112_C_C_CONFIG_A_U_X_POLARITY", "AUX Output Polarity", "", // - EnumPoint.Type.ENUM16, true /* mandatory? */, AccessMode.READ_ONLY, S64112_CC_Config_AUX_polarity.values())), // - C_C_CONFIG_A_U_X_L_BATT_DISC(new ScaledValuePoint("S64112_C_C_CONFIG_A_U_X_L_BATT_DISC", "AUX Low Battery Disconnect", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // - C_C_CONFIG_A_U_X_L_BATT_RCON(new ScaledValuePoint("S64112_C_C_CONFIG_A_U_X_L_BATT_RCON", "AUX Low Battery Reconnect", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // - C_C_CONFIG_A_U_X_L_BATT_DLY(new ValuePoint("S64112_C_C_CONFIG_A_U_X_L_BATT_DLY", "AUX Low Battery Disconnect Delay", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + ENUM16, true /* mandatory? */, READ_ONLY, S64112_CC_Config_AUX_polarity.values())), // + C_C_CONFIG_A_U_X_L_BATT_DISC( + new ScaledValuePoint("S64112_C_C_CONFIG_A_U_X_L_BATT_DISC", "AUX Low Battery Disconnect", "", // + UINT16, true /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // + C_C_CONFIG_A_U_X_L_BATT_RCON( + new ScaledValuePoint("S64112_C_C_CONFIG_A_U_X_L_BATT_RCON", "AUX Low Battery Reconnect", "", // + UINT16, true /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // + C_C_CONFIG_A_U_X_L_BATT_DLY( + new ValuePoint("S64112_C_C_CONFIG_A_U_X_L_BATT_DLY", "AUX Low Battery Disconnect Delay", "", // + UINT16, true /* mandatory? */, READ_ONLY, Unit.NONE)), // C_C_CONFIG_A_U_X_VENT_FAN_V(new ScaledValuePoint("S64112_C_C_CONFIG_A_U_X_VENT_FAN_V", "AUX Vent Fan", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // - C_C_CONFIG_A_U_X_P_V_TRIGGER_V(new ScaledValuePoint("S64112_C_C_CONFIG_A_U_X_P_V_TRIGGER_V", "AUX PV Trigger", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // - C_C_CONFIG_A_U_X_P_V_TRG_H_TM(new ValuePoint("S64112_C_C_CONFIG_A_U_X_P_V_TRG_H_TM", "AUX PV Trigger Hold Time", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // - C_C_CONFIG_A_U_X_NLITE_THRS_V(new ScaledValuePoint("S64112_C_C_CONFIG_A_U_X_NLITE_THRS_V", "AUX Night Light Threshold", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // - C_C_CONFIG_A_U_X_NLITE_ON_TM(new ScaledValuePoint("S64112_C_C_CONFIG_A_U_X_NLITE_ON_TM", "AUX Night Light On Time", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE, "H_SF")), // - C_C_CONFIG_A_U_X_NLITE_ON_HIST(new ValuePoint("S64112_C_C_CONFIG_A_U_X_NLITE_ON_HIST", "AUX Night Light On Hysteresis", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // - C_C_CONFIG_A_U_X_NLITE_OFF_HIST(new ValuePoint("S64112_C_C_CONFIG_A_U_X_NLITE_OFF_HIST", "AUX Night Light Off Hysteresis", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // - C_C_CONFIG_A_U_X_ERROR_BATT_V(new ScaledValuePoint("S64112_C_C_CONFIG_A_U_X_ERROR_BATT_V", "AUX Error Output Low Battery", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // - C_C_CONFIG_A_U_X_DIVERT_H_TIME(new ScaledValuePoint("S64112_C_C_CONFIG_A_U_X_DIVERT_H_TIME", "AUX Divert Hold Time", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE, "V_SF")), // - C_C_CONFIG_A_U_X_DIVERT_DLY_TIME(new ValuePoint("S64112_C_C_CONFIG_A_U_X_DIVERT_DLY_TIME", "AUX Divert Delay Time", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // - C_C_CONFIG_A_U_X_DIVERT_REL_V(new ScaledValuePoint("S64112_C_C_CONFIG_A_U_X_DIVERT_REL_V", "AUX Divert Relative", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // - C_C_CONFIG_A_U_X_DIVERT_HYST_V(new ScaledValuePoint("S64112_C_C_CONFIG_A_U_X_DIVERT_HYST_V", "AUX Divert Hysteresis", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // + C_C_CONFIG_A_U_X_P_V_TRIGGER_V( + new ScaledValuePoint("S64112_C_C_CONFIG_A_U_X_P_V_TRIGGER_V", "AUX PV Trigger", "", // + UINT16, true /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // + C_C_CONFIG_A_U_X_P_V_TRG_H_TM( + new ValuePoint("S64112_C_C_CONFIG_A_U_X_P_V_TRG_H_TM", "AUX PV Trigger Hold Time", "", // + UINT16, true /* mandatory? */, READ_ONLY, Unit.NONE)), // + C_C_CONFIG_A_U_X_NLITE_THRS_V( + new ScaledValuePoint("S64112_C_C_CONFIG_A_U_X_NLITE_THRS_V", "AUX Night Light Threshold", "", // + UINT16, true /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // + C_C_CONFIG_A_U_X_NLITE_ON_TM( + new ScaledValuePoint("S64112_C_C_CONFIG_A_U_X_NLITE_ON_TM", "AUX Night Light On Time", "", // + UINT16, true /* mandatory? */, READ_ONLY, Unit.NONE, "H_SF")), // + C_C_CONFIG_A_U_X_NLITE_ON_HIST( + new ValuePoint("S64112_C_C_CONFIG_A_U_X_NLITE_ON_HIST", "AUX Night Light On Hysteresis", "", // + UINT16, true /* mandatory? */, READ_ONLY, Unit.NONE)), // + C_C_CONFIG_A_U_X_NLITE_OFF_HIST( + new ValuePoint("S64112_C_C_CONFIG_A_U_X_NLITE_OFF_HIST", "AUX Night Light Off Hysteresis", "", // + UINT16, true /* mandatory? */, READ_ONLY, Unit.NONE)), // + C_C_CONFIG_A_U_X_ERROR_BATT_V( + new ScaledValuePoint("S64112_C_C_CONFIG_A_U_X_ERROR_BATT_V", "AUX Error Output Low Battery", "", // + UINT16, true /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // + C_C_CONFIG_A_U_X_DIVERT_H_TIME( + new ScaledValuePoint("S64112_C_C_CONFIG_A_U_X_DIVERT_H_TIME", "AUX Divert Hold Time", "", // + UINT16, true /* mandatory? */, READ_ONLY, Unit.NONE, "V_SF")), // + C_C_CONFIG_A_U_X_DIVERT_DLY_TIME( + new ValuePoint("S64112_C_C_CONFIG_A_U_X_DIVERT_DLY_TIME", "AUX Divert Delay Time", "", // + UINT16, true /* mandatory? */, READ_ONLY, Unit.NONE)), // + C_C_CONFIG_A_U_X_DIVERT_REL_V( + new ScaledValuePoint("S64112_C_C_CONFIG_A_U_X_DIVERT_REL_V", "AUX Divert Relative", "", // + UINT16, true /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // + C_C_CONFIG_A_U_X_DIVERT_HYST_V( + new ScaledValuePoint("S64112_C_C_CONFIG_A_U_X_DIVERT_HYST_V", "AUX Divert Hysteresis", "", // + UINT16, true /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // C_C_CONFIG_MAJOR_F_W_REV(new ValuePoint("S64112_C_C_CONFIG_MAJOR_F_W_REV", "FM CC Major Firmware Number", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.NONE)), // C_C_CONFIG_MID_F_W_REV(new ValuePoint("S64112_C_C_CONFIG_MID_F_W_REV", "FM CC Mid Firmware Number", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.NONE)), // C_C_CONFIG_MINOR_F_W_REV(new ValuePoint("S64112_C_C_CONFIG_MINOR_F_W_REV", "FM CC Minor Firmware Number", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // - C_C_CONFIG_DATA_LOG_DAY_OFFSET(new ValuePoint("S64112_C_C_CONFIG_DATA_LOG_DAY_OFFSET", "Set Data Log Day Offset", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // - C_C_CONFIG_DATA_LOG_CUR_DAY_OFF(new ValuePoint("S64112_C_C_CONFIG_DATA_LOG_CUR_DAY_OFF", "Current Data Log Day Offset", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.NONE)), // + C_C_CONFIG_DATA_LOG_DAY_OFFSET( + new ValuePoint("S64112_C_C_CONFIG_DATA_LOG_DAY_OFFSET", "Set Data Log Day Offset", "", // + UINT16, true /* mandatory? */, READ_ONLY, Unit.NONE)), // + C_C_CONFIG_DATA_LOG_CUR_DAY_OFF( + new ValuePoint("S64112_C_C_CONFIG_DATA_LOG_CUR_DAY_OFF", "Current Data Log Day Offset", "", // + UINT16, true /* mandatory? */, READ_ONLY, Unit.NONE)), // C_C_CONFIG_DATA_LOG_DAILY_A_H(new ValuePoint("S64112_C_C_CONFIG_DATA_LOG_DAILY_A_H", "Data Log Daily (Ah)", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE_HOURS)), // - C_C_CONFIG_DATA_LOG_DAILY_K_W_H(new ScaledValuePoint("S64112_C_C_CONFIG_DATA_LOG_DAILY_K_W_H", "Data Log Daily (kWh)", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.KILOWATT_HOURS, "KWH_SF")), // - C_C_CONFIG_DATA_LOG_MAX_OUT_A(new ScaledValuePoint("S64112_C_C_CONFIG_DATA_LOG_MAX_OUT_A", "Data Log Daily Maximum Output (A)", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.AMPERE, "V_SF")), // - C_C_CONFIG_DATA_LOG_MAX_OUT_W(new ScaledValuePoint("S64112_C_C_CONFIG_DATA_LOG_MAX_OUT_W", "Data Log Daily Maximum Output (W)", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.WATT, "V_SF")), // - C_C_CONFIG_DATA_LOG_ABSORB_T(new ValuePoint("S64112_C_C_CONFIG_DATA_LOG_ABSORB_T", "Data Log Daily Absorb Time", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // - C_C_CONFIG_DATA_LOG_FLOAT_T(new ValuePoint("S64112_C_C_CONFIG_DATA_LOG_FLOAT_T", "Data Log Daily Float Time", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // - C_C_CONFIG_DATA_LOG_MIN_BATT_V(new ScaledValuePoint("S64112_C_C_CONFIG_DATA_LOG_MIN_BATT_V", "Data Log Daily Minimum Battery", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // - C_C_CONFIG_DATA_LOG_MAX_BATT_V(new ScaledValuePoint("S64112_C_C_CONFIG_DATA_LOG_MAX_BATT_V", "Data Log Daily Maximum Battery", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // - C_C_CONFIG_DATA_LOG_MAX_INPUT_V(new ScaledValuePoint("S64112_C_C_CONFIG_DATA_LOG_MAX_INPUT_V", "Data Log Daily Maximum Input", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.VOLT, "V_SF")), // + UINT16, true /* mandatory? */, READ_ONLY, Unit.AMPERE_HOURS)), // + C_C_CONFIG_DATA_LOG_DAILY_K_W_H( + new ScaledValuePoint("S64112_C_C_CONFIG_DATA_LOG_DAILY_K_W_H", "Data Log Daily (kWh)", "", // + UINT16, true /* mandatory? */, READ_ONLY, Unit.KILOWATT_HOURS, "KWH_SF")), // + C_C_CONFIG_DATA_LOG_MAX_OUT_A( + new ScaledValuePoint("S64112_C_C_CONFIG_DATA_LOG_MAX_OUT_A", "Data Log Daily Maximum Output (A)", "", // + UINT16, true /* mandatory? */, READ_ONLY, Unit.AMPERE, "V_SF")), // + C_C_CONFIG_DATA_LOG_MAX_OUT_W( + new ScaledValuePoint("S64112_C_C_CONFIG_DATA_LOG_MAX_OUT_W", "Data Log Daily Maximum Output (W)", "", // + UINT16, true /* mandatory? */, READ_ONLY, Unit.WATT, "V_SF")), // + C_C_CONFIG_DATA_LOG_ABSORB_T( + new ValuePoint("S64112_C_C_CONFIG_DATA_LOG_ABSORB_T", "Data Log Daily Absorb Time", "", // + UINT16, true /* mandatory? */, READ_ONLY, Unit.NONE)), // + C_C_CONFIG_DATA_LOG_FLOAT_T( + new ValuePoint("S64112_C_C_CONFIG_DATA_LOG_FLOAT_T", "Data Log Daily Float Time", "", // + UINT16, true /* mandatory? */, READ_ONLY, Unit.NONE)), // + C_C_CONFIG_DATA_LOG_MIN_BATT_V( + new ScaledValuePoint("S64112_C_C_CONFIG_DATA_LOG_MIN_BATT_V", "Data Log Daily Minimum Battery", "", // + UINT16, true /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // + C_C_CONFIG_DATA_LOG_MAX_BATT_V( + new ScaledValuePoint("S64112_C_C_CONFIG_DATA_LOG_MAX_BATT_V", "Data Log Daily Maximum Battery", "", // + UINT16, true /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // + C_C_CONFIG_DATA_LOG_MAX_INPUT_V( + new ScaledValuePoint("S64112_C_C_CONFIG_DATA_LOG_MAX_INPUT_V", "Data Log Daily Maximum Input", "", // + UINT16, true /* mandatory? */, READ_ONLY, Unit.VOLT, "V_SF")), // C_C_CONFIG_DATA_LOG_CLEAR(new ValuePoint("S64112_C_C_CONFIG_DATA_LOG_CLEAR", "Data Log Clear", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)), // - C_C_CONFIG_DATA_LOG_CLR_COMP(new ValuePoint("S64112_C_C_CONFIG_DATA_LOG_CLR_COMP", "Data Log Clear Complement", "", // - ValuePoint.Type.UINT16, true /* mandatory? */, AccessMode.READ_ONLY, Unit.NONE)); + UINT16, true /* mandatory? */, READ_ONLY, Unit.NONE)), // + C_C_CONFIG_DATA_LOG_CLR_COMP( + new ValuePoint("S64112_C_C_CONFIG_DATA_LOG_CLR_COMP", "Data Log Clear Complement", "", // + UINT16, true /* mandatory? */, READ_ONLY, Unit.NONE)); private final Point point; diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/test/DummyModbusBridge.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/test/DummyModbusBridge.java index 1d508bb9363..c6a22a6cf49 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/test/DummyModbusBridge.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/test/DummyModbusBridge.java @@ -1,25 +1,60 @@ package io.openems.edge.bridge.modbus.test; +import static io.openems.common.utils.ReflectionUtils.getValueViaReflection; +import static io.openems.edge.common.event.EdgeEventConstants.TOPIC_CYCLE_BEFORE_PROCESS_IMAGE; + import java.net.InetAddress; import java.net.UnknownHostException; -import java.util.HashMap; -import java.util.Map; +import java.util.function.Consumer; + +import org.osgi.service.event.Event; +import com.ghgande.j2mod.modbus.ModbusException; import com.ghgande.j2mod.modbus.io.ModbusTransaction; +import com.ghgande.j2mod.modbus.msg.ModbusRequest; +import com.ghgande.j2mod.modbus.msg.ModbusResponse; +import com.ghgande.j2mod.modbus.net.AbstractModbusListener; +import com.ghgande.j2mod.modbus.procimg.ProcessImage; +import com.ghgande.j2mod.modbus.procimg.SimpleProcessImage; +import com.ghgande.j2mod.modbus.procimg.SimpleRegister; import io.openems.common.exceptions.OpenemsException; import io.openems.edge.bridge.modbus.api.AbstractModbusBridge; import io.openems.edge.bridge.modbus.api.BridgeModbus; import io.openems.edge.bridge.modbus.api.BridgeModbusTcp; +import io.openems.edge.bridge.modbus.api.Config; import io.openems.edge.bridge.modbus.api.LogVerbosity; -import io.openems.edge.bridge.modbus.api.ModbusProtocol; -import io.openems.edge.common.channel.Channel; +import io.openems.edge.bridge.modbus.api.worker.internal.DefectiveComponents; +import io.openems.edge.bridge.modbus.api.worker.internal.TasksSupplier; import io.openems.edge.common.component.OpenemsComponent; public class DummyModbusBridge extends AbstractModbusBridge implements BridgeModbusTcp, BridgeModbus, OpenemsComponent { - private final Map protocols = new HashMap<>(); + private final ModbusTransaction modbusTransaction = new ModbusTransaction() { + @Override + public void execute() throws ModbusException { + this.response = DummyModbusBridge.this.executeModbusRequest(this.request); + } + }; + private final AbstractModbusListener modbusListener = new AbstractModbusListener() { + @Override + public ProcessImage getProcessImage(int unitId) { + return DummyModbusBridge.this.processImage; + } + + @Override + public void run() { + } + + @Override + public void stop() { + } + }; + private final TasksSupplier tasksSupplier; + private final DefectiveComponents defectiveComponents; + + private SimpleProcessImage processImage = null; private InetAddress ipAddress = null; public DummyModbusBridge(String id) { @@ -32,10 +67,20 @@ public DummyModbusBridge(String id, LogVerbosity logVerbosity) { BridgeModbus.ChannelId.values(), // BridgeModbusTcp.ChannelId.values() // ); - for (Channel channel : this.channels()) { + for (var channel : this.channels()) { channel.nextProcessImage(); } - super.activate(null, id, "", true, logVerbosity, 2); + super.activate(null, new Config(id, "", false, logVerbosity, 2)); + this.tasksSupplier = getValueViaReflection(this.worker, "tasksSupplier"); + this.defectiveComponents = getValueViaReflection(this.worker, "defectiveComponents"); + } + + private synchronized DummyModbusBridge withProcessImage(Consumer callback) { + if (this.processImage == null) { + this.processImage = new SimpleProcessImage(); + } + callback.accept(this.processImage); + return this; } /** @@ -50,14 +95,135 @@ public DummyModbusBridge withIpAddress(String ipAddress) throws UnknownHostExcep return this; } - @Override - public void addProtocol(String sourceId, ModbusProtocol protocol) { - this.protocols.put(sourceId, protocol); + /** + * Sets the value of a FC3HoldingRegister. + * + * @param address the Register address + * @param b1 first byte + * @param b2 second byte + * @return myself + */ + public DummyModbusBridge withRegister(int address, byte b1, byte b2) { + return this.withProcessImage(pi -> pi.addRegister(address, new SimpleRegister(b1, b2))); } + /** + * Sets the value of a FC3HoldingRegister. + * + * @param address the Register address + * @param value the value + * @return myself + */ + public DummyModbusBridge withRegister(int address, int value) { + return this.withProcessImage(pi -> pi.addRegister(address, new SimpleRegister(value))); + } + + /** + * Sets the value of a FC4InputRegister. + * + * @param address the Register address + * @param value the value + * @return myself + */ + public DummyModbusBridge withInputRegister(int address, int value) { + return this.withProcessImage(pi -> pi.addInputRegister(address, new SimpleRegister(value))); + } + + /** + * Sets the value of a FC4InputRegister. + * + * @param address the Register address + * @param b1 first byte + * @param b2 second byte + * @return myself + */ + public DummyModbusBridge withInputRegister(int address, byte b1, byte b2) { + return this.withProcessImage(pi -> pi.addInputRegister(address, new SimpleRegister(b1, b2))); + } + + /** + * Sets the values of FC3HoldingRegisters. + * + * @param startAddress the start Register address + * @param values the values + * @return myself + */ + public DummyModbusBridge withRegisters(int startAddress, int... values) { + for (var value : values) { + this.withRegister(startAddress++, value); + } + return this; + } + + /** + * Sets the values of FC3HoldingRegisters. + * + * @param startAddress the start Register address + * @param values the values + * @return myself + */ + public DummyModbusBridge withRegisters(int startAddress, int[]... values) { + for (var a : values) { + for (var b : a) { + this.withRegister(startAddress++, b); + } + } + return this; + } + + /** + * Sets the values of FC4InputRegisters. + * + * @param startAddress the start Register address + * @param values the values + * @return myself + */ + public DummyModbusBridge withInputRegisters(int startAddress, int... values) { + for (var value : values) { + this.withInputRegister(startAddress++, value); + } + return this; + } + + /** + * Sets the values of FC4InputRegisters. + * + * @param startAddress the start Register address + * @param values the values + * @return myself + */ + public DummyModbusBridge withInputRegisters(int startAddress, int[]... values) { + for (var a : values) { + for (var b : a) { + this.withInputRegister(startAddress++, b); + } + } + return this; + } + + /** + * NOTE: {@link DummyModbusBridge} does not call parent handleEvent(). + */ @Override - public void removeProtocol(String sourceId) { - this.protocols.remove(sourceId); + public synchronized void handleEvent(Event event) { + // NOTE: TOPIC_CYCLE_EXECUTE_WRITE is not implemented (yet) + if (this.processImage == null) { + return; + } + switch (event.getTopic()) { + case TOPIC_CYCLE_BEFORE_PROCESS_IMAGE -> this.onBeforeProcessImage(); + } + } + + private ModbusResponse executeModbusRequest(ModbusRequest request) { + return request.createResponse(this.modbusListener); + } + + private void onBeforeProcessImage() { + var cycleTasks = this.tasksSupplier.getCycleTasks(this.defectiveComponents); + for (var readTask : cycleTasks.reads()) { + readTask.execute(this); + } } @Override @@ -70,12 +236,11 @@ public InetAddress getIpAddress() { @Override public ModbusTransaction getNewModbusTransaction() throws OpenemsException { - throw new UnsupportedOperationException("getNewModbusTransaction() Unsupported by Dummy Class"); + return this.modbusTransaction; } @Override public void closeModbusConnection() { - throw new UnsupportedOperationException("closeModbusConnection() Unsupported by Dummy Class"); } } diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/BridgeModbusSerialImplTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/BridgeModbusSerialImplTest.java index 3645d2d6fdf..1d6d1e780ba 100644 --- a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/BridgeModbusSerialImplTest.java +++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/BridgeModbusSerialImplTest.java @@ -9,13 +9,11 @@ public class BridgeModbusSerialImplTest { - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new BridgeModbusSerialImpl()) // .activate(MyConfigSerial.create() // - .setId(MODBUS_ID) // + .setId("modbus0") // .setPortName("/etc/ttyUSB0") // .setBaudRate(9600) // .setDatabits(8) // diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/BridgeModbusTcpImplTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/BridgeModbusTcpImplTest.java index 276dc8dcce6..cfcf6d532c0 100644 --- a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/BridgeModbusTcpImplTest.java +++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/BridgeModbusTcpImplTest.java @@ -1,6 +1,8 @@ package io.openems.edge.bridge.modbus; -import org.junit.Ignore; +import static io.openems.common.test.TestUtils.findRandomOpenPortOnAllLocalInterfaces; +import static io.openems.edge.bridge.modbus.api.ModbusComponent.ChannelId.MODBUS_COMMUNICATION_FAILED; + import org.junit.Test; import com.ghgande.j2mod.modbus.procimg.Register; @@ -11,7 +13,6 @@ import io.openems.common.exceptions.OpenemsException; import io.openems.common.function.ThrowingRunnable; -import io.openems.common.types.ChannelAddress; import io.openems.common.types.OpenemsType; import io.openems.edge.bridge.modbus.api.AbstractModbusBridge; import io.openems.edge.bridge.modbus.api.LogVerbosity; @@ -22,25 +23,17 @@ import io.openems.edge.common.taskmanager.Priority; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; -import io.openems.edge.common.test.TestUtils; public class BridgeModbusTcpImplTest { - private static final String MODBUS_ID = "modbus0"; - private static final String DEVICE_ID = "device0"; private static final int UNIT_ID = 1; private static final int CYCLE_TIME = 100; - private static final ChannelAddress REGISTER_100 = new ChannelAddress(DEVICE_ID, "Register100"); - private static final ChannelAddress MODBUS_COMMUNICATION_FAILED = new ChannelAddress(DEVICE_ID, - "ModbusCommunicationFailed"); - - @Ignore @Test public void test() throws Exception { final ThrowingRunnable sleep = () -> Thread.sleep(CYCLE_TIME); - var port = TestUtils.findRandomOpenPortOnAllLocalInterfaces(); + var port = findRandomOpenPortOnAllLocalInterfaces(); ModbusSlave slave = null; try { /* @@ -57,16 +50,15 @@ public void test() throws Exception { * Instantiate Modbus-Bridge */ var sut = new BridgeModbusTcpImpl(); - var device = new MyModbusComponent(DEVICE_ID, sut, UNIT_ID); var test = new ComponentTest(sut) // - .addComponent(device) // .activate(MyConfigTcp.create() // - .setId(MODBUS_ID) // + .setId("modbus0") // .setIp("127.0.0.1") // .setPort(port) // .setInvalidateElementsAfterReadErrors(1) // .setLogVerbosity(LogVerbosity.NONE) // .build()); + test.addComponent(new MyModbusComponent("device0", sut, UNIT_ID)); /* * Successfully read Register @@ -76,34 +68,22 @@ public void test() throws Exception { .onAfterProcessImage(sleep)) // .next(new TestCase() // .onAfterProcessImage(sleep) // - .output(REGISTER_100, 123) // - .output(MODBUS_COMMUNICATION_FAILED, false)); // + .output("device0", MyModbusComponent.ChannelId.REGISTER_100, 123) // + .output("device0", MODBUS_COMMUNICATION_FAILED, false)); // /* - * Reading Register fails after debounce of 10 + * Remove Protocol and unset channel values */ - processImage.removeRegister(register100); - for (var i = 0; i < 9; i++) { - test.next(new TestCase() // - .onAfterProcessImage(sleep)); - } + sut.removeProtocol("device0"); + test // .next(new TestCase() // - .onAfterProcessImage(sleep) // - .output(MODBUS_COMMUNICATION_FAILED, false)) // + .onAfterProcessImage(sleep)) // .next(new TestCase() // .onAfterProcessImage(sleep) // - .output(MODBUS_COMMUNICATION_FAILED, true)); + .output("device0", MyModbusComponent.ChannelId.REGISTER_100, null) // + .output("device0", MODBUS_COMMUNICATION_FAILED, false)); // - /* - * Successfully read Register - */ - processImage.addRegister(100, register100); - test // - .next(new TestCase() // - .onAfterProcessImage(sleep) // - .output(REGISTER_100, 123) // - .output(MODBUS_COMMUNICATION_FAILED, false)); // } finally { if (slave != null) { slave.close(); diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/BitsWordElementTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/BitsWordElementTest.java index 65b0e885a56..d3f068826da 100644 --- a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/BitsWordElementTest.java +++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/BitsWordElementTest.java @@ -3,6 +3,7 @@ import static io.openems.common.channel.AccessMode.READ_WRITE; import static io.openems.common.types.OpenemsType.BOOLEAN; import static io.openems.common.types.OpenemsType.INTEGER; +import static io.openems.common.utils.ReflectionUtils.setAttributeViaReflection; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; @@ -129,12 +130,7 @@ public void testNotBoolean() throws Exception { private static ModbusTest.FC3ReadRegisters generateSut() throws IllegalArgumentException, IllegalAccessException, OpenemsException, NoSuchFieldException, SecurityException { var sut = new ModbusTest.FC3ReadRegisters<>(new BitsWordElement(0, null), INTEGER); - - // Some Reflection to properly initialize the BitsWordElement - var field = BitsWordElement.class.getDeclaredField("component"); - field.setAccessible(true); - field.set(sut.element, sut); - + setAttributeViaReflection(sut.element, "component", sut); return sut; } diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/CycleTasksManagerTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/CycleTasksManagerTest.java index 0d764fdecef..e57a4b32917 100644 --- a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/CycleTasksManagerTest.java +++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/CycleTasksManagerTest.java @@ -1,15 +1,21 @@ package io.openems.edge.bridge.modbus.api.worker.internal; +import static io.openems.edge.bridge.modbus.api.worker.internal.CycleTasksManager.StateMachine.FINISHED; +import static io.openems.edge.bridge.modbus.api.worker.internal.CycleTasksManager.StateMachine.WAIT_BEFORE_READ; +import static io.openems.edge.bridge.modbus.api.worker.internal.CycleTasksManager.StateMachine.WRITE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import java.util.function.Consumer; +import java.util.function.Supplier; import org.junit.Before; import org.junit.Test; import io.openems.common.exceptions.OpenemsException; import io.openems.edge.bridge.modbus.DummyModbusComponent; +import io.openems.edge.bridge.modbus.api.Config; +import io.openems.edge.bridge.modbus.api.LogVerbosity; import io.openems.edge.bridge.modbus.api.task.WaitTask; import io.openems.edge.bridge.modbus.api.worker.DummyReadTask; import io.openems.edge.bridge.modbus.api.worker.DummyWriteTask; @@ -21,6 +27,8 @@ public class CycleTasksManagerTest { }; public static final Consumer CYCLE_DELAY = (cycleDelay) -> { }; + private static final Config CONFIG = new Config("foo", "bar", true, LogVerbosity.NONE, 1); + public static final Supplier LOG_HANDLER = () -> CONFIG.log; private static DummyReadTask RT_H_1; private static DummyReadTask RT_H_2; @@ -48,9 +56,10 @@ public void testIdealConditions() throws OpenemsException, InterruptedException .writes(WT_1) // .build(); var tasksSupplier = new DummyTasksSupplier(cycle1, cycle2); - var defectiveComponents = new DefectiveComponents(); + var defectiveComponents = new DefectiveComponents(LOG_HANDLER); - var sut = new CycleTasksManager(tasksSupplier, defectiveComponents, CYCLE_TIME_IS_TOO_SHORT, CYCLE_DELAY); + var sut = new CycleTasksManager(tasksSupplier, defectiveComponents, // + CYCLE_TIME_IS_TOO_SHORT, CYCLE_DELAY, LOG_HANDLER); // Cycle 1 sut.onBeforeProcessImage(); @@ -117,6 +126,32 @@ public void testIdealConditions() throws OpenemsException, InterruptedException // task.execute(null); -> this would block in single-threaded JUnit test } + @Test + public void testExecuteWriteBeforeNextProcessImage() throws OpenemsException, InterruptedException { + var cycle1 = CycleTasks.create() // + .reads(RT_L_1, RT_H_1, RT_H_2) // + .writes(WT_1) // + .build(); + var cycle2 = CycleTasks.create() // + .reads(RT_L_2, RT_H_1, RT_H_2) // + .writes(WT_1) // + .build(); + var tasksSupplier = new DummyTasksSupplier(cycle1, cycle2); + var defectiveComponents = new DefectiveComponents(LOG_HANDLER); + + var sut = new CycleTasksManager(tasksSupplier, defectiveComponents, // + CYCLE_TIME_IS_TOO_SHORT, CYCLE_DELAY, LOG_HANDLER); + + sut.getNextTask(); + assertEquals(FINISHED, sut.getState()); + sut.onExecuteWrite(); + sut.getNextTask(); + assertEquals(WRITE, sut.getState()); + sut.onBeforeProcessImage(); + sut.getNextTask(); + assertEquals(WAIT_BEFORE_READ, sut.getState()); + } + @Test public void testDefective() throws OpenemsException, InterruptedException { var component = new DummyModbusComponent(); @@ -131,10 +166,11 @@ public void testDefective() throws OpenemsException, InterruptedException { .writes(WT_1) // .build(); var tasksSupplier = new DummyTasksSupplier(cycle1, cycle2); - var defectiveComponents = new DefectiveComponents(); + var defectiveComponents = new DefectiveComponents(LOG_HANDLER); defectiveComponents.add(component.id()); - var sut = new CycleTasksManager(tasksSupplier, defectiveComponents, CYCLE_TIME_IS_TOO_SHORT, CYCLE_DELAY); + var sut = new CycleTasksManager(tasksSupplier, defectiveComponents, // + CYCLE_TIME_IS_TOO_SHORT, CYCLE_DELAY, LOG_HANDLER); // Cycle 1 sut.onBeforeProcessImage(); @@ -162,9 +198,10 @@ public void testNoTasks() throws OpenemsException, InterruptedException { var cycle1 = CycleTasks.create() // .build(); var tasksSupplier = new DummyTasksSupplier(cycle1); - var defectiveComponents = new DefectiveComponents(); + var defectiveComponents = new DefectiveComponents(LOG_HANDLER); - var sut = new CycleTasksManager(tasksSupplier, defectiveComponents, CYCLE_TIME_IS_TOO_SHORT, CYCLE_DELAY); + var sut = new CycleTasksManager(tasksSupplier, defectiveComponents, // + CYCLE_TIME_IS_TOO_SHORT, CYCLE_DELAY, LOG_HANDLER); // Cycle 1 sut.onBeforeProcessImage(); diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/DefectiveComponentsTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/DefectiveComponentsTest.java index 3f8d57ea014..fe8246dfb2f 100644 --- a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/DefectiveComponentsTest.java +++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/DefectiveComponentsTest.java @@ -1,5 +1,7 @@ package io.openems.edge.bridge.modbus.api.worker.internal; +import static io.openems.common.test.TestUtils.createDummyClock; +import static io.openems.edge.bridge.modbus.api.worker.internal.CycleTasksManagerTest.LOG_HANDLER; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -8,16 +10,14 @@ import org.junit.Test; -import io.openems.common.test.TimeLeapClock; - public class DefectiveComponentsTest { private static final String CMP = "foo"; @Test public void testIsDueForNextTry() { - var clock = new TimeLeapClock(); - var sut = new DefectiveComponents(clock); + final var clock = createDummyClock(); + var sut = new DefectiveComponents(clock, LOG_HANDLER); assertNull(sut.isDueForNextTry(CMP)); sut.add(CMP); @@ -28,8 +28,8 @@ public void testIsDueForNextTry() { @Test public void testAddRemove() { - var clock = new TimeLeapClock(); - var sut = new DefectiveComponents(clock); + final var clock = createDummyClock(); + var sut = new DefectiveComponents(clock, LOG_HANDLER); sut.add(CMP); clock.leap(30_001, ChronoUnit.MILLIS); @@ -40,7 +40,7 @@ public void testAddRemove() { @Test public void testIsKnownw() { - var sut = new DefectiveComponents(); + var sut = new DefectiveComponents(LOG_HANDLER); sut.add(CMP); assertTrue(sut.isKnown(CMP)); diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/TasksSupplierImplTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/TasksSupplierImplTest.java index 0af815bda47..46b13094b91 100644 --- a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/TasksSupplierImplTest.java +++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/TasksSupplierImplTest.java @@ -1,5 +1,7 @@ package io.openems.edge.bridge.modbus.api.worker.internal; +import static io.openems.common.test.TestUtils.createDummyClock; +import static io.openems.edge.bridge.modbus.api.worker.internal.CycleTasksManagerTest.LOG_HANDLER; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -11,6 +13,7 @@ import io.openems.common.exceptions.OpenemsException; import io.openems.common.test.TimeLeapClock; +import io.openems.common.utils.FunctionUtils; import io.openems.edge.bridge.modbus.DummyModbusComponent; import io.openems.edge.bridge.modbus.api.worker.DummyReadTask; import io.openems.edge.bridge.modbus.api.worker.DummyWriteTask; @@ -35,14 +38,14 @@ public void before() { @Test public void testFull() throws OpenemsException { - var clock = new TimeLeapClock(); - var defectiveComponents = new DefectiveComponents(clock); - var sut = new TasksSupplierImpl(); + final var clock = createDummyClock(); + var defectiveComponents = new DefectiveComponents(clock, LOG_HANDLER); + var sut = new TasksSupplierImpl(LOG_HANDLER); var component = new DummyModbusComponent(); var protocol = component.getModbusProtocol(); protocol.addTasks(RT_H_1, RT_H_2, RT_L_1, RT_L_2, WT_1); - sut.addProtocol(component.id(), protocol); + sut.addProtocol(component.id(), protocol, FunctionUtils::doNothing); // 1st Cycle var tasks = sut.getCycleTasks(defectiveComponents); @@ -82,19 +85,19 @@ public void testFull() throws OpenemsException { assertEquals(4, tasks.reads().size() + tasks.writes().size()); // Finish - sut.removeProtocol(component.id()); + sut.removeProtocol(component.id(), FunctionUtils::doNothing); } @Test public void testHighOnly() throws OpenemsException { var clock = new TimeLeapClock(); - var defectiveComponents = new DefectiveComponents(clock); - var sut = new TasksSupplierImpl(); + var defectiveComponents = new DefectiveComponents(clock, LOG_HANDLER); + var sut = new TasksSupplierImpl(LOG_HANDLER); var component = new DummyModbusComponent(); var protocol = component.getModbusProtocol(); protocol.addTasks(RT_H_1, RT_H_2, WT_1); - sut.addProtocol(component.id(), protocol); + sut.addProtocol(component.id(), protocol, FunctionUtils::doNothing); var tasks = sut.getCycleTasks(defectiveComponents); assertEquals(3, tasks.reads().size() + tasks.writes().size()); diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/sunspec/AbstractOpenemsSunSpecComponentTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/sunspec/AbstractOpenemsSunSpecComponentTest.java index 2ce0c196ec0..d55f8c1b395 100644 --- a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/sunspec/AbstractOpenemsSunSpecComponentTest.java +++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/sunspec/AbstractOpenemsSunSpecComponentTest.java @@ -1,12 +1,13 @@ package io.openems.edge.bridge.modbus.sunspec; +import static io.openems.common.test.TestUtils.findRandomOpenPortOnAllLocalInterfaces; import static io.openems.edge.bridge.modbus.sunspec.AbstractOpenemsSunSpecComponent.preprocessModbusElements; +import static java.util.stream.IntStream.range; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import java.util.ArrayList; -import java.util.stream.IntStream; import org.junit.Before; import org.junit.Ignore; @@ -24,13 +25,19 @@ import io.openems.edge.bridge.modbus.api.LogVerbosity; import io.openems.edge.bridge.modbus.api.element.ModbusElement; import io.openems.edge.bridge.modbus.api.element.StringWordElement; +import io.openems.edge.bridge.modbus.sunspec.DefaultSunSpecModel.S1; +import io.openems.edge.bridge.modbus.sunspec.DefaultSunSpecModel.S101; +import io.openems.edge.bridge.modbus.sunspec.DefaultSunSpecModel.S103; +import io.openems.edge.bridge.modbus.sunspec.DefaultSunSpecModel.S701; +import io.openems.edge.bridge.modbus.sunspec.DefaultSunSpecModel.S701_ACType; import io.openems.edge.bridge.modbus.sunspec.Point.ModbusElementPoint; import io.openems.edge.bridge.modbus.sunspec.dummy.MyConfig; import io.openems.edge.bridge.modbus.sunspec.dummy.MySunSpecComponentImpl; +import io.openems.edge.bridge.modbus.test.DummyModbusBridge; +import io.openems.edge.common.channel.ChannelId; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.common.test.TestUtils; public class AbstractOpenemsSunSpecComponentTest { @@ -60,6 +67,76 @@ public void changeLogLevel() { java.lang.System.setProperty("org.ops4j.pax.logging.DefaultServiceLog.level", "INFO"); } + @Test + public void testReadFromModbus() throws Exception { + var sut = new MySunSpecComponentImpl(); + new ComponentTest(sut) // + .addReference("cm", new DummyConfigurationAdmin()) // + .addReference("setModbus", new DummyModbusBridge("modbus0") // + .withRegisters(40000, 0x5375, 0x6e53) // isSunSpec + .withRegisters(40002, 1, 66) // Block 1 + .withRegisters(40004, // + new int[] { 0x4D79, 0x204D, 0x616E, 0x7566, 0x6163, 0x7475, 0x7265, 0x7200, 0, 0, 0, 0, + 0, 0, 0, 0 }, // S1_MN + new int[] { 0x4D79, 0x204D, 0x6F64, 0x656C, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // S1_MD + range(0, 43).map(i -> 0).toArray()) // + .withRegisters(40070, 101, 50) // Block 101 + .withRegisters(40072, // + new int[] { 123, 234, 345, 456, 1 }, // + range(0, 45).map(i -> 0).toArray()) // + .withRegisters(40122, 103, 50) // Block 103 + .withRegisters(40124, // + new int[] { 124, 235, 346, 457, 1 }, // + range(0, 45).map(i -> 0).toArray()) // + .withRegisters(40174, 701, 121) // Block 701 + .withRegisters(40176, // + new int[] { 1, }, // + range(0, 120).map(i -> 0).toArray()) // + .withRegisters(40297, 702, 50) // Block 702 + .withRegisters(40299, // + new int[] { 1, }, // + range(0, 49).map(i -> 0).toArray()) // + .withRegisters(40375, 0xFFFF) // END_OF_MAP + ) // + .activate(MyConfig.create() // + .setId("cmp0") // + .setModbusId("modbus0") // + .setModbusUnitId(UNIT_ID) // + .setReadFromModbusBlock(1) // + .build()) + + .next(new TestCase()) // + .next(new TestCase() // + .output(c(S1.MN), null) // + .output(c(S1.MD), null)) + + .next(new TestCase() // + .output(c(S1.MN), "My Manufacturer") // + .output(c(S1.MD), "My Model")) + + .next(new TestCase() // + .output(c(S101.A), null) // + .output(c(S101.APH_A), null) // + .output(c(S103.A), null) // + .output(c(S103.APH_A), null)) // + + .next(new TestCase() // + .output(c(S101.A), 1230F) // + .output(c(S101.APH_A), 2340F) // + .output(c(S701.A_C_TYPE), S701_ACType.UNDEFINED)) // + + .next(new TestCase() // + .output(c(S103.A), 1240F) // + .output(c(S103.APH_A), 2350F) // + .output(c(S701.A_C_TYPE), S701_ACType.SPLIT_PHASE)) // + + .deactivate(); + } + + private static ChannelId c(SunSpecPoint point) { + return point.getChannelId(); + } + private static ImmutableSortedMap.Builder generateSunSpec() { var b = ImmutableSortedMap.naturalOrder() // .put(40000, 0x5375) // SunSpec identifier @@ -67,18 +144,18 @@ private static ImmutableSortedMap.Builder generateSunSpec() { .put(40002, 1) // SunSpec Block-ID .put(40003, 66); // Length of the SunSpec Block - IntStream.range(40004, 40070).forEach(i -> b.put(i, 0)); + range(40004, 40070).forEach(i -> b.put(i, 0)); b // .put(40070, 103) // SunSpec Block-ID .put(40071, 24); // Length of the SunSpec Block - IntStream.range(40072, 40096).forEach(i -> b.put(i, 0)); + range(40072, 40096).forEach(i -> b.put(i, 0)); b // .put(40096, 999) // SunSpec Block-ID .put(40097, 10) // Length of the SunSpec Block .put(40108, 702) // SunSpec Block-ID .put(40109, 50); // Length of the SunSpec Block - IntStream.range(40110, 40160).forEach(i -> b.put(i, 0)); + range(40110, 40160).forEach(i -> b.put(i, 0)); return b; } @@ -86,7 +163,7 @@ private static ImmutableSortedMap.Builder generateSunSpec() { @Ignore @Test public void test() throws Exception { - var port = TestUtils.findRandomOpenPortOnAllLocalInterfaces(); + var port = findRandomOpenPortOnAllLocalInterfaces(); ModbusSlave slave = null; try { /* diff --git a/io.openems.edge.bridge.onewire/.classpath b/io.openems.edge.bridge.onewire/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.bridge.onewire/.classpath +++ b/io.openems.edge.bridge.onewire/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.bridge.onewire/src/com/dalsemi/onewire/adapter/NetAdapterSim.java b/io.openems.edge.bridge.onewire/src/com/dalsemi/onewire/adapter/NetAdapterSim.java index e2732dfacae..b92d9054cb4 100644 --- a/io.openems.edge.bridge.onewire/src/com/dalsemi/onewire/adapter/NetAdapterSim.java +++ b/io.openems.edge.bridge.onewire/src/com/dalsemi/onewire/adapter/NetAdapterSim.java @@ -230,7 +230,11 @@ public NetAdapterSim(String execCmd, String logFilename, boolean multiThread) th public NetAdapterSim(String execCmd, String logFilename, int listenPort, boolean multiThread) throws IOException { // save references to file and command this.execCommand = execCmd; - this.process = Runtime.getRuntime().exec(execCmd); + + // Use ProcessBuilder instead of Runtime.getRuntime().exec() + ProcessBuilder processBuilder = new ProcessBuilder(execCmd.split(" ")); + processBuilder.redirectErrorStream(true); // Redirect error stream to standard output + this.process = processBuilder.start(); this.processOutput = new BufferedReader(new InputStreamReader(this.process.getInputStream())); this.processError = new BufferedReader(new InputStreamReader(this.process.getErrorStream())); this.processInput = new OutputStreamWriter(this.process.getOutputStream()); @@ -323,7 +327,11 @@ public NetAdapterSim(String execCmd, String logFilename, ServerSocket serverSock throws IOException { // save references to file and command this.execCommand = execCmd; - this.process = Runtime.getRuntime().exec(execCmd); + + // Use ProcessBuilder instead of Runtime.getRuntime().exec() + ProcessBuilder processBuilder = new ProcessBuilder(execCmd.split(" ")); + processBuilder.redirectErrorStream(true); // Redirect error stream to standard output + this.process = processBuilder.start(); this.processOutput = new BufferedReader(new InputStreamReader(this.process.getInputStream())); this.processError = new BufferedReader(new InputStreamReader(this.process.getErrorStream())); this.processInput = new OutputStreamWriter(this.process.getOutputStream()); diff --git a/io.openems.edge.bridge.onewire/src/com/dalsemi/onewire/adapter/UAdapterState.java b/io.openems.edge.bridge.onewire/src/com/dalsemi/onewire/adapter/UAdapterState.java index ed93664f6c5..381ae0db30c 100644 --- a/io.openems.edge.bridge.onewire/src/com/dalsemi/onewire/adapter/UAdapterState.java +++ b/io.openems.edge.bridge.onewire/src/com/dalsemi/onewire/adapter/UAdapterState.java @@ -148,9 +148,9 @@ class UAdapterState { /** * This is the current 'real' speed that the OneWire is operating at. This is - * used to represent the actual mode that the DS2480 is operating in. For example - * the logical speed might be USPEED_REGULAR but for RF emission reasons we may - * put the actual DS2480 in SPEED_FLEX. + * used to represent the actual mode that the DS2480 is operating in. For + * example the logical speed might be USPEED_REGULAR but for RF emission reasons + * we may put the actual DS2480 in SPEED_FLEX. *

    * The valid values for this are: *

      diff --git a/io.openems.edge.bridge.onewire/src/io/openems/edge/bridge/onewire/impl/BridgeOnewireImpl.java b/io.openems.edge.bridge.onewire/src/io/openems/edge/bridge/onewire/impl/BridgeOnewireImpl.java index b03ef071bd0..d1f60ff3073 100644 --- a/io.openems.edge.bridge.onewire/src/io/openems/edge/bridge/onewire/impl/BridgeOnewireImpl.java +++ b/io.openems.edge.bridge.onewire/src/io/openems/edge/bridge/onewire/impl/BridgeOnewireImpl.java @@ -82,7 +82,8 @@ protected void logError(Logger log, String message) { @Override public void buildJsonApiRoutes(JsonApiBuilder builder) { - builder.handleRequest(GetDevicesRequest.METHOD, call -> this.taskWorker.handleGetDevicesRequest(call.getRequest())); + builder.handleRequest(GetDevicesRequest.METHOD, + call -> this.taskWorker.handleGetDevicesRequest(call.getRequest())); } } \ No newline at end of file diff --git a/io.openems.edge.bridge.onewire/src/io/openems/edge/bridge/onewire/jsonrpc/GetDeviceResponse.java b/io.openems.edge.bridge.onewire/src/io/openems/edge/bridge/onewire/jsonrpc/GetDeviceResponse.java index e3dd4845c81..04549846a09 100644 --- a/io.openems.edge.bridge.onewire/src/io/openems/edge/bridge/onewire/jsonrpc/GetDeviceResponse.java +++ b/io.openems.edge.bridge.onewire/src/io/openems/edge/bridge/onewire/jsonrpc/GetDeviceResponse.java @@ -16,8 +16,6 @@ /** * Wraps a JSON-RPC Response to "getDevices" Request. * - *

      - * *

        * {
        *   "jsonrpc": "2.0",
      diff --git a/io.openems.edge.bridge.onewire/test/io/openems/edge/bridge/onewire/impl/BridgeOnewireImplTest.java b/io.openems.edge.bridge.onewire/test/io/openems/edge/bridge/onewire/impl/BridgeOnewireImplTest.java
      index 744192de7c8..c81a5ca33c2 100644
      --- a/io.openems.edge.bridge.onewire/test/io/openems/edge/bridge/onewire/impl/BridgeOnewireImplTest.java
      +++ b/io.openems.edge.bridge.onewire/test/io/openems/edge/bridge/onewire/impl/BridgeOnewireImplTest.java
      @@ -6,13 +6,11 @@
       
       public class BridgeOnewireImplTest {
       
      -	private static final String BRIDGE_ID = "onewire0";
      -
       	@Test
       	public void test() throws Exception {
       		new ComponentTest(new BridgeOnewireImpl()) //
       				.activate(MyConfig.create() //
      -						.setId(BRIDGE_ID) //
      +						.setId("onewire0") //
       						.setPort("USB1") //
       						.build()) //
       		;
      diff --git a/io.openems.edge.common/.classpath b/io.openems.edge.common/.classpath
      index bbfbdbe40e7..b4cffd0fe60 100644
      --- a/io.openems.edge.common/.classpath
      +++ b/io.openems.edge.common/.classpath
      @@ -1,7 +1,7 @@
       
       
       	
      -	
      +	
       	
       	
       		
      diff --git a/io.openems.edge.common/src/io/openems/edge/common/currency/Currency.java b/io.openems.edge.common/src/io/openems/edge/common/currency/Currency.java
      index 1aaa95601f7..d1750f819fe 100644
      --- a/io.openems.edge.common/src/io/openems/edge/common/currency/Currency.java
      +++ b/io.openems.edge.common/src/io/openems/edge/common/currency/Currency.java
      @@ -1,5 +1,6 @@
       package io.openems.edge.common.currency;
       
      +import io.openems.common.types.CurrencyConfig;
       import io.openems.common.types.OptionsEnum;
       
       public enum Currency implements OptionsEnum {
      @@ -30,4 +31,17 @@ public OptionsEnum getUndefined() {
       		return Currency.UNDEFINED;
       	}
       
      +	/**
      +	 * Converts the {@link CurrencyConfig} to the {@link Currency}.
      +	 * 
      +	 * @param config currencyConfig to be transformed
      +	 * @return The {@link Currency}.
      +	 */
      +	public static Currency fromCurrencyConfig(CurrencyConfig config) {
      +		return switch (config) {
      +		case EUR -> Currency.EUR;
      +		case SEK -> Currency.SEK;
      +		case CHF -> Currency.CHF;
      +		};
      +	}
       }
      diff --git a/io.openems.edge.common/src/io/openems/edge/common/currency/CurrencyConfig.java b/io.openems.edge.common/src/io/openems/edge/common/currency/CurrencyConfig.java
      deleted file mode 100644
      index 2448ff5b004..00000000000
      --- a/io.openems.edge.common/src/io/openems/edge/common/currency/CurrencyConfig.java
      +++ /dev/null
      @@ -1,38 +0,0 @@
      -package io.openems.edge.common.currency;
      -
      -import io.openems.edge.common.meta.Meta;
      -import io.openems.edge.common.meta.Meta.ChannelId;
      -
      -/**
      - * The {@link ChannelId#CURRENCY} mandates the selection of the 'currency'
      - * configuration property of this specific type. Subsequently, this selected
      - * property is transformed into the corresponding {@link Currency} type before
      - * being written through {@link Meta#_setCurrency(Currency)}.
      - */
      -public enum CurrencyConfig {
      -	/**
      -	 * Euro.
      -	 */
      -	EUR,
      -	/**
      -	 * Swedish Krona.
      -	 */
      -	SEK,
      -	/**
      -	 * Swiss Francs.
      -	 */
      -	CHF;
      -
      -	/**
      -	 * Converts the {@link CurrencyConfig} to the {@link Currency}.
      -	 * 
      -	 * @return The {@link Currency}.
      -	 */
      -	public Currency toCurrency() {
      -		return switch (this) {
      -		case EUR -> Currency.EUR;
      -		case SEK -> Currency.SEK;
      -		case CHF -> Currency.CHF;
      -		};
      -	}
      -}
      diff --git a/io.openems.edge.common/src/io/openems/edge/common/host/DummyHost.java b/io.openems.edge.common/src/io/openems/edge/common/host/DummyHost.java
      index 18378ccece7..dee7a3e84fe 100644
      --- a/io.openems.edge.common/src/io/openems/edge/common/host/DummyHost.java
      +++ b/io.openems.edge.common/src/io/openems/edge/common/host/DummyHost.java
      @@ -1,5 +1,9 @@
       package io.openems.edge.common.host;
       
      +import java.net.Inet4Address;
      +import java.util.List;
      +
      +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
       import io.openems.edge.common.component.OpenemsComponent;
       import io.openems.edge.common.test.AbstractDummyOpenemsComponent;
       import io.openems.edge.common.test.TestUtils;
      @@ -31,4 +35,9 @@ public DummyHost withHostname(String value) {
       		return this;
       	}
       
      +	@Override
      +	public List getSystemIPs() throws OpenemsNamedException {
      +		return List.of();
      +	}
      +
       }
      \ No newline at end of file
      diff --git a/io.openems.edge.common/src/io/openems/edge/common/host/Host.java b/io.openems.edge.common/src/io/openems/edge/common/host/Host.java
      index 27366c7d401..304fc98d217 100644
      --- a/io.openems.edge.common/src/io/openems/edge/common/host/Host.java
      +++ b/io.openems.edge.common/src/io/openems/edge/common/host/Host.java
      @@ -1,6 +1,10 @@
       package io.openems.edge.common.host;
       
      +import java.net.Inet4Address;
      +import java.util.List;
      +
       import io.openems.common.channel.Level;
      +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
       import io.openems.common.types.OpenemsType;
       import io.openems.edge.common.channel.Doc;
       import io.openems.edge.common.channel.StateChannel;
      @@ -17,6 +21,15 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId {
       		DISK_IS_FULL(Doc.of(Level.INFO) //
       				.text("Disk is full")), //
       		HOSTNAME(Doc.of(OpenemsType.STRING)), //
      +
      +		/**
      +		 * Operating System Version.
      +		 * 
      +		 * 

      + * e. g. 'Raspbian GNU/Linux 11 (bullseye)' or 'Windows 11' + */ + OS_VERSION(Doc.of(OpenemsType.STRING) // + .text("Operating system version")), // ; private final Doc doc; @@ -69,7 +82,7 @@ public default StringReadChannel getHostnameChannel() { } /** - * Gets the Disk is Full Warning State. See {@link ChannelId#HOSTNAME}. + * Gets the hostname. See {@link ChannelId#HOSTNAME}. * * @return the Channel {@link Value} */ @@ -86,4 +99,40 @@ public default void _setHostname(String value) { this.getHostnameChannel().setNextValue(value); } + /** + * Gets all the IPs of the current system. + * + * @return A list of all the IPs + * @throws OpenemsNamedException exception + */ + public List getSystemIPs() throws OpenemsNamedException; + + /** + * Gets the Channel for {@link ChannelId#OS_VERSION}. + * + * @return the Channel + */ + public default StringReadChannel getOsVersionChannel() { + return this.channel(ChannelId.OS_VERSION); + } + + /** + * Gets the operating system version. See {@link ChannelId#OS_VERSION}. + * + * @return the Channel {@link Value} + */ + public default Value getOsVersion() { + return this.getOsVersionChannel().value(); + } + + /** + * Internal method to set the 'nextValue' on {@link ChannelId#OS_VERSION} + * Channel. + * + * @param value the next value + */ + public default void _setOsVersion(String value) { + this.getOsVersionChannel().setNextValue(value); + } + } diff --git a/io.openems.edge.common/src/io/openems/edge/common/jsonapi/MultipleJsonApiBinder.java b/io.openems.edge.common/src/io/openems/edge/common/jsonapi/MultipleJsonApiBinder.java index f051eaa18eb..369064bc85e 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/jsonapi/MultipleJsonApiBinder.java +++ b/io.openems.edge.common/src/io/openems/edge/common/jsonapi/MultipleJsonApiBinder.java @@ -19,8 +19,8 @@ public class MultipleJsonApiBinder { * logs a warning. * *

      - * Commonly used like this with OSGi injection to bind all {@link JsonApi} - * which target the specific {@code ENTRY_POINT}:
      + * Commonly used like this with OSGi injection to bind all {@link JsonApi} which + * target the specific {@code ENTRY_POINT}:
      * *

       	 * {@code @Reference}(//
      diff --git a/io.openems.edge.common/src/io/openems/edge/common/meta/Meta.java b/io.openems.edge.common/src/io/openems/edge/common/meta/Meta.java
      index c10ee0d08f2..e481f7f4371 100644
      --- a/io.openems.edge.common/src/io/openems/edge/common/meta/Meta.java
      +++ b/io.openems.edge.common/src/io/openems/edge/common/meta/Meta.java
      @@ -1,10 +1,16 @@
       package io.openems.edge.common.meta;
       
      +import static io.openems.common.channel.PersistencePriority.HIGH;
      +import static io.openems.common.channel.PersistencePriority.VERY_LOW;
      +import static io.openems.common.channel.Unit.SECONDS;
      +import static io.openems.common.types.OpenemsType.BOOLEAN;
      +import static io.openems.common.types.OpenemsType.LONG;
      +import static io.openems.common.types.OpenemsType.STRING;
      +
       import io.openems.common.OpenemsConstants;
       import io.openems.common.channel.AccessMode;
      -import io.openems.common.channel.PersistencePriority;
       import io.openems.common.oem.OpenemsEdgeOem;
      -import io.openems.common.types.OpenemsType;
      +import io.openems.edge.common.channel.BooleanReadChannel;
       import io.openems.edge.common.channel.Doc;
       import io.openems.edge.common.channel.EnumReadChannel;
       import io.openems.edge.common.channel.value.Value;
      @@ -12,6 +18,7 @@
       import io.openems.edge.common.modbusslave.ModbusSlave;
       import io.openems.edge.common.modbusslave.ModbusSlaveNatureTable;
       import io.openems.edge.common.modbusslave.ModbusSlaveTable;
      +import io.openems.edge.common.modbusslave.ModbusType;
       
       public interface Meta extends ModbusSlave {
       
      @@ -27,9 +34,20 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId {
       		 * 
    • Type: String *
    */ - VERSION(Doc.of(OpenemsType.STRING) // - .persistencePriority(PersistencePriority.HIGH)), - + VERSION(Doc.of(STRING) // + .persistencePriority(HIGH)), + /** + * System Time: seconds since 1st January 1970 00:00:00 UTC. + * + *
      + *
    • Interface: Meta + *
    • Type: Long + *
    + */ + SYSTEM_TIME_UTC(Doc.of(LONG) // + .unit(SECONDS) // + .text("System Time: seconds since 1st January 1970 00:00:00 UTC") // + .persistencePriority(VERY_LOW)), /** * Edge currency. * @@ -39,7 +57,18 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { *
*/ CURRENCY(Doc.of(Currency.values()) // - .persistencePriority(PersistencePriority.HIGH)); + .persistencePriority(HIGH)), + + /** + * Is it allowed to charge the ESS from Grid?. + * + *
    + *
  • Interface: Meta + *
  • Type: Boolean + *
+ */ + IS_ESS_CHARGE_FROM_GRID_ALLOWED(Doc.of(BOOLEAN) // + .persistencePriority(HIGH)); private final Doc doc; @@ -73,6 +102,7 @@ public static ModbusSlaveTable getModbusSlaveTable(AccessMode accessMode, Openem .string16(51, "Manufacturer Version", oem.getManufacturerVersion()) // .string16(67, "Manufacturer Serial Number", oem.getManufacturerSerialNumber()) // .string16(83, "Manufacturer EMS Serial Number", oem.getManufacturerEmsSerialNumber()) // + .channel(99, ChannelId.SYSTEM_TIME_UTC, ModbusType.UINT64) // .build()); } @@ -102,4 +132,33 @@ public default Currency getCurrency() { public default void _setCurrency(Currency value) { this.getCurrencyChannel().setNextValue(value); } + + /** + * Gets the Channel for {@link ChannelId#IS_ESS_CHARGE_FROM_GRID_ALLOWED}. + * + * @return the Channel + */ + public default BooleanReadChannel getIsEssChargeFromGridAllowedChannel() { + return this.channel(ChannelId.IS_ESS_CHARGE_FROM_GRID_ALLOWED); + } + + /** + * Gets whether charging the ESS from grid is allowed. See + * {@link ChannelId#IS_ESS_CHARGE_FROM_GRID_ALLOWED}. + * + * @return the Channel {@link Value} + */ + public default boolean getIsEssChargeFromGridAllowed() { + return this.getIsEssChargeFromGridAllowedChannel().value().orElse(false); + } + + /** + * Internal method to set the 'nextValue' on + * {@link ChannelId#IS_ESS_CHARGE_FROM_GRID_ALLOWED} Channel. + * + * @param value the next value + */ + public default void _setIsEssChargeFromGridAllowed(boolean value) { + this.getIsEssChargeFromGridAllowedChannel().setNextValue(value); + } } diff --git a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordChannel.java b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordChannel.java index 670a58a3ebc..0a8f7239042 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordChannel.java +++ b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordChannel.java @@ -41,6 +41,7 @@ public ModbusRecordChannel(int offset, ModbusType type, ChannelId channelId, Acc case STRING16 -> ModbusRecordString16.BYTE_LENGTH; case ENUM16, UINT16 -> ModbusRecordUint16.BYTE_LENGTH; case UINT32 -> ModbusRecordUint32.BYTE_LENGTH; + case UINT64 -> ModbusRecordUint64.BYTE_LENGTH; }; this.writeValueBuffer = new Byte[byteLength]; } @@ -140,6 +141,11 @@ public byte[] getValue(OpenemsComponent component) { case READ_ONLY, READ_WRITE -> ModbusRecordUint32.toByteArray(value); case WRITE_ONLY -> ModbusRecordUint32.UNDEFINED_VALUE; }; + case UINT64 -> // + switch (this.accessMode) { + case READ_ONLY, READ_WRITE -> ModbusRecordUint64.toByteArray(value); + case WRITE_ONLY -> ModbusRecordUint64.UNDEFINED_VALUE; + }; }; } @@ -190,6 +196,7 @@ public void writeValue(int index, byte byte1, byte byte2) { case STRING16 -> ""; // TODO implement String conversion case ENUM16, UINT16 -> buff.getShort(); case UINT32 -> buff.getInt(); + case UINT64 -> buff.getLong(); }; // Forward Value to ApiWorker diff --git a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordConstant.java b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordConstant.java index 7158dd50e88..9a5b376f6c3 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordConstant.java +++ b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordConstant.java @@ -1,5 +1,8 @@ package io.openems.edge.common.modbusslave; +import java.util.function.Consumer; +import java.util.function.Function; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -49,4 +52,44 @@ public AccessMode getAccessMode() { return AccessMode.READ_ONLY; } + /** + * Generates a common toString() method for implementations of + * {@link ModbusRecordConstant}. + * + * @param the type of the value + * @param name the name of the implementation class + * @param callback a {@link StringBuilder} callback + * @param value the actual value + * @param toHexString the toHexString() method + * @return a {@link String} + */ + protected String generateToString(String name, Consumer callback, T value, + Function toHexString) { + var b = new StringBuilder() // + .append(name) // + .append(" ["); + if (callback != null) { + callback.accept(b); + } + b.append("value="); + if (value != null) { + b.append(value); + if (toHexString != null) { + b.append("/0x").append(toHexString.apply(value)); + } + } else { + b.append("UNDEFINED"); + } + return b.append(", type=").append(this.getType()) // + .append("]") // + .toString(); + } + + protected String generateToString(String name, T value, Function toHexString) { + return this.generateToString(name, null, value, toHexString); + } + + protected String generateToString(String name, T value) { + return this.generateToString(name, null, value, null); + } } diff --git a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordCycleValue.java b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordCycleValue.java index 11a846349de..47cfe30253d 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordCycleValue.java +++ b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordCycleValue.java @@ -64,21 +64,14 @@ public void updateValue(T component) { @Override public byte[] getValue(OpenemsComponent component) { - switch (this.getType()) { - case FLOAT32: - return ModbusRecordFloat32.toByteArray(this.value); - case FLOAT64: - return ModbusRecordFloat64.toByteArray(this.value); - case STRING16: - return ModbusRecordString16.toByteArray(this.value); - case ENUM16: - case UINT16: - return ModbusRecordUint16.toByteArray(this.value); - case UINT32: - return ModbusRecordUint32.toByteArray(this.value); - } - assert true; - return new byte[0]; + return switch (this.getType()) { + case FLOAT32 -> ModbusRecordFloat32.toByteArray(this.value); + case FLOAT64 -> ModbusRecordFloat64.toByteArray(this.value); + case STRING16 -> ModbusRecordString16.toByteArray(this.value); + case ENUM16, UINT16 -> ModbusRecordUint16.toByteArray(this.value); + case UINT32 -> ModbusRecordUint32.toByteArray(this.value); + case UINT64 -> ModbusRecordUint64.toByteArray(this.value); + }; } @Override diff --git a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordFloat32.java b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordFloat32.java index 591bfbd7d9f..61cdce90776 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordFloat32.java +++ b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordFloat32.java @@ -20,7 +20,7 @@ public ModbusRecordFloat32(int offset, String name, Float value) { @Override public String toString() { - return "ModbusRecordFloat32 [value=" + this.value + ", type=" + this.getType() + "]"; + return this.generateToString("ModbusRecordFloat32", this.value); } /** diff --git a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordFloat64.java b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordFloat64.java index 6cb32c747cf..34613a120c0 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordFloat64.java +++ b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordFloat64.java @@ -21,7 +21,7 @@ public ModbusRecordFloat64(int offset, String name, Double value) { @Override public String toString() { - return "ModbusRecordFloat64 [value=" + this.value + ", type=" + this.getType() + "]"; + return this.generateToString("ModbusRecordFloat64", this.value); } /** diff --git a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordString16.java b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordString16.java index 532e9e07c28..f5cdfb6466b 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordString16.java +++ b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordString16.java @@ -20,7 +20,7 @@ public ModbusRecordString16(int offset, String name, String value) { @Override public String toString() { - return "ModbusRecordString16 [value=" + this.value + ", type=" + this.getType() + "]"; + return this.generateToString("ModbusRecordString16", this.value); } /** @@ -30,6 +30,9 @@ public String toString() { * @return the byte array */ public static byte[] toByteArray(String value) { + if (value == null) { + return UNDEFINED_VALUE; + } var result = new byte[BYTE_LENGTH]; var converted = value.getBytes(StandardCharsets.US_ASCII); System.arraycopy(converted, 0, result, 0, Math.min(BYTE_LENGTH, converted.length)); diff --git a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint16.java b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint16.java index dc92046984c..3e2e751cde0 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint16.java +++ b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint16.java @@ -20,8 +20,7 @@ public ModbusRecordUint16(int offset, String name, Short value) { @Override public String toString() { - return "ModbusRecordUInt16 [value=" + this.value + "/0x" + Integer.toHexString(this.value) + ", type=" - + this.getType() + "]"; + return generateToString("ModbusRecordUInt16", this.value, v -> Integer.toHexString(v)); } /** diff --git a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint16BlockLength.java b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint16BlockLength.java index 03fb4a11ecf..e66ceb2641e 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint16BlockLength.java +++ b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint16BlockLength.java @@ -11,8 +11,9 @@ public ModbusRecordUint16BlockLength(int offset, String blockName, short length) @Override public String toString() { - return "ModbusRecordUint16BlockLength [blockName=" + this.blockName + ", value=" + this.value + "/0x" - + Integer.toHexString(this.value) + ", type=" + this.getType() + "]"; + return generateToString("ModbusRecordUint16BlockLength", + b -> b.append("blockName=").append(this.blockName).append(", "), this.value, + v -> Integer.toHexString(v)); } } diff --git a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint16Hash.java b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint16Hash.java index 14780d4fc35..e1e093c1280 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint16Hash.java +++ b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint16Hash.java @@ -11,8 +11,8 @@ public ModbusRecordUint16Hash(int offset, String text) { @Override public String toString() { - return "ModbusRecordUint16Hash [text=" + this.text + ", value=" + this.value + "/0x" - + Integer.toHexString(this.value) + ", type=" + this.getType() + "]"; + return generateToString("ModbusRecordUint16Hash", b -> b.append("text=").append(this.text).append(", "), + this.value, v -> Integer.toHexString(v & 0xffff)); } @Override diff --git a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint32.java b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint32.java index 0e0a9b05e4a..23bae440c32 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint32.java +++ b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint32.java @@ -20,8 +20,7 @@ public ModbusRecordUint32(int offset, String name, Integer value) { @Override public String toString() { - return "ModbusRecordUInt32 [value=" + this.value + "/0x" + Integer.toHexString(this.value) + ", type=" - + this.getType() + "]"; + return generateToString("ModbusRecordUInt32", this.value, Integer::toHexString); } /** diff --git a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint64.java b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint64.java new file mode 100644 index 00000000000..87ccc8e779d --- /dev/null +++ b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint64.java @@ -0,0 +1,59 @@ +package io.openems.edge.common.modbusslave; + +import java.nio.ByteBuffer; + +import io.openems.common.types.OpenemsType; +import io.openems.edge.common.type.TypeUtils; + +public class ModbusRecordUint64 extends ModbusRecordConstant { + + public static final byte[] UNDEFINED_VALUE = { // + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, // + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF }; + + public static final int BYTE_LENGTH = 8; + + protected final Long value; + + public ModbusRecordUint64(int offset, String name, Long value) { + super(offset, name, ModbusType.UINT64, toByteArray(value)); + this.value = value; + } + + @Override + public String toString() { + return this.generateToString("ModbusRecordUInt64", this.value, Long::toHexString); + } + + /** + * Convert to byte array. + * + * @param value the value + * @return the byte array + */ + public static byte[] toByteArray(long value) { + return ByteBuffer.allocate(BYTE_LENGTH).putLong(value).array(); + } + + /** + * Convert to byte array. + * + * @param value the value + * @return the byte array + */ + public static byte[] toByteArray(Object value) { + if (value == null || value instanceof io.openems.common.types.OptionsEnum + && ((io.openems.common.types.OptionsEnum) value).isUndefined()) { + return UNDEFINED_VALUE; + } + return toByteArray((long) TypeUtils.getAsType(OpenemsType.LONG, value)); + } + + @Override + public String getValueDescription() { + return this.value != null // + ? "\"" + Long.toString(this.value) + "\"" // + : ""; + } + +} diff --git a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint64Reserved.java b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint64Reserved.java new file mode 100644 index 00000000000..d6018217403 --- /dev/null +++ b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint64Reserved.java @@ -0,0 +1,14 @@ +package io.openems.edge.common.modbusslave; + +public class ModbusRecordUint64Reserved extends ModbusRecordUint64 { + + public ModbusRecordUint64Reserved(int offset) { + super(offset, "Reserved", null); + } + + @Override + public String toString() { + return "ModbusRecordUint64Reserved [type=" + this.getType() + "]"; + } + +} diff --git a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusSlaveNatureTable.java b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusSlaveNatureTable.java index da1d57c03af..f358ad8511b 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusSlaveNatureTable.java +++ b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusSlaveNatureTable.java @@ -63,22 +63,12 @@ public Builder channel(int offset, ChannelId channelId, ModbusType type) { } else { // Channel did not pass filter -> show as Reserved switch (type) { - case FLOAT32: - this.float32Reserved(offset); - break; - case FLOAT64: - this.float64Reserved(offset); - break; - case STRING16: - this.string16Reserved(offset); - break; - case ENUM16: - case UINT16: - this.uint16Reserved(offset); - break; - case UINT32: - this.uint32Reserved(offset); - break; + case FLOAT32 -> this.float32Reserved(offset); + case FLOAT64 -> this.float64Reserved(offset); + case STRING16 -> this.string16Reserved(offset); + case ENUM16, UINT16 -> this.uint16Reserved(offset); + case UINT32 -> this.uint32Reserved(offset); + case UINT64 -> this.uint64Reserved(offset); } } return this; @@ -158,6 +148,18 @@ public Builder uint32Reserved(int offset) { return this; } + /** + * Add a Unsigned Int 64 Reserved value to the {@link ModbusSlaveNatureTable} + * {@link Builder}. + * + * @param offset the address offset + * @return myself + */ + public Builder uint64Reserved(int offset) { + this.add(new ModbusRecordUint64Reserved(offset)); + return this; + } + /** * Add a Float 32 value to the {@link ModbusSlaveNatureTable} {@link Builder}. * diff --git a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusType.java b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusType.java index 5da35991871..df071d14fdc 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusType.java +++ b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusType.java @@ -4,6 +4,7 @@ public enum ModbusType { ENUM16(1, "enum16"), // UINT16(1, "uint16"), // UINT32(2, "uint32"), // + UINT64(4, "uint64"), // FLOAT32(2, "float32"), // FLOAT64(4, "float64"), // STRING16(16, "string16"); diff --git a/io.openems.edge.common/src/io/openems/edge/common/sum/DummySum.java b/io.openems.edge.common/src/io/openems/edge/common/sum/DummySum.java index 41a246d6b33..2a2b9bd7568 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/sum/DummySum.java +++ b/io.openems.edge.common/src/io/openems/edge/common/sum/DummySum.java @@ -49,4 +49,48 @@ public DummySum withGridActivePower(int value) { return this.self(); } + /** + * Set {@link Sum.ChannelId#ESS_CAPACITY}. + * + * @param value the value + * @return myself + */ + public DummySum withEssCapacity(int value) { + withValue(this, Sum.ChannelId.ESS_CAPACITY, value); + return this.self(); + } + + /** + * Set {@link Sum.ChannelId#ESS_SOC}. + * + * @param value the value + * @return myself + */ + public DummySum withEssSoc(int value) { + withValue(this, Sum.ChannelId.ESS_SOC, value); + return this.self(); + } + + /** + * Set {@link Sum.ChannelId#ESS_MIN_DISCHARGE_POWER}. + * + * @param value the value + * @return myself + */ + public DummySum withEssMinDischargePower(int value) { + withValue(this, Sum.ChannelId.ESS_MIN_DISCHARGE_POWER, value); + return this.self(); + } + + /** + * Set {@link Sum.ChannelId#ESS_MAX_DISCHARGE_POWER}. + * + * @param value the value + * @return myself + */ + public DummySum withEssMaxDischargePower(int value) { + withValue(this, Sum.ChannelId.ESS_MAX_DISCHARGE_POWER, value); + return this.self(); + } + } diff --git a/io.openems.edge.common/src/io/openems/edge/common/sum/Sum.java b/io.openems.edge.common/src/io/openems/edge/common/sum/Sum.java index c6a903df67c..b065cb50c18 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/sum/Sum.java +++ b/io.openems.edge.common/src/io/openems/edge/common/sum/Sum.java @@ -629,6 +629,7 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { CONSUMPTION_ACTIVE_ENERGY(Doc.of(OpenemsType.LONG) // .unit(Unit.CUMULATED_WATT_HOURS) // .persistencePriority(PersistencePriority.VERY_HIGH)), // + /** * Is there any Component Info/Warning/Fault that is getting ignored/hidden * because of the 'ignoreStateComponents' configuration setting?. @@ -716,6 +717,7 @@ public static ModbusSlaveNatureTable getModbusSlaveNatureTable(AccessMode access .channel(113, ChannelId.ESS_DISCHARGE_POWER, ModbusType.FLOAT32) // .channel(115, ChannelId.GRID_MODE, ModbusType.ENUM16) // .channel(116, ChannelId.GRID_MODE_OFF_GRID_TIME, ModbusType.FLOAT32) // + .channel(118, ChannelId.ESS_CAPACITY, ModbusType.FLOAT32) // .build(); } diff --git a/io.openems.edge.common/src/io/openems/edge/common/test/AbstractComponentTest.java b/io.openems.edge.common/src/io/openems/edge/common/test/AbstractComponentTest.java index f714fb70658..9096c7d91ef 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/test/AbstractComponentTest.java +++ b/io.openems.edge.common/src/io/openems/edge/common/test/AbstractComponentTest.java @@ -1,5 +1,9 @@ package io.openems.edge.common.test; +import static io.openems.common.utils.ReflectionUtils.invokeMethodViaReflection; +import static io.openems.common.utils.ReflectionUtils.invokeMethodWithoutArgumentsViaReflection; +import static io.openems.common.utils.ReflectionUtils.setAttributeViaReflection; + import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -34,6 +38,7 @@ import io.openems.common.types.ChannelAddress; import io.openems.common.types.OpenemsType; import io.openems.common.types.OptionsEnum; +import io.openems.common.utils.ReflectionUtils.ReflectionException; import io.openems.edge.common.channel.Channel; import io.openems.edge.common.channel.ChannelId; import io.openems.edge.common.channel.EnumDoc; @@ -42,6 +47,11 @@ import io.openems.edge.common.component.ComponentManager; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.event.EdgeEventConstants; +import io.openems.edge.common.sum.Sum; +import io.openems.edge.common.test.AbstractComponentTest.ChannelValue.ChannelAddressValue; +import io.openems.edge.common.test.AbstractComponentTest.ChannelValue.ChannelIdValue; +import io.openems.edge.common.test.AbstractComponentTest.ChannelValue.ChannelNameValue; +import io.openems.edge.common.test.AbstractComponentTest.ChannelValue.ComponentChannelIdValue; import io.openems.edge.common.type.TypeUtils; /** @@ -49,10 +59,49 @@ */ public abstract class AbstractComponentTest, SUT extends OpenemsComponent> { - public record ChannelValue(ChannelAddress address, Object value, boolean force) { - @Override - public String toString() { - return this.address.toString() + ":" + this.value; + public sealed interface ChannelValue { + + /** + * Gets the value. + * + * @return the value + */ + public Object value(); + + /** + * Is the value enforced?. + * + * @return true for force + */ + public boolean force(); + + public record ChannelAddressValue(ChannelAddress address, Object value, boolean force) implements ChannelValue { + @Override + public String toString() { + return this.address.toString() + ":" + this.value; + } + } + + public record ChannelIdValue(ChannelId channelId, Object value, boolean force) implements ChannelValue { + @Override + public String toString() { + return this.channelId.id() + ":" + this.value; + } + } + + public record ChannelNameValue(String channelName, Object value, boolean force) implements ChannelValue { + @Override + public String toString() { + return this.channelName + ":" + this.value; + } + } + + public record ComponentChannelIdValue(String componentId, ChannelId channelId, Object value, boolean force) + implements ChannelValue { + @Override + public String toString() { + return this.componentId + "/" + this.channelId.id() + ":" + this.value; + } } } @@ -109,19 +158,71 @@ public TestCase(String description) { } /** - * Adds an input value for a Channel. + * Adds an input value for a {@link ChannelAddress}. * * @param address the {@link ChannelAddress} * @param value the value {@link Object} * @return myself */ public TestCase input(ChannelAddress address, Object value) { - this.inputs.add(new ChannelValue(address, value, false)); + this.inputs.add(new ChannelAddressValue(address, value, false)); + return this; + } + + /** + * Adds an input value for a ChannelId of the given Component. + * + * @param componentId the Component-ID + * @param channelId the Channel-ID in CamelCase + * @param value the value {@link Object} + * @return myself + */ + public TestCase input(String componentId, String channelId, Object value) { + return this.input(new ChannelAddress(componentId, channelId), value); + } + + /** + * Adds an input value for a {@link ChannelId} of the given Component. + * + * @param componentId the Component-ID + * @param channelId the {@link ChannelId} + * @param value the value {@link Object} + * @return myself + */ + public TestCase input(String componentId, ChannelId channelId, Object value) { + this.inputs.add(new ComponentChannelIdValue(componentId, channelId, value, false)); + return this; + } + + /** + * Adds an input value for a {@link ChannelId} of the system-under-test. + * + * @param channelId the {@link ChannelId} + * @param value the value {@link Object} + * @return myself + */ + public TestCase input(ChannelId channelId, Object value) { + if (channelId instanceof Sum.ChannelId) { + return this.input("_sum", channelId, value); + } + this.inputs.add(new ChannelIdValue(channelId, value, false)); + return this; + } + + /** + * Adds an input value for a ChannelId of the system-under-test. + * + * @param channelName the Channel + * @param value the value {@link Object} + * @return myself + */ + public TestCase input(String channelName, Object value) { + this.inputs.add(new ChannelNameValue(channelName, value, false)); return this; } /** - * Enforces an input value for a Channel. + * Enforces an input value for a {@link ChannelAddress}. * *

* Use this method if you want to be sure, that the Channel actually applies the @@ -132,19 +233,130 @@ public TestCase input(ChannelAddress address, Object value) { * @return myself */ public TestCase inputForce(ChannelAddress address, Object value) { - this.inputs.add(new ChannelValue(address, value, true)); + this.inputs.add(new ChannelAddressValue(address, value, true)); + return this; + } + + /** + * Enforces an input value for a {@link ChannelAddress}. + * + *

+ * Use this method if you want to be sure, that the Channel actually applies the + * value, e.g. to override a {@link Debounce} setting. + * + * @param componentId the Component-ID + * @param channelId the Channel-ID + * @param value the value {@link Object} + * @return myself + */ + public TestCase inputForce(String componentId, String channelId, Object value) { + return this.inputForce(new ChannelAddress(componentId, channelId), value); + } + + /** + * Enforces an input value for a {@link ChannelId} of the given Component. + * + * @param componentId the Component-ID + * @param channelId the {@link ChannelId} + * @param value the value {@link Object} + * @return myself + */ + public TestCase inputForce(String componentId, ChannelId channelId, Object value) { + this.inputs.add(new ComponentChannelIdValue(componentId, channelId, value, true)); return this; } /** - * Adds an expected output value for a Channel. + * Enforces an input value for a {@link ChannelId} of the system-under-test. + * + *

+ * Use this method if you want to be sure, that the Channel actually applies the + * value, e.g. to override a {@link Debounce} setting. + * + * @param channelId the {@link ChannelId} + * @param value the value {@link Object} + * @return myself + */ + public TestCase inputForce(ChannelId channelId, Object value) { + this.inputs.add(new ChannelIdValue(channelId, value, true)); + return this; + } + + /** + * Enforces an input value for a Channel of the system-under-test. + * + *

+ * Use this method if you want to be sure, that the Channel actually applies the + * value, e.g. to override a {@link Debounce} setting. + * + * @param channelName the Channel + * @param value the value {@link Object} + * @return myself + */ + public TestCase inputForce(String channelName, Object value) { + this.inputs.add(new ChannelNameValue(channelName, value, true)); + return this; + } + + /** + * Adds an expected output value for a {@link ChannelAddress}. * * @param address the {@link ChannelAddress} * @param value the value {@link Object} * @return myself */ public TestCase output(ChannelAddress address, Object value) { - this.outputs.add(new ChannelValue(address, value, false)); + this.outputs.add(new ChannelAddressValue(address, value, false)); + return this; + } + + /** + * Adds an expected output value for a {@link ChannelAddress}. + * + * @param componentId the Component-ID + * @param channelId the Channel-ID in CamelCase + * @param value the value {@link Object} + * @return myself + */ + public TestCase output(String componentId, String channelId, Object value) { + return this.output(new ChannelAddress(componentId, channelId), value); + } + + /** + * Adds an expected output value for a {@link ChannelId} of the given Component. + * + * @param componentId the Component-ID + * @param channelId the {@link ChannelId} + * @param value the value {@link Object} + * @return myself + */ + public TestCase output(String componentId, ChannelId channelId, Object value) { + this.outputs.add(new ComponentChannelIdValue(componentId, channelId, value, true)); + return this; + } + + /** + * Adds an expected output value for a {@link ChannelId} of the + * system-under-test. + * + * @param channelId the {@link ChannelId} + * @param value the value {@link Object} + * @return myself + */ + public TestCase output(ChannelId channelId, Object value) { + this.outputs.add(new ChannelIdValue(channelId, value, false)); + return this; + } + + /** + * Adds an expected output value for a Channel of the system-under-test. + * + * @param channelName the Channel + * @param value the value {@link Object} + * @return myself + */ + public TestCase output(String channelName, Object value) { + this.outputs.add(new ChannelNameValue(channelName, value, false)); return this; } @@ -286,20 +498,14 @@ public void applyTimeLeap() { /** * Applies the values for input channels. * - * @param components Referenced components + * @param act the {@link AbstractComponentTest} * @throws OpenemsNamedException on error * @throws IllegalArgumentException on error */ - protected void applyInputs(Map components) + protected void applyInputs(AbstractComponentTest act) throws IllegalArgumentException, OpenemsNamedException { for (var input : this.inputs) { - var component = components.get(input.address.getComponentId()); - if (component == null) { - throw new IllegalArgumentException("On TestCase [" + this.description + "]: " // - + "the component [" + input.address.getComponentId() + "] " // - + "was not added to the OpenEMS Component test framework!"); - } - var channel = component.channel(input.address.getChannelId()); + final Channel channel = this.getChannel(act, input); // (Force) set the Read-Value do { @@ -317,38 +523,79 @@ protected void applyInputs(Map components) /** * Validates the output values. * - * @param components Referenced components + * @param act the {@link AbstractComponentTest} * @throws Exception on validation failure */ - protected void validateOutputs(Map components) throws Exception { + @SuppressWarnings("unchecked") + protected void validateOutputs(AbstractComponentTest act) throws Exception { for (var output : this.outputs) { - var expected = output.value; - var channel = components.get(output.address.getComponentId()).channel(output.address.getChannelId()); + final Channel channel = this.getChannel(act, output); + Object got; - if (channel instanceof WriteChannel) { - got = ((WriteChannel) channel).getNextWriteValueAndReset().orElse(null); + final String readWriteInfo; + if (channel instanceof WriteChannel wc) { + got = wc.getNextWriteValueAndReset().orElse(null); + readWriteInfo = "WriteValue"; } else { var value = channel.getNextValue(); got = value.orElse(null); + readWriteInfo = "ReadValue"; } - // Try to parse an Enum if (channel.channelDoc() instanceof EnumDoc) { var enumDoc = (EnumDoc) channel.channelDoc(); var intGot = TypeUtils.getAsType(OpenemsType.INTEGER, got); got = enumDoc.getOption(intGot); } - if (!Objects.equals(expected, got)) { + if (!Objects.equals(output.value(), got)) { throw new Exception("On TestCase [" + this.description + "]: " // - + "expected [" + output.value + "] " // + + "expected " + readWriteInfo + " [" + output.value() + "] " // + "got [" + got + "] " // - + "for Channel [" + output.address.toString() + "] " // + + "for Channel [" + output.toString() + "] " // + "on Inputs [" + this.inputs + "]"); } } } + + private OpenemsComponent getComponent(Map components, String componentId) { + var component = components.get(componentId); + if (component != null) { + return component; + } + throw new IllegalArgumentException("On TestCase [" + this.description + "]: " // + + "the component [" + componentId + "] " // + + "was not added to the OpenEMS Component test framework!"); + } + + private Channel getChannel(AbstractComponentTest act, ChannelValue cv) + throws IllegalArgumentException { + if (cv instanceof ChannelAddressValue cav) { + var component = this.getComponent(act.components, cav.address.getComponentId()); + return component.channel(cav.address.getChannelId()); + } + + if (cv instanceof ChannelIdValue civ) { + return act.sut.channel(civ.channelId); + } + + if (cv instanceof ChannelNameValue civ2) { + return act.sut.channel(civ2.channelName); + } + + if (cv instanceof ComponentChannelIdValue cciv) { + var component = this.getComponent(act.components, cciv.componentId()); + return component.channel(cciv.channelId()); + } + + throw new IllegalArgumentException("Unhandled subtype of ChannelValue"); + } } + /** + * The {@link OpenemsComponent} to be tested. "sut" is for system-under-test. + */ + public final SUT sut; + /** * References added by {@link #addReference()}. */ @@ -359,11 +606,6 @@ protected void validateOutputs(Map components) throws */ private final Map components = new HashMap<>(); - /** - * The {@link OpenemsComponent} to be tested. "sut" is for system-under-test. - */ - private final SUT sut; - /** * Constructs the Component-Test and validates the implemented Channel-IDs. * @@ -469,11 +711,9 @@ public SELF addReference(String memberName, Object object) throws Exception { private boolean addReference(Class clazz, String memberName, Object object) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { try { - var field = clazz.getDeclaredField(memberName); - field.setAccessible(true); - field.set(this.sut, object); + setAttributeViaReflection(this.sut, memberName, object); return true; - } catch (NoSuchFieldException e) { + } catch (ReflectionException e) { // Ignore. Try method. if (this.invokeSingleArgMethod(clazz, memberName, object)) { return true; @@ -603,8 +843,7 @@ private boolean callActivateOrModified(String methodName, AbstractComponentConfi } args[i] = arg; } - method.setAccessible(true); - method.invoke(this.sut, args); + invokeMethodViaReflection(this.sut, method, args); return true; } return false; @@ -624,14 +863,10 @@ private void callModified(AbstractComponentConfig config) throws Exception { private void callDeactivate() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException { - Class clazz = this.sut.getClass(); - var method = clazz.getDeclaredMethod("deactivate"); - method.setAccessible(true); - method.invoke(this.sut); + invokeMethodWithoutArgumentsViaReflection(this.sut, "deactivate"); } - private boolean invokeSingleArgMethod(Class clazz, String methodName, Object arg) - throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { + private boolean invokeSingleArgMethod(Class clazz, String methodName, Object arg) throws ReflectionException { var methods = clazz.getDeclaredMethods(); for (Method method : methods) { if (!method.getName().equals(methodName)) { @@ -646,8 +881,7 @@ private boolean invokeSingleArgMethod(Class clazz, String methodName, Object continue; } - method.setAccessible(true); - method.invoke(this.sut, arg); + invokeMethodViaReflection(this.sut, method, arg); return true; } @@ -670,7 +904,7 @@ public SELF next(TestCase testCase) throws Exception { for (Channel channel : this.getSut().channels()) { channel.nextProcessImage(); } - testCase.applyInputs(this.components); + testCase.applyInputs(this); this.onAfterProcessImage(); executeCallbacks(testCase.onAfterProcessImageCallbacks); this.handleEvent(EdgeEventConstants.TOPIC_CYCLE_AFTER_PROCESS_IMAGE); @@ -691,7 +925,7 @@ public SELF next(TestCase testCase) throws Exception { this.onAfterWrite(); executeCallbacks(testCase.onAfterWriteCallbacks); this.handleEvent(EdgeEventConstants.TOPIC_CYCLE_AFTER_WRITE); - testCase.validateOutputs(this.components); + testCase.validateOutputs(this); return this.self(); } @@ -725,9 +959,11 @@ private static void executeCallbacks(List> callbacks * */ protected void handleEvent(String topic) throws Exception { - if (this.sut instanceof EventHandler) { - var event = new Event(topic, new HashMap()); - ((EventHandler) this.sut).handleEvent(event); + var event = new Event(topic, new HashMap()); + for (var component : this.components.values()) { + if (component instanceof EventHandler eh) { + eh.handleEvent(event); + } } } @@ -802,7 +1038,5 @@ protected void onExecuteWrite() throws OpenemsNamedException { * @throws OpenemsNamedException on error */ protected void onAfterWrite() throws OpenemsNamedException { - } - } diff --git a/io.openems.edge.common/src/io/openems/edge/common/test/DummyChannel.java b/io.openems.edge.common/src/io/openems/edge/common/test/DummyChannel.java new file mode 100644 index 00000000000..a62c84f0176 --- /dev/null +++ b/io.openems.edge.common/src/io/openems/edge/common/test/DummyChannel.java @@ -0,0 +1,27 @@ +package io.openems.edge.common.test; + +import io.openems.edge.common.channel.ChannelId; +import io.openems.edge.common.channel.ChannelId.ChannelIdImpl; +import io.openems.edge.common.channel.IntegerDoc; +import io.openems.edge.common.channel.IntegerReadChannel; +import io.openems.edge.common.component.OpenemsComponent; + +public class DummyChannel extends IntegerReadChannel { + + /** + * Creates a {@link DummyChannel} with the given name. + * + * @param name the channel name + * @return a {@link DummyChannel} + */ + public static DummyChannel of(String name) { + var doc = new IntegerDoc(); + var channelId = new ChannelIdImpl(name, doc); + return new DummyChannel(null, channelId, doc); + } + + protected DummyChannel(OpenemsComponent component, ChannelId channelId, IntegerDoc channelDoc) { + super(component, channelId, channelDoc); + } + +} diff --git a/io.openems.edge.common/src/io/openems/edge/common/test/DummyComponentManager.java b/io.openems.edge.common/src/io/openems/edge/common/test/DummyComponentManager.java index a3a43bbe07f..fe893c48292 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/test/DummyComponentManager.java +++ b/io.openems.edge.common/src/io/openems/edge/common/test/DummyComponentManager.java @@ -1,10 +1,12 @@ package io.openems.edge.common.test; +import static io.openems.common.test.TestUtils.createDummyClock; +import static java.util.Collections.unmodifiableList; + import java.io.IOException; import java.time.Clock; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.Hashtable; import java.util.List; @@ -47,7 +49,7 @@ public class DummyComponentManager implements ComponentManager, ComponentJsonApi private ConfigurationAdmin configurationAdmin = null; public DummyComponentManager() { - this(Clock.systemDefaultZone()); + this(createDummyClock()); } public DummyComponentManager(Clock clock) { @@ -56,12 +58,12 @@ public DummyComponentManager(Clock clock) { @Override public List getEnabledComponents() { - return Collections.unmodifiableList(this.components); + return unmodifiableList(this.components); } @Override public List getAllComponents() { - return Collections.unmodifiableList(this.components); + return unmodifiableList(this.components); } @Override diff --git a/io.openems.edge.common/src/io/openems/edge/common/test/DummyMeta.java b/io.openems.edge.common/src/io/openems/edge/common/test/DummyMeta.java index b77fcb3356f..726bbe59bff 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/test/DummyMeta.java +++ b/io.openems.edge.common/src/io/openems/edge/common/test/DummyMeta.java @@ -40,4 +40,14 @@ public DummyMeta withCurrency(Currency value) { return this.self(); } + /** + * Set {@link Meta.ChannelId#IS_ESS_CHARGE_FROM_GRID_ALLOWED}. + * + * @param value the value + * @return myself + */ + public DummyMeta withIsEssChargeFromGridAllowed(boolean value) { + TestUtils.withValue(this, Meta.ChannelId.IS_ESS_CHARGE_FROM_GRID_ALLOWED, value); + return this.self(); + } } diff --git a/io.openems.edge.common/src/io/openems/edge/common/test/TestUtils.java b/io.openems.edge.common/src/io/openems/edge/common/test/TestUtils.java index d58c7dbf484..5f99649086e 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/test/TestUtils.java +++ b/io.openems.edge.common/src/io/openems/edge/common/test/TestUtils.java @@ -1,31 +1,16 @@ package io.openems.edge.common.test; -import java.io.IOException; -import java.net.ServerSocket; +import java.util.function.BiFunction; +import java.util.function.Function; import io.openems.edge.common.channel.Channel; import io.openems.edge.common.channel.ChannelId; +import io.openems.edge.common.channel.value.Value; import io.openems.edge.common.component.OpenemsComponent; public class TestUtils { private TestUtils() { - - } - - /** - * Finds and returns an open port. - * - *

- * Source https://stackoverflow.com/a/26644672 - * - * @return an open port - * @throws IOException on error - */ - public static int findRandomOpenPortOnAllLocalInterfaces() throws IOException { - try (var socket = new ServerSocket(0);) { - return socket.getLocalPort(); - } } /** @@ -69,4 +54,24 @@ public static void withValue(Channel channel, Object value) { channel.setNextValue(value); channel.nextProcessImage(); } + + /** + * Helper to test a {@link #withValue(Channel, Object)} method in a JUnit test. + * + * @param the type of the {@link AbstractDummyOpenemsComponent} + * @param sut the actual system-under-test + * @param setter the getChannel getter method + * @param getter the withChannel setter method + */ + public static void testWithValue(T sut, BiFunction setter, Function> getter) { + var before = getter.apply(sut).get(); + if (before != null) { + throw new IllegalArgumentException("TestUtils.testWithValue() expected [null] got [" + before + "]"); + } + setter.apply(sut, 123); + var after = getter.apply(sut).get().intValue(); + if (after != 123) { + throw new IllegalArgumentException("TestUtils.testWithValue() expected [123] got [" + after + "]"); + } + } } diff --git a/io.openems.edge.common/src/io/openems/edge/common/type/QuarterlyValues.java b/io.openems.edge.common/src/io/openems/edge/common/type/QuarterlyValues.java new file mode 100644 index 00000000000..40523fba4b0 --- /dev/null +++ b/io.openems.edge.common/src/io/openems/edge/common/type/QuarterlyValues.java @@ -0,0 +1,186 @@ +package io.openems.edge.common.type; + +import static io.openems.common.utils.DateUtils.roundDownToQuarter; +import static java.util.Collections.emptyNavigableMap; +import static java.util.Collections.unmodifiableNavigableMap; + +import java.time.ZonedDateTime; +import java.util.Map.Entry; +import java.util.NavigableMap; +import java.util.Objects; +import java.util.TreeMap; +import java.util.function.IntFunction; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.google.common.base.MoreObjects; +import com.google.common.collect.ImmutableSortedMap; + +public abstract class QuarterlyValues { + + /** + * Immutable Map of Quarters (rounded down to 15 minutes) and their respective + * predicted values. + */ + protected final ImmutableSortedMap valuePerQuarter; + + protected QuarterlyValues(ImmutableSortedMap valuePerQuarter) { + // Validate times + if (valuePerQuarter.keySet().stream() // + .anyMatch(t -> !t.isEqual(roundDownToQuarter(t)))) { + throw new IllegalArgumentException("Times must be rounded to quarters: " // + + valuePerQuarter.keySet().stream() // + .map(ZonedDateTime::toString) // + .collect(Collectors.joining(","))); + } + + this.valuePerQuarter = valuePerQuarter; + } + + protected QuarterlyValues(ZonedDateTime time, T[] values) { + time = roundDownToQuarter(time); + var result = ImmutableSortedMap.naturalOrder(); + for (var value : values) { + if (value != null) { + result.put(time, value); + } + time = time.plusMinutes(15); + } + this.valuePerQuarter = result.build(); + } + + /** + * Returns {@code true} if there are no values. + * + * @return {@code true} if there are no values + */ + public final boolean isEmpty() { + return this.valuePerQuarter.isEmpty(); + } + + /** + * Gets the {@link ZonedDateTime} of the first value. + * + * @return ZonedDateTime or null if there are no values + */ + public final ZonedDateTime getFirstTime() { + if (this.valuePerQuarter.isEmpty()) { + return null; + } + return this.valuePerQuarter.firstKey(); + } + + /** + * Gets the first value. + * + * @return the value; or null + */ + public final T getFirst() { + if (this.valuePerQuarter.isEmpty()) { + return null; + } + return this.valuePerQuarter.firstEntry().getValue(); + } + + /** + * Gets the {@link ZonedDateTime} of the last value. + * + * @return ZonedDateTime or null if there are no values + */ + public final ZonedDateTime getLastTime() { + var lastEntry = this.valuePerQuarter.lastEntry(); + if (lastEntry != null) { + return lastEntry.getKey(); + } + return null; + } + + /** + * Gets the value at the given {@link ZonedDateTime}. + * + *

+ * As a fallback, this internally also compares via Instant, see + * {@link ZonedDateTime#isEqual(java.time.chrono.ChronoZonedDateTime)}. + * + * @param time the {@link ZonedDateTime} + * @return the value; or null + */ + public final T getAt(ZonedDateTime time) { + return this.getAtOrElse(time, null); + } + + /** + * Gets the value at the given {@link ZonedDateTime}. + * + *

+ * As a fallback, this internally also compares via Instant, see + * {@link ZonedDateTime#isEqual(java.time.chrono.ChronoZonedDateTime)}. + * + * @param time the {@link ZonedDateTime} + * @param orElse the alternative value + * @return the value; or the alternative value if none was found + */ + public final T getAtOrElse(ZonedDateTime time, T orElse) { + var result = this.valuePerQuarter.get(time); + if (result != null) { + return result; + } + // Fallback: compare Instant + return this.valuePerQuarter.entrySet().stream() // + .filter(e -> e.getKey().isEqual(time)) // + .map(Entry::getValue) // + .findFirst() // + .orElse(orElse); + } + + /** + * Gets a {@link Stream} of values between from (inclusive) and to (exclusive). + * + * @param from the from {@link ZonedDateTime} + * @param to the to {@link ZonedDateTime} + * @return a Stream of values; possibly empty + */ + public final Stream getBetween(ZonedDateTime from, ZonedDateTime to) { + return Stream.iterate(from, t -> t.plusMinutes(15)) // + .takeWhile(t -> t.isBefore(to)) // + .map(t -> this.getAt(t)) // + .filter(Objects::nonNull); + } + + @Override + public String toString() { + var sh = MoreObjects.toStringHelper(this); + if (this.isEmpty()) { + sh.addValue("EMPTY"); + } else { + sh.add("start", this.getFirstTime().toString()); + sh.add("values", this.toMapWithAllQuarters().values().stream() // + .map(Object::toString) // + .collect(Collectors.joining(","))); + } + return sh.toString(); + } + + /** + * Converts the internal non-nullable value-map to a map with all quarters as + * keys. + * + * @return a map with possible null values + */ + public NavigableMap toMapWithAllQuarters() { + if (this.isEmpty()) { + return emptyNavigableMap(); + } + var first = this.getFirstTime(); + var last = this.getLastTime(); + final var result = new TreeMap(); + Stream.iterate(first, t -> t.plusMinutes(15)) // + .takeWhile(t -> !t.isAfter(last)) // + .forEach(t -> result.put(t, this.getAt(t))); + return unmodifiableNavigableMap(result); + } + + protected T[] asArray(IntFunction generator) { + return this.valuePerQuarter.values().toArray(generator); + } +} diff --git a/io.openems.edge.common/src/io/openems/edge/common/type/TypeUtils.java b/io.openems.edge.common/src/io/openems/edge/common/type/TypeUtils.java index babada99a81..d43a8bc6716 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/type/TypeUtils.java +++ b/io.openems.edge.common/src/io/openems/edge/common/type/TypeUtils.java @@ -364,23 +364,15 @@ public static JsonElement getAsJson(OpenemsType type, Object originalValue) { return JsonNull.INSTANCE; } var value = TypeUtils.getAsType(type, originalValue); - switch (type) { - case BOOLEAN: - return new JsonPrimitive((Boolean) value ? 1 : 0); - case SHORT: - return new JsonPrimitive((Short) value); - case INTEGER: - return new JsonPrimitive((Integer) value); - case LONG: - return new JsonPrimitive((Long) value); - case FLOAT: - return new JsonPrimitive((Float) value); - case DOUBLE: - return new JsonPrimitive((Double) value); - case STRING: - return new JsonPrimitive((String) value); - } - throw new IllegalArgumentException("Converter for value [" + value + "] to JSON is not implemented."); + return switch (type) { + case BOOLEAN -> new JsonPrimitive((Boolean) value ? 1 : 0); + case SHORT -> new JsonPrimitive((Short) value); + case INTEGER -> new JsonPrimitive((Integer) value); + case LONG -> new JsonPrimitive((Long) value); + case FLOAT -> new JsonPrimitive((Float) value); + case DOUBLE -> new JsonPrimitive((Double) value); + case STRING -> new JsonPrimitive((String) value); + }; } /** @@ -427,6 +419,28 @@ public static Integer sum(Integer... values) { return result; } + /** + * Safely add Floats. If one of them is null it is considered '0'. If all of + * them are null, 'null' is returned. + * + * @param values the {@link Float} values + * @return the sum + */ + public static Float sum(Float... values) { + Float result = null; + for (Float value : values) { + if (value == null) { + continue; + } + if (result == null) { + result = value; + } else { + result += value; + } + } + return result; + } + /** * Safely add Longs. If one of them is null it is considered '0'. If all of them * are null, 'null' is returned. diff --git a/io.openems.edge.common/test/io/openems/edge/common/jsonapi/CallTest.java b/io.openems.edge.common/test/io/openems/edge/common/jsonapi/CallTest.java index 56a1820b518..3b3b4125502 100644 --- a/io.openems.edge.common/test/io/openems/edge/common/jsonapi/CallTest.java +++ b/io.openems.edge.common/test/io/openems/edge/common/jsonapi/CallTest.java @@ -43,7 +43,7 @@ public void testMapRequest() { class DummyRequestClass { } - + final var dummyRequest = new DummyRequestClass(); final var newCall = call.mapRequest(dummyRequest); @@ -58,7 +58,7 @@ public void testMapResponse() { class DummyResponseClass { } - + final var mappedCall = call.mapResponse(); final var originalResponse = new GenericJsonrpcResponseSuccess(call.getRequest().getId()); diff --git a/io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordFloat32Test.java b/io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordFloat32Test.java new file mode 100644 index 00000000000..197fbff8dcf --- /dev/null +++ b/io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordFloat32Test.java @@ -0,0 +1,30 @@ +package io.openems.edge.common.modbusslave; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public class ModbusRecordFloat32Test { + + @Test + public void testValue() { + var sut = new ModbusRecordFloat32(0, "foo", 1234567.89F); + assertEquals("ModbusRecordFloat32 [value=1234567.9, type=float32]", sut.toString()); + assertEquals("\"1234567.9\"", sut.getValueDescription()); + } + + @Test + public void testNull() { + var sut = new ModbusRecordFloat32(0, "bar", null); + assertEquals("ModbusRecordFloat32 [value=UNDEFINED, type=float32]", sut.toString()); + assertEquals("", sut.getValueDescription()); + } + + @Test + public void testReserved() { + var sut = new ModbusRecordFloat32Reserved(0); + assertEquals("ModbusRecordFloat32Reserved [type=float32]", sut.toString()); + assertEquals("", sut.getValueDescription()); + } + +} diff --git a/io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordFloat64Test.java b/io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordFloat64Test.java new file mode 100644 index 00000000000..70a21900022 --- /dev/null +++ b/io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordFloat64Test.java @@ -0,0 +1,30 @@ +package io.openems.edge.common.modbusslave; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public class ModbusRecordFloat64Test { + + @Test + public void testValue() { + var sut = new ModbusRecordFloat64(0, "foo", 1234567.89); + assertEquals("ModbusRecordFloat64 [value=1234567.89, type=float64]", sut.toString()); + assertEquals("\"1234567.89\"", sut.getValueDescription()); + } + + @Test + public void testNull() { + var sut = new ModbusRecordFloat64(0, "bar", null); + assertEquals("ModbusRecordFloat64 [value=UNDEFINED, type=float64]", sut.toString()); + assertEquals("", sut.getValueDescription()); + } + + @Test + public void testReserved() { + var sut = new ModbusRecordFloat64Reserved(0); + assertEquals("ModbusRecordFloat64Reserved [type=float64]", sut.toString()); + assertEquals("", sut.getValueDescription()); + } + +} diff --git a/io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordString16Test.java b/io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordString16Test.java new file mode 100644 index 00000000000..d6bea0edd02 --- /dev/null +++ b/io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordString16Test.java @@ -0,0 +1,42 @@ +package io.openems.edge.common.modbusslave; + +import static org.junit.Assert.assertEquals; + +import java.util.Arrays; + +import org.junit.Test; + +public class ModbusRecordString16Test { + + @Test + public void testValue() { + var sut = new ModbusRecordString16(0, "foo", "bar"); + assertEquals("ModbusRecordString16 [value=bar, type=string16]", sut.toString()); + assertEquals("\"bar\"", sut.getValueDescription()); + } + + @Test + public void testNull() { + var sut = new ModbusRecordString16(0, "bar", null); + assertEquals("ModbusRecordString16 [value=UNDEFINED, type=string16]", sut.toString()); + assertEquals("", sut.getValueDescription()); + } + + @Test + public void testReserved() { + var sut = new ModbusRecordString16Reserved(0); + assertEquals("ModbusRecordString16Reserved [type=string16]", sut.toString()); + assertEquals("", sut.getValueDescription()); + } + + @Test + public void testToByteArray() { + assertEquals("[72, 101, 108, 108, 111, "// + + "32, " // + + "87, 111, 114, 108, 100, " // + + "0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]", + Arrays.toString(ModbusRecordString16.toByteArray((Object) "Hello World"))); + assertEquals("[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]", + Arrays.toString(ModbusRecordString16.toByteArray((Object) null))); + } +} diff --git a/io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordUint16Test.java b/io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordUint16Test.java new file mode 100644 index 00000000000..7752f124cd2 --- /dev/null +++ b/io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordUint16Test.java @@ -0,0 +1,52 @@ +package io.openems.edge.common.modbusslave; + +import static io.openems.common.test.DummyOptionsEnum.UNDEFINED; +import static io.openems.common.test.DummyOptionsEnum.VALUE_1; +import static org.junit.Assert.assertEquals; + +import java.util.Arrays; + +import org.junit.Test; + +public class ModbusRecordUint16Test { + + @Test + public void testValue() { + var sut = new ModbusRecordUint16(0, "foo", (short) 12345); + assertEquals("ModbusRecordUInt16 [value=12345/0x3039, type=uint16]", sut.toString()); + assertEquals("\"12345\"", sut.getValueDescription()); + } + + @Test + public void testNull() { + var sut = new ModbusRecordUint16(0, "bar", null); + assertEquals("ModbusRecordUInt16 [value=UNDEFINED, type=uint16]", sut.toString()); + assertEquals("", sut.getValueDescription()); + } + + @Test + public void testOptionsEnum() { + assertEquals("[-1, -1]", Arrays.toString(ModbusRecordUint16.toByteArray(UNDEFINED))); + assertEquals("[0, 1]", Arrays.toString(ModbusRecordUint16.toByteArray(VALUE_1))); + } + + @Test + public void testReserved() { + var sut = new ModbusRecordUint16Reserved(0); + assertEquals("ModbusRecordUint16Reserved [type=uint16]", sut.toString()); + assertEquals("", sut.getValueDescription()); + } + + @Test + public void testBlockLength() { + assertEquals("ModbusRecordUint16BlockLength [blockName=block, value=12345/0x3039, type=uint16]", + new ModbusRecordUint16BlockLength(0, "block", (short) 12345).toString()); + } + + @Test + public void testHash() { + var sut = new ModbusRecordUint16Hash(0, "hash"); + assertEquals("ModbusRecordUint16Hash [text=hash, value=-16114/0xc10e, type=uint16]", sut.toString()); + assertEquals("\"0xc10e\"", sut.getValueDescription()); + } +} diff --git a/io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordUint32Test.java b/io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordUint32Test.java new file mode 100644 index 00000000000..0f5504bf857 --- /dev/null +++ b/io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordUint32Test.java @@ -0,0 +1,40 @@ +package io.openems.edge.common.modbusslave; + +import static io.openems.common.test.DummyOptionsEnum.UNDEFINED; +import static io.openems.common.test.DummyOptionsEnum.VALUE_1; +import static org.junit.Assert.assertEquals; + +import java.util.Arrays; + +import org.junit.Test; + +public class ModbusRecordUint32Test { + + @Test + public void testValue() { + var sut = new ModbusRecordUint32(0, "foo", 123456789); + assertEquals("ModbusRecordUInt32 [value=123456789/0x75bcd15, type=uint32]", sut.toString()); + assertEquals("\"123456789\"", sut.getValueDescription()); + } + + @Test + public void testNull() { + var sut = new ModbusRecordUint32(0, "bar", null); + assertEquals("ModbusRecordUInt32 [value=UNDEFINED, type=uint32]", sut.toString()); + assertEquals("", sut.getValueDescription()); + } + + @Test + public void testOptionsEnum() { + assertEquals("[-1, -1, -1, -1]", Arrays.toString(ModbusRecordUint32.toByteArray(UNDEFINED))); + assertEquals("[0, 0, 0, 1]", Arrays.toString(ModbusRecordUint32.toByteArray(VALUE_1))); + } + + @Test + public void testReserved() { + var sut = new ModbusRecordUint32Reserved(0); + assertEquals("ModbusRecordUint32Reserved [type=uint32]", sut.toString()); + assertEquals("", sut.getValueDescription()); + } + +} diff --git a/io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordUint64Test.java b/io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordUint64Test.java new file mode 100644 index 00000000000..903a5139236 --- /dev/null +++ b/io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordUint64Test.java @@ -0,0 +1,40 @@ +package io.openems.edge.common.modbusslave; + +import static io.openems.common.test.DummyOptionsEnum.UNDEFINED; +import static io.openems.common.test.DummyOptionsEnum.VALUE_1; +import static org.junit.Assert.assertEquals; + +import java.util.Arrays; + +import org.junit.Test; + +public class ModbusRecordUint64Test { + + @Test + public void testValue() { + var sut = new ModbusRecordUint64(0, "foo", 123456789L); + assertEquals("ModbusRecordUInt64 [value=123456789/0x75bcd15, type=uint64]", sut.toString()); + assertEquals("\"123456789\"", sut.getValueDescription()); + } + + @Test + public void testNull() { + var sut = new ModbusRecordUint64(0, "bar", null); + assertEquals("ModbusRecordUInt64 [value=UNDEFINED, type=uint64]", sut.toString()); + assertEquals("", sut.getValueDescription()); + } + + @Test + public void testOptionsEnum() { + assertEquals("[-1, -1, -1, -1, -1, -1, -1, -1]", Arrays.toString(ModbusRecordUint64.toByteArray(UNDEFINED))); + assertEquals("[0, 0, 0, 0, 0, 0, 0, 1]", Arrays.toString(ModbusRecordUint64.toByteArray(VALUE_1))); + } + + @Test + public void testReserved() { + var sut = new ModbusRecordUint64Reserved(0); + assertEquals("ModbusRecordUint64Reserved [type=uint64]", sut.toString()); + assertEquals("", sut.getValueDescription()); + } + +} diff --git a/io.openems.edge.common/test/io/openems/edge/common/sum/DummySumTest.java b/io.openems.edge.common/test/io/openems/edge/common/sum/DummySumTest.java new file mode 100644 index 00000000000..8cb89d3a6d2 --- /dev/null +++ b/io.openems.edge.common/test/io/openems/edge/common/sum/DummySumTest.java @@ -0,0 +1,22 @@ +package io.openems.edge.common.sum; + +import static io.openems.edge.common.test.TestUtils.testWithValue; + +import org.junit.Test; + +import io.openems.common.exceptions.OpenemsException; + +public class DummySumTest { + + @Test + public void test() throws OpenemsException { + final var sut = new DummySum(); + + testWithValue(sut, DummySum::withProductionAcActivePower, Sum::getProductionAcActivePower); + testWithValue(sut, DummySum::withGridActivePower, Sum::getGridActivePower); + testWithValue(sut, DummySum::withEssCapacity, Sum::getEssCapacity); + testWithValue(sut, DummySum::withEssSoc, Sum::getEssSoc); + testWithValue(sut, DummySum::withEssMinDischargePower, Sum::getEssMinDischargePower); + testWithValue(sut, DummySum::withEssMaxDischargePower, Sum::getEssMaxDischargePower); + } +} diff --git a/io.openems.edge.common/test/io/openems/edge/common/type/QuarterlyValuesTest.java b/io.openems.edge.common/test/io/openems/edge/common/type/QuarterlyValuesTest.java new file mode 100644 index 00000000000..56e5cf9032d --- /dev/null +++ b/io.openems.edge.common/test/io/openems/edge/common/type/QuarterlyValuesTest.java @@ -0,0 +1,79 @@ +package io.openems.edge.common.type; + +import static io.openems.common.test.TestUtils.createDummyClock; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.time.ZonedDateTime; + +import org.junit.Test; + +import com.google.common.collect.ImmutableSortedMap; + +public class QuarterlyValuesTest { + + private static class MyQuarterlyValues extends QuarterlyValues { + + protected MyQuarterlyValues(ImmutableSortedMap valuePerQuarter) { + super(valuePerQuarter); + } + + protected MyQuarterlyValues(ZonedDateTime time, Double... values) { + super(time, values); + } + + protected Double[] asArray() { + return super.asArray(Double[]::new); + } + } + + @Test(expected = IllegalArgumentException.class) + public void testExpectError() { + var time = ZonedDateTime.now(createDummyClock()); + new MyQuarterlyValues(ImmutableSortedMap.of(// + time.plusMinutes(1), 0.1)); + } + + @Test + public void testEmpty() { + var time = ZonedDateTime.now(createDummyClock()); + var sut = new MyQuarterlyValues(time); + assertTrue(sut.isEmpty()); + assertNull(sut.getFirst()); + assertNull(sut.getFirstTime()); + assertNull(sut.getLastTime()); + assertNull(sut.getAt(time)); + assertEquals("MyQuarterlyValues{EMPTY}", sut.toString()); + assertTrue(sut.toMapWithAllQuarters().isEmpty()); + assertEquals(0, sut.getBetween(time, time.plusMinutes(50)).toList().size()); + } + + @Test + public void test() { + var time = ZonedDateTime.now(createDummyClock()); + var sut = new MyQuarterlyValues(ImmutableSortedMap.of(// + time, 0.1, // + time.plusMinutes(15), 0.2, // + time.plusMinutes(30), 0.3, // + time.plusMinutes(45), 0.4, // + time.plusMinutes(60), 0.5)); + assertFalse(sut.isEmpty()); + assertEquals(0.1, sut.getFirst(), 0.001); + assertEquals(time, sut.getFirstTime()); + assertEquals(time.plusMinutes(60), sut.getLastTime()); + assertEquals(0.2, sut.getAt(time.plusMinutes(15)), 0.001); + assertEquals("MyQuarterlyValues{start=2020-01-01T00:00Z, values=0.1,0.2,0.3,0.4,0.5}", sut.toString()); + assertEquals(4, sut.getBetween(time, time.plusMinutes(50)).toList().size()); + } + + @Test + public void test2() { + var time = ZonedDateTime.now(createDummyClock()); + var sut = new MyQuarterlyValues(time, 0.1, 0.2, null, 0.3); + assertEquals(3, sut.asArray().length); + assertEquals(4, sut.toMapWithAllQuarters().size()); + } + +} diff --git a/io.openems.edge.common/test/io/openems/edge/common/type/TypeUtilsTest.java b/io.openems.edge.common/test/io/openems/edge/common/type/TypeUtilsTest.java index 83c76584a1c..f5ff84b47f3 100644 --- a/io.openems.edge.common/test/io/openems/edge/common/type/TypeUtilsTest.java +++ b/io.openems.edge.common/test/io/openems/edge/common/type/TypeUtilsTest.java @@ -1,5 +1,15 @@ package io.openems.edge.common.type; +import static com.google.gson.JsonNull.INSTANCE; +import static io.openems.common.types.OpenemsType.BOOLEAN; +import static io.openems.common.types.OpenemsType.DOUBLE; +import static io.openems.common.types.OpenemsType.FLOAT; +import static io.openems.common.types.OpenemsType.INTEGER; +import static io.openems.common.types.OpenemsType.LONG; +import static io.openems.common.types.OpenemsType.SHORT; +import static io.openems.common.types.OpenemsType.STRING; +import static io.openems.edge.common.type.TypeUtils.getAsJson; +import static io.openems.edge.common.type.TypeUtils.sum; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; @@ -7,8 +17,9 @@ import org.junit.Test; +import com.google.gson.JsonPrimitive; + import io.openems.common.function.ThrowingRunnable; -import io.openems.common.types.OpenemsType; import io.openems.common.types.OptionsEnum; import io.openems.edge.common.channel.value.Value; @@ -81,8 +92,8 @@ public void testMin() { @Test public void testSumDouble() { - assertNull(TypeUtils.sum((Double) null, null)); - assertEquals(4.0, TypeUtils.sum(1.5, 2.5), 0.1); + assertNull(sum((Double) null, null)); + assertEquals(4.0, sum(1.5, 2.5), 0.1); } @Test @@ -269,6 +280,47 @@ public void testGetAsType() { } } + @Test + public void testGetAsJson() { + assertEquals(INSTANCE, getAsJson(INTEGER, null)); + assertEquals(new JsonPrimitive(0), getAsJson(BOOLEAN, false)); + assertEquals(new JsonPrimitive(1), getAsJson(BOOLEAN, true)); + assertEquals(new JsonPrimitive(123), getAsJson(SHORT, 123)); + assertEquals(new JsonPrimitive(234), getAsJson(INTEGER, 234)); + assertEquals(new JsonPrimitive(345), getAsJson(LONG, 345)); + assertEquals(new JsonPrimitive(45.6F), getAsJson(FLOAT, 45.6F)); + assertEquals(new JsonPrimitive(56.7), getAsJson(DOUBLE, 56.7)); + assertEquals(new JsonPrimitive("678"), getAsJson(STRING, "678")); + } + + @Test + public void sumInteger() { + assertEquals(6, sum(1, 2, 3).intValue()); + assertNull(sum((Integer) null)); + assertEquals(6, sum(1, null, 2, 3).intValue()); + } + + @Test + public void sumFloat() { + assertEquals(6F, sum(1F, 2F, 3F).floatValue(), 0.001F); + assertNull(sum((Float) null)); + assertEquals(6F, sum(1F, null, 2F, 3F).floatValue(), 0.001F); + } + + @Test + public void sumLong() { + assertEquals(6L, sum(1L, 2L, 3L).longValue()); + assertNull(sum((Long) null)); + assertEquals(6L, sum(1L, null, 2L, 3L).longValue()); + } + + @Test + public void sumDouble() { + assertEquals(6., sum(1., 2., 3.).doubleValue(), 0.001); + assertNull(sum((Double) null)); + assertEquals(6., sum(1., null, 2., 3.).doubleValue(), 0.001); + } + private static void assertException(ThrowingRunnable runnable) { try { runnable.run(); @@ -279,31 +331,31 @@ private static void assertException(ThrowingRunnable runnable) { } private static Boolean getAsBoolean(Object value) { - return TypeUtils.getAsType(OpenemsType.BOOLEAN, value); + return TypeUtils.getAsType(BOOLEAN, value); } private static Short getAsShort(Object value) { - return TypeUtils.getAsType(OpenemsType.SHORT, value); + return TypeUtils.getAsType(SHORT, value); } private static Integer getAsInteger(Object value) { - return TypeUtils.getAsType(OpenemsType.INTEGER, value); + return TypeUtils.getAsType(INTEGER, value); } private static Long getAsLong(Object value) { - return TypeUtils.getAsType(OpenemsType.LONG, value); + return TypeUtils.getAsType(LONG, value); } private static Float getAsFloat(Object value) { - return TypeUtils.getAsType(OpenemsType.FLOAT, value); + return TypeUtils.getAsType(FLOAT, value); } private static Double getAsDouble(Object value) { - return TypeUtils.getAsType(OpenemsType.DOUBLE, value); + return TypeUtils.getAsType(DOUBLE, value); } private static String getAsString(Object value) { - return TypeUtils.getAsType(OpenemsType.STRING, value); + return TypeUtils.getAsType(STRING, value); } private static enum MyOptionsEnum implements OptionsEnum { diff --git a/io.openems.edge.controller.api.backend/.classpath b/io.openems.edge.controller.api.backend/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.controller.api.backend/.classpath +++ b/io.openems.edge.controller.api.backend/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/SendChannelValuesWorker.java b/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/SendChannelValuesWorker.java index 2adacd67077..95a01a9e335 100644 --- a/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/SendChannelValuesWorker.java +++ b/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/SendChannelValuesWorker.java @@ -6,7 +6,7 @@ import java.time.Duration; import java.time.Instant; import java.time.LocalDateTime; -import java.time.temporal.ChronoUnit; +import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -14,7 +14,10 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.Random; import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -65,6 +68,11 @@ public class SendChannelValuesWorker { .build(), // new ThreadPoolExecutor.DiscardOldestPolicy()); + private final ScheduledExecutorService aggregatedExecutor = Executors.newScheduledThreadPool(1, + new ThreadFactoryBuilder() + .setNameFormat(ControllerApiBackendImpl.COMPONENT_NAME + ":SendAggregatedWorker-%d").build()); + private final int randomWaitSeconds = new Random().nextInt((int) (AGGREGATION_MINUTES * 60 * 0.9)); + /** * If true: next 'send' sends all channel values. */ @@ -101,6 +109,7 @@ public synchronized void sendValuesOfAllChannelsOnce() { public void deactivate() { // Shutdown executor ThreadPoolUtils.shutdownAndAwaitTermination(this.executor, 5); + ThreadPoolUtils.shutdownAndAwaitTermination(this.aggregatedExecutor, 5); } /** @@ -108,18 +117,20 @@ public void deactivate() { * triggers asynchronous sending. */ public synchronized void collectData() { - var now = Instant.now(this.parent.componentManager.getClock()); + final var now = ZonedDateTime.now(this.parent.componentManager.getClock()); // Update the values of all channels final var enabledComponents = this.parent.componentManager.getEnabledComponents(); final var allValues = this.collectData(enabledComponents); - final var aggregatedValues = this.collectAggregatedData(enabledComponents); + final var aggregatedValues = this.collectAggregatedData(now, enabledComponents); // Add to send Queue - this.executor.execute(new SendTask(this, now, allValues)); + this.executor.execute(new SendTask(this, now.toInstant(), allValues)); if (aggregatedValues != null && !aggregatedValues.isEmpty()) { aggregatedValues.rowMap().forEach((timestamp, data) -> { - this.executor.execute(new SendAggregatedDataTask(this, Instant.ofEpochMilli(timestamp), data)); + this.aggregatedExecutor.schedule( + new SendAggregatedDataTask(this, Instant.ofEpochMilli(timestamp), data), this.randomWaitSeconds, + TimeUnit.SECONDS); }); } } @@ -157,13 +168,12 @@ private ImmutableMap collectData(List ena } } - private TreeBasedTable collectAggregatedData(List enabledComponents) { - final var now = LocalDateTime.now(this.parent.componentManager.getClock()); + private TreeBasedTable collectAggregatedData(ZonedDateTime now, + List enabledComponents) { final var endTime = now.truncatedTo(DurationUnit.ofMinutes(AGGREGATION_MINUTES)); final var startTime = endTime.minusMinutes(AGGREGATION_MINUTES); - final var timestamp = Instant.now().truncatedTo(DurationUnit.ofMinutes(AGGREGATION_MINUTES)) // - .minus(AGGREGATION_MINUTES, ChronoUnit.MINUTES); + final var timestamp = startTime.toInstant(); if (this.lastSendAggregatedDataTimestamp == null) { this.lastSendAggregatedDataTimestamp = timestamp; return null; @@ -189,14 +199,15 @@ private TreeBasedTable collectAggregatedData(List e.getKey().isBefore(endTime)) // + .filter(e -> e.getKey().isBefore(endTime.toLocalDateTime())) // .filter(e -> e.getValue().isDefined()).map(e -> e.getValue().get()) // .collect(aggregateCollector(channel.channelDoc().getUnit().isCumulated(), // channel.getType())); @@ -204,7 +215,7 @@ private TreeBasedTable collectAggregatedData(List { diff --git a/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/WsData.java b/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/WsData.java deleted file mode 100644 index 2dfdd355c5b..00000000000 --- a/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/WsData.java +++ /dev/null @@ -1,16 +0,0 @@ -package io.openems.edge.controller.api.backend; - -import org.java_websocket.WebSocket; - -public class WsData extends io.openems.common.websocket.WsData { - - public WsData(WebSocket ws) { - super(ws); - } - - @Override - public String toString() { - return "BackendApi.WsData []"; - } - -} diff --git a/io.openems.edge.controller.api.backend/test/io/openems/edge/controller/api/backend/ControllerApiBackendImplTest.java b/io.openems.edge.controller.api.backend/test/io/openems/edge/controller/api/backend/ControllerApiBackendImplTest.java index 9d594615157..d99ef35e3bf 100644 --- a/io.openems.edge.controller.api.backend/test/io/openems/edge/controller/api/backend/ControllerApiBackendImplTest.java +++ b/io.openems.edge.controller.api.backend/test/io/openems/edge/controller/api/backend/ControllerApiBackendImplTest.java @@ -17,8 +17,6 @@ public class ControllerApiBackendImplTest { - private static final String CTRL_ID = "ctrl0"; - @Test public void test() throws Exception { @@ -44,7 +42,7 @@ public void test() throws Exception { .addReference("oem", new DummyOpenemsEdgeOem()) // .addComponent(new DummySum()) // .activate(MyConfig.create() // - .setId(CTRL_ID) // + .setId("ctrl0") // .setUri("ws://localhost:" + port) // .setApikey("12345") // .setProxyType(Type.DIRECT) // diff --git a/io.openems.edge.controller.api.backend/test/io/openems/edge/controller/api/backend/DummyBackendOnRequestFactory.java b/io.openems.edge.controller.api.backend/test/io/openems/edge/controller/api/backend/DummyBackendOnRequestFactory.java index 05aed052db3..bb10ff7fd07 100644 --- a/io.openems.edge.controller.api.backend/test/io/openems/edge/controller/api/backend/DummyBackendOnRequestFactory.java +++ b/io.openems.edge.controller.api.backend/test/io/openems/edge/controller/api/backend/DummyBackendOnRequestFactory.java @@ -1,21 +1,20 @@ package io.openems.edge.controller.api.backend; -import java.lang.reflect.InvocationTargetException; +import static io.openems.common.utils.ReflectionUtils.setAttributeViaReflection; import org.osgi.framework.ServiceReference; import org.osgi.service.component.ComponentServiceObjects; -import io.openems.common.utils.ReflectionUtils; +import io.openems.common.utils.ReflectionUtils.ReflectionException; import io.openems.edge.controller.api.backend.handler.BindingRoutesJsonApiHandler; import io.openems.edge.controller.api.backend.handler.RootRequestHandler; import io.openems.edge.controller.api.common.handler.RoutesJsonApiHandler; public class DummyBackendOnRequestFactory extends BackendOnRequest.Factory { - public DummyBackendOnRequestFactory() - throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { + public DummyBackendOnRequestFactory() throws ReflectionException { super(); - ReflectionUtils.setAttribute(BackendOnRequest.Factory.class, this, "cso", new DummyBackendOnRequestCso()); + setAttributeViaReflection(this, "cso", new DummyBackendOnRequestCso()); } private static class DummyBackendOnRequestCso implements ComponentServiceObjects { diff --git a/io.openems.edge.controller.api.backend/test/io/openems/edge/controller/api/backend/DummyResendHistoricDataWorkerFactory.java b/io.openems.edge.controller.api.backend/test/io/openems/edge/controller/api/backend/DummyResendHistoricDataWorkerFactory.java index 91aff08460e..55e5e7f439c 100644 --- a/io.openems.edge.controller.api.backend/test/io/openems/edge/controller/api/backend/DummyResendHistoricDataWorkerFactory.java +++ b/io.openems.edge.controller.api.backend/test/io/openems/edge/controller/api/backend/DummyResendHistoricDataWorkerFactory.java @@ -1,19 +1,17 @@ package io.openems.edge.controller.api.backend; -import java.lang.reflect.InvocationTargetException; +import static io.openems.common.utils.ReflectionUtils.setAttributeViaReflection; import org.osgi.framework.ServiceReference; import org.osgi.service.component.ComponentServiceObjects; -import io.openems.common.utils.ReflectionUtils; +import io.openems.common.utils.ReflectionUtils.ReflectionException; public class DummyResendHistoricDataWorkerFactory extends ResendHistoricDataWorkerFactory { - public DummyResendHistoricDataWorkerFactory() - throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { + public DummyResendHistoricDataWorkerFactory() throws ReflectionException { super(); - ReflectionUtils.setAttribute(ResendHistoricDataWorkerFactory.class, this, "cso", - new DummyResendHistoricDataWorkerCso()); + setAttributeViaReflection(this, "cso", new DummyResendHistoricDataWorkerCso()); } private static class DummyResendHistoricDataWorkerCso implements ComponentServiceObjects { diff --git a/io.openems.edge.controller.api.common/.classpath b/io.openems.edge.controller.api.common/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.controller.api.common/.classpath +++ b/io.openems.edge.controller.api.common/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.controller.api.common/src/io/openems/edge/controller/api/common/Status.java b/io.openems.edge.controller.api.common/src/io/openems/edge/controller/api/common/Status.java index 14c18bde3e8..27edce1e31c 100644 --- a/io.openems.edge.controller.api.common/src/io/openems/edge/controller/api/common/Status.java +++ b/io.openems.edge.controller.api.common/src/io/openems/edge/controller/api/common/Status.java @@ -3,14 +3,10 @@ import io.openems.common.types.OptionsEnum; public enum Status implements OptionsEnum { - ACTIVE(0, "Active"), // - INACTIVE(1, "Inactive"), // - ERROR(2, "Error"); // - private final int value; private final String name; @@ -33,5 +29,4 @@ public String getName() { public OptionsEnum getUndefined() { return INACTIVE; } - } diff --git a/io.openems.edge.controller.api.common/src/io/openems/edge/controller/api/common/WriteObject.java b/io.openems.edge.controller.api.common/src/io/openems/edge/controller/api/common/WriteObject.java index 02c4533867d..1b5b4b9fcd3 100644 --- a/io.openems.edge.controller.api.common/src/io/openems/edge/controller/api/common/WriteObject.java +++ b/io.openems.edge.controller.api.common/src/io/openems/edge/controller/api/common/WriteObject.java @@ -118,7 +118,7 @@ public void notifyTimeout() { * @return the value as String */ public abstract String valueToString(); - + /** * Gets the value of the current object. * diff --git a/io.openems.edge.controller.api.common/src/io/openems/edge/controller/api/common/handler/QueryRequestHandler.java b/io.openems.edge.controller.api.common/src/io/openems/edge/controller/api/common/handler/QueryRequestHandler.java index 9bed397860b..a21ac88bb8b 100644 --- a/io.openems.edge.controller.api.common/src/io/openems/edge/controller/api/common/handler/QueryRequestHandler.java +++ b/io.openems.edge.controller.api.common/src/io/openems/edge/controller/api/common/handler/QueryRequestHandler.java @@ -1,11 +1,18 @@ package io.openems.edge.controller.api.common.handler; +import static java.time.temporal.ChronoUnit.MINUTES; + +import java.io.IOException; +import java.util.Collections; +import java.util.TreeSet; + import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; import org.osgi.service.component.annotations.ReferenceCardinality; import org.osgi.service.component.annotations.ReferencePolicy; import org.osgi.service.component.annotations.ReferencePolicyOption; +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.exceptions.OpenemsException; import io.openems.common.jsonrpc.request.QueryHistoricTimeseriesDataRequest; import io.openems.common.jsonrpc.request.QueryHistoricTimeseriesEnergyPerPeriodRequest; @@ -14,6 +21,13 @@ import io.openems.common.jsonrpc.response.QueryHistoricTimeseriesDataResponse; import io.openems.common.jsonrpc.response.QueryHistoricTimeseriesEnergyPerPeriodResponse; import io.openems.common.jsonrpc.response.QueryHistoricTimeseriesEnergyResponse; +import io.openems.common.jsonrpc.response.QueryHistoricTimeseriesExportXlsxResponse; +import io.openems.common.session.Language; +import io.openems.common.timedata.Resolution; +import io.openems.common.timedata.XlsxExportDetailData.XlsxExportDataEntry.HistoricTimedataSaveType; +import io.openems.common.timedata.XlsxExportUtil; +import io.openems.common.types.ChannelAddress; +import io.openems.edge.common.component.ComponentManager; import io.openems.edge.common.jsonapi.EdgeKeys; import io.openems.edge.common.jsonapi.JsonApi; import io.openems.edge.common.jsonapi.JsonApiBuilder; @@ -29,6 +43,9 @@ public class QueryRequestHandler implements JsonApi { ) private volatile Timedata timedata; + @Reference + private ComponentManager componentManager; + @Override public void buildJsonApiRoutes(JsonApiBuilder builder) { builder.handleRequest(QueryHistoricTimeseriesDataRequest.METHOD, call -> { @@ -54,14 +71,39 @@ public void buildJsonApiRoutes(JsonApiBuilder builder) { request.getFromDate(), request.getToDate(), request.getChannels(), request.getResolution()); return new QueryHistoricTimeseriesEnergyPerPeriodResponse(request.getId(), data); }); - builder.handleRequest(QueryHistoricTimeseriesExportXlxsRequest.METHOD, call -> { final var request = QueryHistoricTimeseriesExportXlxsRequest.from(call.getRequest()); - return this.getTimedata().handleQueryHistoricTimeseriesExportXlxsRequest(null /* ignore Edge-ID */, request, + return this.handleQueryHistoricTimeseriesExportXlxsRequest(request, call.get(EdgeKeys.USER_KEY).getLanguage()); }); } + private QueryHistoricTimeseriesExportXlsxResponse handleQueryHistoricTimeseriesExportXlxsRequest( + QueryHistoricTimeseriesExportXlxsRequest request, Language language) throws OpenemsNamedException { + final var powerChannels = new TreeSet(QueryHistoricTimeseriesExportXlsxResponse.POWER_CHANNELS); + final var energyChannels = new TreeSet( + QueryHistoricTimeseriesExportXlsxResponse.ENERGY_CHANNELS); + final var detailData = XlsxExportUtil.getDetailData(this.componentManager.getEdgeConfig()); + final var channelsByType = detailData.getChannelsBySaveType(); + powerChannels.addAll(channelsByType.getOrDefault(HistoricTimedataSaveType.POWER, Collections.emptyList())); + energyChannels.addAll(channelsByType.getOrDefault(HistoricTimedataSaveType.ENERGY, Collections.emptyList())); + var powerData = this.timedata.queryHistoricData(null, request.getFromDate(), request.getToDate(), powerChannels, + new Resolution(15, MINUTES)); + + var energyData = this.timedata.queryHistoricEnergy(null, request.getFromDate(), request.getToDate(), + energyChannels); + if (powerData == null || energyData == null) { + return null; + } + try { + return new QueryHistoricTimeseriesExportXlsxResponse(request.getId(), null, request.getFromDate(), + request.getToDate(), powerData, energyData, language, detailData); + + } catch (IOException e) { + throw new OpenemsException("QueryHistoricTimeseriesExportXlxsRequest failed: " + e.getMessage()); + } + } + private final Timedata getTimedata() throws OpenemsException { final var currentTimedata = this.timedata; if (currentTimedata == null) { diff --git a/io.openems.edge.controller.api.modbus/.classpath b/io.openems.edge.controller.api.modbus/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.controller.api.modbus/.classpath +++ b/io.openems.edge.controller.api.modbus/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.controller.api.modbus/bnd.bnd b/io.openems.edge.controller.api.modbus/bnd.bnd index 420b544a143..87fd5d5a4a8 100644 --- a/io.openems.edge.controller.api.modbus/bnd.bnd +++ b/io.openems.edge.controller.api.modbus/bnd.bnd @@ -10,6 +10,7 @@ Bundle-Version: 1.0.0.${tstamp} io.openems.edge.common,\ io.openems.edge.controller.api,\ io.openems.edge.controller.api.common,\ + io.openems.edge.ess.api,\ io.openems.edge.timedata.api,\ io.openems.wrapper.fastexcel diff --git a/io.openems.edge.controller.api.modbus/src/io/openems/edge/controller/api/modbus/AbstractModbusTcpApi.java b/io.openems.edge.controller.api.modbus/src/io/openems/edge/controller/api/modbus/AbstractModbusTcpApi.java index 52e5f67deae..7aff8bc95ec 100644 --- a/io.openems.edge.controller.api.modbus/src/io/openems/edge/controller/api/modbus/AbstractModbusTcpApi.java +++ b/io.openems.edge.controller.api.modbus/src/io/openems/edge/controller/api/modbus/AbstractModbusTcpApi.java @@ -87,15 +87,15 @@ protected synchronized void addComponent(OpenemsComponent component) { protected abstract Consumer, WriteObject>> handleWrites(); protected abstract void setOverrideStatus(Status status); - + protected abstract Runnable handleTimeouts(); protected synchronized void removeComponent(OpenemsComponent component) { + this._components.remove(component); if (this.invalidComponents.remove(component)) { if (this.invalidComponents.isEmpty()) { this._setComponentNoModbusApiFault(false); } - this._components.remove(component); return; } this.updateComponents(); @@ -231,8 +231,9 @@ protected void forever() { this.slave.addProcessImage(UNIT_ID, AbstractModbusTcpApi.this.processImage); if (isEnabled()) { this.slave.open(); - AbstractModbusTcpApi.this.logInfo(this.log, AbstractModbusTcpApi.this.implementationName - + " started on port [" + port + "] with UnitId [" + AbstractModbusTcpApi.UNIT_ID + "]."); + AbstractModbusTcpApi.this.logInfo(this.log, + AbstractModbusTcpApi.this.implementationName + " started " // + + "on port [" + port + "] with UnitId [" + AbstractModbusTcpApi.UNIT_ID + "]."); } } catch (ModbusException e) { ModbusSlaveFactory.close(); @@ -274,6 +275,7 @@ protected int getCycleTime() { */ private void initializeModbusRecords(Meta metaComponent, String[] componentIds) { // Add generic header + this.records.clear(); this.records.put(0, new ModbusRecordUint16Hash(0, "OpenEMS")); var nextAddress = 1; @@ -492,21 +494,20 @@ protected ModbusSlave getPossiblyDisabledComponent(String componentId) { public static record ConfigRecord(String id, String alias, boolean enabled, Meta metaComponent, String[] componentIds, int apiTimeout, int port, int maxConcurrentConnections) { - + @Override public boolean equals(Object other) { - - if (this == other) { - return true; - } - if (other == null) { - return false; - } - if (!(other instanceof ConfigRecord)) { - return false; - } + if (this == other) { + return true; + } + if (other == null) { + return false; + } + if (!(other instanceof ConfigRecord)) { + return false; + } ConfigRecord config = (ConfigRecord) other; - + if (config.id.equals(this.id) && config.alias.equals(this.alias) // && config.enabled == this.enabled && config.metaComponent.equals(this.metaComponent) // && Arrays.equals(config.componentIds, this.componentIds) // @@ -514,14 +515,10 @@ public boolean equals(Object other) { && config.maxConcurrentConnections == this.maxConcurrentConnections) { return true; } - return false; - } } - ; - /** * Format a given channelAddress to a ChannelId. * @@ -529,6 +526,6 @@ public boolean equals(Object other) { * @return component_channelId as String */ public static String formatChannelName(WriteChannel channel) { - return channel.getComponent().alias() + "_" + channel.channelId().name(); + return channel.getComponent().id() + "_" + channel.channelId().name(); } } diff --git a/io.openems.edge.controller.api.modbus/src/io/openems/edge/controller/api/modbus/jsonrpc/GetModbusProtocolExportXlsxResponse.java b/io.openems.edge.controller.api.modbus/src/io/openems/edge/controller/api/modbus/jsonrpc/GetModbusProtocolExportXlsxResponse.java index 21f2447965c..9ddab6e1a8a 100644 --- a/io.openems.edge.controller.api.modbus/src/io/openems/edge/controller/api/modbus/jsonrpc/GetModbusProtocolExportXlsxResponse.java +++ b/io.openems.edge.controller.api.modbus/src/io/openems/edge/controller/api/modbus/jsonrpc/GetModbusProtocolExportXlsxResponse.java @@ -19,13 +19,12 @@ import io.openems.edge.common.modbusslave.ModbusRecordString16; import io.openems.edge.common.modbusslave.ModbusRecordUint16; import io.openems.edge.common.modbusslave.ModbusRecordUint32; +import io.openems.edge.common.modbusslave.ModbusRecordUint64; import io.openems.edge.common.modbusslave.ModbusType; /** * Represents a JSON-RPC Response for 'getModbusProtocolExportXlsx'. * - *

- * *

  * {
  *   "jsonrpc": "2.0",
@@ -148,25 +147,14 @@ private static void addUndefinedSheet(Workbook wb) {
 
 		var nextRow = 2;
 		for (ModbusType modbusType : ModbusType.values()) {
-			byte[] value = {};
-			switch (modbusType) {
-			case FLOAT32:
-				value = ModbusRecordFloat32.UNDEFINED_VALUE;
-				break;
-			case FLOAT64:
-				value = ModbusRecordFloat64.UNDEFINED_VALUE;
-				break;
-			case STRING16:
-				value = ModbusRecordString16.UNDEFINED_VALUE;
-				break;
-			case ENUM16:
-			case UINT16:
-				value = ModbusRecordUint16.UNDEFINED_VALUE;
-				break;
-			case UINT32:
-				value = ModbusRecordUint32.UNDEFINED_VALUE;
-				break;
-			}
+			byte[] value = switch (modbusType) {
+			case FLOAT32 -> ModbusRecordFloat32.UNDEFINED_VALUE;
+			case FLOAT64 -> ModbusRecordFloat64.UNDEFINED_VALUE;
+			case STRING16 -> ModbusRecordString16.UNDEFINED_VALUE;
+			case ENUM16, UINT16 -> ModbusRecordUint16.UNDEFINED_VALUE;
+			case UINT32 -> ModbusRecordUint32.UNDEFINED_VALUE;
+			case UINT64 -> ModbusRecordUint64.UNDEFINED_VALUE;
+			};
 			nextRow++;
 			ws.value(nextRow, 0, modbusType.toString());
 			ws.value(nextRow, 1, byteArrayToString(value));
diff --git a/io.openems.edge.controller.api.modbus/src/io/openems/edge/controller/api/modbus/jsonrpc/GetModbusProtocolResponse.java b/io.openems.edge.controller.api.modbus/src/io/openems/edge/controller/api/modbus/jsonrpc/GetModbusProtocolResponse.java
index a46c4436da7..7f53ac28cef 100644
--- a/io.openems.edge.controller.api.modbus/src/io/openems/edge/controller/api/modbus/jsonrpc/GetModbusProtocolResponse.java
+++ b/io.openems.edge.controller.api.modbus/src/io/openems/edge/controller/api/modbus/jsonrpc/GetModbusProtocolResponse.java
@@ -15,8 +15,6 @@
 /**
  * Wraps a JSON-RPC Response to "getModbusProtocol" Request.
  *
- * 

- * *

  * {
  *   "jsonrpc": "2.0",
diff --git a/io.openems.edge.controller.api.modbus/src/io/openems/edge/controller/api/modbus/readonly/ControllerApiModbusTcpReadOnlyImpl.java b/io.openems.edge.controller.api.modbus/src/io/openems/edge/controller/api/modbus/readonly/ControllerApiModbusTcpReadOnlyImpl.java
index 01c6e47638a..6f7e36683f8 100644
--- a/io.openems.edge.controller.api.modbus/src/io/openems/edge/controller/api/modbus/readonly/ControllerApiModbusTcpReadOnlyImpl.java
+++ b/io.openems.edge.controller.api.modbus/src/io/openems/edge/controller/api/modbus/readonly/ControllerApiModbusTcpReadOnlyImpl.java
@@ -66,8 +66,8 @@ public ControllerApiModbusTcpReadOnlyImpl() {
 	@Activate
 	private void activate(ComponentContext context, Config config) throws ModbusException, OpenemsException {
 		super.activate(context, this.cm,
-				new ConfigRecord(config.id(), config.alias(), config.enabled(),this.metaComponent, config.component_ids(), 0 /* no timeout */, config.port(),
-						config.maxConcurrentConnections()));
+				new ConfigRecord(config.id(), config.alias(), config.enabled(), this.metaComponent,
+						config.component_ids(), 0 /* no timeout */, config.port(), config.maxConcurrentConnections()));
 	}
 
 	@Override
@@ -83,7 +83,8 @@ protected AccessMode getAccessMode() {
 
 	@Override
 	protected Consumer, WriteObject>> handleWrites() {
-		return entry -> { };
+		return entry -> {
+		};
 	}
 
 	@Override
@@ -92,6 +93,7 @@ protected void setOverrideStatus(Status status) {
 
 	@Override
 	protected Runnable handleTimeouts() {
-		return () -> { };
+		return () -> {
+		};
 	}
 }
diff --git a/io.openems.edge.controller.api.modbus/src/io/openems/edge/controller/api/modbus/readwrite/Config.java b/io.openems.edge.controller.api.modbus/src/io/openems/edge/controller/api/modbus/readwrite/Config.java
index 0614487de2f..9edd49da1bd 100644
--- a/io.openems.edge.controller.api.modbus/src/io/openems/edge/controller/api/modbus/readwrite/Config.java
+++ b/io.openems.edge.controller.api.modbus/src/io/openems/edge/controller/api/modbus/readwrite/Config.java
@@ -27,7 +27,7 @@
 
 	// TODO: Currently unused
 	@AttributeDefinition(name = "Read Channel-IDs", description = "Contains the channelnames of all read channels.")
-	String[] readChannels() default {  };
+	String[] readChannels() default {};
 
 	@AttributeDefinition(name = "Write Channel-IDs", description = "Contains the channelnames of all overridden channels.")
 	String[] writeChannels();
diff --git a/io.openems.edge.controller.api.modbus/src/io/openems/edge/controller/api/modbus/readwrite/ControllerApiModbusTcpReadWrite.java b/io.openems.edge.controller.api.modbus/src/io/openems/edge/controller/api/modbus/readwrite/ControllerApiModbusTcpReadWrite.java
index 8a18a1aada2..f39092ee732 100644
--- a/io.openems.edge.controller.api.modbus/src/io/openems/edge/controller/api/modbus/readwrite/ControllerApiModbusTcpReadWrite.java
+++ b/io.openems.edge.controller.api.modbus/src/io/openems/edge/controller/api/modbus/readwrite/ControllerApiModbusTcpReadWrite.java
@@ -16,18 +16,18 @@
 public interface ControllerApiModbusTcpReadWrite extends OpenemsComponent {
 
 	public enum ChannelId implements io.openems.edge.common.channel.ChannelId {
-		
+
 		OVERRIDE_STATUS(Doc.of(Status.values()) //
 				.persistencePriority(PersistencePriority.HIGH)), //
-		
+
 		CUMULATED_ACTIVE_TIME(Doc.of(LONG)//
 				.unit(CUMULATED_SECONDS) //
 				.persistencePriority(HIGH)), //
-		
+
 		CUMULATED_INACTIVE_TIME(Doc.of(LONG)//
 				.unit(CUMULATED_SECONDS) //
 				.persistencePriority(HIGH)), //
-		
+
 		API_WORKER_LOG(Doc.of(OpenemsType.STRING) //
 				.text("Logs Write-Commands via ApiWorker")); //
 
@@ -71,7 +71,8 @@ public default Status getOverrideStatus() {
 	}
 
 	/**
-	 * Internal method to set the 'nextValue' on {@link ChannelId#OVERRIDE_STATUS} Channel.
+	 * Internal method to set the 'nextValue' on {@link ChannelId#OVERRIDE_STATUS}
+	 * Channel.
 	 *
 	 * @param value the next value
 	 */
diff --git a/io.openems.edge.controller.api.modbus/src/io/openems/edge/controller/api/modbus/readwrite/ControllerApiModbusTcpReadWriteImpl.java b/io.openems.edge.controller.api.modbus/src/io/openems/edge/controller/api/modbus/readwrite/ControllerApiModbusTcpReadWriteImpl.java
index 74055d005b1..6f1a47ad49c 100644
--- a/io.openems.edge.controller.api.modbus/src/io/openems/edge/controller/api/modbus/readwrite/ControllerApiModbusTcpReadWriteImpl.java
+++ b/io.openems.edge.controller.api.modbus/src/io/openems/edge/controller/api/modbus/readwrite/ControllerApiModbusTcpReadWriteImpl.java
@@ -1,5 +1,8 @@
 package io.openems.edge.controller.api.modbus.readwrite;
 
+import static io.openems.edge.common.channel.ChannelId.channelIdCamelToUpper;
+import static io.openems.edge.common.channel.ChannelId.channelIdUpperToCamel;
+
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -27,20 +30,26 @@
 
 import io.openems.common.channel.AccessMode;
 import io.openems.common.channel.PersistencePriority;
+import io.openems.common.channel.Unit;
 import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
 import io.openems.common.exceptions.OpenemsException;
-import io.openems.common.types.OpenemsType;
 import io.openems.edge.common.channel.ChannelId.ChannelIdImpl;
 import io.openems.edge.common.channel.Doc;
+import io.openems.edge.common.channel.IntegerReadChannel;
 import io.openems.edge.common.channel.WriteChannel;
 import io.openems.edge.common.component.OpenemsComponent;
 import io.openems.edge.common.jsonapi.ComponentJsonApi;
 import io.openems.edge.common.meta.Meta;
+import io.openems.edge.common.modbusslave.ModbusSlave;
+import io.openems.edge.common.modbusslave.ModbusSlaveNatureTable;
+import io.openems.edge.common.modbusslave.ModbusSlaveTable;
+import io.openems.edge.common.modbusslave.ModbusType;
 import io.openems.edge.controller.api.Controller;
 import io.openems.edge.controller.api.common.Status;
 import io.openems.edge.controller.api.common.WriteObject;
 import io.openems.edge.controller.api.modbus.AbstractModbusTcpApi;
 import io.openems.edge.controller.api.modbus.ModbusTcpApi;
+import io.openems.edge.ess.api.ManagedSymmetricEss;
 import io.openems.edge.timedata.api.Timedata;
 import io.openems.edge.timedata.api.TimedataProvider;
 import io.openems.edge.timedata.api.utils.CalculateActiveTime;
@@ -52,20 +61,23 @@
 		configurationPolicy = ConfigurationPolicy.REQUIRE //
 )
 public class ControllerApiModbusTcpReadWriteImpl extends AbstractModbusTcpApi
-		implements ControllerApiModbusTcpReadWrite, ModbusTcpApi, Controller, OpenemsComponent, ComponentJsonApi, TimedataProvider {
+		implements ControllerApiModbusTcpReadWrite, ModbusTcpApi, Controller, OpenemsComponent, ComponentJsonApi,
+		TimedataProvider, ModbusSlave {
 
 	private final Logger log = LoggerFactory.getLogger(ControllerApiModbusTcpReadWriteImpl.class);
-	
+
 	private final CalculateActiveTime calculateCumulatedActiveTime = new CalculateActiveTime(this,
 			ControllerApiModbusTcpReadWrite.ChannelId.CUMULATED_ACTIVE_TIME);
-	
+
 	private final CalculateActiveTime calculateCumulatedInactiveTime = new CalculateActiveTime(this,
 			ControllerApiModbusTcpReadWrite.ChannelId.CUMULATED_INACTIVE_TIME);
-	
+
 	private List writeChannels;
-	
+
+	private List components = new ArrayList<>();
+
 	private boolean isActive = false;
-	
+
 	@Reference(policy = ReferencePolicy.DYNAMIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.OPTIONAL)
 	private volatile Timedata timedata = null;
 
@@ -83,10 +95,12 @@ public class ControllerApiModbusTcpReadWriteImpl extends AbstractModbusTcpApi
 	)
 	protected void addComponent(OpenemsComponent component) {
 		super.addComponent(component);
+		this.components.add(component);
 	}
 
 	protected void removeComponent(OpenemsComponent component) {
 		super.removeComponent(component);
+		this.components.remove(component);
 	}
 
 	public ControllerApiModbusTcpReadWriteImpl() {
@@ -105,7 +119,6 @@ private void activate(ComponentContext context, Config config) throws ModbusExce
 				new ConfigRecord(config.id(), config.alias(), config.enabled(), this.metaComponent,
 						config.component_ids(), config.apiTimeout(), config.port(), config.maxConcurrentConnections()));
 		this.applyConfig(config);
-		this.handleTimeDataChannels();
 	}
 
 	@Modified
@@ -114,7 +127,6 @@ private void modified(ComponentContext context, Config config) throws OpenemsNam
 				new ConfigRecord(config.id(), config.alias(), config.enabled(), this.metaComponent,
 						config.component_ids(), config.apiTimeout(), config.port(), config.maxConcurrentConnections()));
 		this.applyConfig(config);
-		this.handleTimeDataChannels();
 	}
 
 	private void applyConfig(Config config) {
@@ -126,12 +138,12 @@ private void applyConfig(Config config) {
 	protected void deactivate() {
 		super.deactivate();
 	}
-	
+
 	@Override
 	public void run() throws OpenemsNamedException {
 		this.isActive = false;
 		super.run();
-		
+
 		this.calculateCumulatedActiveTime.update(this.isActive);
 		this.calculateCumulatedInactiveTime.update(!this.isActive);
 	}
@@ -166,7 +178,17 @@ private void configUpdate(String targetProperty, String requiredValue) {
 			this.logError(this.log, "ERROR: " + e.getMessage());
 		}
 	}
-	
+
+	protected static String getChannelNameUpper(String componentId,
+			io.openems.edge.common.channel.ChannelId channelId) {
+		return channelIdCamelToUpper(componentId) + "_" + channelId.name();
+	}
+
+	protected static String getChannelNameCamel(String componentId,
+			io.openems.edge.common.channel.ChannelId channelId) {
+		return channelIdUpperToCamel(getChannelNameUpper(componentId, channelId));
+	}
+
 	@Override
 	protected Consumer, WriteObject>> handleWrites() {
 		return entry -> {
@@ -174,15 +196,19 @@ protected Consumer, WriteObject>> handleWrites() {
 			WriteChannel channel = entry.getKey();
 			var writeObject = entry.getValue();
 
-			String channelName = formatChannelName(channel);
-			var currentChannel = new ChannelIdImpl(channelName,
-					Doc.of(channel.getType()).persistencePriority(PersistencePriority.HIGH));
-			if (!channels().stream().anyMatch(p -> p.channelId().name().equals(currentChannel.name()))) {
-				addChannel(currentChannel).setNextValue(writeObject.value());
-			} else {
-				channel(currentChannel).setNextValue(writeObject.value());
+			var channelNameCamel = getChannelNameCamel(channel.getComponent().id(), channel.channelId());
+
+			@SuppressWarnings("deprecation")
+			var logChannel = this._channel(channelNameCamel);
+			if (logChannel == null) {
+				var channelNameUpper = getChannelNameUpper(channel.getComponent().id(), channel.channelId());
+				var currentChannel = new ChannelIdImpl(channelNameUpper,
+						Doc.of(channel.getType()).persistencePriority(PersistencePriority.HIGH));
+				addChannel(currentChannel);
+				logChannel = channel(currentChannel);
 			}
-			this.configUpdate("writeChannels", channel(currentChannel).channelId().id());
+			logChannel.setNextValue(writeObject.value());
+			this.configUpdate("writeChannels", logChannel.channelId().id());
 		};
 	}
 
@@ -205,23 +231,28 @@ protected Runnable handleTimeouts() {
 	public Timedata getTimedata() {
 		return this.timedata;
 	}
-	
-	/**
-	 * Checks, if timedata channels are already set.
-	 * If not, they will be created and added to current channels.
-	 */
-	protected void handleTimeDataChannels() {
-		var activeTimeChannel = new ChannelIdImpl("CUMULATED_ACTIVE_TIME", //
-				Doc.of(OpenemsType.DOUBLE).persistencePriority(PersistencePriority.HIGH));
-		var inactiveTimeChannel = new ChannelIdImpl("CUMULATED_INACTIVE_TIME", //
-				Doc.of(OpenemsType.DOUBLE).persistencePriority(PersistencePriority.HIGH));
-
-		List timeChannels = Arrays.asList(activeTimeChannel, inactiveTimeChannel);
-		timeChannels.forEach(channel -> {
-		    if (channels().stream().noneMatch(ch -> ch.channelId().id().equals(channel.id()))) {
-		        addChannel(channel);
-		    }
-		});
+
+	protected Integer getChannelValue(String componentId, io.openems.edge.common.channel.ChannelId channelId) {
+		@SuppressWarnings("deprecation")
+		var channel = this._channel(getChannelNameCamel(componentId, channelId));
+		if (channel == null) {
+			return null;
+		}
+		return ((IntegerReadChannel) channel).value().get();
+	}
+
+	@Override
+	public ModbusSlaveTable getModbusSlaveTable(AccessMode accessMode) {
+		return new ModbusSlaveTable(//
+				OpenemsComponent.getModbusSlaveNatureTable(AccessMode.READ_ONLY),
+				ModbusSlaveNatureTable.of(ControllerApiModbusTcpReadWriteImpl.class, AccessMode.READ_ONLY, 300)
+						.cycleValue(0, this.id() + "/  Ess0ActivePowerLimit", Unit.WATT, "", ModbusType.FLOAT32,
+								t -> this.getChannelValue("ess0",
+										ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_EQUALS))
+						.cycleValue(2, this.id() + "/Ess0ReactivePowerLimit", Unit.WATT, "", ModbusType.FLOAT32,
+								t -> this.getChannelValue("ess0",
+										ManagedSymmetricEss.ChannelId.SET_REACTIVE_POWER_EQUALS))
+						.build());
 	}
 
 }
diff --git a/io.openems.edge.controller.api.modbus/test/io/openems/edge/controller/api/modbus/readonly/ControllerApiModbusTcpReadOnlyImplTest.java b/io.openems.edge.controller.api.modbus/test/io/openems/edge/controller/api/modbus/readonly/ControllerApiModbusTcpReadOnlyImplTest.java
index a39ef6c3555..6248a9b4e41 100644
--- a/io.openems.edge.controller.api.modbus/test/io/openems/edge/controller/api/modbus/readonly/ControllerApiModbusTcpReadOnlyImplTest.java
+++ b/io.openems.edge.controller.api.modbus/test/io/openems/edge/controller/api/modbus/readonly/ControllerApiModbusTcpReadOnlyImplTest.java
@@ -1,28 +1,27 @@
 package io.openems.edge.controller.api.modbus.readonly;
 
+import static io.openems.edge.controller.api.modbus.AbstractModbusTcpApi.DEFAULT_PORT;
+
 import org.junit.Test;
 
 import io.openems.edge.common.test.AbstractComponentTest.TestCase;
 import io.openems.edge.common.test.DummyConfigurationAdmin;
-import io.openems.edge.controller.api.modbus.AbstractModbusTcpApi;
 import io.openems.edge.controller.test.ControllerTest;
 
 public class ControllerApiModbusTcpReadOnlyImplTest {
 
-	private static final String CTRL_ID = "ctrl0";
-
 	@Test
 	public void test() throws Exception {
 		new ControllerTest(new ControllerApiModbusTcpReadOnlyImpl()) //
 				.addReference("cm", new DummyConfigurationAdmin()) //
 				.activate(MyConfig.create() //
-						.setId(CTRL_ID) //
+						.setId("ctrl0") //
 						.setEnabled(false) // do not actually start server
 						.setComponentIds() //
 						.setMaxConcurrentConnections(5) //
-						.setPort(AbstractModbusTcpApi.DEFAULT_PORT) //
+						.setPort(DEFAULT_PORT) //
 						.build()) //
 				.next(new TestCase()) //
-		;
+				.deactivate();
 	}
 }
diff --git a/io.openems.edge.controller.api.modbus/test/io/openems/edge/controller/api/modbus/readwrite/ControllerApiModbusTcpReadWriteImplTest.java b/io.openems.edge.controller.api.modbus/test/io/openems/edge/controller/api/modbus/readwrite/ControllerApiModbusTcpReadWriteImplTest.java
index 58ea9ca90c5..f87124a5295 100644
--- a/io.openems.edge.controller.api.modbus/test/io/openems/edge/controller/api/modbus/readwrite/ControllerApiModbusTcpReadWriteImplTest.java
+++ b/io.openems.edge.controller.api.modbus/test/io/openems/edge/controller/api/modbus/readwrite/ControllerApiModbusTcpReadWriteImplTest.java
@@ -1,43 +1,48 @@
 package io.openems.edge.controller.api.modbus.readwrite;
 
+import static io.openems.edge.controller.api.modbus.AbstractModbusTcpApi.DEFAULT_PORT;
+import static io.openems.edge.controller.api.modbus.readwrite.ControllerApiModbusTcpReadWriteImpl.getChannelNameCamel;
+import static io.openems.edge.controller.api.modbus.readwrite.ControllerApiModbusTcpReadWriteImpl.getChannelNameUpper;
+import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_EQUALS;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+
 import org.junit.Test;
+
 import io.openems.edge.common.test.AbstractComponentTest.TestCase;
 import io.openems.edge.common.test.DummyConfigurationAdmin;
 import io.openems.edge.common.test.DummyCycle;
-import io.openems.edge.controller.api.modbus.AbstractModbusTcpApi;
 import io.openems.edge.controller.test.ControllerTest;
 
 public class ControllerApiModbusTcpReadWriteImplTest {
 
-	private static final String CTRL_ID = "ctrl0";
-
 	@Test
 	public void test() throws Exception {
 		new ControllerTest(new ControllerApiModbusTcpReadWriteImpl()) //
 				.addReference("cm", new DummyConfigurationAdmin()) //
 				.activate(MyConfig.create() //
-						.setId(CTRL_ID) //
+						.setId("ctrl0") //
 						.setEnabled(false) // do not actually start server
 						.setComponentIds() //
 						.setMaxConcurrentConnections(5) //
-						.setPort(AbstractModbusTcpApi.DEFAULT_PORT) //
+						.setPort(DEFAULT_PORT) //
 						.setApiTimeout(60) //
 						.build()) //
 				.next(new TestCase()) //
+				.deactivate();
 		;
 	}
-	
+
 	@Test
 	public void testTimedataChannels() throws Exception {
 		var controller = new ControllerApiModbusTcpReadWriteImpl(); //
 		boolean channelNotFound = controller.channels().stream().noneMatch(//
 				ch -> ch.channelId().id().equals("CumulatedActiveTime") //
-				|| ch.channelId().id().equals("CumulatedInactiveTime")); //
-	    assertFalse(channelNotFound);
+						|| ch.channelId().id().equals("CumulatedInactiveTime")); //
+		assertFalse(channelNotFound);
 	}
-	
+
 	@Test
 	public void testAddFalseComponents() throws Exception {
 		var controller = new ControllerApiModbusTcpReadWriteImpl(); //
@@ -45,4 +50,16 @@ public void testAddFalseComponents() throws Exception {
 		controller.getComponentNoModbusApiFaultChannel().nextProcessImage(); //
 		assertTrue(controller.getComponentNoModbusApiFault().get()); //
 	}
+
+	@Test
+	public void testGetChannelNameUpper() {
+		assertEquals("ESS0_SET_ACTIVE_POWER_EQUALS", getChannelNameUpper("ess0", SET_ACTIVE_POWER_EQUALS));
+		assertEquals("ESS0_SET_ACTIVE_POWER_EQUALS", getChannelNameUpper("Ess0", SET_ACTIVE_POWER_EQUALS));
+	}
+
+	@Test
+	public void testGetChannelNameCamel() {
+		assertEquals("Ess0SetActivePowerEquals", getChannelNameCamel("ess0", SET_ACTIVE_POWER_EQUALS));
+		assertEquals("Ess0SetActivePowerEquals", getChannelNameCamel("Ess0", SET_ACTIVE_POWER_EQUALS));
+	}
 }
diff --git a/io.openems.edge.controller.api.modbus/test/io/openems/edge/controller/api/modbus/readwrite/MyConfig.java b/io.openems.edge.controller.api.modbus/test/io/openems/edge/controller/api/modbus/readwrite/MyConfig.java
index 5c06b078554..1b761e3d72a 100644
--- a/io.openems.edge.controller.api.modbus/test/io/openems/edge/controller/api/modbus/readwrite/MyConfig.java
+++ b/io.openems.edge.controller.api.modbus/test/io/openems/edge/controller/api/modbus/readwrite/MyConfig.java
@@ -1,12 +1,7 @@
 package io.openems.edge.controller.api.modbus.readwrite;
 
-import java.nio.channels.Channels;
-
 import io.openems.common.test.AbstractComponentConfig;
-import io.openems.common.types.EdgeConfig.Component.Channel;
 import io.openems.common.utils.ConfigUtils;
-import io.openems.edge.common.channel.ChannelId;
-import io.openems.edge.common.channel.ChannelId.ChannelIdImpl;
 
 @SuppressWarnings("all")
 public class MyConfig extends AbstractComponentConfig implements Config {
@@ -23,12 +18,12 @@ protected static class Builder {
 
 		private Builder() {
 		}
-		
+
 		public Builder setWriteChannels(String... writeChannels) {
 			this.writeChannels = writeChannels;
 			return this;
 		}
-		
+
 		public Builder setReadChannels(String... readChannels) {
 			this.readChannels = readChannels;
 			return this;
diff --git a/io.openems.edge.controller.api.mqtt/.classpath b/io.openems.edge.controller.api.mqtt/.classpath
index bbfbdbe40e7..b4cffd0fe60 100644
--- a/io.openems.edge.controller.api.mqtt/.classpath
+++ b/io.openems.edge.controller.api.mqtt/.classpath
@@ -1,7 +1,7 @@
 
 
 	
-	
+	
 	
 	
 		
diff --git a/io.openems.edge.controller.api.mqtt/bnd.bnd b/io.openems.edge.controller.api.mqtt/bnd.bnd
index ae32a25bd42..880e35262f0 100644
--- a/io.openems.edge.controller.api.mqtt/bnd.bnd
+++ b/io.openems.edge.controller.api.mqtt/bnd.bnd
@@ -5,8 +5,8 @@ Bundle-Version: 1.0.0.${tstamp}
 
 -buildpath: \
 	${buildpath},\
-	bcpkix;version='1.70',\
-	bcprov;version='1.70',\
+	bcpkix;version='1.78.1',\
+	bcprov;version='1.78.1',\
 	io.openems.common,\
 	io.openems.edge.common,\
 	io.openems.edge.controller.api,\
diff --git a/io.openems.edge.controller.api.mqtt/test/io/openems/edge/controller/api/mqtt/ControllerApiMqttImplTest.java b/io.openems.edge.controller.api.mqtt/test/io/openems/edge/controller/api/mqtt/ControllerApiMqttImplTest.java
index 10981834b04..89486aa7d0b 100644
--- a/io.openems.edge.controller.api.mqtt/test/io/openems/edge/controller/api/mqtt/ControllerApiMqttImplTest.java
+++ b/io.openems.edge.controller.api.mqtt/test/io/openems/edge/controller/api/mqtt/ControllerApiMqttImplTest.java
@@ -1,5 +1,6 @@
 package io.openems.edge.controller.api.mqtt;
 
+import static io.openems.common.channel.PersistencePriority.VERY_LOW;
 import static io.openems.edge.controller.api.mqtt.ControllerApiMqttImpl.createTopicPrefix;
 import static org.junit.Assert.assertEquals;
 
@@ -8,7 +9,6 @@
 
 import org.junit.Test;
 
-import io.openems.common.channel.PersistencePriority;
 import io.openems.common.test.TimeLeapClock;
 import io.openems.edge.common.sum.DummySum;
 import io.openems.edge.common.test.ComponentTest;
@@ -16,8 +16,6 @@
 
 public class ControllerApiMqttImplTest {
 
-	private static final String CTRL_ID = "ctrl0";
-
 	@Test
 	public void test() throws Exception {
 		final var clock = new TimeLeapClock(Instant.ofEpochSecond(1577836800L) /* starts at 1. January 2020 00:00:00 */,
@@ -26,13 +24,13 @@ public void test() throws Exception {
 				.addReference("componentManager", new DummyComponentManager(clock)) //
 				.addComponent(new DummySum()) //
 				.activate(MyConfig.create() //
-						.setId(CTRL_ID) //
+						.setId("ctrl0") //
 						.setClientId("edge0") //
 						.setTopicPrefix("") //
 						.setUsername("guest") //
 						.setPassword("guest") //
 						.setUri("ws://localhost:1883") //
-						.setPersistencePriority(PersistencePriority.VERY_LOW) //
+						.setPersistencePriority(VERY_LOW) //
 						.setDebugMode(true) //
 						.setCertPem("") //
 						.setPrivateKeyPem("") //
diff --git a/io.openems.edge.controller.api.rest/.classpath b/io.openems.edge.controller.api.rest/.classpath
index bbfbdbe40e7..b4cffd0fe60 100644
--- a/io.openems.edge.controller.api.rest/.classpath
+++ b/io.openems.edge.controller.api.rest/.classpath
@@ -1,7 +1,7 @@
 
 
 	
-	
+	
 	
 	
 		
diff --git a/io.openems.edge.controller.api.rest/test/io/openems/edge/controller/api/rest/DummyJsonRpcRestHandlerFactory.java b/io.openems.edge.controller.api.rest/test/io/openems/edge/controller/api/rest/DummyJsonRpcRestHandlerFactory.java
index 2b99bdb06c3..927c148ca6c 100644
--- a/io.openems.edge.controller.api.rest/test/io/openems/edge/controller/api/rest/DummyJsonRpcRestHandlerFactory.java
+++ b/io.openems.edge.controller.api.rest/test/io/openems/edge/controller/api/rest/DummyJsonRpcRestHandlerFactory.java
@@ -1,21 +1,19 @@
 package io.openems.edge.controller.api.rest;
 
-import java.lang.reflect.InvocationTargetException;
+import static io.openems.common.utils.ReflectionUtils.setAttributeViaReflection;
 
 import org.osgi.framework.ServiceReference;
 import org.osgi.service.component.ComponentServiceObjects;
 
 import com.google.common.base.Supplier;
 
-import io.openems.common.utils.ReflectionUtils;
+import io.openems.common.utils.ReflectionUtils.ReflectionException;
 
 public class DummyJsonRpcRestHandlerFactory extends JsonRpcRestHandler.Factory {
 
-	public DummyJsonRpcRestHandlerFactory(Supplier factoryMethod)
-			throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
+	public DummyJsonRpcRestHandlerFactory(Supplier factoryMethod) throws ReflectionException {
 		super();
-		ReflectionUtils.setAttribute(JsonRpcRestHandler.Factory.class, this, "cso",
-				new DummyJsonRpcRestHandlerCso(factoryMethod));
+		setAttributeViaReflection(this, "cso", new DummyJsonRpcRestHandlerCso(factoryMethod));
 	}
 
 	private static class DummyJsonRpcRestHandlerCso implements ComponentServiceObjects {
diff --git a/io.openems.edge.controller.api.rest/test/io/openems/edge/controller/api/rest/readonly/ControllerApiRestReadOnlyImplTest.java b/io.openems.edge.controller.api.rest/test/io/openems/edge/controller/api/rest/readonly/ControllerApiRestReadOnlyImplTest.java
index e7bc5a974ef..6c93e76983e 100644
--- a/io.openems.edge.controller.api.rest/test/io/openems/edge/controller/api/rest/readonly/ControllerApiRestReadOnlyImplTest.java
+++ b/io.openems.edge.controller.api.rest/test/io/openems/edge/controller/api/rest/readonly/ControllerApiRestReadOnlyImplTest.java
@@ -1,33 +1,33 @@
 package io.openems.edge.controller.api.rest.readonly;
 
+import static io.openems.common.test.TestUtils.findRandomOpenPortOnAllLocalInterfaces;
+
 import org.junit.Test;
 
 import io.openems.common.exceptions.OpenemsException;
 import io.openems.edge.common.test.DummyComponentManager;
 import io.openems.edge.common.test.DummyUserService;
-import io.openems.edge.common.test.TestUtils;
 import io.openems.edge.controller.api.rest.DummyJsonRpcRestHandlerFactory;
 import io.openems.edge.controller.api.rest.JsonRpcRestHandler;
 import io.openems.edge.controller.test.ControllerTest;
 
 public class ControllerApiRestReadOnlyImplTest {
 
-	private static final String CTRL_ID = "ctrlApiRest0";
-
 	@Test
 	public void test() throws OpenemsException, Exception {
-		final var port = TestUtils.findRandomOpenPortOnAllLocalInterfaces();
+		final var port = findRandomOpenPortOnAllLocalInterfaces();
 
 		new ControllerTest(new ControllerApiRestReadOnlyImpl()) //
 				.addReference("componentManager", new DummyComponentManager()) //
 				.addReference("userService", new DummyUserService()) //
 				.addReference("restHandlerFactory", new DummyJsonRpcRestHandlerFactory(JsonRpcRestHandler::new)) //
 				.activate(MyConfig.create() //
-						.setId(CTRL_ID) //
+						.setId("ctrlApiRest0") //
 						.setEnabled(false) // do not actually start server
 						.setConnectionlimit(5) //
 						.setDebugMode(false) //
 						.setPort(port) //
-						.build());
+						.build()) //
+				.deactivate();
 	}
 }
diff --git a/io.openems.edge.controller.api.rest/test/io/openems/edge/controller/api/rest/readwrite/ControllerApiRestReadWriteImplTest.java b/io.openems.edge.controller.api.rest/test/io/openems/edge/controller/api/rest/readwrite/ControllerApiRestReadWriteImplTest.java
index 6baf712a911..29c3f530886 100644
--- a/io.openems.edge.controller.api.rest/test/io/openems/edge/controller/api/rest/readwrite/ControllerApiRestReadWriteImplTest.java
+++ b/io.openems.edge.controller.api.rest/test/io/openems/edge/controller/api/rest/readwrite/ControllerApiRestReadWriteImplTest.java
@@ -1,9 +1,12 @@
 package io.openems.edge.controller.api.rest.readwrite;
 
+import static io.openems.common.test.TestUtils.findRandomOpenPortOnAllLocalInterfaces;
+import static io.openems.common.utils.JsonUtils.getAsJsonObject;
 import static io.openems.edge.common.test.DummyUser.DUMMY_ADMIN;
 import static io.openems.edge.common.test.DummyUser.DUMMY_GUEST;
 import static io.openems.edge.common.test.DummyUser.DUMMY_INSTALLER;
 import static io.openems.edge.common.test.DummyUser.DUMMY_OWNER;
+import static io.openems.edge.controller.api.rest.readwrite.ControllerApiRestReadWrite.ChannelId.API_WORKER_LOG;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
@@ -11,7 +14,7 @@
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.net.HttpURLConnection;
-import java.net.URL;
+import java.net.URI;
 import java.util.Base64;
 
 import org.junit.Test;
@@ -27,7 +30,6 @@
 import io.openems.common.exceptions.OpenemsException;
 import io.openems.common.jsonrpc.base.JsonrpcResponseSuccess;
 import io.openems.common.jsonrpc.request.GetEdgeConfigRequest;
-import io.openems.common.types.ChannelAddress;
 import io.openems.common.types.OpenemsType;
 import io.openems.common.utils.JsonUtils;
 import io.openems.edge.common.channel.Doc;
@@ -51,12 +53,9 @@
 
 public class ControllerApiRestReadWriteImplTest {
 
-	private static final String CTRL_ID = "ctrlApiRest0";
-	private static final String DUMMY_ID = "dummy0";
-
 	@Test
 	public void test() throws OpenemsException, Exception {
-		final var port = TestUtils.findRandomOpenPortOnAllLocalInterfaces();
+		final var port = findRandomOpenPortOnAllLocalInterfaces();
 
 		final var componentManager = new DummyComponentManager();
 
@@ -81,10 +80,10 @@ public void test() throws OpenemsException, Exception {
 				.addReference("userService", new DummyUserService(//
 						DUMMY_GUEST, DUMMY_OWNER, DUMMY_INSTALLER, DUMMY_ADMIN)) //
 				.addReference("restHandlerFactory", factory) //
-				.addComponent(new DummyComponent(DUMMY_ID) //
+				.addComponent(new DummyComponent("dummy0") //
 						.withReadChannel(1234)) //
 				.activate(MyConfig.create() //
-						.setId(CTRL_ID) //
+						.setId("ctrlApiRest0") //
 						.setApiTimeout(60) //
 						.setConnectionlimit(5) //
 						.setDebugMode(false) //
@@ -98,7 +97,7 @@ public void test() throws OpenemsException, Exception {
 		var channelGet = sendGetRequest(port, DUMMY_GUEST.password, "/rest/channel/dummy0/ReadChannel");
 		assertEquals(JsonUtils.buildJsonObject() //
 				.addProperty("address", "dummy0/ReadChannel") //
-				.addProperty("type", "INTEGER") //
+				.addProperty("type", "INTEGER") // s
 				.addProperty("accessMode", "RO") //
 				.addProperty("text", "This is a Read-Channel") //
 				.addProperty("unit", "W") //
@@ -113,8 +112,8 @@ public void test() throws OpenemsException, Exception {
 		assertEquals(new JsonObject(), channelPost);
 		test //
 				.next(new TestCase() //
-						.output(new ChannelAddress("dummy0", "WriteChannel"), 4321) //
-						.output(new ChannelAddress(CTRL_ID, "ApiWorkerLog"), "dummy0/WriteChannel:4321"));
+						.output("dummy0", DummyComponent.ChannelId.WRITE_CHANNEL, 4321) //
+						.output(API_WORKER_LOG, "dummy0/WriteChannel:4321"));
 
 		// POST fails as GUEST
 		try {
@@ -132,7 +131,7 @@ public void test() throws OpenemsException, Exception {
 		// POST successful as OWNER
 		var request = new GetEdgeConfigRequest().toJsonObject();
 		JsonrpcResponseSuccess.from(//
-				JsonUtils.getAsJsonObject(//
+				getAsJsonObject(//
 						sendPostRequest(port, DUMMY_OWNER.password, "/jsonrpc", request)));
 
 		// POST fails as GUEST
@@ -158,7 +157,8 @@ private static JsonElement sendPostRequest(int port, String password, String end
 	private static JsonElement sendRequest(int port, String requestMethod, String password, String endpoint,
 			JsonObject request) throws OpenemsNamedException {
 		try {
-			var url = new URL("http://127.0.0.1:" + port + endpoint);
+			var uri = URI.create("http://127.0.0.1:" + port + endpoint);
+			var url = uri.toURL();
 			var con = (HttpURLConnection) url.openConnection();
 			con.setRequestProperty("Authorization",
 					"Basic " + new String(Base64.getEncoder().encode(("x:" + password).getBytes())));
diff --git a/io.openems.edge.controller.api.websocket/.classpath b/io.openems.edge.controller.api.websocket/.classpath
index bbfbdbe40e7..b4cffd0fe60 100644
--- a/io.openems.edge.controller.api.websocket/.classpath
+++ b/io.openems.edge.controller.api.websocket/.classpath
@@ -1,7 +1,7 @@
 
 
 	
-	
+	
 	
 	
 		
diff --git a/io.openems.edge.controller.api.websocket/src/io/openems/edge/controller/api/websocket/WsData.java b/io.openems.edge.controller.api.websocket/src/io/openems/edge/controller/api/websocket/WsData.java
index a92eca5bba8..d70d2299659 100644
--- a/io.openems.edge.controller.api.websocket/src/io/openems/edge/controller/api/websocket/WsData.java
+++ b/io.openems.edge.controller.api.websocket/src/io/openems/edge/controller/api/websocket/WsData.java
@@ -142,14 +142,15 @@ public Optional getUser() {
 	}
 
 	@Override
-	public String toString() {
-		String tokenString;
-		if (this.sessionToken != null) {
-			tokenString = this.sessionToken.toString();
-		} else {
-			tokenString = "UNKNOWN";
-		}
-		return "WebsocketApi.WsData [sessionToken=" + tokenString + ", user=" + this.user + "]";
+	public String toLogString() {
+		return new StringBuilder("WebsocketApi.WsData [sessionToken=") //
+				.append(this.sessionToken != null //
+						? this.sessionToken.toString() //
+						: "UNKNOWN") //
+				.append(", user=") //
+				.append(this.user) //
+				.append("]") //
+				.toString();
 	}
 
 	/**
diff --git a/io.openems.edge.controller.api.websocket/src/io/openems/edge/controller/api/websocket/handler/EdgeRequestHandler.java b/io.openems.edge.controller.api.websocket/src/io/openems/edge/controller/api/websocket/handler/EdgeRequestHandler.java
index 45234bfaa4f..d47cecfd29b 100644
--- a/io.openems.edge.controller.api.websocket/src/io/openems/edge/controller/api/websocket/handler/EdgeRequestHandler.java
+++ b/io.openems.edge.controller.api.websocket/src/io/openems/edge/controller/api/websocket/handler/EdgeRequestHandler.java
@@ -31,7 +31,8 @@ public void buildJsonApiRoutes(JsonApiBuilder builder) {
 			return new GetEdgeResponse(call.getRequest().getId(), Utils.getEdgeMetadata(user.getGlobalRole()));
 		});
 
-		builder.handleRequest(SubscribeEdgesRequest.METHOD, call -> new GenericJsonrpcResponseSuccess(call.getRequest().getId()));
+		builder.handleRequest(SubscribeEdgesRequest.METHOD,
+				call -> new GenericJsonrpcResponseSuccess(call.getRequest().getId()));
 	}
 
 }
diff --git a/io.openems.edge.controller.api.websocket/test/io/openems/edge/controller/api/websocket/ControllerApiWebsocketImplTest.java b/io.openems.edge.controller.api.websocket/test/io/openems/edge/controller/api/websocket/ControllerApiWebsocketImplTest.java
index b37f1fb4929..f2321dcc2cf 100644
--- a/io.openems.edge.controller.api.websocket/test/io/openems/edge/controller/api/websocket/ControllerApiWebsocketImplTest.java
+++ b/io.openems.edge.controller.api.websocket/test/io/openems/edge/controller/api/websocket/ControllerApiWebsocketImplTest.java
@@ -1,5 +1,7 @@
 package io.openems.edge.controller.api.websocket;
 
+import static io.openems.edge.controller.api.websocket.ControllerApiWebsocket.DEFAULT_PORT;
+
 import org.junit.Test;
 
 import io.openems.edge.common.test.AbstractComponentTest.TestCase;
@@ -8,20 +10,18 @@
 
 public class ControllerApiWebsocketImplTest {
 
-	private static final String CTRL_ID = "ctrl0";
-
 	@Test
 	public void test() throws Exception {
 		new ControllerTest(new ControllerApiWebsocketImpl()) //
 				.addReference("componentManager", new DummyComponentManager()) //
 				.addReference("onRequestFactory", new DummyOnRequestFactory()) //
 				.activate(MyConfig.create() //
-						.setId(CTRL_ID) //
+						.setId("ctrl0") //
 						.setApiTimeout(60) //
-						.setPort(ControllerApiWebsocket.DEFAULT_PORT) //
+						.setPort(DEFAULT_PORT) //
 						.build()) //
 				.next(new TestCase()) //
-		;
+				.deactivate();
 	}
 
 }
diff --git a/io.openems.edge.controller.api.websocket/test/io/openems/edge/controller/api/websocket/DummyOnRequestFactory.java b/io.openems.edge.controller.api.websocket/test/io/openems/edge/controller/api/websocket/DummyOnRequestFactory.java
index a953b39803c..852eb16321e 100644
--- a/io.openems.edge.controller.api.websocket/test/io/openems/edge/controller/api/websocket/DummyOnRequestFactory.java
+++ b/io.openems.edge.controller.api.websocket/test/io/openems/edge/controller/api/websocket/DummyOnRequestFactory.java
@@ -1,17 +1,17 @@
 package io.openems.edge.controller.api.websocket;
 
-import java.lang.reflect.InvocationTargetException;
+import static io.openems.common.utils.ReflectionUtils.setAttributeViaReflection;
 
 import org.osgi.framework.ServiceReference;
 import org.osgi.service.component.ComponentServiceObjects;
 
-import io.openems.common.utils.ReflectionUtils;
+import io.openems.common.utils.ReflectionUtils.ReflectionException;
 
 public class DummyOnRequestFactory extends OnRequest.Factory {
 
-	public DummyOnRequestFactory() throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
+	public DummyOnRequestFactory() throws ReflectionException {
 		super();
-		ReflectionUtils.setAttribute(OnRequest.Factory.class, this, "cso", new DummyOnRequestCso());
+		setAttributeViaReflection(this, "cso", new DummyOnRequestCso());
 	}
 
 	private static class DummyOnRequestCso implements ComponentServiceObjects {
diff --git a/io.openems.edge.controller.api/.classpath b/io.openems.edge.controller.api/.classpath
index bbfbdbe40e7..b4cffd0fe60 100644
--- a/io.openems.edge.controller.api/.classpath
+++ b/io.openems.edge.controller.api/.classpath
@@ -1,7 +1,7 @@
 
 
 	
-	
+	
 	
 	
 		
diff --git a/io.openems.edge.controller.asymmetric.balancingcosphi/.classpath b/io.openems.edge.controller.asymmetric.balancingcosphi/.classpath
index bbfbdbe40e7..b4cffd0fe60 100644
--- a/io.openems.edge.controller.asymmetric.balancingcosphi/.classpath
+++ b/io.openems.edge.controller.asymmetric.balancingcosphi/.classpath
@@ -1,7 +1,7 @@
 
 
 	
-	
+	
 	
 	
 		
diff --git a/io.openems.edge.controller.asymmetric.balancingcosphi/test/io/openems/edge/controller/asymmetric/balancingcosphi/ControllerAsymmetricBalancingCosPhiImplTest.java b/io.openems.edge.controller.asymmetric.balancingcosphi/test/io/openems/edge/controller/asymmetric/balancingcosphi/ControllerAsymmetricBalancingCosPhiImplTest.java
index 8f59d84837e..8f8fee699d0 100644
--- a/io.openems.edge.controller.asymmetric.balancingcosphi/test/io/openems/edge/controller/asymmetric/balancingcosphi/ControllerAsymmetricBalancingCosPhiImplTest.java
+++ b/io.openems.edge.controller.asymmetric.balancingcosphi/test/io/openems/edge/controller/asymmetric/balancingcosphi/ControllerAsymmetricBalancingCosPhiImplTest.java
@@ -9,24 +9,20 @@
 
 public class ControllerAsymmetricBalancingCosPhiImplTest {
 
-	private static final String CTRL_ID = "ctrl0";
-	private static final String ESS_ID = "ess0";
-	private static final String METER_ID = "meter0";
-
 	@Test
 	public void test() throws Exception {
 		new ControllerTest(new ControllerAsymmetricBalancingCosPhiImpl()) //
-				.addComponent(new DummyManagedAsymmetricEss(ESS_ID)) //
-				.addComponent(new DummyElectricityMeter(METER_ID)) //
+				.addComponent(new DummyManagedAsymmetricEss("ess0")) //
+				.addComponent(new DummyElectricityMeter("meter0")) //
 				.addReference("componentManager", new DummyComponentManager()) //
 				.activate(MyConfig.create() //
-						.setId(CTRL_ID) //
-						.setEssId(ESS_ID) //
-						.setMeterId(METER_ID) //
+						.setId("ctrl0") //
+						.setEssId("ess0") //
+						.setMeterId("meter0") //
 						.setCosPhi(0.9) //
 						.setDirection(CosPhiDirection.CAPACITIVE) //
-						.build()); //
-		;
+						.build()) //
+				.deactivate();
 	}
 
 }
diff --git a/io.openems.edge.controller.asymmetric.fixreactivepower/.classpath b/io.openems.edge.controller.asymmetric.fixreactivepower/.classpath
index bbfbdbe40e7..b4cffd0fe60 100644
--- a/io.openems.edge.controller.asymmetric.fixreactivepower/.classpath
+++ b/io.openems.edge.controller.asymmetric.fixreactivepower/.classpath
@@ -1,7 +1,7 @@
 
 
 	
-	
+	
 	
 	
 		
diff --git a/io.openems.edge.controller.asymmetric.fixreactivepower/test/io/openems/edge/controller/asymmetric/fixreactivepower/ControllerAsymmetricFixReactivePowerImplTest.java b/io.openems.edge.controller.asymmetric.fixreactivepower/test/io/openems/edge/controller/asymmetric/fixreactivepower/ControllerAsymmetricFixReactivePowerImplTest.java
index 53a2d3a18c1..f0c26cb202d 100644
--- a/io.openems.edge.controller.asymmetric.fixreactivepower/test/io/openems/edge/controller/asymmetric/fixreactivepower/ControllerAsymmetricFixReactivePowerImplTest.java
+++ b/io.openems.edge.controller.asymmetric.fixreactivepower/test/io/openems/edge/controller/asymmetric/fixreactivepower/ControllerAsymmetricFixReactivePowerImplTest.java
@@ -9,23 +9,20 @@
 
 public class ControllerAsymmetricFixReactivePowerImplTest {
 
-	private static final String CTRL_ID = "ctrl0";
-	private static final String ESS_ID = "ess0";
-
 	@Test
 	public void test() throws Exception {
 		new ControllerTest(new ControllerAsymmetricFixReactivePowerImpl()) //
-				.addComponent(new DummyManagedAsymmetricEss(ESS_ID)) //
+				.addComponent(new DummyManagedAsymmetricEss("ess0")) //
 				.addReference("componentManager", new DummyComponentManager()) //
 				.activate(MyConfig.create() //
-						.setId(CTRL_ID) //
-						.setEssId(ESS_ID) //
+						.setId("ctrl0") //
+						.setEssId("ess0") //
 						.setPowerL1(0) //
 						.setPowerL2(0) //
 						.setPowerL3(0) //
 						.build()) //
-				.next(new TestCase()); //
-		;
+				.next(new TestCase()) //
+				.deactivate();
 	}
 
 }
diff --git a/io.openems.edge.controller.asymmetric.peakshaving/.classpath b/io.openems.edge.controller.asymmetric.peakshaving/.classpath
index bbfbdbe40e7..b4cffd0fe60 100644
--- a/io.openems.edge.controller.asymmetric.peakshaving/.classpath
+++ b/io.openems.edge.controller.asymmetric.peakshaving/.classpath
@@ -1,7 +1,7 @@
 
 
 	
-	
+	
 	
 	
 		
diff --git a/io.openems.edge.controller.asymmetric.peakshaving/test/io/openems/edge/controller/asymmetric/peakshaving/ControllerAsymmetricPeakShavingImplTest.java b/io.openems.edge.controller.asymmetric.peakshaving/test/io/openems/edge/controller/asymmetric/peakshaving/ControllerAsymmetricPeakShavingImplTest.java
index e8090feb15a..7d59fb27424 100644
--- a/io.openems.edge.controller.asymmetric.peakshaving/test/io/openems/edge/controller/asymmetric/peakshaving/ControllerAsymmetricPeakShavingImplTest.java
+++ b/io.openems.edge.controller.asymmetric.peakshaving/test/io/openems/edge/controller/asymmetric/peakshaving/ControllerAsymmetricPeakShavingImplTest.java
@@ -1,8 +1,13 @@
 package io.openems.edge.controller.asymmetric.peakshaving;
 
+import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_EQUALS;
+import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.ACTIVE_POWER;
+import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.ACTIVE_POWER_L1;
+import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.ACTIVE_POWER_L2;
+import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.ACTIVE_POWER_L3;
+
 import org.junit.Test;
 
-import io.openems.common.types.ChannelAddress;
 import io.openems.edge.common.test.AbstractComponentTest.TestCase;
 import io.openems.edge.common.test.DummyComponentManager;
 import io.openems.edge.controller.test.ControllerTest;
@@ -12,128 +17,117 @@
 
 public class ControllerAsymmetricPeakShavingImplTest {
 
-	private static final String CTRL_ID = "ctrl0";
-
-	private static final String METER_ID = "meter0";
-	private static final ChannelAddress GRID_ACTIVE_POWER = new ChannelAddress(METER_ID, "ActivePower");
-	private static final ChannelAddress GRID_ACTIVE_POWER_L1 = new ChannelAddress(METER_ID, "ActivePowerL1");
-	private static final ChannelAddress GRID_ACTIVE_POWER_L2 = new ChannelAddress(METER_ID, "ActivePowerL2");
-	private static final ChannelAddress GRID_ACTIVE_POWER_L3 = new ChannelAddress(METER_ID, "ActivePowerL3");
-
-	private static final String ESS_ID = "ess0";
-	private static final ChannelAddress ESS_ACTIVE_POWER = new ChannelAddress(ESS_ID, "ActivePower");
-	private static final ChannelAddress ESS_SET_ACTIVE_POWER_EQUALS = new ChannelAddress(ESS_ID,
-			"SetActivePowerEquals");
-
 	@Test
 	public void symmetricMeterTest() throws Exception {
 		new ControllerTest(new ControllerAsymmetricPeakShavingImpl()) //
 				.addReference("componentManager", new DummyComponentManager()) //
-				.addComponent(new DummyElectricityMeter(METER_ID)) //
-				.addComponent(new DummyManagedSymmetricEss(ESS_ID) //
+				.addComponent(new DummyElectricityMeter("meter0")) //
+				.addComponent(new DummyManagedSymmetricEss("ess0") //
 						.setPower(new DummyPower(0.3, 0.3, 0.1))) //
 				.activate(MyConfig.create() //
-						.setId(CTRL_ID) //
-						.setMeterId(METER_ID) //
-						.setEssId(ESS_ID) //
+						.setId("ctrl0") //
+						.setMeterId("meter0") //
+						.setEssId("ess0") //
 						.setPeakShavingPower(33333) //
 						.setRechargePower(16666) //
 						.build())
 				.next(new TestCase() //
-						.input(ESS_ACTIVE_POWER, 0) //
-						.input(GRID_ACTIVE_POWER, 120000) //
-						.output(ESS_SET_ACTIVE_POWER_EQUALS, 6000)) //
+						.input("ess0", ACTIVE_POWER, 0) //
+						.input("meter0", ACTIVE_POWER, 120000) //
+						.output("ess0", SET_ACTIVE_POWER_EQUALS, 6000)) //
 				.next(new TestCase() //
-						.input(ESS_ACTIVE_POWER, 0) //
-						.input(GRID_ACTIVE_POWER, 120000) //
-						.output(ESS_SET_ACTIVE_POWER_EQUALS, 12001)) //
+						.input("ess0", ACTIVE_POWER, 0) //
+						.input("meter0", ACTIVE_POWER, 120000) //
+						.output("ess0", SET_ACTIVE_POWER_EQUALS, 12001)) //
 				.next(new TestCase() //
-						.input(ESS_ACTIVE_POWER, 3793) //
-						.input(GRID_ACTIVE_POWER, 120000 - 3793) //
-						.output(ESS_SET_ACTIVE_POWER_EQUALS, 16484)) //
+						.input("ess0", ACTIVE_POWER, 3793) //
+						.input("meter0", ACTIVE_POWER, 120000 - 3793) //
+						.output("ess0", SET_ACTIVE_POWER_EQUALS, 16484)) //
 				.next(new TestCase() //
-						.input(ESS_ACTIVE_POWER, 8981) //
-						.input(GRID_ACTIVE_POWER, 120000 - 8981) //
-						.output(ESS_SET_ACTIVE_POWER_EQUALS, 19650)) //
+						.input("ess0", ACTIVE_POWER, 8981) //
+						.input("meter0", ACTIVE_POWER, 120000 - 8981) //
+						.output("ess0", SET_ACTIVE_POWER_EQUALS, 19650)) //
 				.next(new TestCase() //
-						.input(ESS_ACTIVE_POWER, 13723) //
-						.input(GRID_ACTIVE_POWER, 120000 - 13723) //
-						.output(ESS_SET_ACTIVE_POWER_EQUALS, 21578)) //
+						.input("ess0", ACTIVE_POWER, 13723) //
+						.input("meter0", ACTIVE_POWER, 120000 - 13723) //
+						.output("ess0", SET_ACTIVE_POWER_EQUALS, 21578)) //
 				.next(new TestCase() //
-						.input(ESS_ACTIVE_POWER, 17469) //
-						.input(GRID_ACTIVE_POWER, 120000 - 17469) //
-						.output(ESS_SET_ACTIVE_POWER_EQUALS, 22437)) //
+						.input("ess0", ACTIVE_POWER, 17469) //
+						.input("meter0", ACTIVE_POWER, 120000 - 17469) //
+						.output("ess0", SET_ACTIVE_POWER_EQUALS, 22437)) //
 				.next(new TestCase() //
-						.input(ESS_ACTIVE_POWER, 20066) //
-						.input(GRID_ACTIVE_POWER, 120000 - 20066) //
-						.output(ESS_SET_ACTIVE_POWER_EQUALS, 22533)) //
+						.input("ess0", ACTIVE_POWER, 20066) //
+						.input("meter0", ACTIVE_POWER, 120000 - 20066) //
+						.output("ess0", SET_ACTIVE_POWER_EQUALS, 22533)) //
 				.next(new TestCase() //
-						.input(ESS_ACTIVE_POWER, 21564) //
-						.input(GRID_ACTIVE_POWER, 120000 - 21564) //
-						.output(ESS_SET_ACTIVE_POWER_EQUALS, 22174)) //
+						.input("ess0", ACTIVE_POWER, 21564) //
+						.input("meter0", ACTIVE_POWER, 120000 - 21564) //
+						.output("ess0", SET_ACTIVE_POWER_EQUALS, 22174)) //
 				.next(new TestCase() //
-						.input(ESS_ACTIVE_POWER, 22175) //
-						.input(GRID_ACTIVE_POWER, 120000 - 22175) //
-						.output(ESS_SET_ACTIVE_POWER_EQUALS, 21610)) //
+						.input("ess0", ACTIVE_POWER, 22175) //
+						.input("meter0", ACTIVE_POWER, 120000 - 22175) //
+						.output("ess0", SET_ACTIVE_POWER_EQUALS, 21610)) //
 				.next(new TestCase() //
-						.input(ESS_ACTIVE_POWER, 22173) //
-						.input(GRID_ACTIVE_POWER, 120000 - 22173) //
-						.output(ESS_SET_ACTIVE_POWER_EQUALS, 21020)) //
+						.input("ess0", ACTIVE_POWER, 22173) //
+						.input("meter0", ACTIVE_POWER, 120000 - 22173) //
+						.output("ess0", SET_ACTIVE_POWER_EQUALS, 21020)) //
 				.next(new TestCase() //
-						.input(ESS_ACTIVE_POWER, 21816) //
-						.input(GRID_ACTIVE_POWER, 120000 - 21816) //
-						.output(ESS_SET_ACTIVE_POWER_EQUALS, 20511)) //
+						.input("ess0", ACTIVE_POWER, 21816) //
+						.input("meter0", ACTIVE_POWER, 120000 - 21816) //
+						.output("ess0", SET_ACTIVE_POWER_EQUALS, 20511)) //
 				.next(new TestCase() //
-						.input(ESS_ACTIVE_POWER, 21311) //
-						.input(GRID_ACTIVE_POWER, 120000 - 21311) //
-						.output(ESS_SET_ACTIVE_POWER_EQUALS, 20133)) //
+						.input("ess0", ACTIVE_POWER, 21311) //
+						.input("meter0", ACTIVE_POWER, 120000 - 21311) //
+						.output("ess0", SET_ACTIVE_POWER_EQUALS, 20133)) //
 				.next(new TestCase() //
-						.input(ESS_ACTIVE_POWER, 20803) //
-						.input(GRID_ACTIVE_POWER, 120000 - 20803) //
-						.output(ESS_SET_ACTIVE_POWER_EQUALS, 19893)) //
+						.input("ess0", ACTIVE_POWER, 20803) //
+						.input("meter0", ACTIVE_POWER, 120000 - 20803) //
+						.output("ess0", SET_ACTIVE_POWER_EQUALS, 19893)) //
 				.next(new TestCase() //
-						.input(ESS_ACTIVE_POWER, 20377) //
-						.input(GRID_ACTIVE_POWER, 120000 - 20377) //
-						.output(ESS_SET_ACTIVE_POWER_EQUALS, 19772)); //
+						.input("ess0", ACTIVE_POWER, 20377) //
+						.input("meter0", ACTIVE_POWER, 120000 - 20377) //
+						.output("ess0", SET_ACTIVE_POWER_EQUALS, 19772)) //
+				.deactivate();
 	}
 
 	@Test
 	public void asymmetricMeterTest() throws Exception {
 		new ControllerTest(new ControllerAsymmetricPeakShavingImpl()) //
 				.addReference("componentManager", new DummyComponentManager()) //
-				.addComponent(new DummyElectricityMeter(METER_ID)) //
-				.addComponent(new DummyManagedSymmetricEss(ESS_ID) //
+				.addComponent(new DummyElectricityMeter("meter0")) //
+				.addComponent(new DummyManagedSymmetricEss("ess0") //
 						.setPower(new DummyPower(0.3, 0.3, 0.1))) //
 				.activate(MyConfig.create() //
-						.setId(CTRL_ID) //
-						.setMeterId(METER_ID) //
-						.setEssId(ESS_ID) //
+						.setId("ctrl0") //
+						.setMeterId("meter0") //
+						.setEssId("ess0") //
 						.setPeakShavingPower(33333) //
 						.setRechargePower(16666) //
 						.build())
 				.next(new TestCase() //
-						.input(ESS_ACTIVE_POWER, 0) //
-						.input(GRID_ACTIVE_POWER_L1, 20000) //
-						.input(GRID_ACTIVE_POWER_L2, 40000) //
-						.input(GRID_ACTIVE_POWER_L3, 10000) //
-						.output(ESS_SET_ACTIVE_POWER_EQUALS, 6000)) //
-				.next(new TestCase() //
-						.input(ESS_ACTIVE_POWER, 0) //
-						.input(GRID_ACTIVE_POWER_L1, 20000) //
-						.input(GRID_ACTIVE_POWER_L2, 40000) //
-						.input(GRID_ACTIVE_POWER_L3, 10000) //
-						.output(ESS_SET_ACTIVE_POWER_EQUALS, 12001)) //
-				.next(new TestCase() //
-						.input(ESS_ACTIVE_POWER, 3793) //
-						.input(GRID_ACTIVE_POWER_L1, 20000 - 3793 / 3) //
-						.input(GRID_ACTIVE_POWER_L2, 40000 - 3793 / 3) //
-						.input(GRID_ACTIVE_POWER_L3, 10000 - 3793 / 3) //
-						.output(ESS_SET_ACTIVE_POWER_EQUALS, 16484)) //
-				.next(new TestCase() //
-						.input(ESS_ACTIVE_POWER, 8981) //
-						.input(GRID_ACTIVE_POWER_L1, 20000 - 8981 / 3) //
-						.input(GRID_ACTIVE_POWER_L2, 40000 - 8981 / 3) //
-						.input(GRID_ACTIVE_POWER_L3, 10000 - 8981 / 3) //
-						.output(ESS_SET_ACTIVE_POWER_EQUALS, 19651)); //
+						.input("ess0", ACTIVE_POWER, 0) //
+						.input("meter0", ACTIVE_POWER_L1, 20000) //
+						.input("meter0", ACTIVE_POWER_L2, 40000) //
+						.input("meter0", ACTIVE_POWER_L3, 10000) //
+						.output("ess0", SET_ACTIVE_POWER_EQUALS, 6000)) //
+				.next(new TestCase() //
+						.input("ess0", ACTIVE_POWER, 0) //
+						.input("meter0", ACTIVE_POWER_L1, 20000) //
+						.input("meter0", ACTIVE_POWER_L2, 40000) //
+						.input("meter0", ACTIVE_POWER_L3, 10000) //
+						.output("ess0", SET_ACTIVE_POWER_EQUALS, 12001)) //
+				.next(new TestCase() //
+						.input("ess0", ACTIVE_POWER, 3793) //
+						.input("meter0", ACTIVE_POWER_L1, 20000 - 3793 / 3) //
+						.input("meter0", ACTIVE_POWER_L2, 40000 - 3793 / 3) //
+						.input("meter0", ACTIVE_POWER_L3, 10000 - 3793 / 3) //
+						.output("ess0", SET_ACTIVE_POWER_EQUALS, 16484)) //
+				.next(new TestCase() //
+						.input("ess0", ACTIVE_POWER, 8981) //
+						.input("meter0", ACTIVE_POWER_L1, 20000 - 8981 / 3) //
+						.input("meter0", ACTIVE_POWER_L2, 40000 - 8981 / 3) //
+						.input("meter0", ACTIVE_POWER_L3, 10000 - 8981 / 3) //
+						.output("ess0", SET_ACTIVE_POWER_EQUALS, 19651)) //
+				.deactivate();
 	}
 }
diff --git a/io.openems.edge.controller.asymmetric.phaserectification/.classpath b/io.openems.edge.controller.asymmetric.phaserectification/.classpath
index bbfbdbe40e7..b4cffd0fe60 100644
--- a/io.openems.edge.controller.asymmetric.phaserectification/.classpath
+++ b/io.openems.edge.controller.asymmetric.phaserectification/.classpath
@@ -1,7 +1,7 @@
 
 
 	
-	
+	
 	
 	
 		
diff --git a/io.openems.edge.controller.asymmetric.phaserectification/test/io/openems/edge/controller/asymmetric/phaserectification/ControllerAsymmetricPhaseRectificationImplTest.java b/io.openems.edge.controller.asymmetric.phaserectification/test/io/openems/edge/controller/asymmetric/phaserectification/ControllerAsymmetricPhaseRectificationImplTest.java
index f18c8669b3a..882d083baed 100644
--- a/io.openems.edge.controller.asymmetric.phaserectification/test/io/openems/edge/controller/asymmetric/phaserectification/ControllerAsymmetricPhaseRectificationImplTest.java
+++ b/io.openems.edge.controller.asymmetric.phaserectification/test/io/openems/edge/controller/asymmetric/phaserectification/ControllerAsymmetricPhaseRectificationImplTest.java
@@ -9,22 +9,18 @@
 
 public class ControllerAsymmetricPhaseRectificationImplTest {
 
-	private static final String CTRL_ID = "ctrl0";
-	private static final String METER_ID = "meter0";
-	private static final String ESS_ID = "ess0";
-
 	@Test
 	public void test() throws Exception {
 		new ControllerTest(new ControllerAsymmetricPhaseRectificationImpl()) //
-				.addComponent(new DummyManagedAsymmetricEss(ESS_ID)) //
-				.addComponent(new DummyElectricityMeter(METER_ID)) //
+				.addComponent(new DummyManagedAsymmetricEss("ess0")) //
+				.addComponent(new DummyElectricityMeter("meter0")) //
 				.addReference("componentManager", new DummyComponentManager()) //
 				.activate(MyConfig.create() //
-						.setId(CTRL_ID) //
-						.setEssId(ESS_ID) //
-						.setMeterId(METER_ID) //
-						.build()); //
-		;
+						.setId("ctrl0") //
+						.setEssId("ess0") //
+						.setMeterId("meter0") //
+						.build()) //
+				.deactivate();
 	}
 
 }
diff --git a/io.openems.edge.controller.channelthreshold/.classpath b/io.openems.edge.controller.channelthreshold/.classpath
index bbfbdbe40e7..b4cffd0fe60 100644
--- a/io.openems.edge.controller.channelthreshold/.classpath
+++ b/io.openems.edge.controller.channelthreshold/.classpath
@@ -1,7 +1,7 @@
 
 
 	
-	
+	
 	
 	
 		
diff --git a/io.openems.edge.controller.channelthreshold/test/io/openems/edge/controller/channelthreshold/ControllerChannelThresholdImplTest.java b/io.openems.edge.controller.channelthreshold/test/io/openems/edge/controller/channelthreshold/ControllerChannelThresholdImplTest.java
index b5418c94413..5c2501c8981 100644
--- a/io.openems.edge.controller.channelthreshold/test/io/openems/edge/controller/channelthreshold/ControllerChannelThresholdImplTest.java
+++ b/io.openems.edge.controller.channelthreshold/test/io/openems/edge/controller/channelthreshold/ControllerChannelThresholdImplTest.java
@@ -8,7 +8,6 @@
 
 public class ControllerChannelThresholdImplTest {
 
-	private static final String CTRL_ID = "ctrl0";
 	private static final ChannelAddress IO0_INPUT = new ChannelAddress("io0", "Input0");
 	private static final ChannelAddress IO0_OUTPUT = new ChannelAddress("io0", "Output0");
 
@@ -17,12 +16,12 @@ public void test() throws Exception {
 		new ControllerTest(new ControllerChannelThresholdImpl()) //
 				.addReference("componentManager", new DummyComponentManager()) //
 				.activate(MyConfig.create() //
-						.setId(CTRL_ID) //
-						.setInputChannelAddress(IO0_INPUT.toString()) //
-						.setOutputChannelAddress(IO0_OUTPUT.toString()) //
+						.setId("ctrl0") //
+						.setInputChannelAddress(IO0_INPUT) //
+						.setOutputChannelAddress(IO0_OUTPUT) //
 						.setLowThreshold(40) //
 						.setHighThreshold(80) //
-						.build()); //
+						.build()) //
+				.deactivate();
 	}
-
 }
diff --git a/io.openems.edge.controller.channelthreshold/test/io/openems/edge/controller/channelthreshold/MyConfig.java b/io.openems.edge.controller.channelthreshold/test/io/openems/edge/controller/channelthreshold/MyConfig.java
index ab9677f984e..7bb1199c7bc 100644
--- a/io.openems.edge.controller.channelthreshold/test/io/openems/edge/controller/channelthreshold/MyConfig.java
+++ b/io.openems.edge.controller.channelthreshold/test/io/openems/edge/controller/channelthreshold/MyConfig.java
@@ -1,6 +1,7 @@
 package io.openems.edge.controller.channelthreshold;
 
 import io.openems.common.test.AbstractComponentConfig;
+import io.openems.common.types.ChannelAddress;
 
 @SuppressWarnings("all")
 public class MyConfig extends AbstractComponentConfig implements Config {
@@ -22,13 +23,13 @@ public Builder setId(String id) {
 			return this;
 		}
 
-		public Builder setInputChannelAddress(String inputChannelAddress) {
-			this.inputChannelAddress = inputChannelAddress;
+		public Builder setInputChannelAddress(ChannelAddress inputChannelAddress) {
+			this.inputChannelAddress = inputChannelAddress.toString();
 			return this;
 		}
 
-		public Builder setOutputChannelAddress(String outputChannelAddress) {
-			this.outputChannelAddress = outputChannelAddress;
+		public Builder setOutputChannelAddress(ChannelAddress outputChannelAddress) {
+			this.outputChannelAddress = outputChannelAddress.toString();
 			return this;
 		}
 
diff --git a/io.openems.edge.controller.chp.soc/.classpath b/io.openems.edge.controller.chp.soc/.classpath
index bbfbdbe40e7..b4cffd0fe60 100644
--- a/io.openems.edge.controller.chp.soc/.classpath
+++ b/io.openems.edge.controller.chp.soc/.classpath
@@ -1,7 +1,7 @@
 
 
 	
-	
+	
 	
 	
 		
diff --git a/io.openems.edge.controller.chp.soc/test/io/openems/edge/controller/chp/soc/ControllerChpSocImplTest.java b/io.openems.edge.controller.chp.soc/test/io/openems/edge/controller/chp/soc/ControllerChpSocImplTest.java
index 8937f49f4ce..a469e21f2bd 100644
--- a/io.openems.edge.controller.chp.soc/test/io/openems/edge/controller/chp/soc/ControllerChpSocImplTest.java
+++ b/io.openems.edge.controller.chp.soc/test/io/openems/edge/controller/chp/soc/ControllerChpSocImplTest.java
@@ -1,8 +1,10 @@
 package io.openems.edge.controller.chp.soc;
 
+import static io.openems.edge.ess.api.SymmetricEss.ChannelId.SOC;
+import static io.openems.edge.io.test.DummyInputOutput.ChannelId.INPUT_OUTPUT0;
+
 import org.junit.Test;
 
-import io.openems.common.types.ChannelAddress;
 import io.openems.edge.common.test.AbstractComponentTest.TestCase;
 import io.openems.edge.common.test.DummyComponentManager;
 import io.openems.edge.controller.test.ControllerTest;
@@ -11,56 +13,49 @@
 
 public class ControllerChpSocImplTest {
 
-	private static final String CTRL_ID = "ctrl0";
-
-	private static final String ESS_ID = "ess0";
-	private static final ChannelAddress ESS_SOC = new ChannelAddress(ESS_ID, "Soc");
-
-	private static final String IO_ID = "io0";
-	private static final ChannelAddress IO_OUTPUT0 = new ChannelAddress(IO_ID, "InputOutput0");
-
 	@Test
 	public void test() throws Exception {
 		new ControllerTest(new ControllerChpSocImpl()) //
 				.addReference("componentManager", new DummyComponentManager()) //
-				.addComponent(new DummyManagedSymmetricEss(ESS_ID)) //
-				.addComponent(new DummyInputOutput(IO_ID)) //
+				.addComponent(new DummyManagedSymmetricEss("ess0")) //
+				.addComponent(new DummyInputOutput("io0")) //
 				.activate(MyConfig.create() //
-						.setId(CTRL_ID) //
-						.setInputChannelAddress(ESS_SOC.toString()) //
-						.setOutputChannelAddress(IO_OUTPUT0.toString()) //
+						.setId("ctrl0") //
+						.setInputChannelAddress("ess0/Soc") //
+						.setOutputChannelAddress("io0/InputOutput0") //
 						.setLowThreshold(15) //
 						.setHighThreshold(85) //
 						.setMode(Mode.AUTOMATIC) //
 						.setInvert(false) //
 						.build())
 				.next(new TestCase() //
-						.input(ESS_SOC, 14) //
-						.output(IO_OUTPUT0, true)) //
+						.input("ess0", SOC, 14) //
+						.output("io0", INPUT_OUTPUT0, true)) //
 				.next(new TestCase() //
-						.input(ESS_SOC, 50) //
-						.output(IO_OUTPUT0, null)) //
+						.input("ess0", SOC, 50) //
+						.output("io0", INPUT_OUTPUT0, null)) //
 				.next(new TestCase() //
-						.input(ESS_SOC, 90) //
-						.output(IO_OUTPUT0, false)) //
+						.input("ess0", SOC, 90) //
+						.output("io0", INPUT_OUTPUT0, false)) //
 				.next(new TestCase() //
-						.input(ESS_SOC, 50) //
-						.output(IO_OUTPUT0, null)) //
+						.input("ess0", SOC, 50) //
+						.output("io0", INPUT_OUTPUT0, null)) //
 				.next(new TestCase() //
-						.input(ESS_SOC, 15) //
-						.output(IO_OUTPUT0, true)) //
+						.input("ess0", SOC, 15) //
+						.output("io0", INPUT_OUTPUT0, true)) //
 				.next(new TestCase() //
-						.input(ESS_SOC, 85) //
-						.output(IO_OUTPUT0, false)) //
+						.input("ess0", SOC, 85) //
+						.output("io0", INPUT_OUTPUT0, false)) //
 				.next(new TestCase() //
-						.input(ESS_SOC, 86) //
-						.output(IO_OUTPUT0, false)) //
+						.input("ess0", SOC, 86) //
+						.output("io0", INPUT_OUTPUT0, false)) //
 				.next(new TestCase() //
-						.input(ESS_SOC, 14) //
-						.output(IO_OUTPUT0, true)) //
+						.input("ess0", SOC, 14) //
+						.output("io0", INPUT_OUTPUT0, true)) //
 				.next(new TestCase() //
-						.input(ESS_SOC, 45) //
-						.output(IO_OUTPUT0, null));
+						.input("ess0", SOC, 45) //
+						.output("io0", INPUT_OUTPUT0, null)) //
+				.deactivate();
 	}
 
 }
\ No newline at end of file
diff --git a/io.openems.edge.controller.debug.detailedlog/.classpath b/io.openems.edge.controller.debug.detailedlog/.classpath
index bbfbdbe40e7..b4cffd0fe60 100644
--- a/io.openems.edge.controller.debug.detailedlog/.classpath
+++ b/io.openems.edge.controller.debug.detailedlog/.classpath
@@ -1,7 +1,7 @@
 
 
 	
-	
+	
 	
 	
 		
diff --git a/io.openems.edge.controller.debug.detailedlog/test/io/openems/edge/controller/debug/detailedlog/ControllerDebugDetailedLogImplTest.java b/io.openems.edge.controller.debug.detailedlog/test/io/openems/edge/controller/debug/detailedlog/ControllerDebugDetailedLogImplTest.java
index ed138710c30..0ae17386d3d 100644
--- a/io.openems.edge.controller.debug.detailedlog/test/io/openems/edge/controller/debug/detailedlog/ControllerDebugDetailedLogImplTest.java
+++ b/io.openems.edge.controller.debug.detailedlog/test/io/openems/edge/controller/debug/detailedlog/ControllerDebugDetailedLogImplTest.java
@@ -7,16 +7,15 @@
 
 public class ControllerDebugDetailedLogImplTest {
 
-	private static final String CTRL_ID = "ctrl0";
-
 	@Test
 	public void test() throws Exception {
 		new ControllerTest(new ControllerDebugDetailedLogImpl()) //
 				.addReference("componentManager", new DummyComponentManager()) //
 				.activate(MyConfig.create() //
-						.setId(CTRL_ID) //
+						.setId("ctrl0") //
 						.setComponentIds() //
-						.build()); //
+						.build()) //
+				.deactivate();
 	}
 
 }
diff --git a/io.openems.edge.controller.debug.log/.classpath b/io.openems.edge.controller.debug.log/.classpath
index bbfbdbe40e7..b4cffd0fe60 100644
--- a/io.openems.edge.controller.debug.log/.classpath
+++ b/io.openems.edge.controller.debug.log/.classpath
@@ -1,7 +1,7 @@
 
 
 	
-	
+	
 	
 	
 		
diff --git a/io.openems.edge.controller.debug.log/test/io/openems/edge/controller/debuglog/ControllerDebugLogImplTest.java b/io.openems.edge.controller.debug.log/test/io/openems/edge/controller/debuglog/ControllerDebugLogImplTest.java
index 0f0d97848c6..beabd1ba311 100644
--- a/io.openems.edge.controller.debug.log/test/io/openems/edge/controller/debuglog/ControllerDebugLogImplTest.java
+++ b/io.openems.edge.controller.debug.log/test/io/openems/edge/controller/debuglog/ControllerDebugLogImplTest.java
@@ -1,5 +1,6 @@
 package io.openems.edge.controller.debuglog;
 
+import static io.openems.edge.common.sum.Sum.ChannelId.ESS_SOC;
 import static org.junit.Assert.assertEquals;
 
 import java.util.ArrayList;
@@ -7,7 +8,6 @@
 
 import org.junit.Test;
 
-import io.openems.common.types.ChannelAddress;
 import io.openems.edge.common.component.OpenemsComponent;
 import io.openems.edge.common.sum.DummySum;
 import io.openems.edge.common.test.AbstractComponentTest.TestCase;
@@ -16,20 +16,6 @@
 
 public class ControllerDebugLogImplTest {
 
-	private static final String CTRL_ID = "ctrl0";
-
-	private static final String DUMMY0_ID = "dummy0";
-	private static final String DUMMY1_ID = "dummy1";
-	private static final String DUMMY1_ALIAS = "This is Dummy1";
-	private static final String DUMMY2_ID = "dummy2";
-	private static final String DUMMY2_ALIAS = DUMMY2_ID;
-	private static final String DUMMY10_ID = "dummy10";
-
-	private static final String ANY_DUMMY = "dummy*";
-
-	private static final ChannelAddress SUM_ESS_SOC = new ChannelAddress("_sum", "EssSoc");
-	private static final ChannelAddress SUM_FOO_BAR = new ChannelAddress("_sum", "FooBar");
-
 	@Test
 	public void test() throws Exception {
 		List components = new ArrayList<>();
@@ -39,25 +25,26 @@ public String debugLog() {
 				return "foo:bar";
 			}
 		});
-		components.add(new DummyController(DUMMY0_ID) {
+		components.add(new DummyController("dummy0") {
 			@Override
 			public String debugLog() {
 				return "abc:xyz";
 			}
 		});
-		components.add(new DummyController(DUMMY1_ID, DUMMY1_ALIAS) {
+		components.add(new DummyController("dummy1", "This is Dummy1") {
+
 			@Override
 			public String debugLog() {
 				return "def:uvw";
 			}
 		});
-		components.add(new DummyController(DUMMY2_ID, DUMMY2_ALIAS) {
+		components.add(new DummyController("dummy2", "dummy2") {
 			@Override
 			public String debugLog() {
 				return "ghi:rst";
 			}
 		});
-		components.add(new DummyController(DUMMY10_ID) {
+		components.add(new DummyController("dummy10") {
 			@Override
 			public String debugLog() {
 				return "jkl:opq";
@@ -68,19 +55,14 @@ public String debugLog() {
 		new ControllerTest(sut) //
 				.addReference("components", components) //
 				.activate(MyConfig.create() //
-						.setId(CTRL_ID) //
+						.setId("ctrl0") //
 						.setShowAlias(true) //
 						.setCondensedOutput(true) //
-						.setAdditionalChannels(new String[] { //
-								SUM_ESS_SOC.toString(), //
-								SUM_FOO_BAR.toString() //
-						}) //
-						.setIgnoreComponents(new String[] { //
-								DUMMY0_ID //
-						}) //
+						.setAdditionalChannels("_sum/EssSoc", "_sum/FooBar") //
+						.setIgnoreComponents("dummy0") //
 						.build()) //
 				.next(new TestCase() //
-						.input(SUM_ESS_SOC, 50));
+						.input(ESS_SOC, 50));
 
 		assertEquals(
 				"_sum[Core.Sum|foo:bar|EssSoc:50 %|FooBar:CHANNEL_IS_NOT_DEFINED] dummy1[This is Dummy1|def:uvw] dummy2[ghi:rst] dummy10[jkl:opq]",
@@ -97,13 +79,14 @@ public String debugLog() {
 				return "foo:bar";
 			}
 		});
-		components.add(new DummyController(DUMMY0_ID) {
+		components.add(new DummyController("dummy0") {
 			@Override
 			public String debugLog() {
 				return "abc:xyz";
 			}
 		});
-		components.add(new DummyController(DUMMY1_ID) {
+		components.add(new DummyController("dummy1") {
+
 			@Override
 			public String debugLog() {
 				return "def:uvw";
@@ -116,17 +99,14 @@ public String debugLog() {
 				.addComponent(components.get(0)) //
 				.addComponent(components.get(1)) //
 				.activate(MyConfig.create() //
-						.setId(CTRL_ID) //
+						.setId("ctrl0") //
 						.setCondensedOutput(true) //
-						.setAdditionalChannels(new String[] { //
-								SUM_ESS_SOC.toString() //
-						}) //
-						.setIgnoreComponents(new String[] { //
-								ANY_DUMMY //
-						}) //
+						.setAdditionalChannels("_sum/EssSoc") //
+						.setIgnoreComponents("dummy*") //
 						.build()) //
 				.next(new TestCase() //
-						.input(SUM_ESS_SOC, 50));
+						.input(ESS_SOC, 50)) //
+				.deactivate();
 
 		assertEquals("_sum[foo:bar|EssSoc:50 %]", sut.getLogMessage());
 
diff --git a/io.openems.edge.controller.debug.log/test/io/openems/edge/controller/debuglog/MyConfig.java b/io.openems.edge.controller.debug.log/test/io/openems/edge/controller/debuglog/MyConfig.java
index f23d1d89e1a..b8edc931126 100644
--- a/io.openems.edge.controller.debug.log/test/io/openems/edge/controller/debuglog/MyConfig.java
+++ b/io.openems.edge.controller.debug.log/test/io/openems/edge/controller/debuglog/MyConfig.java
@@ -25,12 +25,12 @@ public Builder setShowAlias(boolean showAlias) {
 			return this;
 		}
 
-		public Builder setAdditionalChannels(String[] additionalChannels) {
+		public Builder setAdditionalChannels(String... additionalChannels) {
 			this.additionalChannels = additionalChannels;
 			return this;
 		}
 
-		public Builder setIgnoreComponents(String[] ignoreComponents) {
+		public Builder setIgnoreComponents(String... ignoreComponents) {
 			this.ignoreComponents = ignoreComponents;
 			return this;
 		}
diff --git a/io.openems.edge.controller.ess.acisland/.classpath b/io.openems.edge.controller.ess.acisland/.classpath
index bbfbdbe40e7..b4cffd0fe60 100644
--- a/io.openems.edge.controller.ess.acisland/.classpath
+++ b/io.openems.edge.controller.ess.acisland/.classpath
@@ -1,7 +1,7 @@
 
 
 	
-	
+	
 	
 	
 		
diff --git a/io.openems.edge.controller.ess.acisland/test/io/openems/edge/controller/ess/acisland/ControllerEssAcIslandImplTest.java b/io.openems.edge.controller.ess.acisland/test/io/openems/edge/controller/ess/acisland/ControllerEssAcIslandImplTest.java
index 1a25df66607..7208178a501 100644
--- a/io.openems.edge.controller.ess.acisland/test/io/openems/edge/controller/ess/acisland/ControllerEssAcIslandImplTest.java
+++ b/io.openems.edge.controller.ess.acisland/test/io/openems/edge/controller/ess/acisland/ControllerEssAcIslandImplTest.java
@@ -2,35 +2,26 @@
 
 import org.junit.Test;
 
-import io.openems.common.types.ChannelAddress;
 import io.openems.edge.common.test.DummyComponentManager;
 import io.openems.edge.controller.test.ControllerTest;
 
 public class ControllerEssAcIslandImplTest {
 
-	private static final String CTRL_ID = "ctrl0";
-
-	private static final String ESS_ID = "ess0";
-
-	private static final String IO_ID = "io0";
-	private static final ChannelAddress IO_OUTPUT0 = new ChannelAddress(IO_ID, "Output0");
-	private static final ChannelAddress IO_OUTPUT1 = new ChannelAddress(IO_ID, "Output1");
-
 	@Test
 	public void test() throws Exception {
 		new ControllerTest(new ControllerEssAcIslandImpl()) //
 				.addReference("componentManager", new DummyComponentManager()) //
 				.activate(MyConfig.create() //
-						.setId(CTRL_ID) //
-						.setEssId(ESS_ID) //
+						.setId("ctrl0") //
+						.setEssId("ess0") //
 						.setInvertOffGridOutput(false) //
 						.setInvertOnGridOutput(false) //
 						.setMaxSoc(90) //
 						.setMinSoc(4) //
-						.setOffGridOutputChannelAddress(IO_OUTPUT0.toString()) //
-						.setOnGridOutputChannelAddress(IO_OUTPUT1.toString()) //
+						.setOffGridOutputChannelAddress("io0/Output0") //
+						.setOnGridOutputChannelAddress("io0/Output1") //
 						.build()) //
-		;
+				.deactivate();
 	}
 
 }
diff --git a/io.openems.edge.controller.ess.activepowervoltagecharacteristic/.classpath b/io.openems.edge.controller.ess.activepowervoltagecharacteristic/.classpath
index bbfbdbe40e7..b4cffd0fe60 100644
--- a/io.openems.edge.controller.ess.activepowervoltagecharacteristic/.classpath
+++ b/io.openems.edge.controller.ess.activepowervoltagecharacteristic/.classpath
@@ -1,7 +1,7 @@
 
 
 	
-	
+	
 	
 	
 		
diff --git a/io.openems.edge.controller.ess.activepowervoltagecharacteristic/test/io/openems/edge/controller/ess/activepowervoltagecharacteristic/CharacteristicImplTest.java b/io.openems.edge.controller.ess.activepowervoltagecharacteristic/test/io/openems/edge/controller/ess/activepowervoltagecharacteristic/CharacteristicImplTest.java
index 26f3eb8d98f..1c1cdea75dc 100644
--- a/io.openems.edge.controller.ess.activepowervoltagecharacteristic/test/io/openems/edge/controller/ess/activepowervoltagecharacteristic/CharacteristicImplTest.java
+++ b/io.openems.edge.controller.ess.activepowervoltagecharacteristic/test/io/openems/edge/controller/ess/activepowervoltagecharacteristic/CharacteristicImplTest.java
@@ -1,5 +1,9 @@
 package io.openems.edge.controller.ess.activepowervoltagecharacteristic;
 
+import static io.openems.common.utils.JsonUtils.buildJsonArray;
+import static io.openems.common.utils.JsonUtils.buildJsonObject;
+import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_EQUALS;
+
 import java.time.Instant;
 import java.time.ZoneOffset;
 import java.time.temporal.ChronoUnit;
@@ -8,7 +12,6 @@
 
 import io.openems.common.test.TimeLeapClock;
 import io.openems.common.types.ChannelAddress;
-import io.openems.common.utils.JsonUtils;
 import io.openems.edge.common.test.AbstractComponentTest.TestCase;
 import io.openems.edge.common.test.DummyComponentManager;
 import io.openems.edge.common.test.DummyConfigurationAdmin;
@@ -18,11 +21,7 @@
 
 public class CharacteristicImplTest {
 
-	private static final String CTRL_ID = "ctrlActivePowerVoltageCharacteristic0";
-	private static final String ESS_ID = "ess1";
-	private static final String METER_ID = "meter0";
-	private static final ChannelAddress ESS_ACTIVE_POWER = new ChannelAddress(ESS_ID, "SetActivePowerEquals");
-	private static final ChannelAddress METER_VOLTAGE = new ChannelAddress(METER_ID, "Voltage");
+	private static final ChannelAddress METER_VOLTAGE = new ChannelAddress("meter0", "Voltage");
 
 	@Test
 	public void test() throws Exception {
@@ -30,36 +29,36 @@ public void test() throws Exception {
 		new ControllerTest(new ControllerEssActivePowerVoltageCharacteristicImpl())//
 				.addReference("cm", new DummyConfigurationAdmin()) //
 				.addReference("componentManager", new DummyComponentManager(clock)) //
-				.addReference("meter", new DummyElectricityMeter(METER_ID)) //
-				.addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) //
+				.addReference("meter", new DummyElectricityMeter("meter0")) //
+				.addReference("ess", new DummyManagedSymmetricEss("ess1")) //
 				.activate(MyConfig.create()//
-						.setId(CTRL_ID)//
-						.setEssId(ESS_ID)//
-						.setMeterId(METER_ID)//
+						.setId("ctrlActivePowerVoltageCharacteristic0")//
+						.setEssId("ess1")//
+						.setMeterId("meter0")//
 						.setNominalVoltage(240)//
 						.setWaitForHysteresis(5)//
-						.setPowerVoltConfig(JsonUtils.buildJsonArray()//
-								.add(JsonUtils.buildJsonObject()//
+						.setPowerVoltConfig(buildJsonArray()//
+								.add(buildJsonObject()//
 										.addProperty("voltageRatio", 0.95) //
 										.addProperty("power", 4000) //
 										.build()) //
-								.add(JsonUtils.buildJsonObject()//
+								.add(buildJsonObject()//
 										.addProperty("voltageRatio", 0.98) //
 										.addProperty("power", 1000) //
 										.build()) //
-								.add(JsonUtils.buildJsonObject()//
+								.add(buildJsonObject()//
 										.addProperty("voltageRatio", 0.98001) //
 										.addProperty("power", 0) //
 										.build()) //
-								.add(JsonUtils.buildJsonObject()//
+								.add(buildJsonObject()//
 										.addProperty("voltageRatio", 1.02999) //
 										.addProperty("power", 0) //
 										.build()) //
-								.add(JsonUtils.buildJsonObject()//
+								.add(buildJsonObject()//
 										.addProperty("voltageRatio", 1.03) //
 										.addProperty("power", -1000) //
 										.build()) //
-								.add(JsonUtils.buildJsonObject()//
+								.add(buildJsonObject()//
 										.addProperty("voltageRatio", 1.05) //
 										.addProperty("power", -4000) //
 										.build() //
@@ -67,52 +66,52 @@ public void test() throws Exception {
 						).build()) //
 				.next(new TestCase("First Input") //
 						.input(METER_VOLTAGE, 250_000) // [mV]
-						.output(ESS_ACTIVE_POWER, -2749)) //
+						.output("ess1", SET_ACTIVE_POWER_EQUALS, -2749)) //
 				.next(new TestCase("Second Input, \"Power: -1500 \"") //
 						.timeleap(clock, 5, ChronoUnit.SECONDS) //
 						.input(METER_VOLTAGE, 248_000) // [mV]
-						.output(ESS_ACTIVE_POWER, -1499))//
+						.output("ess1", SET_ACTIVE_POWER_EQUALS, -1499))//
 				.next(new TestCase() //
 						.input(METER_VOLTAGE, 240_200) // [mV]
-						.output(ESS_ACTIVE_POWER, null)) //
+						.output("ess1", SET_ACTIVE_POWER_EQUALS, null)) //
 				.next(new TestCase("Third Input, \"Power: 0 \"") //
 						.timeleap(clock, 5, ChronoUnit.SECONDS) //
 						.input(METER_VOLTAGE, 238_100) // [mV]
-						.output(ESS_ACTIVE_POWER, 0)) //
+						.output("ess1", SET_ACTIVE_POWER_EQUALS, 0)) //
 				.next(new TestCase() //
 						.input(METER_VOLTAGE, 240_000) // [mV]
-						.output(ESS_ACTIVE_POWER, null)) //
+						.output("ess1", SET_ACTIVE_POWER_EQUALS, null)) //
 				.next(new TestCase() //
 						.input(METER_VOLTAGE, 238_800)// [mV]
-						.output(ESS_ACTIVE_POWER, null)) //
+						.output("ess1", SET_ACTIVE_POWER_EQUALS, null)) //
 				.next(new TestCase("Fourth Input, \"Power: 0 \"") //
 						.timeleap(clock, 5, ChronoUnit.SECONDS) //
 						.input(METER_VOLTAGE, 235_200) // [mV]
-						.output(ESS_ACTIVE_POWER, 998)) //
+						.output("ess1", SET_ACTIVE_POWER_EQUALS, 998)) //
 				.next(new TestCase() //
 						.timeleap(clock, 2, ChronoUnit.SECONDS) //
 						.input(METER_VOLTAGE, 235_600) // [mV]
-						.output(ESS_ACTIVE_POWER, null)) //
+						.output("ess1", SET_ACTIVE_POWER_EQUALS, null)) //
 				.next(new TestCase() //
 						.timeleap(clock, 2, ChronoUnit.SECONDS) //
 						.input(METER_VOLTAGE, 234_000) // [mV]
-						.output(ESS_ACTIVE_POWER, null)) //
+						.output("ess1", SET_ACTIVE_POWER_EQUALS, null)) //
 				.next(new TestCase("Fifth Input, \"Power: 1625 \"") //
 						.timeleap(clock, 1, ChronoUnit.SECONDS) //
 						.input(METER_VOLTAGE, 233_700) // [mV]
-						.output(ESS_ACTIVE_POWER, 1625)) //
+						.output("ess1", SET_ACTIVE_POWER_EQUALS, 1625)) //
 				.next(new TestCase("Fourth Input, \"Power: 0 \"") //
 						.timeleap(clock, 5, ChronoUnit.SECONDS) //
 						.input(METER_VOLTAGE, 225_000) // [mV]
-						.output(ESS_ACTIVE_POWER, 4000)) //
+						.output("ess1", SET_ACTIVE_POWER_EQUALS, 4000)) //
 				.next(new TestCase("Smaller then Min Key, \"Power: 0 \"") //
 						.timeleap(clock, 5, ChronoUnit.SECONDS) //
 						.input(METER_VOLTAGE, 255_000) // [mV]
-						.output(ESS_ACTIVE_POWER, -4000)) //
+						.output("ess1", SET_ACTIVE_POWER_EQUALS, -4000)) //
 				.next(new TestCase("Bigger than Max Key, \"Power: 0 \"") //
 						.timeleap(clock, 5, ChronoUnit.SECONDS) //
 						.input(METER_VOLTAGE, 270_000) // [mV]
-						.output(ESS_ACTIVE_POWER, -4000)) //
-		;
+						.output("ess1", SET_ACTIVE_POWER_EQUALS, -4000)) //
+				.deactivate();
 	}
 }
diff --git a/io.openems.edge.controller.ess.balancing/.classpath b/io.openems.edge.controller.ess.balancing/.classpath
index bbfbdbe40e7..b4cffd0fe60 100644
--- a/io.openems.edge.controller.ess.balancing/.classpath
+++ b/io.openems.edge.controller.ess.balancing/.classpath
@@ -1,7 +1,7 @@
 
 
 	
-	
+	
 	
 	
 		
diff --git a/io.openems.edge.controller.ess.balancing/test/io/openems/edge/controller/ess/balancing/BalancingImplTest.java b/io.openems.edge.controller.ess.balancing/test/io/openems/edge/controller/ess/balancing/BalancingImplTest.java
index e6a320f6733..4cab4cf6d5a 100644
--- a/io.openems.edge.controller.ess.balancing/test/io/openems/edge/controller/ess/balancing/BalancingImplTest.java
+++ b/io.openems.edge.controller.ess.balancing/test/io/openems/edge/controller/ess/balancing/BalancingImplTest.java
@@ -1,96 +1,90 @@
 package io.openems.edge.controller.ess.balancing;
 
+import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_EQUALS;
+
 import org.junit.Test;
 
-import io.openems.common.types.ChannelAddress;
 import io.openems.edge.common.test.AbstractComponentTest.TestCase;
 import io.openems.edge.common.test.DummyConfigurationAdmin;
 import io.openems.edge.controller.test.ControllerTest;
+import io.openems.edge.ess.api.SymmetricEss;
 import io.openems.edge.ess.test.DummyManagedSymmetricEss;
 import io.openems.edge.ess.test.DummyPower;
+import io.openems.edge.meter.api.ElectricityMeter;
 import io.openems.edge.meter.test.DummyElectricityMeter;
 
 public class BalancingImplTest {
 
-	private static final String CTRL_ID = "ctrl0";
-
-	private static final String ESS_ID = "ess0";
-	private static final ChannelAddress ESS_ACTIVE_POWER = new ChannelAddress(ESS_ID, "ActivePower");
-	private static final ChannelAddress ESS_SET_ACTIVE_POWER_EQUALS = new ChannelAddress(ESS_ID,
-			"SetActivePowerEquals");
-
-	private static final String METER_ID = "meter0";
-	private static final ChannelAddress METER_ACTIVE_POWER = new ChannelAddress(METER_ID, "ActivePower");
-
 	@Test
 	public void test() throws Exception {
 		new ControllerTest(new ControllerEssBalancingImpl()) //
 				.addReference("cm", new DummyConfigurationAdmin()) //
-				.addReference("ess", new DummyManagedSymmetricEss(ESS_ID) //
+				.addReference("ess", new DummyManagedSymmetricEss("ess0") //
 						.setPower(new DummyPower(0.3, 0.3, 0.1))) //
-				.addReference("meter", new DummyElectricityMeter(METER_ID)) //
+				.addReference("meter", new DummyElectricityMeter("meter0")) //
 				.activate(MyConfig.create() //
-						.setId(CTRL_ID) //
-						.setEssId(ESS_ID) //
-						.setMeterId(METER_ID) //
+						.setId("ctrl0") //
+						.setEssId("ess0") //
+						.setMeterId("meter0") //
 						.setTargetGridSetpoint(0) //
 						.build())
 				.next(new TestCase() //
-						.input(ESS_ACTIVE_POWER, 0) //
-						.input(METER_ACTIVE_POWER, 20000) //
-						.output(ESS_SET_ACTIVE_POWER_EQUALS, 6000)) //
+						.input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) //
+						.input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 20000) //
+						.output("ess0", SET_ACTIVE_POWER_EQUALS, 6000)) //
 				.next(new TestCase() //
-						.input(ESS_ACTIVE_POWER, 0) //
-						.input(METER_ACTIVE_POWER, 20000) //
-						.output(ESS_SET_ACTIVE_POWER_EQUALS, 12000)) //
+						.input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) //
+						.input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 20000) //
+						.output("ess0", SET_ACTIVE_POWER_EQUALS, 12000)) //
 				.next(new TestCase() //
-						.input(ESS_ACTIVE_POWER, 3793) //
-						.input(METER_ACTIVE_POWER, 20000 - 3793) //
-						.output(ESS_SET_ACTIVE_POWER_EQUALS, 16483)) //
+						.input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 3793) //
+						.input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 20000 - 3793) //
+						.output("ess0", SET_ACTIVE_POWER_EQUALS, 16483)) //
 				.next(new TestCase() //
-						.input(ESS_ACTIVE_POWER, 8981) //
-						.input(METER_ACTIVE_POWER, 20000 - 8981) //
-						.output(ESS_SET_ACTIVE_POWER_EQUALS, 19649)) //
+						.input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 8981) //
+						.input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 20000 - 8981) //
+						.output("ess0", SET_ACTIVE_POWER_EQUALS, 19649)) //
 				.next(new TestCase() //
-						.input(ESS_ACTIVE_POWER, 13723) //
-						.input(METER_ACTIVE_POWER, 20000 - 13723) //
-						.output(ESS_SET_ACTIVE_POWER_EQUALS, 21577)) //
+						.input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 13723) //
+						.input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 20000 - 13723) //
+						.output("ess0", SET_ACTIVE_POWER_EQUALS, 21577)) //
 				.next(new TestCase() //
-						.input(ESS_ACTIVE_POWER, 17469) //
-						.input(METER_ACTIVE_POWER, 20000 - 17469) //
-						.output(ESS_SET_ACTIVE_POWER_EQUALS, 22436)) //
+						.input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 17469) //
+						.input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 20000 - 17469) //
+						.output("ess0", SET_ACTIVE_POWER_EQUALS, 22436)) //
 				.next(new TestCase() //
-						.input(ESS_ACTIVE_POWER, 20066) //
-						.input(METER_ACTIVE_POWER, 20000 - 20066) //
-						.output(ESS_SET_ACTIVE_POWER_EQUALS, 22531)) //
+						.input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 20066) //
+						.input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 20000 - 20066) //
+						.output("ess0", SET_ACTIVE_POWER_EQUALS, 22531)) //
 				.next(new TestCase() //
-						.input(ESS_ACTIVE_POWER, 21564) //
-						.input(METER_ACTIVE_POWER, 20000 - 21564) //
-						.output(ESS_SET_ACTIVE_POWER_EQUALS, 22171)) //
+						.input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 21564) //
+						.input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 20000 - 21564) //
+						.output("ess0", SET_ACTIVE_POWER_EQUALS, 22171)) //
 				.next(new TestCase() //
-						.input(ESS_ACTIVE_POWER, 22175) //
-						.input(METER_ACTIVE_POWER, 20000 - 22175) //
-						.output(ESS_SET_ACTIVE_POWER_EQUALS, 21608)) //
+						.input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 22175) //
+						.input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 20000 - 22175) //
+						.output("ess0", SET_ACTIVE_POWER_EQUALS, 21608)) //
 				.next(new TestCase() //
-						.input(ESS_ACTIVE_POWER, 22173) //
-						.input(METER_ACTIVE_POWER, 20000 - 22173) //
-						.output(ESS_SET_ACTIVE_POWER_EQUALS, 21017)) //
+						.input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 22173) //
+						.input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 20000 - 22173) //
+						.output("ess0", SET_ACTIVE_POWER_EQUALS, 21017)) //
 				.next(new TestCase() //
-						.input(ESS_ACTIVE_POWER, 21816) //
-						.input(METER_ACTIVE_POWER, 20000 - 21816) //
-						.output(ESS_SET_ACTIVE_POWER_EQUALS, 20508)) //
+						.input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 21816) //
+						.input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 20000 - 21816) //
+						.output("ess0", SET_ACTIVE_POWER_EQUALS, 20508)) //
 				.next(new TestCase() //
-						.input(ESS_ACTIVE_POWER, 21311) //
-						.input(METER_ACTIVE_POWER, 20000 - 21311) //
-						.output(ESS_SET_ACTIVE_POWER_EQUALS, 20129)) //
+						.input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 21311) //
+						.input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 20000 - 21311) //
+						.output("ess0", SET_ACTIVE_POWER_EQUALS, 20129)) //
 				.next(new TestCase() //
-						.input(ESS_ACTIVE_POWER, 20803) //
-						.input(METER_ACTIVE_POWER, 20000 - 20803) //
-						.output(ESS_SET_ACTIVE_POWER_EQUALS, 19889)) //
+						.input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 20803) //
+						.input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 20000 - 20803) //
+						.output("ess0", SET_ACTIVE_POWER_EQUALS, 19889)) //
 				.next(new TestCase() //
-						.input(ESS_ACTIVE_POWER, 20377) //
-						.input(METER_ACTIVE_POWER, 20000 - 20377) //
-						.output(ESS_SET_ACTIVE_POWER_EQUALS, 19767));
+						.input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 20377) //
+						.input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 20000 - 20377) //
+						.output("ess0", SET_ACTIVE_POWER_EQUALS, 19767)) //
+				.deactivate();
 	}
 
 }
diff --git a/io.openems.edge.controller.ess.cycle/.classpath b/io.openems.edge.controller.ess.cycle/.classpath
index bbfbdbe40e7..b4cffd0fe60 100644
--- a/io.openems.edge.controller.ess.cycle/.classpath
+++ b/io.openems.edge.controller.ess.cycle/.classpath
@@ -1,7 +1,7 @@
 
 
 	
-	
+	
 	
 	
 		
diff --git a/io.openems.edge.controller.ess.cycle/test/io/openems/edge/controller/ess/cycle/ControllerEssCycleImplTest.java b/io.openems.edge.controller.ess.cycle/test/io/openems/edge/controller/ess/cycle/ControllerEssCycleImplTest.java
index 3018b8a775c..93c3db68b5e 100644
--- a/io.openems.edge.controller.ess.cycle/test/io/openems/edge/controller/ess/cycle/ControllerEssCycleImplTest.java
+++ b/io.openems.edge.controller.ess.cycle/test/io/openems/edge/controller/ess/cycle/ControllerEssCycleImplTest.java
@@ -1,5 +1,15 @@
 package io.openems.edge.controller.ess.cycle;
 
+import static io.openems.edge.controller.ess.cycle.ControllerEssCycle.ChannelId.COMPLETED_CYCLES;
+import static io.openems.edge.controller.ess.cycle.ControllerEssCycle.ChannelId.STATE_MACHINE;
+import static io.openems.edge.controller.ess.cycle.CycleOrder.START_WITH_DISCHARGE;
+import static io.openems.edge.controller.ess.cycle.HybridEssMode.TARGET_AC;
+import static io.openems.edge.controller.ess.cycle.Mode.MANUAL_ON;
+import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.ALLOWED_CHARGE_POWER;
+import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.ALLOWED_DISCHARGE_POWER;
+import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_EQUALS;
+import static io.openems.edge.ess.api.SymmetricEss.ChannelId.SOC;
+
 import java.time.Instant;
 import java.time.ZoneOffset;
 import java.time.temporal.ChronoUnit;
@@ -7,7 +17,6 @@
 import org.junit.Test;
 
 import io.openems.common.test.TimeLeapClock;
-import io.openems.common.types.ChannelAddress;
 import io.openems.edge.common.test.AbstractComponentTest.TestCase;
 import io.openems.edge.common.test.DummyComponentManager;
 import io.openems.edge.common.test.DummyConfigurationAdmin;
@@ -18,74 +27,61 @@
 
 public class ControllerEssCycleImplTest {
 
-	private static final String CTRL_ID = "ctrl0";
-	private static final String ESS_ID = "ess0";
-
-	private static final ChannelAddress STATE_MACHINE = new ChannelAddress(CTRL_ID,
-			ControllerEssCycle.ChannelId.STATE_MACHINE.id());
-
-	private static final ChannelAddress ESS_SOC = new ChannelAddress(ESS_ID, "Soc");
-	private static final ChannelAddress MAX_CHARGE_POWER = new ChannelAddress(ESS_ID, "AllowedChargePower");
-	private static final ChannelAddress MAX_DISCHARGE_POWER = new ChannelAddress(ESS_ID, "AllowedDischargePower");
-	private static final ChannelAddress SET_ACTIVE_POWER_EQUALS = new ChannelAddress(ESS_ID, "SetActivePowerEquals");
-
-	private static final ChannelAddress COMPLETED_CYCLES = new ChannelAddress(CTRL_ID, "CompletedCycles");
-
 	@Test
 	public void test() throws Exception {
 		final var clock = new TimeLeapClock(Instant.parse("2000-01-01T01:00:00.00Z"), ZoneOffset.UTC);
 		final var power = new DummyPower(10_000);
-		final var ess = new DummyManagedSymmetricEss(ESS_ID) //
+		final var ess = new DummyManagedSymmetricEss("ess0") //
 				.setPower(power);
 		final var test = new ControllerTest(new ControllerEssCycleImpl()) //
 				.addReference("componentManager", new DummyComponentManager(clock)) //
 				.addReference("cm", new DummyConfigurationAdmin()) //
 				.addReference("ess", ess) //
 				.activate(MyConfig.create()//
-						.setId(CTRL_ID) //
-						.setEssId(ESS_ID) //
-						.setCycleOrder(CycleOrder.START_WITH_DISCHARGE) //
+						.setId("ctrl0") //
+						.setEssId("ess0") //
+						.setCycleOrder(START_WITH_DISCHARGE) //
 						.setStandbyTime(10)//
 						.setStartTime("2000-01-01 01:00")//
 						.setMaxSoc(100)//
 						.setMinSoc(0)//
 						.setPower(10000)//
-						.setMode(Mode.MANUAL_ON)//
-						.setHybridEssMode(HybridEssMode.TARGET_AC)//
+						.setMode(MANUAL_ON)//
+						.setHybridEssMode(TARGET_AC)//
 						.setTotalCycleNumber(3)//
 						.setFinalSoc(50)//
 						.build());
 		power.addEss(ess);
 		test.next(new TestCase()//
 				.input(STATE_MACHINE, State.UNDEFINED)//
-				.input(MAX_CHARGE_POWER, -10_000)//
-				.input(MAX_DISCHARGE_POWER, 10_000)//
-				.input(SET_ACTIVE_POWER_EQUALS, 10_000) //
-				.input(ESS_SOC, 50)) //
+				.input("ess0", ALLOWED_CHARGE_POWER, -10_000)//
+				.input("ess0", ALLOWED_DISCHARGE_POWER, 10_000)//
+				.input("ess0", SET_ACTIVE_POWER_EQUALS, 10_000) //
+				.input("ess0", SOC, 50)) //
 				.next(new TestCase("First Discharge") //
 						.output(STATE_MACHINE, State.START_DISCHARGE))//
 				.next(new TestCase()//
-						.input(MAX_CHARGE_POWER, -10_000)//
-						.input(MAX_DISCHARGE_POWER, 1000))//
+						.input("ess0", ALLOWED_CHARGE_POWER, -10_000)//
+						.input("ess0", ALLOWED_DISCHARGE_POWER, 1000))//
 				.next(new TestCase()//
 						.timeleap(clock, 10, ChronoUnit.MINUTES))//
 				.next(new TestCase()//
 						.output(STATE_MACHINE, State.START_DISCHARGE))//
 				.next(new TestCase()//
-						.input(MAX_CHARGE_POWER, -10_000)//
-						.input(MAX_DISCHARGE_POWER, 0))//
+						.input("ess0", ALLOWED_CHARGE_POWER, -10_000)//
+						.input("ess0", ALLOWED_DISCHARGE_POWER, 0))//
 				.next(new TestCase()//
 						.timeleap(clock, 11, ChronoUnit.MINUTES))//
 				.next(new TestCase("First Charge")//
 						.output(STATE_MACHINE, State.CONTINUE_WITH_CHARGE))//
 				.next(new TestCase()//
-						.input(MAX_CHARGE_POWER, -1000)//
-						.input(MAX_DISCHARGE_POWER, 10_000))//
+						.input("ess0", ALLOWED_CHARGE_POWER, -1000)//
+						.input("ess0", ALLOWED_DISCHARGE_POWER, 10_000))//
 				.next(new TestCase()//
 						.output(STATE_MACHINE, State.CONTINUE_WITH_CHARGE))//
 				.next(new TestCase()//
-						.input(MAX_CHARGE_POWER, 0)//
-						.input(MAX_DISCHARGE_POWER, 10_000))//
+						.input("ess0", ALLOWED_CHARGE_POWER, 0)//
+						.input("ess0", ALLOWED_DISCHARGE_POWER, 10_000))//
 				.next(new TestCase()//
 						.timeleap(clock, 11, ChronoUnit.MINUTES))//
 				.next(new TestCase() //
@@ -94,17 +90,17 @@ public void test() throws Exception {
 						.output(COMPLETED_CYCLES, 1)//
 						.output(STATE_MACHINE, State.START_DISCHARGE))//
 				.next(new TestCase("Second Discharge")//
-						.input(ESS_SOC, 0)//
-						.input(MAX_CHARGE_POWER, -10_000)//
-						.input(MAX_DISCHARGE_POWER, 0))//
+						.input("ess0", SOC, 0)//
+						.input("ess0", ALLOWED_CHARGE_POWER, -10_000)//
+						.input("ess0", ALLOWED_DISCHARGE_POWER, 0))//
 				.next(new TestCase()//
 						.timeleap(clock, 11, ChronoUnit.MINUTES))//
 				.next(new TestCase()//
 						.output(STATE_MACHINE, State.CONTINUE_WITH_CHARGE))//
 				.next(new TestCase("Second Charge")//
-						.input(ESS_SOC, 100)//
-						.input(MAX_CHARGE_POWER, 0)//
-						.input(MAX_DISCHARGE_POWER, 10_000))//
+						.input("ess0", SOC, 100)//
+						.input("ess0", ALLOWED_CHARGE_POWER, 0)//
+						.input("ess0", ALLOWED_DISCHARGE_POWER, 10_000))//
 				.next(new TestCase()//
 						.timeleap(clock, 11, ChronoUnit.MINUTES))//
 				.next(new TestCase("Second completed cycle") //
@@ -113,17 +109,17 @@ public void test() throws Exception {
 						.output(COMPLETED_CYCLES, 2)//
 						.output(STATE_MACHINE, State.START_DISCHARGE))//
 				.next(new TestCase("Third Discharge")//
-						.input(ESS_SOC, 0)//
-						.input(MAX_CHARGE_POWER, -10_000)//
-						.input(MAX_DISCHARGE_POWER, 0))//
+						.input("ess0", SOC, 0)//
+						.input("ess0", ALLOWED_CHARGE_POWER, -10_000)//
+						.input("ess0", ALLOWED_DISCHARGE_POWER, 0))//
 				.next(new TestCase()//
 						.timeleap(clock, 11, ChronoUnit.MINUTES))//
 				.next(new TestCase()//
 						.output(STATE_MACHINE, State.CONTINUE_WITH_CHARGE))//
 				.next(new TestCase("Third Charge")//
-						.input(ESS_SOC, 100)//
-						.input(MAX_CHARGE_POWER, 0)//
-						.input(MAX_DISCHARGE_POWER, 10_000))//
+						.input("ess0", SOC, 100)//
+						.input("ess0", ALLOWED_CHARGE_POWER, 0)//
+						.input("ess0", ALLOWED_DISCHARGE_POWER, 10_000))//
 				.next(new TestCase()//
 						.timeleap(clock, 11, ChronoUnit.MINUTES))//
 				.next(new TestCase("Cycle Number 3 Test")//
@@ -132,11 +128,11 @@ public void test() throws Exception {
 				.next(new TestCase()//
 						.output(STATE_MACHINE, State.FINAL_SOC))//
 				.next(new TestCase()//
-						.input(ESS_SOC, 50)//
-						.input(MAX_CHARGE_POWER, -10_000)//
-						.input(MAX_DISCHARGE_POWER, 10_000))//
+						.input("ess0", SOC, 50)//
+						.input("ess0", ALLOWED_CHARGE_POWER, -10_000)//
+						.input("ess0", ALLOWED_DISCHARGE_POWER, 10_000))//
 				.next(new TestCase() //
-						.output(STATE_MACHINE, State.FINISHED))//
-		; //
+						.output(STATE_MACHINE, State.FINISHED)) //
+				.deactivate();
 	}
 }
diff --git a/io.openems.edge.controller.ess.delaycharge/.classpath b/io.openems.edge.controller.ess.delaycharge/.classpath
index bbfbdbe40e7..b4cffd0fe60 100644
--- a/io.openems.edge.controller.ess.delaycharge/.classpath
+++ b/io.openems.edge.controller.ess.delaycharge/.classpath
@@ -1,7 +1,7 @@
 
 
 	
-	
+	
 	
 	
 		
diff --git a/io.openems.edge.controller.ess.delaycharge/test/io/openems/edge/controller/ess/delaycharge/ControllerEssDelayChargeImplTest.java b/io.openems.edge.controller.ess.delaycharge/test/io/openems/edge/controller/ess/delaycharge/ControllerEssDelayChargeImplTest.java
index 4c495a15a52..7f8dc9e62c8 100644
--- a/io.openems.edge.controller.ess.delaycharge/test/io/openems/edge/controller/ess/delaycharge/ControllerEssDelayChargeImplTest.java
+++ b/io.openems.edge.controller.ess.delaycharge/test/io/openems/edge/controller/ess/delaycharge/ControllerEssDelayChargeImplTest.java
@@ -1,13 +1,14 @@
 package io.openems.edge.controller.ess.delaycharge;
 
+import static io.openems.edge.controller.ess.delaycharge.ControllerEssDelayCharge.ChannelId.CHARGE_POWER_LIMIT;
+import static java.time.temporal.ChronoUnit.HOURS;
+
 import java.time.Instant;
 import java.time.ZoneId;
-import java.time.temporal.ChronoUnit;
 
 import org.junit.Test;
 
 import io.openems.common.test.TimeLeapClock;
-import io.openems.common.types.ChannelAddress;
 import io.openems.edge.common.test.AbstractComponentTest.TestCase;
 import io.openems.edge.common.test.DummyComponentManager;
 import io.openems.edge.controller.test.ControllerTest;
@@ -15,11 +16,6 @@
 
 public class ControllerEssDelayChargeImplTest {
 
-	private static final String CTRL_ID = "ctrl0";
-	private static final ChannelAddress CTRL_CHARGE_POWER_LIMIT = new ChannelAddress(CTRL_ID, "ChargePowerLimit");
-
-	private static final String ESS_ID = "ess0";
-
 	@Test
 	public void test() throws Exception {
 		// Initialize mocked Clock
@@ -27,32 +23,32 @@ public void test() throws Exception {
 				Instant.ofEpochMilli(1546300800000L /* Tuesday, 1. January 2019 00:00:00 */), ZoneId.of("UTC"));
 		new ControllerTest(new ControllerEssDelayChargeImpl()) //
 				.addReference("componentManager", new DummyComponentManager(clock)) //
-				.addComponent(new DummyManagedSymmetricEss(ESS_ID) //
+				.addComponent(new DummyManagedSymmetricEss("ess0") //
 						.withSoc(20) //
 						.withCapacity(9000)) //
 				.activate(MyConfig.create() //
-						.setId(CTRL_ID) //
-						.setEssId(ESS_ID) //
+						.setId("ctrl0") //
+						.setEssId("ess0") //
 						.setTargetHour(15) //
 						.build())
 				.next(new TestCase() //
-						.timeleap(clock, 6, ChronoUnit.HOURS) // = 6 am
-						.output(CTRL_CHARGE_POWER_LIMIT, 800))
+						.timeleap(clock, 6, HOURS) // = 6 am
+						.output(CHARGE_POWER_LIMIT, 800))
 				.next(new TestCase() //
-						.timeleap(clock, 2, ChronoUnit.HOURS) // = 8 am
-						.output(CTRL_CHARGE_POWER_LIMIT, 1028))
+						.timeleap(clock, 2, HOURS) // = 8 am
+						.output(CHARGE_POWER_LIMIT, 1028))
 				.next(new TestCase() //
-						.timeleap(clock, 2, ChronoUnit.HOURS) // = 10 am
-						.output(CTRL_CHARGE_POWER_LIMIT, 1440))
+						.timeleap(clock, 2, HOURS) // = 10 am
+						.output(CHARGE_POWER_LIMIT, 1440))
 				.next(new TestCase() //
-						.timeleap(clock, 2, ChronoUnit.HOURS) // = 12 am
-						.output(CTRL_CHARGE_POWER_LIMIT, 2400))
+						.timeleap(clock, 2, HOURS) // = 12 am
+						.output(CHARGE_POWER_LIMIT, 2400))
 				.next(new TestCase() //
-						.timeleap(clock, 2, ChronoUnit.HOURS) // = 14 am
-						.output(CTRL_CHARGE_POWER_LIMIT, 7200))
+						.timeleap(clock, 2, HOURS) // = 14 am
+						.output(CHARGE_POWER_LIMIT, 7200))
 				.next(new TestCase() //
-						.timeleap(clock, 3, ChronoUnit.HOURS) // = 16 am
-						.output(CTRL_CHARGE_POWER_LIMIT, 0));
+						.timeleap(clock, 3, HOURS) // = 16 am
+						.output(CHARGE_POWER_LIMIT, 0)) //
+				.deactivate();
 	}
-
 }
diff --git a/io.openems.edge.controller.ess.delayedselltogrid/.classpath b/io.openems.edge.controller.ess.delayedselltogrid/.classpath
index bbfbdbe40e7..b4cffd0fe60 100644
--- a/io.openems.edge.controller.ess.delayedselltogrid/.classpath
+++ b/io.openems.edge.controller.ess.delayedselltogrid/.classpath
@@ -1,7 +1,7 @@
 
 
 	
-	
+	
 	
 	
 		
diff --git a/io.openems.edge.controller.ess.delayedselltogrid/test/io/openems/edge/controller/ess/delayedselltogrid/ControllerEssDelayedSellToGridImplTest.java b/io.openems.edge.controller.ess.delayedselltogrid/test/io/openems/edge/controller/ess/delayedselltogrid/ControllerEssDelayedSellToGridImplTest.java
index 3d258054b4f..6843f1ca737 100644
--- a/io.openems.edge.controller.ess.delayedselltogrid/test/io/openems/edge/controller/ess/delayedselltogrid/ControllerEssDelayedSellToGridImplTest.java
+++ b/io.openems.edge.controller.ess.delayedselltogrid/test/io/openems/edge/controller/ess/delayedselltogrid/ControllerEssDelayedSellToGridImplTest.java
@@ -1,76 +1,70 @@
 package io.openems.edge.controller.ess.delayedselltogrid;
 
+import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_EQUALS;
+
 import org.junit.Test;
 
-import io.openems.common.types.ChannelAddress;
 import io.openems.edge.common.test.AbstractComponentTest.TestCase;
 import io.openems.edge.common.test.DummyConfigurationAdmin;
 import io.openems.edge.controller.test.ControllerTest;
+import io.openems.edge.ess.api.SymmetricEss;
 import io.openems.edge.ess.test.DummyManagedSymmetricEss;
 import io.openems.edge.meter.test.DummyElectricityMeter;
 
 public class ControllerEssDelayedSellToGridImplTest {
 
-	private static final String CTRL_ID = "ctrlDelayedSellToGrid0";
-	private static final String ESS_ID = "ess0";
-	private static final String METER_ID = "meter0";
-	private static final ChannelAddress ESS_ACTIVE_POWER = new ChannelAddress(ESS_ID, "ActivePower");
-	private static final ChannelAddress ESS_SET_ACTIVE_POWER_EQUALS = new ChannelAddress(ESS_ID,
-			"SetActivePowerEquals");
-	private static final ChannelAddress METER_ACTIVE_POWER = new ChannelAddress(METER_ID, "ActivePower");
-
 	@Test
 	public void test() throws Exception {
 		new ControllerTest(new ControllerEssDelayedSellToGridImpl())//
 				.addReference("cm", new DummyConfigurationAdmin()) //
-				.addReference("meter", new DummyElectricityMeter(METER_ID)) //
-				.addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) //
+				.addReference("meter", new DummyElectricityMeter("meter0")) //
+				.addReference("ess", new DummyManagedSymmetricEss("ess0")) //
 				.activate(MyConfig.create()//
-						.setId(CTRL_ID)//
-						.setEssId(ESS_ID)//
-						.setMeterId(METER_ID)//
+						.setId("ctrlDelayedSellToGrid0")//
+						.setEssId("ess0")//
+						.setMeterId("meter0")//
 						.setSellToGridPowerLimit(12_500_000)//
 						.setContinuousSellToGridPower(500_000).build())//
 				.next(new TestCase() //
-						.input(ESS_ACTIVE_POWER, 0) //
-						.input(METER_ACTIVE_POWER, 0) //
-						.output(ESS_SET_ACTIVE_POWER_EQUALS, 500_000)) //
+						.input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) //
+						.input("meter0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) //
+						.output("ess0", SET_ACTIVE_POWER_EQUALS, 500_000)) //
 				.next(new TestCase() //
-						.input(ESS_ACTIVE_POWER, 0) //
-						.input(METER_ACTIVE_POWER, -30_000)//
-						.output(ESS_SET_ACTIVE_POWER_EQUALS, 470_000)) //
+						.input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) //
+						.input("meter0", SymmetricEss.ChannelId.ACTIVE_POWER, -30_000)//
+						.output("ess0", SET_ACTIVE_POWER_EQUALS, 470_000)) //
 				.next(new TestCase() //
-						.input(ESS_ACTIVE_POWER, 500_000) //
-						.input(METER_ACTIVE_POWER, -500_000)//
-						.output(ESS_SET_ACTIVE_POWER_EQUALS, 500_000)) //
+						.input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 500_000) //
+						.input("meter0", SymmetricEss.ChannelId.ACTIVE_POWER, -500_000)//
+						.output("ess0", SET_ACTIVE_POWER_EQUALS, 500_000)) //
 				.next(new TestCase() //
-						.input(ESS_ACTIVE_POWER, 50_000) //
-						.input(METER_ACTIVE_POWER, -500_000)//
-						.output(ESS_SET_ACTIVE_POWER_EQUALS, 50_000)) //
+						.input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 50_000) //
+						.input("meter0", SymmetricEss.ChannelId.ACTIVE_POWER, -500_000)//
+						.output("ess0", SET_ACTIVE_POWER_EQUALS, 50_000)) //
 				.next(new TestCase() //
-						.input(ESS_ACTIVE_POWER, -50_000) //
-						.input(METER_ACTIVE_POWER, -500_000)//
-						.output(ESS_SET_ACTIVE_POWER_EQUALS, 0)) //
+						.input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -50_000) //
+						.input("meter0", SymmetricEss.ChannelId.ACTIVE_POWER, -500_000)//
+						.output("ess0", SET_ACTIVE_POWER_EQUALS, 0)) //
 				.next(new TestCase() //
-						.input(ESS_ACTIVE_POWER, 150_000) //
-						.input(METER_ACTIVE_POWER, -500_000)//
-						.output(ESS_SET_ACTIVE_POWER_EQUALS, 150_000)) //
+						.input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 150_000) //
+						.input("meter0", SymmetricEss.ChannelId.ACTIVE_POWER, -500_000)//
+						.output("ess0", SET_ACTIVE_POWER_EQUALS, 150_000)) //
 				.next(new TestCase() //
-						.input(ESS_ACTIVE_POWER, 0) //
-						.input(METER_ACTIVE_POWER, -1_500_000)//
-						.output(ESS_SET_ACTIVE_POWER_EQUALS, 0)) //
+						.input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) //
+						.input("meter0", SymmetricEss.ChannelId.ACTIVE_POWER, -1_500_000)//
+						.output("ess0", SET_ACTIVE_POWER_EQUALS, 0)) //
 				.next(new TestCase() //
-						.input(ESS_ACTIVE_POWER, -100_000) //
-						.input(METER_ACTIVE_POWER, -15_000_000)//
-						.output(ESS_SET_ACTIVE_POWER_EQUALS, -2_600_000)) //
+						.input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -100_000) //
+						.input("meter0", SymmetricEss.ChannelId.ACTIVE_POWER, -15_000_000)//
+						.output("ess0", SET_ACTIVE_POWER_EQUALS, -2_600_000)) //
 				.next(new TestCase() //
-						.input(ESS_ACTIVE_POWER, -1_000_000) //
-						.input(METER_ACTIVE_POWER, -16_000_000)//
-						.output(ESS_SET_ACTIVE_POWER_EQUALS, -4_500_000)) //
+						.input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -1_000_000) //
+						.input("meter0", SymmetricEss.ChannelId.ACTIVE_POWER, -16_000_000)//
+						.output("ess0", SET_ACTIVE_POWER_EQUALS, -4_500_000)) //
 				.next(new TestCase() //
-						.input(ESS_ACTIVE_POWER, 0) //
-						.input(METER_ACTIVE_POWER, -16_000_000)//
-						.output(ESS_SET_ACTIVE_POWER_EQUALS, -3_500_000)) //
-		;
+						.input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) //
+						.input("meter0", SymmetricEss.ChannelId.ACTIVE_POWER, -16_000_000)//
+						.output("ess0", SET_ACTIVE_POWER_EQUALS, -3_500_000)) //
+				.deactivate();
 	}
 }
diff --git a/io.openems.edge.controller.ess.emergencycapacityreserve/.classpath b/io.openems.edge.controller.ess.emergencycapacityreserve/.classpath
index bbfbdbe40e7..b4cffd0fe60 100644
--- a/io.openems.edge.controller.ess.emergencycapacityreserve/.classpath
+++ b/io.openems.edge.controller.ess.emergencycapacityreserve/.classpath
@@ -1,7 +1,7 @@
 
 
 	
-	
+	
 	
 	
 		
diff --git a/io.openems.edge.controller.ess.emergencycapacityreserve/bnd.bnd b/io.openems.edge.controller.ess.emergencycapacityreserve/bnd.bnd
index 3f814d45c0c..0a8bfd62f05 100644
--- a/io.openems.edge.controller.ess.emergencycapacityreserve/bnd.bnd
+++ b/io.openems.edge.controller.ess.emergencycapacityreserve/bnd.bnd
@@ -8,6 +8,7 @@ Bundle-Version: 1.0.0.${tstamp}
 	io.openems.common,\
 	io.openems.edge.common,\
 	io.openems.edge.controller.api,\
+	io.openems.edge.energy.api,\
 	io.openems.edge.ess.api,\
 	io.openems.edge.ess.generic,\
 
diff --git a/io.openems.edge.controller.ess.emergencycapacityreserve/doc/statemachine.md b/io.openems.edge.controller.ess.emergencycapacityreserve/doc/statemachine.md
index a0264a69c01..67bc1553aec 100644
--- a/io.openems.edge.controller.ess.emergencycapacityreserve/doc/statemachine.md
+++ b/io.openems.edge.controller.ess.emergencycapacityreserve/doc/statemachine.md
@@ -2,21 +2,23 @@
 
 ```mermaid
 graph TD
-NO_LIMIT["NO_LIMIT
SoC > 21 %
No Limit
[infinity, infinity]
1 %p/sec"] -->|discharge, SoC <= 21 %| ABOVE_RESERVE_SOC["ABOVE_RESERVE_SOC
[infinity, max(50 % mAP, DC-PV)]
1 %p/sec"] - -ABOVE_RESERVE_SOC -->|discharge, SoC <= 20 %| AT_RESERVE_SOC["AT_RESERVE_SOC
[infinity, max(0, DC-PV)]
1 %p/sec"] - -AT_RESERVE_SOC -->|discharge, SoC < 20| UNDER_RESERVE_SOC["UNDER_RESERVE_SOC
[infinity, 0]
5 %p/sec"] - -UNDER_RESERVE_SOC -->|discharge, SoC <= 16| FORCE_CHARGE["FORCE_CHARGE
Force-Charge with AC-PV
[infinity, AC-PV * -1]
1 %p/sec"] - -FORCE_CHARGE -->|charge, SoC >= 20| AT_RESERVE_SOC - -AT_RESERVE_SOC -->|charge, SoC > 20| ABOVE_RESERVE_SOC - +NO_LIMIT -->|discharge, SoC <= 21 %| ABOVE_RESERVE_SOC +ABOVE_RESERVE_SOC -->|discharge, SoC <= 20 %| AT_RESERVE_SOC ABOVE_RESERVE_SOC -->|charge, SoC > 21| NO_LIMIT - +AT_RESERVE_SOC -->|discharge, SoC < 20| BELOW_RESERVE_SOC["BELOW_RESERVE_SOC
[infinity, 0]
5 %p/sec"] +AT_RESERVE_SOC -->|charge,
if last state FORCE_CHARGE_GRID:
SoC > 21,
else: SoC > 20| ABOVE_RESERVE_SOC +BELOW_RESERVE_SOC -->|discharge, SoC <= 19| FORCE_CHARGE_PV +BELOW_RESERVE_SOC -->|discharge, SoC < 18| FORCE_CHARGE_GRID +FORCE_CHARGE_PV -->|charge, SoC >= 20| AT_RESERVE_SOC +FORCE_CHARGE_PV --> |discharge, SoC < 18| FORCE_CHARGE_GRID +FORCE_CHARGE_GRID -->|charge, SoC >= 21| AT_RESERVE_SOC +UNDEFINED["UNDEFINED
Initial State
[Start of Process]"] -->|SoC < 18 %| FORCE_CHARGE_GRID["FORCE_CHARGE_GRID
Force-Charge with Grid Power
[infinity, max[10 % mAP if SoC >= 17%, else 50 % mAP]]
1 %p/sec"] +UNDEFINED -->|SoC == 19 %| FORCE_CHARGE_PV["FORCE_CHARGE_PV
Force-Charge with AC-PV
[infinity, AC-PV * -1]
1 %p/sec"] +UNDEFINED -->|SoC == 20 %| AT_RESERVE_SOC["AT_RESERVE_SOC
[infinity, max(0, DC-PV)]
1 %p/sec"] +UNDEFINED -->|SoC == 21 %| ABOVE_RESERVE_SOC["ABOVE_RESERVE_SOC
[infinity, max(50 % mAP, DC-PV)]
1 %p/sec"] +UNDEFINED -->|SoC > 21 %| NO_LIMIT["NO_LIMIT
SoC > 21 %
No Limit
[infinity, infinity]
1 %p/sec"] DESCRIPTION["Example ReserveSoc = 20%
ReserveSoc in [5;100]
%p = Percentage Point
mAP = MaxApparentPower
Ramp increase/decrease by 1 %p/sec of MaxApparentPower"] + ``` View using Mermaid, e.g. https://mermaid-js.github.io/mermaid-live-editor \ No newline at end of file diff --git a/io.openems.edge.controller.ess.emergencycapacityreserve/src/io/openems/edge/controller/ess/emergencycapacityreserve/ControllerEssEmergencyCapacityReserve.java b/io.openems.edge.controller.ess.emergencycapacityreserve/src/io/openems/edge/controller/ess/emergencycapacityreserve/ControllerEssEmergencyCapacityReserve.java index 2fc861054af..43eaecfffa3 100644 --- a/io.openems.edge.controller.ess.emergencycapacityreserve/src/io/openems/edge/controller/ess/emergencycapacityreserve/ControllerEssEmergencyCapacityReserve.java +++ b/io.openems.edge.controller.ess.emergencycapacityreserve/src/io/openems/edge/controller/ess/emergencycapacityreserve/ControllerEssEmergencyCapacityReserve.java @@ -1,9 +1,12 @@ package io.openems.edge.controller.ess.emergencycapacityreserve; +import static io.openems.common.channel.PersistencePriority.HIGH; +import static io.openems.common.channel.Unit.PERCENT; +import static io.openems.common.channel.Unit.WATT; +import static io.openems.common.types.OpenemsType.FLOAT; +import static io.openems.common.types.OpenemsType.INTEGER; + import io.openems.common.channel.Level; -import io.openems.common.channel.PersistencePriority; -import io.openems.common.channel.Unit; -import io.openems.common.types.OpenemsType; import io.openems.edge.common.channel.Channel; import io.openems.edge.common.channel.Doc; import io.openems.edge.common.channel.FloatReadChannel; @@ -23,28 +26,31 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { * Current state of the StateMachine. */ STATE_MACHINE(Doc.of(State.values()) // - .text("Current State of State-Machine")), // + .text("Current State of State-Machine") // + .persistencePriority(HIGH)), // /** * Holds {@link ManagedSymmetricEss.ChannelId#SET_ACTIVE_POWER_LESS_OR_EQUALS} * for debug purpose. */ - DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.WATT) // + DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS(Doc.of(INTEGER) // + .unit(WATT) // .text("The debug SetActivePowerLessOrEquals")), // /** * Holds target power to reach. */ - DEBUG_TARGET_POWER(Doc.of(OpenemsType.FLOAT) // - .unit(Unit.WATT) // - .text("The debug target power to reach")), // + DEBUG_TARGET_POWER(Doc.of(FLOAT) // + .unit(WATT) // + .text("The debug target power to reach") // + .persistencePriority(HIGH) // + ), // /** * Holds power to increase/decrease ramp for every cycle. */ - DEBUG_RAMP_POWER(Doc.of(OpenemsType.FLOAT) // - .unit(Unit.WATT) // + DEBUG_RAMP_POWER(Doc.of(FLOAT) // + .unit(WATT) // .text("The debug ramp power to decrease power")), // /** @@ -56,10 +62,10 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { /** * Holds the actual reserve soc value. Holds null if reserve soc is disabled. */ - ACTUAL_RESERVE_SOC(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.PERCENT) // + ACTUAL_RESERVE_SOC(Doc.of(INTEGER) // + .unit(PERCENT) // .text("The reserve soc value") // - .persistencePriority(PersistencePriority.HIGH)); // + .persistencePriority(HIGH)); // private final Doc doc; diff --git a/io.openems.edge.controller.ess.emergencycapacityreserve/src/io/openems/edge/controller/ess/emergencycapacityreserve/ControllerEssEmergencyCapacityReserveImpl.java b/io.openems.edge.controller.ess.emergencycapacityreserve/src/io/openems/edge/controller/ess/emergencycapacityreserve/ControllerEssEmergencyCapacityReserveImpl.java index 35ba0a200aa..3d35a5c5fa2 100644 --- a/io.openems.edge.controller.ess.emergencycapacityreserve/src/io/openems/edge/controller/ess/emergencycapacityreserve/ControllerEssEmergencyCapacityReserveImpl.java +++ b/io.openems.edge.controller.ess.emergencycapacityreserve/src/io/openems/edge/controller/ess/emergencycapacityreserve/ControllerEssEmergencyCapacityReserveImpl.java @@ -1,6 +1,10 @@ package io.openems.edge.controller.ess.emergencycapacityreserve; +import static io.openems.edge.energy.api.EnergyUtils.socToEnergy; +import static java.lang.Math.max; + import java.util.OptionalInt; +import java.util.function.Supplier; import org.osgi.service.cm.ConfigurationAdmin; import org.osgi.service.component.ComponentContext; @@ -20,11 +24,14 @@ import io.openems.edge.common.component.ComponentManager; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.filter.RampFilter; +import io.openems.edge.common.meta.Meta; import io.openems.edge.common.sum.Sum; import io.openems.edge.controller.api.Controller; import io.openems.edge.controller.ess.emergencycapacityreserve.statemachine.Context; import io.openems.edge.controller.ess.emergencycapacityreserve.statemachine.StateMachine; import io.openems.edge.controller.ess.emergencycapacityreserve.statemachine.StateMachine.State; +import io.openems.edge.energy.api.EnergySchedulable; +import io.openems.edge.energy.api.EnergyScheduleHandler; import io.openems.edge.ess.api.ManagedSymmetricEss; @Designate(ocd = Config.class, factory = true) @@ -34,7 +41,7 @@ configurationPolicy = ConfigurationPolicy.REQUIRE // ) public class ControllerEssEmergencyCapacityReserveImpl extends AbstractOpenemsComponent - implements ControllerEssEmergencyCapacityReserve, Controller, OpenemsComponent { + implements ControllerEssEmergencyCapacityReserve, EnergySchedulable, Controller, OpenemsComponent { /** Minimum reserve SoC value in [%]. */ private static final int reservSocMinValue = 5; @@ -42,7 +49,8 @@ public class ControllerEssEmergencyCapacityReserveImpl extends AbstractOpenemsCo private static final int reservSocMaxValue = 100; private final Logger log = LoggerFactory.getLogger(ControllerEssEmergencyCapacityReserveImpl.class); - private final StateMachine stateMachine = new StateMachine(State.NO_LIMIT); + private final EnergyScheduleHandler energyScheduleHandler; + private final StateMachine stateMachine = new StateMachine(State.UNDEFINED); private final RampFilter rampFilter = new RampFilter(); @Reference @@ -54,6 +62,9 @@ public class ControllerEssEmergencyCapacityReserveImpl extends AbstractOpenemsCo @Reference private Sum sum; + @Reference + private Meta meta; + @Reference private ManagedSymmetricEss ess; @@ -65,6 +76,10 @@ public ControllerEssEmergencyCapacityReserveImpl() { Controller.ChannelId.values(), // ControllerEssEmergencyCapacityReserve.ChannelId.values() // ); + this.energyScheduleHandler = buildEnergyScheduleHandler(// + () -> this.config.isReserveSocEnabled() // + ? this.config.reserveSoc() // + : null); } @Activate @@ -77,6 +92,7 @@ private void activate(ComponentContext context, Config config) { protected void modified(ComponentContext context, String id, String alias, boolean enabled) { super.modified(context, id, alias, enabled); this.updateConfig(this.config); + this.energyScheduleHandler.triggerReschedule("ControllerEssEmergencyCapacityReserveImpl::modified()"); } @Override @@ -161,8 +177,8 @@ private Context handleStateMachine() { if (socToUse == null || !maxApparentPower.isDefined()) { this.stateMachine.forceNextState(State.NO_LIMIT); } - - var context = new Context(this, this.sum, maxApparentPower.get(), socToUse, this.config.reserveSoc()); + var context = new Context(this, this.sum, maxApparentPower.get(), socToUse, this.config.reserveSoc(), + this.meta.getIsEssChargeFromGridAllowed()); try { this.stateMachine.run(context); @@ -191,4 +207,32 @@ private OptionalInt getLastValidSoc(IntegerReadChannel channel) { .mapToInt(Value::get) // .findFirst(); } + + /** + * Builds the {@link EnergyScheduleHandler}. + * + *

+ * This is public so that it can be used by the EnergyScheduler integration + * test. + * + * @param minSoc supplier for the configured minSoc + * @return a {@link EnergyScheduleHandler} + */ + public static EnergyScheduleHandler buildEnergyScheduleHandler(Supplier minSoc) { + return EnergyScheduleHandler.WithOnlyOneState.create() // + .setContextFunction(simContext -> minSoc.get() == null // + ? null // + : socToEnergy(simContext.ess().totalEnergy(), minSoc.get())) // + .setSimulator((simContext, period, energyFlow, minEnergy) -> { + if (minEnergy != null) { + energyFlow.setEssMaxDischarge(max(0, simContext.ess.getInitialEnergy() - minEnergy)); + } + }) // + .build(); + } + + @Override + public EnergyScheduleHandler getEnergyScheduleHandler() { + return this.energyScheduleHandler; + } } diff --git a/io.openems.edge.controller.ess.emergencycapacityreserve/src/io/openems/edge/controller/ess/emergencycapacityreserve/statemachine/AtReserveSocHandler.java b/io.openems.edge.controller.ess.emergencycapacityreserve/src/io/openems/edge/controller/ess/emergencycapacityreserve/statemachine/AtReserveSocHandler.java index cdf5d039757..ec4e2b3b7e8 100644 --- a/io.openems.edge.controller.ess.emergencycapacityreserve/src/io/openems/edge/controller/ess/emergencycapacityreserve/statemachine/AtReserveSocHandler.java +++ b/io.openems.edge.controller.ess.emergencycapacityreserve/src/io/openems/edge/controller/ess/emergencycapacityreserve/statemachine/AtReserveSocHandler.java @@ -23,8 +23,13 @@ protected State runAndGetNextState(Context context) throws OpenemsNamedException return State.BELOW_RESERVE_SOC; } + int reserveSocBuffer = switch (context.getLastActiveState()) { + case FORCE_CHARGE_GRID -> 1; + default -> 0; + }; + // SoC is under configured reserveSoC - if (soc > reserveSoc) { + if (soc > reserveSoc + reserveSocBuffer) { return State.ABOVE_RESERVE_SOC; } diff --git a/io.openems.edge.controller.ess.emergencycapacityreserve/src/io/openems/edge/controller/ess/emergencycapacityreserve/statemachine/BelowReserveSocHandler.java b/io.openems.edge.controller.ess.emergencycapacityreserve/src/io/openems/edge/controller/ess/emergencycapacityreserve/statemachine/BelowReserveSocHandler.java index f68c5ca26fa..ff1ffdd1ac5 100644 --- a/io.openems.edge.controller.ess.emergencycapacityreserve/src/io/openems/edge/controller/ess/emergencycapacityreserve/statemachine/BelowReserveSocHandler.java +++ b/io.openems.edge.controller.ess.emergencycapacityreserve/src/io/openems/edge/controller/ess/emergencycapacityreserve/statemachine/BelowReserveSocHandler.java @@ -15,9 +15,14 @@ protected State runAndGetNextState(Context context) throws OpenemsNamedException var reserveSoc = context.reserveSoc; int soc = context.soc; - // SoC is 4% under configured reserveSoC - if (soc <= reserveSoc - 4 || soc <= 0) { - return State.FORCE_CHARGE; + // SoC is atleast 2% under configured reserveSoC and gridcharging is enabled + if ((soc <= reserveSoc - 2) && context.isEssChargeFromGridAllowed) { + return State.FORCE_CHARGE_GRID; + } + + // Enter FORCE_CHARGE_PV sooner when grid charge is allowed + if (soc <= reserveSoc - 1 || soc <= 0) { + return State.FORCE_CHARGE_PV; } // SoC is greater then configured reserveSoC diff --git a/io.openems.edge.controller.ess.emergencycapacityreserve/src/io/openems/edge/controller/ess/emergencycapacityreserve/statemachine/Context.java b/io.openems.edge.controller.ess.emergencycapacityreserve/src/io/openems/edge/controller/ess/emergencycapacityreserve/statemachine/Context.java index 633cffcbe0d..68f2031ba43 100644 --- a/io.openems.edge.controller.ess.emergencycapacityreserve/src/io/openems/edge/controller/ess/emergencycapacityreserve/statemachine/Context.java +++ b/io.openems.edge.controller.ess.emergencycapacityreserve/src/io/openems/edge/controller/ess/emergencycapacityreserve/statemachine/Context.java @@ -3,10 +3,12 @@ import io.openems.edge.common.statemachine.AbstractContext; import io.openems.edge.common.sum.Sum; import io.openems.edge.controller.ess.emergencycapacityreserve.ControllerEssEmergencyCapacityReserve; +import io.openems.edge.controller.ess.emergencycapacityreserve.statemachine.StateMachine.State; public class Context extends AbstractContext { protected final Sum sum; + protected final boolean isEssChargeFromGridAllowed; /** * MaxApparentPower is guaranteed to be not-null in any State other than @@ -21,14 +23,16 @@ public class Context extends AbstractContext { + + @Override + protected State runAndGetNextState(Context context) throws OpenemsNamedException { + var reserveSoc = context.reserveSoc; + int soc = context.soc; + + // leave grid charge logic when grid charge gets disabled + if (!context.isEssChargeFromGridAllowed) { + if (soc <= reserveSoc - 1) { + return State.BELOW_RESERVE_SOC; + } + return State.AT_RESERVE_SOC; + } + + float targetPower; + + if (soc <= reserveSoc - 4 || soc <= 0) { + targetPower = context.maxApparentPower * -0.5f; + } else { + targetPower = context.maxApparentPower * -0.1f; + } + + // calculate target and ramp power + context.setTargetPower(targetPower); + context.setRampPower(context.maxApparentPower * 0.01); + + // SoC is greater or equals then configured reserveSoC or 100 + if (soc >= reserveSoc + 1 || soc == 100) { + return State.AT_RESERVE_SOC; + } + + return State.FORCE_CHARGE_GRID; + } + +} diff --git a/io.openems.edge.controller.ess.emergencycapacityreserve/src/io/openems/edge/controller/ess/emergencycapacityreserve/statemachine/ForceChargeHandler.java b/io.openems.edge.controller.ess.emergencycapacityreserve/src/io/openems/edge/controller/ess/emergencycapacityreserve/statemachine/ForceChargePvHandler.java similarity index 75% rename from io.openems.edge.controller.ess.emergencycapacityreserve/src/io/openems/edge/controller/ess/emergencycapacityreserve/statemachine/ForceChargeHandler.java rename to io.openems.edge.controller.ess.emergencycapacityreserve/src/io/openems/edge/controller/ess/emergencycapacityreserve/statemachine/ForceChargePvHandler.java index bd085edea49..8daafbe6aeb 100644 --- a/io.openems.edge.controller.ess.emergencycapacityreserve/src/io/openems/edge/controller/ess/emergencycapacityreserve/statemachine/ForceChargeHandler.java +++ b/io.openems.edge.controller.ess.emergencycapacityreserve/src/io/openems/edge/controller/ess/emergencycapacityreserve/statemachine/ForceChargePvHandler.java @@ -7,7 +7,7 @@ import io.openems.edge.common.sum.Sum; import io.openems.edge.controller.ess.emergencycapacityreserve.statemachine.StateMachine.State; -public class ForceChargeHandler extends StateHandler { +public class ForceChargePvHandler extends StateHandler { @Override protected State runAndGetNextState(Context context) throws OpenemsNamedException { @@ -20,12 +20,16 @@ protected State runAndGetNextState(Context context) throws OpenemsNamedException var reserveSoc = context.reserveSoc; int soc = context.soc; - // SoC is greater or equals then configured reserveSoC - if (soc >= reserveSoc) { + // SoC is greater or equals then configured reserveSoC or 100 + if (soc >= reserveSoc + 1 || soc == 100) { return State.AT_RESERVE_SOC; } - return State.FORCE_CHARGE; + if (soc <= reserveSoc - 2 && context.isEssChargeFromGridAllowed) { + return State.FORCE_CHARGE_GRID; + } + + return State.FORCE_CHARGE_PV; } /** diff --git a/io.openems.edge.controller.ess.emergencycapacityreserve/src/io/openems/edge/controller/ess/emergencycapacityreserve/statemachine/StateMachine.java b/io.openems.edge.controller.ess.emergencycapacityreserve/src/io/openems/edge/controller/ess/emergencycapacityreserve/statemachine/StateMachine.java index 362234ccf3e..ad0286d4054 100644 --- a/io.openems.edge.controller.ess.emergencycapacityreserve/src/io/openems/edge/controller/ess/emergencycapacityreserve/statemachine/StateMachine.java +++ b/io.openems.edge.controller.ess.emergencycapacityreserve/src/io/openems/edge/controller/ess/emergencycapacityreserve/statemachine/StateMachine.java @@ -2,6 +2,7 @@ import com.google.common.base.CaseFormat; +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.types.OptionsEnum; import io.openems.edge.common.statemachine.AbstractStateMachine; import io.openems.edge.common.statemachine.StateHandler; @@ -10,6 +11,11 @@ public class StateMachine extends AbstractStateMachine, OptionsEnum { + /** + * Start state for new StateMachine, never entered again. + */ + UNDEFINED(-1), // + /** * State if SoC is greater then configured reserve SoC. */ @@ -31,9 +37,15 @@ public enum State implements io.openems.edge.common.statemachine.State, O BELOW_RESERVE_SOC(4), // /** - * State if SoC is 4% under configured reserve SoC. + * State if SoC is 1% under configured reserve SoC. + */ + FORCE_CHARGE_PV(5), // + + /** + * State if SoC is 2 % under configured reserve SoC. */ - FORCE_CHARGE(5); + FORCE_CHARGE_GRID(6), // + ; private final int value; @@ -53,7 +65,7 @@ public String getName() { @Override public OptionsEnum getUndefined() { - return NO_LIMIT; + return UNDEFINED; } @Override @@ -62,25 +74,32 @@ public State[] getStates() { } } + private State lastActiveState = State.UNDEFINED; + + @Override + public void run(Context context) throws OpenemsNamedException { + if (!this.getPreviousState().equals(this.getCurrentState())) { + this.lastActiveState = this.getPreviousState(); + } + context.setLastActiveState(this.lastActiveState); + super.run(context); + } + public StateMachine(State initialState) { super(initialState); } @Override public StateHandler getStateHandler(State state) { - switch (state) { - case NO_LIMIT: - return new NoLimitHandler(); - case ABOVE_RESERVE_SOC: - return new AboveReserveSocHandler(); - case AT_RESERVE_SOC: - return new AtReserveSocHandler(); - case BELOW_RESERVE_SOC: - return new BelowReserveSocHandler(); - case FORCE_CHARGE: - return new ForceChargeHandler(); - } - throw new IllegalArgumentException("Unknown State [" + state + "]"); + return switch (state) { + case NO_LIMIT -> new NoLimitHandler(); + case ABOVE_RESERVE_SOC -> new AboveReserveSocHandler(); + case AT_RESERVE_SOC -> new AtReserveSocHandler(); + case BELOW_RESERVE_SOC -> new BelowReserveSocHandler(); + case FORCE_CHARGE_PV -> new ForceChargePvHandler(); + case FORCE_CHARGE_GRID -> new ForceChargeGridHandler(); + case UNDEFINED -> new UndefinedHandler(); + }; } } \ No newline at end of file diff --git a/io.openems.edge.controller.ess.emergencycapacityreserve/src/io/openems/edge/controller/ess/emergencycapacityreserve/statemachine/UndefinedHandler.java b/io.openems.edge.controller.ess.emergencycapacityreserve/src/io/openems/edge/controller/ess/emergencycapacityreserve/statemachine/UndefinedHandler.java new file mode 100644 index 00000000000..5290da716a5 --- /dev/null +++ b/io.openems.edge.controller.ess.emergencycapacityreserve/src/io/openems/edge/controller/ess/emergencycapacityreserve/statemachine/UndefinedHandler.java @@ -0,0 +1,37 @@ +package io.openems.edge.controller.ess.emergencycapacityreserve.statemachine; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.edge.common.statemachine.StateHandler; +import io.openems.edge.controller.ess.emergencycapacityreserve.statemachine.StateMachine.State; + +public class UndefinedHandler extends StateHandler { + + @Override + protected State runAndGetNextState(Context context) throws OpenemsNamedException { + if (context.soc != null && context.maxApparentPower != null) { + var reserveSoc = context.reserveSoc; + var soc = context.soc; + + if (soc < reserveSoc - 1) { + return State.FORCE_CHARGE_GRID; + } + + if (soc == reserveSoc - 1) { + return State.FORCE_CHARGE_PV; + } + + if (soc == reserveSoc) { + return State.AT_RESERVE_SOC; + } + + if (soc == reserveSoc + 1) { + return State.ABOVE_RESERVE_SOC; + } + + return State.NO_LIMIT; + } + + return State.UNDEFINED; + } + +} diff --git a/io.openems.edge.controller.ess.emergencycapacityreserve/test/io/openems/edge/controller/ess/emergencycapacityreserve/ControllerEssEmergencyCapacityReserveImplTest.java b/io.openems.edge.controller.ess.emergencycapacityreserve/test/io/openems/edge/controller/ess/emergencycapacityreserve/ControllerEssEmergencyCapacityReserveImplTest.java index 127acdc997d..2ab88d2be77 100644 --- a/io.openems.edge.controller.ess.emergencycapacityreserve/test/io/openems/edge/controller/ess/emergencycapacityreserve/ControllerEssEmergencyCapacityReserveImplTest.java +++ b/io.openems.edge.controller.ess.emergencycapacityreserve/test/io/openems/edge/controller/ess/emergencycapacityreserve/ControllerEssEmergencyCapacityReserveImplTest.java @@ -1,113 +1,111 @@ package io.openems.edge.controller.ess.emergencycapacityreserve; +import static io.openems.edge.common.sum.Sum.ChannelId.PRODUCTION_AC_ACTIVE_POWER; +import static io.openems.edge.common.sum.Sum.ChannelId.PRODUCTION_DC_ACTUAL_POWER; +import static io.openems.edge.controller.ess.emergencycapacityreserve.ControllerEssEmergencyCapacityReserve.ChannelId.DEBUG_RAMP_POWER; +import static io.openems.edge.controller.ess.emergencycapacityreserve.ControllerEssEmergencyCapacityReserve.ChannelId.DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS; +import static io.openems.edge.controller.ess.emergencycapacityreserve.ControllerEssEmergencyCapacityReserve.ChannelId.DEBUG_TARGET_POWER; +import static io.openems.edge.controller.ess.emergencycapacityreserve.ControllerEssEmergencyCapacityReserve.ChannelId.RANGE_OF_RESERVE_SOC_OUTSIDE_ALLOWED_VALUE; +import static io.openems.edge.controller.ess.emergencycapacityreserve.ControllerEssEmergencyCapacityReserve.ChannelId.STATE_MACHINE; +import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_LESS_OR_EQUALS; +import static io.openems.edge.ess.api.SymmetricEss.ChannelId.MAX_APPARENT_POWER; +import static io.openems.edge.ess.api.SymmetricEss.ChannelId.SOC; + import org.junit.Test; import io.openems.common.function.ThrowingRunnable; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.sum.DummySum; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyComponentManager; import io.openems.edge.common.test.DummyConfigurationAdmin; +import io.openems.edge.common.test.DummyMeta; import io.openems.edge.controller.ess.emergencycapacityreserve.statemachine.StateMachine.State; import io.openems.edge.controller.test.ControllerTest; import io.openems.edge.ess.test.DummyManagedSymmetricEss; public class ControllerEssEmergencyCapacityReserveImplTest { - private static final String CTRL_ID = "ctrlEmergencyCapacityReserve0"; - private static final String ESS_ID = "ess0"; - private static final String SUM_ID = "_sum"; - - private static final ChannelAddress STATE_MACHINE = new ChannelAddress(CTRL_ID, "StateMachine"); - private static final ChannelAddress DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS = new ChannelAddress(CTRL_ID, - "DebugSetActivePowerLessOrEquals"); - private static final ChannelAddress DEBUG_TARGET_POWER = new ChannelAddress(CTRL_ID, "DebugTargetPower"); - private static final ChannelAddress DEBUG_RAMP_POWER = new ChannelAddress(CTRL_ID, "DebugRampPower"); - - private static final ChannelAddress RANGE_OF_RESERVE_SOC_OUTSIDE_ALLOWED_VALUE = new ChannelAddress(CTRL_ID, - "RangeOfReserveSocOutsideAllowedValue"); - - private static final ChannelAddress ESS_MAX_APPARENT_POWER = new ChannelAddress(ESS_ID, "MaxApparentPower"); - private static final ChannelAddress ESS_SOC = new ChannelAddress(ESS_ID, "Soc"); - private static final ChannelAddress SET_ACTIVE_POWER_LESS_OR_EQUALS = new ChannelAddress(ESS_ID, - "SetActivePowerLessOrEquals"); - - private static final ChannelAddress PRODUCTION_DC_ACTUAL_POWER = new ChannelAddress(SUM_ID, - "ProductionDcActualPower"); - private static final ChannelAddress PRODUCTION_AC_ACTIVE_POWER = new ChannelAddress(SUM_ID, - "ProductionAcActivePower"); - @Test public void testReserveSocRange() throws Exception { new ControllerTest(new ControllerEssEmergencyCapacityReserveImpl()) // .addReference("componentManager", new DummyComponentManager()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("sum", new DummySum()) // - .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) // + .addReference("meta", new DummyMeta("_meta")) // + .addReference("ess", new DummyManagedSymmetricEss("ess0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setReserveSoc(20) // .setReserveSocEnabled(true) // .build()) // .next(new TestCase() // - .output(RANGE_OF_RESERVE_SOC_OUTSIDE_ALLOWED_VALUE, false)); + .output(RANGE_OF_RESERVE_SOC_OUTSIDE_ALLOWED_VALUE, false)) // + .deactivate(); new ControllerTest(new ControllerEssEmergencyCapacityReserveImpl()) // .addReference("componentManager", new DummyComponentManager()) // .addReference("cm", new DummyConfigurationAdmin()) // + .addReference("meta", new DummyMeta("_meta")) // .addReference("sum", new DummySum()) // - .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) // + .addReference("ess", new DummyManagedSymmetricEss("ess0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setReserveSoc(5) // .setReserveSocEnabled(true) // .build()) // .next(new TestCase() // - .output(RANGE_OF_RESERVE_SOC_OUTSIDE_ALLOWED_VALUE, false)); + .output(RANGE_OF_RESERVE_SOC_OUTSIDE_ALLOWED_VALUE, false)) // + .deactivate(); new ControllerTest(new ControllerEssEmergencyCapacityReserveImpl()) // .addReference("componentManager", new DummyComponentManager()) // .addReference("cm", new DummyConfigurationAdmin()) // + .addReference("meta", new DummyMeta("_meta")) // .addReference("sum", new DummySum()) // - .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) // + .addReference("ess", new DummyManagedSymmetricEss("ess0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setReserveSoc(4) // .setReserveSocEnabled(true) // .build()) // .next(new TestCase() // - .output(RANGE_OF_RESERVE_SOC_OUTSIDE_ALLOWED_VALUE, true)); + .output(RANGE_OF_RESERVE_SOC_OUTSIDE_ALLOWED_VALUE, true)) // + .deactivate(); new ControllerTest(new ControllerEssEmergencyCapacityReserveImpl()) // .addReference("componentManager", new DummyComponentManager()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("sum", new DummySum()) // - .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) // + .addReference("meta", new DummyMeta("_meta")) // + .addReference("ess", new DummyManagedSymmetricEss("ess0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setReserveSoc(100) // .setReserveSocEnabled(true) // .build()) // .next(new TestCase() // - .output(RANGE_OF_RESERVE_SOC_OUTSIDE_ALLOWED_VALUE, false)); + .output(RANGE_OF_RESERVE_SOC_OUTSIDE_ALLOWED_VALUE, false)) // + .deactivate(); new ControllerTest(new ControllerEssEmergencyCapacityReserveImpl()) // .addReference("componentManager", new DummyComponentManager()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("sum", new DummySum()) // - .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) // + .addReference("meta", new DummyMeta("_meta")) // + .addReference("ess", new DummyManagedSymmetricEss("ess0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setReserveSoc(101) // .setReserveSocEnabled(true) // .build()) // .next(new TestCase() // - .output(RANGE_OF_RESERVE_SOC_OUTSIDE_ALLOWED_VALUE, true)); + .output(RANGE_OF_RESERVE_SOC_OUTSIDE_ALLOWED_VALUE, true)) // + .deactivate(); } @Test @@ -116,19 +114,20 @@ public void testReachTargetPower() throws Exception { .addReference("componentManager", new DummyComponentManager()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("sum", new DummySum()) // - .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) // + .addReference("meta", new DummyMeta("_meta")) // + .addReference("ess", new DummyManagedSymmetricEss("ess0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setReserveSoc(20) // .setReserveSocEnabled(true) // .build()) // .next(new TestCase() // - .input(ESS_SOC, 22) // - .input(ESS_MAX_APPARENT_POWER, 10000) // - .output(STATE_MACHINE, State.NO_LIMIT)) // + .input("ess0", SOC, 22) // + .input("ess0", MAX_APPARENT_POWER, 10000) // + .output(STATE_MACHINE, State.UNDEFINED)) // .next(new TestCase() // - .input(ESS_SOC, 21) // + .input("ess0", SOC, 21) // .output(STATE_MACHINE, State.NO_LIMIT)); // var maxApparentPower = 10000; @@ -141,14 +140,16 @@ public void testReachTargetPower() throws Exception { result -= rampPower; } - controllerTest.next(new TestCase().input(ESS_SOC, 21) // + controllerTest.next(new TestCase().input("ess0", SOC, 21) // .input(PRODUCTION_DC_ACTUAL_POWER, 0) // .output(STATE_MACHINE, State.ABOVE_RESERVE_SOC) // - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, result) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, result) // .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, result) // .output(DEBUG_TARGET_POWER, targetPower.floatValue()) // ); } + + controllerTest.deactivate(); } @Test @@ -157,41 +158,43 @@ public void testAllStates() throws Exception { .addReference("componentManager", new DummyComponentManager()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("sum", new DummySum()) // - .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) // + .addReference("meta", new DummyMeta("_meta")) // + .addReference("ess", new DummyManagedSymmetricEss("ess0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setReserveSoc(20) // .setReserveSocEnabled(true) // .build()) .next(new TestCase() // - .input(ESS_SOC, 22) // - .input(ESS_MAX_APPARENT_POWER, 10000) // - .output(STATE_MACHINE, State.NO_LIMIT)) // + .input("ess0", SOC, 22) // + .input("ess0", MAX_APPARENT_POWER, 10000) // + .output(STATE_MACHINE, State.UNDEFINED)) // .next(new TestCase() // - .input(ESS_SOC, 21) // + .input("ess0", SOC, 21) // .output(STATE_MACHINE, State.NO_LIMIT)) // .next(new TestCase() // - .input(ESS_SOC, 20) // + .input("ess0", SOC, 20) // .output(STATE_MACHINE, State.ABOVE_RESERVE_SOC)) // .next(new TestCase() // - .input(ESS_SOC, 19) // + .input("ess0", SOC, 19) // .output(STATE_MACHINE, State.AT_RESERVE_SOC)) // .next(new TestCase() // - .input(ESS_SOC, 16) // + .input("ess0", SOC, 16) // .output(STATE_MACHINE, State.BELOW_RESERVE_SOC)) // .next(new TestCase() // - .input(ESS_SOC, 20) // - .output(STATE_MACHINE, State.FORCE_CHARGE)) // - .next(new TestCase() // - .input(ESS_SOC, 21) // + .input("ess0", SOC, 21) // + .output(STATE_MACHINE, State.FORCE_CHARGE_PV)) // + .next(new TestCase() // overcharges by 1% + .input("ess0", SOC, 22) // .output(STATE_MACHINE, State.AT_RESERVE_SOC)) // .next(new TestCase() // - .input(ESS_SOC, 22) // + .input("ess0", SOC, 22) // .output(STATE_MACHINE, State.ABOVE_RESERVE_SOC)) // .next(new TestCase() // - .input(ESS_SOC, 22) // - .output(STATE_MACHINE, State.NO_LIMIT)); + .input("ess0", SOC, 22) // + .output(STATE_MACHINE, State.NO_LIMIT)) // + .deactivate(); } @Test @@ -200,23 +203,25 @@ public void testIncreaseRampByNoLimitState() throws Exception { .addReference("componentManager", new DummyComponentManager()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("sum", new DummySum()) // - .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) // + .addReference("meta", new DummyMeta("_meta")) // + .addReference("ess", new DummyManagedSymmetricEss("ess0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setReserveSoc(20) // .setReserveSocEnabled(true) // .build()) // .next(new TestCase() // - .input(ESS_SOC, 80)) // + .input("ess0", SOC, 80)) // .next(new TestCase() // - .input(ESS_SOC, 80) // - .input(ESS_MAX_APPARENT_POWER, 10000) // + .input("ess0", SOC, 80) // + .input("ess0", MAX_APPARENT_POWER, 10000) // .output(STATE_MACHINE, State.NO_LIMIT) // - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, null) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, null) // .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, null) // .output(DEBUG_TARGET_POWER, 10000f) // - .output(DEBUG_RAMP_POWER, 100f)); + .output(DEBUG_RAMP_POWER, 100f)) // + .deactivate(); } @Test @@ -225,65 +230,67 @@ public void testDecreaseRampByAboveReserveSocState() throws Exception { .addReference("componentManager", new DummyComponentManager()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("sum", new DummySum()) // - .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) // + .addReference("meta", new DummyMeta("_meta")) // + .addReference("ess", new DummyManagedSymmetricEss("ess0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setReserveSoc(20) // .setReserveSocEnabled(true) // .build()) // .next(new TestCase() // - .input(ESS_SOC, 22) // - .input(ESS_MAX_APPARENT_POWER, 10000) // - .output(STATE_MACHINE, State.NO_LIMIT)) // + .input("ess0", SOC, 22) // + .input("ess0", MAX_APPARENT_POWER, 10000) // + .output(STATE_MACHINE, State.UNDEFINED)) // .next(new TestCase() // - .input(ESS_SOC, 21) // + .input("ess0", SOC, 21) // .output(STATE_MACHINE, State.NO_LIMIT)// - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, null) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, null) // .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, null)) // // to reach 50% of maxApparentPower .next(new TestCase() // - .input(ESS_SOC, 21) // + .input("ess0", SOC, 21) // .input(PRODUCTION_DC_ACTUAL_POWER, 0) // .output(STATE_MACHINE, State.ABOVE_RESERVE_SOC)// - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9900)// + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9900)// .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9900)) // .next(new TestCase() // - .input(ESS_SOC, 21) // - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9800)// + .input("ess0", SOC, 21) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9800)// .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9800)) // .next(new TestCase() // - .input(ESS_SOC, 21) // - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9700)// + .input("ess0", SOC, 21) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9700)// .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9700)) // // to reach is DC-PV .next(new TestCase() // - .input(ESS_SOC, 21) // + .input("ess0", SOC, 21) // .input(PRODUCTION_DC_ACTUAL_POWER, 6000) // - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9600)// + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9600)// .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9600)) .next(new TestCase() // - .input(ESS_SOC, 21) // + .input("ess0", SOC, 21) // .input(PRODUCTION_DC_ACTUAL_POWER, 10000) // - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9700)// + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9700)// .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9700)) .next(new TestCase() // - .input(ESS_SOC, 21) // + .input("ess0", SOC, 21) // .input(PRODUCTION_DC_ACTUAL_POWER, 10000) // - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9800)// + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9800)// .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9800)) .next(new TestCase() // - .input(ESS_SOC, 21) // + .input("ess0", SOC, 21) // .input(PRODUCTION_DC_ACTUAL_POWER, 6000) // - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9700)// + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9700)// .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9700)) .next(new TestCase() // - .input(ESS_SOC, 21) // + .input("ess0", SOC, 21) // .input(PRODUCTION_DC_ACTUAL_POWER, 6000) // - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9600)// - .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9600)); + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9600)// + .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9600)) // + .deactivate(); } @Test @@ -292,41 +299,43 @@ public void testDecreaseRampByAtReserveSocState() throws Exception { .addReference("componentManager", new DummyComponentManager()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("sum", new DummySum()) // - .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) // + .addReference("meta", new DummyMeta("_meta")) // + .addReference("ess", new DummyManagedSymmetricEss("ess0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setReserveSoc(20) // .setReserveSocEnabled(true) // .build()) // .next(new TestCase() // - .input(ESS_SOC, 22) // - .input(ESS_MAX_APPARENT_POWER, 10000) // - .output(STATE_MACHINE, State.NO_LIMIT)) // + .input("ess0", SOC, 22) // + .input("ess0", MAX_APPARENT_POWER, 10000) // + .output(STATE_MACHINE, State.UNDEFINED)) // .next(new TestCase() // - .input(ESS_SOC, 20) // + .input("ess0", SOC, 20) // .output(STATE_MACHINE, State.NO_LIMIT)// - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, null)// + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, null)// .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, null)) // .next(new TestCase() // - .input(ESS_SOC, 20) // + .input("ess0", SOC, 20) // .output(STATE_MACHINE, State.ABOVE_RESERVE_SOC)// - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9900)// + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9900)// .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9900)) // .next(new TestCase() // - .input(ESS_SOC, 20) // + .input("ess0", SOC, 20) // .input(PRODUCTION_DC_ACTUAL_POWER, 0) // .output(STATE_MACHINE, State.AT_RESERVE_SOC)// - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9800)// + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9800)// .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9800)) // .next(new TestCase() // - .input(ESS_SOC, 20) // - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9700)// + .input("ess0", SOC, 20) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9700)// .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9700)) // .next(new TestCase() // - .input(ESS_SOC, 20) // - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9600)// - .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9600)); + .input("ess0", SOC, 20) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9600)// + .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9600)) // + .deactivate(); } @Test @@ -335,45 +344,48 @@ public void testDecreaseRampByUnderReserveSocState() throws Exception { .addReference("componentManager", new DummyComponentManager()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("sum", new DummySum()) // - .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) // + .addReference("meta", new DummyMeta("_meta")) // + .addReference("ess", new DummyManagedSymmetricEss("ess0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setReserveSoc(20) // .setReserveSocEnabled(true) // .build()) // .next(new TestCase() // - .input(ESS_SOC, 22) // - .input(ESS_MAX_APPARENT_POWER, 10000) // - .output(STATE_MACHINE, State.NO_LIMIT)) // + .input("ess0", SOC, 22) // + .input("ess0", MAX_APPARENT_POWER, 10000) // + .output(STATE_MACHINE, State.UNDEFINED)) // .next(new TestCase() // - .input(ESS_SOC, 19) // + .input("ess0", SOC, 19) // .output(STATE_MACHINE, State.NO_LIMIT)// - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, null)// + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, null)// .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, null)) // .next(new TestCase() // - .input(ESS_SOC, 19) // + .input("ess0", SOC, 19) // .output(STATE_MACHINE, State.ABOVE_RESERVE_SOC)// - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9900)// + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9900)// .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9900)) // .next(new TestCase() // - .input(ESS_SOC, 19) // + .input("ess0", SOC, 19) // .output(STATE_MACHINE, State.AT_RESERVE_SOC)// - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9800)// + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9800)// .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9800)) // .next(new TestCase() // - .input(ESS_SOC, 19) // + .input("ess0", SOC, 19) // .output(STATE_MACHINE, State.BELOW_RESERVE_SOC)// - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9300)// + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9300)// .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9300)) // .next(new TestCase() // - .input(ESS_SOC, 19) // - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 8800)// - .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 8800)) // + .input("ess0", SOC, 19) // + .output(STATE_MACHINE, State.FORCE_CHARGE_PV)// + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9200)// + .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9200)) // .next(new TestCase() // - .input(ESS_SOC, 19) // - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 8300)// - .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 8300)); + .input("ess0", SOC, 19) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9100)// + .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9100)) // + .deactivate(); } @Test @@ -382,62 +394,64 @@ public void testDecreaseRampByForceStartChargeState() throws Exception { .addReference("componentManager", new DummyComponentManager()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("sum", new DummySum()) // - .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) // + .addReference("meta", new DummyMeta("_meta")) // + .addReference("ess", new DummyManagedSymmetricEss("ess0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setReserveSoc(20) // .setReserveSocEnabled(true) // .build()) // .next(new TestCase() // - .input(ESS_SOC, 22) // - .input(ESS_MAX_APPARENT_POWER, 10000) // - .output(STATE_MACHINE, State.NO_LIMIT)) // + .input("ess0", SOC, 22) // + .input("ess0", MAX_APPARENT_POWER, 10000) // + .output(STATE_MACHINE, State.UNDEFINED)) // .next(new TestCase() // - .input(ESS_SOC, 16) // + .input("ess0", SOC, 16) // .output(STATE_MACHINE, State.NO_LIMIT)// - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, null)// + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, null)// .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, null)) // .next(new TestCase() // - .input(ESS_SOC, 16) // + .input("ess0", SOC, 16) // .output(STATE_MACHINE, State.ABOVE_RESERVE_SOC)// - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9900)// + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9900)// .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9900)) // .next(new TestCase() // - .input(ESS_SOC, 16) // + .input("ess0", SOC, 16) // .output(STATE_MACHINE, State.AT_RESERVE_SOC)// - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9800)// + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9800)// .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9800)) // .next(new TestCase() // - .input(ESS_SOC, 16) // + .input("ess0", SOC, 16) // .output(STATE_MACHINE, State.BELOW_RESERVE_SOC)// - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9300)// + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9300)// .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9300)) // .next(new TestCase() // - .input(ESS_SOC, 16) // + .input("ess0", SOC, 16) // .input(PRODUCTION_AC_ACTIVE_POWER, 100) // - .output(STATE_MACHINE, State.FORCE_CHARGE)// - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9200)// + .output(STATE_MACHINE, State.FORCE_CHARGE_PV)// + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9200)// .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9200)) // .next(new TestCase() // - .input(ESS_SOC, 19) // - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9100)// + .input("ess0", SOC, 19) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9100)// .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9100)) // .next(new TestCase() // - .input(ESS_SOC, 21) // - .output(STATE_MACHINE, State.FORCE_CHARGE) // - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9000) // + .input("ess0", SOC, 21) // + .output(STATE_MACHINE, State.FORCE_CHARGE_PV) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9000) // .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9000)) // - .next(new TestCase() // - .input(ESS_SOC, 21) // + .next(new TestCase() // has to overcharge by 2% + .input("ess0", SOC, 22) // .output(STATE_MACHINE, State.AT_RESERVE_SOC) // - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 8900) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 8900) // .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 8900)) // .next(new TestCase() // - .input(ESS_SOC, 21) // + .input("ess0", SOC, 21) // .output(STATE_MACHINE, State.ABOVE_RESERVE_SOC) // - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 8800)// - .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 8800)); + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 8800)// + .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 8800)) // + .deactivate(); } @Test @@ -451,29 +465,31 @@ public void testUndefinedSoc() throws Exception { .addReference("componentManager", new DummyComponentManager()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("sum", new DummySum()) // - .addReference("ess", new DummyManagedSymmetricEss(ESS_ID) // + .addReference("meta", new DummyMeta("_meta")) // + .addReference("ess", new DummyManagedSymmetricEss("ess0") // .withMaxApparentPower(10000)) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setReserveSoc(20) // .setReserveSocEnabled(true) // .build()) // .next(new TestCase() // .onAfterProcessImage(sleep) // - .output(STATE_MACHINE, State.NO_LIMIT)) // + .output(STATE_MACHINE, State.UNDEFINED)) // .next(new TestCase() // .onAfterProcessImage(sleep) // - .input(ESS_SOC, 16)) // + .input("ess0", SOC, 16)) // .next(new TestCase() // .onAfterProcessImage(sleep) // .output(STATE_MACHINE, State.ABOVE_RESERVE_SOC))// .next(new TestCase() // .onAfterProcessImage(sleep) // - .input(ESS_SOC, null)) // + .input("ess0", SOC, null)) // .next(new TestCase() // .onAfterProcessImage(sleep) // - .output(STATE_MACHINE, State.BELOW_RESERVE_SOC)); + .output(STATE_MACHINE, State.BELOW_RESERVE_SOC)) // + .deactivate(); } @Test @@ -482,18 +498,19 @@ public void testIncreaseRampToMaxApparentPower() throws Exception { .addReference("componentManager", new DummyComponentManager()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("sum", new DummySum()) // - .addReference("ess", new DummyManagedSymmetricEss(ESS_ID) // + .addReference("meta", new DummyMeta("_meta")) // + .addReference("ess", new DummyManagedSymmetricEss("ess0") // .withMaxApparentPower(10000)) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setReserveSoc(20) // .setReserveSocEnabled(true) // .build()) // .next(new TestCase() // - .output(STATE_MACHINE, State.NO_LIMIT)) // + .output(STATE_MACHINE, State.UNDEFINED)) // .next(new TestCase() // - .input(ESS_SOC, 21)) // + .input("ess0", SOC, 21)) // .next(new TestCase() // .output(STATE_MACHINE, State.ABOVE_RESERVE_SOC) // .output(DEBUG_TARGET_POWER, 5000f) // @@ -510,7 +527,7 @@ public void testIncreaseRampToMaxApparentPower() throws Exception { .output(DEBUG_RAMP_POWER, 100f) // .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9700)) // .next(new TestCase() // - .input(ESS_SOC, 22)) // + .input("ess0", SOC, 22)) // .next(new TestCase() // .output(STATE_MACHINE, State.NO_LIMIT) // .output(DEBUG_TARGET_POWER, 10000f) // @@ -531,13 +548,108 @@ public void testIncreaseRampToMaxApparentPower() throws Exception { .output(DEBUG_TARGET_POWER, 10000f) // .output(DEBUG_RAMP_POWER, 100f) // .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, null) // - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, null)) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, null)) // .next(new TestCase() // .output(STATE_MACHINE, State.NO_LIMIT) // .output(DEBUG_TARGET_POWER, 10000f) // .output(DEBUG_RAMP_POWER, 100f) // .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, null) // - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, null)); + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, null)) // + .deactivate(); } + @Test + public void testGridChargingOn() throws Exception { + new ControllerTest(new ControllerEssEmergencyCapacityReserveImpl()) // + .addReference("componentManager", new DummyComponentManager()) // + .addReference("cm", new DummyConfigurationAdmin()) // + .addReference("sum", new DummySum()) // + .addReference("meta", new DummyMeta("_meta") // + .withIsEssChargeFromGridAllowed(true)) // + .addReference("ess", new DummyManagedSymmetricEss("ess0") // + .withMaxApparentPower(10000)) // + .activate(MyConfig.create() // + .setId("ctrl0") // + .setEssId("ess0") // + .setReserveSoc(20) // + .setReserveSocEnabled(true) // + .build()) // + // From Above + .next(new TestCase() // + .input("ess0", SOC, 22) // + .input("ess0", MAX_APPARENT_POWER, 10000) // + .output(STATE_MACHINE, State.UNDEFINED)) // + .next(new TestCase() // + .input("ess0", SOC, 19) // + .output(STATE_MACHINE, State.NO_LIMIT)// + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, null)// + .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, null)) // + .next(new TestCase() // + .input("ess0", SOC, 19) // + .output(STATE_MACHINE, State.ABOVE_RESERVE_SOC)// + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9900)// + .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9900)) // + .next(new TestCase() // + .input("ess0", SOC, 19) // + .output(STATE_MACHINE, State.AT_RESERVE_SOC)// + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9800)// + .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9800)) // + .next(new TestCase() // + .input("ess0", SOC, 19) // + .output(STATE_MACHINE, State.BELOW_RESERVE_SOC)// + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9300)// + .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9300)) // + .next(new TestCase() // + .input("ess0", SOC, 18).output(STATE_MACHINE, State.FORCE_CHARGE_PV) + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9200)// + .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9200) // + )// + .next(new TestCase()// + .input("ess0", SOC, 18).output(STATE_MACHINE, State.FORCE_CHARGE_GRID) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9100)// + .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9100) // + )// + .next(new TestCase()// + .input("ess0", SOC, 18)// + .output(STATE_MACHINE, State.FORCE_CHARGE_GRID) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9000)// + .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9000) // + ) + // From Below + .next(new TestCase()// + .input("ess0", SOC, 15).output(STATE_MACHINE, State.FORCE_CHARGE_GRID) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 8900)// + .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 8900) // + ) + // let ramp run its course + .next(new TestCase()// + .input("ess0", SOC, 14), // + 200) + // active power now 50% of max + .next(new TestCase()// + .input("ess0", SOC, 14).output(STATE_MACHINE, State.FORCE_CHARGE_GRID) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -5000)// + .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, -5000) // + )// + .next(new TestCase()// + .input("ess0", SOC, 17)// + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -4900)// + .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, -4900) // + ) + // let ramp run its course + .next(new TestCase()// + .input("ess0", SOC, 17), // + 100) + // active power now 10% of max + .next(new TestCase()// + .input("ess0", SOC, 17).output(STATE_MACHINE, State.FORCE_CHARGE_GRID) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -1000)// + .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, -1000) // + ) + // Still in Force Charge Grid (PV only form above) + .next(new TestCase()// + .input("ess0", SOC, 19)// + .output(STATE_MACHINE, State.FORCE_CHARGE_GRID) // + ); + } } diff --git a/io.openems.edge.controller.ess.emergencycapacityreserve/test/io/openems/edge/controller/ess/emergencycapacityreserve/statemachine/ForceChargeHandlerTest.java b/io.openems.edge.controller.ess.emergencycapacityreserve/test/io/openems/edge/controller/ess/emergencycapacityreserve/statemachine/ForceChargeHandlerTest.java index 7de4ebc5318..872cc283e4d 100644 --- a/io.openems.edge.controller.ess.emergencycapacityreserve/test/io/openems/edge/controller/ess/emergencycapacityreserve/statemachine/ForceChargeHandlerTest.java +++ b/io.openems.edge.controller.ess.emergencycapacityreserve/test/io/openems/edge/controller/ess/emergencycapacityreserve/statemachine/ForceChargeHandlerTest.java @@ -1,6 +1,6 @@ package io.openems.edge.controller.ess.emergencycapacityreserve.statemachine; -import static io.openems.edge.controller.ess.emergencycapacityreserve.statemachine.ForceChargeHandler.getAcPvProduction; +import static io.openems.edge.controller.ess.emergencycapacityreserve.statemachine.ForceChargePvHandler.getAcPvProduction; import static org.junit.Assert.assertEquals; import org.junit.Test; diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/.classpath b/io.openems.edge.controller.ess.fastfrequencyreserve/.classpath new file mode 100644 index 00000000000..b4cffd0fe60 --- /dev/null +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/.classpath @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/.gitignore b/io.openems.edge.controller.ess.fastfrequencyreserve/.gitignore new file mode 100644 index 00000000000..c2b941a96de --- /dev/null +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/.gitignore @@ -0,0 +1,2 @@ +/bin_test/ +/generated/ diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/.project b/io.openems.edge.controller.ess.fastfrequencyreserve/.project new file mode 100644 index 00000000000..0140daa493d --- /dev/null +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/.project @@ -0,0 +1,23 @@ + + + io.openems.edge.controller.ess.fastfrequencyreserve + + + + + + org.eclipse.jdt.core.javabuilder + + + + + bndtools.core.bndbuilder + + + + + + org.eclipse.jdt.core.javanature + bndtools.core.bndnature + + diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/.settings/org.eclipse.core.resources.prefs b/io.openems.edge.controller.ess.fastfrequencyreserve/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 00000000000..896a9a53a53 --- /dev/null +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding/=UTF-8 \ No newline at end of file diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/bnd.bnd b/io.openems.edge.controller.ess.fastfrequencyreserve/bnd.bnd new file mode 100644 index 00000000000..bee07090a70 --- /dev/null +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/bnd.bnd @@ -0,0 +1,16 @@ +Bundle-Name: OpenEMS Edge Controller Fast Frequency Reserve +Bundle-Vendor: FENECON GmbH +Bundle-License: https://opensource.org/licenses/EPL-2.0 +Bundle-Version: 1.0.0.${tstamp} + +-buildpath: \ + ${buildpath},\ + Java-WebSocket,\ + io.openems.common,\ + io.openems.edge.common,\ + io.openems.edge.controller.api,\ + io.openems.edge.ess.api,\ + io.openems.edge.meter.api,\ + +-testpath: \ + ${testpath} \ No newline at end of file diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/doc/statemachine.md b/io.openems.edge.controller.ess.fastfrequencyreserve/doc/statemachine.md new file mode 100644 index 00000000000..23c71c2221a --- /dev/null +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/doc/statemachine.md @@ -0,0 +1,18 @@ +# State-Machine + +```mermaid +graph TD +start --> Undefined +Undefined --> |condition: Inside set time, task : Charge to maintain soc| PreActivateState +PreActivateState --> |condition: Outside set time, task : do nothing| Undefined +PreActivateState --> |condition: grid freq > freqlimit, task : setpower 0Watt| ActivationTime +ActivationTime --> |condition: 1.7 sec, task : discharge setActivepower| SupportDuration +SupportDuration --> |condition: 30 sec, task : discharge setActivepower| DeactivationTime +DeactivationTime -->|condition: 1.7 sec, task : setpower 0Watt| BufferedTime +BufferedTime --> |condition: 10 sec, task : setpower for 0Watt| BufferedSupportTime +BufferedSupportTime --> |condition: 15 min, Charge to maintain soc| RecoveryTime +RecoveryTime --> PreActivateState +RecoveryTime -->|condition: Outside set time, task : do nothing| Undefined +``` + +View using Mermaid, e.g. https://mermaid-js.github.io/mermaid-live-editor \ No newline at end of file diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/readme.adoc b/io.openems.edge.controller.ess.fastfrequencyreserve/readme.adoc new file mode 100644 index 00000000000..028257dcea9 --- /dev/null +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/readme.adoc @@ -0,0 +1,333 @@ += ESS Fast Frequency Reserve + +== 1.1 Introduction + +In electricity networks, the Fast Frequency Reserve (FFR) controller is providing power available to the system operator within a short interval to meet demand in case of a frequency drop, i.e. in case a generator goes down or there is another disruption to the supply. More details on link:https://en.wikipedia.org/wiki/Operating_reserve[Wikipedia]. + +This controller helps the Energy Storage System (ESS) to provide power, essentially battery discharge, when the measured "Grid frequency" is lower than that of a defined "Frequency limit". + +== 1.2 Controller Parameters + +- **mode**: mode of the controller, On or Off? +- **id**: the id for the controller +- **alias**: Alias for the controller +- **enabled**: enabled or not? +- **meterId**: the id of the meter +- **essId**: the id of the Ess +- **batteryInverterId**: the id of the battery inverter +- **preActivationTime**: A time before the activation time for charging the system(min). +- **schedule**: scheduling of the controller, via JSON see below for the example + +=== 1.2.1 The Example Schedule-JSON + +[source,json] +---- +[ + { + "startTimestamp": 1684792800, + "duration": 86400, + "dischargePowerSetPoint": 92000, + "frequencyLimit": 50000, + "activationRunTime": "LONG_ACTIVATION_RUN", + "supportDuration": "LONG_SUPPORT_DURATION" + }, + { + "startTimestamp": 1684879200, + "duration": 86400, + "dischargePowerSetPoint": 6000, + "frequencyLimit": 50000, + "activationRunTime": "LONG_ACTIVATION_RUN", + "supportDuration": "LONG_SUPPORT_DURATION" + } +] +---- + +=== 1.2.2 JSON Element details + +- `StartTimeStamp`: When the controller should be activated. +- `Duration`: How long is the controller to be activated? +- `frequency limit`: The controller continuously monitors and checks whether a Frequency limit or threshold is less than the +measured grid frequency. +- `DischargePower`: The Ess discharges from the batteries when generating capacity. +- `activationRunTime`: The time in milliseconds required for the reserve to fully activate. Short(700 ms) or Medium(1000 ms) or Long(1300 ms) activation Time. +- `supportDuration`: The time in milliseconds for which the reserve should continue providing support after the frequency has stabilized. Short(5 seconds) or Long(30 seconds) support duration. + +=== 1.2.3 Explanation of the Schedule +The Schedule JSON activates FFR for a full day (86400 seconds or 24 hours) with the following parameters: + +1. Schedule for 23rd May 2023 00:00:00 to 24th May: + - *Threshold frequency:* 49700 mHz + - *Discharge power:* 92000 W + - *Long activation time:* 1.3 seconds + - *Support duration:* 30 seconds + +2. Following Schedule for 24th May 2023 00:00:00 to 25th May: + - *Threshold frequency:* 49700 mHz + - *Discharge power:* 52000 W + - *Long activation time:* 1.3 seconds + - *Support duration:* 30 seconds + + +== 2.1 REST API for updating Fast Frequency Reserve controllers schedule locally + +note : The controller/ App should be activated to update the schedule, which can be done using online monitoring or apache felix. + +== 2.1.1 Overview + +This REST API allows you to update FFR schedule for the specified edge device. The API endpoint takes a JSON payload that specifies the schedule, including the start time, duration, discharge power set point, frequency limit, activation runtime, and support duration. + +== 2.1.2 Endpoint + +- *URL*: http://:8084/jsonrpc +- *Method*: POST +- *Content-Type*: application/json +- *Authorization*: Basic Authentication, username: x, password: owner + +== 2.1.3 Body + +The request body must be a JSON object with the following structure: + +[source,json] +---- +{ + "method": "componentJsonApi", + "params": { + "componentId": "ctrlFastFreqReserve0", + "payload": { + "method": "setActivateFastFreqReserve", + "params": { + "id": "edge0", + "schedule": [ + { + "startTimestamp": 1701871562, + "duration": 999, + "dischargePowerSetPoint": 6000, + "frequencyLimit": 502000, + "activationRunTime": "LONG_ACTIVATION_RUN", + "supportDuration": "LONG_SUPPORT_DURATION" + } + ] + } + } + } +} +---- + +== 2.1.4 Request Parameters + +The request body for this REST API call is a JSON object with the following parameters: + +- *method*: The specific method to call within the component. In this case, it is `componentJsonApi`. +- *params*: The parameters associated with the method call. +- *componentId*: The unique identifier of the component that is receiving the request. +- *payload*: The specific data being sent to the component. See below + +=== 2.1.5 Payload Parameters + +Within the payload parameter, there is another JSON object that specifies the details of the activation request: + +- *method*: The method to call within the component to handle the activation request. In this case, it is `setActivateFastFreqReserve`. +- *params*: The parameters associated with the `setActivateFastFreqReserve` method. +- *id*: The unique identifier of the edge device for which the activation is being requested, locally is always `edge0`. +- *schedule*: An array of schedule items that define the activation pattern for the reserve. + +=== 2.1.6 Schedule Item Parameters + +Each schedule item within the schedule array specifies a specific activation period: + +- *startTimestamp*: The unix time stamp when the FFR should start activating. +- *duration*: The duration in milliseconds for which the reserve should remain active. +- *dischargePowerSetPoint*: The maximum power in kilowatts that the reserve should discharge during activation. +- *frequencyLimit*: The frequency threshold below which the reserve should be activated. +- *activationRunTime*: The time in milliseconds required for the reserve to fully activate. +- *supportDuration*: The time in milliseconds for which the reserve should continue providing support after the frequency has stabilized. + +=== 2.1.7 Example Python code + +[source,python] +---- +import requests +import json +from requests.auth import HTTPBasicAuth + +# API URL +url = 'http://10.0.10.178:8084/jsonrpc' + +# Authentication +auth = HTTPBasicAuth('x', 'owner') + +# Request headers +headers = { + 'Content-Type': 'application/json', +} + +# Request payload +payload = { + 'jsonrpc': '2.0', + 'id': '00000000-0000-0000-0000-000000000000', + 'method': 'componentJsonApi', + 'params': { + 'componentId': 'ctrlFastFreqReserve0', + 'payload': { + 'method': 'setActivateFastFreqReserve', + 'params': { + 'id': 'edge0', + 'schedule': [ + { + 'startTimestamp': 1701871562, + 'duration': 999, + 'dischargePowerSetPoint': 6000, + 'frequencyLimit': 502000, + 'activationRunTime': 'LONG_ACTIVATION_RUN', + 'supportDuration': 'LONG_SUPPORT_DURATION' + } + ] + } + } + } +} + +# Make the request +response = requests.post(url, auth=auth, headers=headers, json=payload) + +# Print the response +print(response.json()) +---- + + +== 3.1 REST API for Activating Fast Frequency Reserve controllers schedule using Backend +note : The controller/ App should be activated to update the schedule, which can be done using online monitoring or apache felix. + +== 3.1.1 Overview + +This REST API allows you to update FFR for a specific edge device. The API endpoint takes a JSON payload that updates activation schedule, including the start time, duration, discharge power set point, frequency limit, activation runtime, and support duration. + +== 3.1.2 Endpoint + +- *URL*: https://femecon.de/fems/rest/jsonrpc +- *Method*: POST +- *Content-Type*: application/json +- *Authorization*: Basic Authentication, username:foo.com, password:**** + +== 3.1.3 Body + +The request body must be a JSON object with the following structure: + +[source,json] +---- +{ + "method": "edgeRpc", + "params": { + "edgeId": "fems3734", + "payload": { + "method": "componentJsonApi", + "params": { + "componentId": "ctrlFastFreqReserve0", + "payload": { + "method": "setActivateFastFreqReserve", + "params": { + "id": "edge3734", + "schedule": [ + { + "startTimestamp": "1701767477", + "duration": "11000", + "dischargePowerSetPoint": "6000", + "frequencyLimit": "52000", + "activationRunTime": "LONG_ACTIVATION_RUN", + "supportDuration": "LONG_SUPPORT_DURATION" + } + ] + } + } + } + } + } +} +---- + +== 3.1.4 Request Parameters + +- *method*: The JSONRPC method to call. In this case, it is `edgeRpc`. +- *params*: The JSONRPC parameters. +- *edgeId*: The ID of the edge device for which to activate the FFR. +- *payload*: The JSONRPC payload. + +== 3.1.5 Payload Parameters + +Within the payload parameter, there is another JSON object that specifies the details of the activation request: + +- *method*: The JSONRPC method to call within the component. In this case, it is `componentJsonApi`. +- *params*: The JSONRPC parameters for the `componentJsonApi` method. +- *componentId*: The ID of the component within which to call the method. In this case, it is `ctrlFastFreqReserve0`. +- *payload*: The JSONRPC payload for the method. + +== 3.1.6 Schedule Item Parameters + +Each schedule item within the schedule array specifies a specific activation period: + +- *startTimestamp*: The unix time stamp in milliseconds when the FFR should start activating. +- *duration*: The duration in milliseconds for which the reserve should remain active. +- *dischargePowerSetPoint*: The maximum power in kilowatts that the reserve should discharge during activation. +- *frequencyLimit*: The frequency threshold below which the reserve should be activated. +- *activationRunTime*: The time in milliseconds required for the reserve to fully activate. +- *supportDuration*: The time in milliseconds for which the reserve should continue providing support after the frequency has stabilized. + +== 3.1.7 Example Python code: + +[source,python] +---- +import requests +import json +from requests.auth import HTTPBasicAuth +import base64 +import os + +url = "https://fenecon.de/fems/rest/jsonrpc" + +username = os.getenv("FENECON_USERNAME") +password = os.getenv("FENECON_PASSWORD") + +headers = { + "Content-Type": "application/json", + "Authorization": "Basic " + base64.b64encode(f"{username}:{password}".encode("utf-8")).decode("utf-8") +} + +body = { + "method": "edgeRpc", + "params": { + "edgeId": "fems3734", + "payload": { + "method": "componentJsonApi", + "params": { + "componentId": "ctrlFastFreqReserve0", + "payload": { + "method": "setActivateFastFreqReserve", + "params": { + "id": "edge3734", + "schedule": [ + { + "startTimestamp": "1701767477", + "duration": "11000", + "dischargePowerSetPoint": "6000", + "frequencyLimit": "52000", + "activationRunTime": "LONG_ACTIVATION_RUN", + "supportDuration": "LONG_SUPPORT_DURATION" + } + ] + } + } + } + } + } +} + +response = requests.post(url, headers=headers, data=json.dumps(body)) + +if response.status_code == 200: + print("Fast Frequency Reserve activated successfully") +else: + print("Error activating Fast Frequency Reserve:", response.text) +---- + + +https://github.com/OpenEMS/openems/tree/develop/io.openems.edge.controller.ess.fastfrequencyreserve[Source Code icon:github[]] \ No newline at end of file diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/Config.java b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/Config.java new file mode 100644 index 00000000000..2ca1b8b52f3 --- /dev/null +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/Config.java @@ -0,0 +1,54 @@ +package io.openems.edge.controller.ess.fastfrequencyreserve; + +import org.osgi.service.metatype.annotations.AttributeDefinition; +import org.osgi.service.metatype.annotations.ObjectClassDefinition; + +import io.openems.edge.controller.ess.fastfrequencyreserve.enums.ControlMode; + +@ObjectClassDefinition(// + name = "Controller Ess Fast Frequency Reserve", // + description = "This Controller helps the energy storage system (ESS) generate capacity, essentially battery discharge. When the measured\n" + + "\"Grid frequency\" is lower than the predefined \"Frequency limit\".") // +@interface Config { + + @AttributeDefinition(name = "Component-ID", description = "Unique ID of this Component") + String id() default "ctrlFastFreqReserve0"; + + @AttributeDefinition(name = "Alias", description = "Human-readable name of this Component; defaults to Component-ID") + String alias() default ""; + + @AttributeDefinition(name = "Is enabled?", description = "Is this Component enabled?") + boolean enabled() default true; + + @AttributeDefinition(name = "Control Mode", description = "Set the type of control mode.") + ControlMode controlMode() default ControlMode.MANUAL_OFF; + + @AttributeDefinition(name = "Activation Schdule", description = "Schedule for the activation.") + String activationScheduleJson() default "[\n" // + + " {\n" // + + " \"startTimestamp\": 1684879200,\n" // + + " \"duration\": 86400,\n" // + + " \"dischargePowerSetPoint\": 92000,\n" // + + " \"frequencyLimit\": 49500,\n" // + + " \"activationRunTime\": \"LONG_ACTIVATION_RUN\",\n" // + + " \"supportDuration\": \"LONG_SUPPORT_DURATION\"\n" // + + " }\n" // + + "]"; // + + @AttributeDefinition(name = "Ess-ID", description = "ID of Ess device.") + String ess_id(); + + @AttributeDefinition(name = "Grid-Meter-Id", description = "ID of the Grid-Meter.") + String meter_id(); + + @AttributeDefinition(name = "Pre activation time", description = "A time before the activation time for charging the system(min).") + int preActivationTime() default 0; + + @AttributeDefinition(name = "Ess target filter", description = "This is auto-generated by 'Ess-ID'.") + String ess_target() default "(enabled=true)"; + + @AttributeDefinition(name = "Meter target filter", description = "This is auto-generated by 'Grid-Meter-ID'.") + String meter_target() default "(enabled=true)"; + + String webconsole_configurationFactory_nameHint() default "Controller Ess Fast Frequency Reserve [{id}]"; +} \ No newline at end of file diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/ControllerFastFrequencyReserve.java b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/ControllerFastFrequencyReserve.java new file mode 100644 index 00000000000..efff694c2ee --- /dev/null +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/ControllerFastFrequencyReserve.java @@ -0,0 +1,283 @@ +package io.openems.edge.controller.ess.fastfrequencyreserve; + +import io.openems.common.channel.AccessMode; +import io.openems.common.channel.Level; +import io.openems.common.channel.PersistencePriority; +import io.openems.common.types.OpenemsType; +import io.openems.edge.common.channel.Channel; +import io.openems.edge.common.channel.Doc; +import io.openems.edge.common.channel.StateChannel; +import io.openems.edge.common.channel.WriteChannel; +import io.openems.edge.common.channel.value.Value; +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.controller.api.Controller; +import io.openems.edge.controller.ess.fastfrequencyreserve.enums.ActivationTime; +import io.openems.edge.controller.ess.fastfrequencyreserve.enums.ControlMode; +import io.openems.edge.controller.ess.fastfrequencyreserve.enums.SupportDuration; +import io.openems.edge.controller.ess.fastfrequencyreserve.statemachine.StateMachine.State; + +public interface ControllerFastFrequencyReserve extends Controller, OpenemsComponent { + + public enum ChannelId implements io.openems.edge.common.channel.ChannelId { + CONTROL_MODE(Doc.of(ControlMode.values()) // + .initialValue(ControlMode.MANUAL_OFF) // + .text("Configured Control Mode")), // + STATE_MACHINE(Doc.of(State.values()) // + .persistencePriority(PersistencePriority.HIGH)// + .text("Current State of State-Machine")), // + SCHEDULE_PARSE_FAILED(Doc.of(Level.FAULT) // + .text("Unable to parse Schedule")), // + NO_ACTIVE_SETPOINT(Doc.of(OpenemsType.BOOLEAN) // + .text("No active Set-Point given")), // + DISCHARGE_POWER_SET_POINT(Doc.of(OpenemsType.INTEGER) // + .persistencePriority(PersistencePriority.HIGH)// + .accessMode(AccessMode.READ_WRITE)), + NO_FREQUENCY_LIMIT(Doc.of(OpenemsType.BOOLEAN) // + .text("No Frequency limit is given")), // + FREQUENCY_LIMIT(Doc.of(OpenemsType.INTEGER) // + .persistencePriority(PersistencePriority.HIGH)// + .accessMode(AccessMode.READ_WRITE)), // + NO_START_TIMESTAMP(Doc.of(OpenemsType.BOOLEAN) // + .text("No start timestamp")), // + START_TIMESTAMP(Doc.of(OpenemsType.LONG) // + .persistencePriority(PersistencePriority.HIGH)// + .accessMode(AccessMode.READ_WRITE)), // + NO_DURATION(Doc.of(OpenemsType.BOOLEAN) // + .text("No duration")), // + DURATION(Doc.of(OpenemsType.INTEGER) // + .persistencePriority(PersistencePriority.HIGH)// + .accessMode(AccessMode.READ_WRITE)), // + ACTIVATION_TIME(Doc.of(ActivationTime.values())// + .accessMode(AccessMode.READ_WRITE)), // + SUPPORT_DURATIN(Doc.of(SupportDuration.values())// + .accessMode(AccessMode.READ_WRITE)), + LAST_TRIGGERED_TIME(Doc.of(OpenemsType.STRING) // + .persistencePriority(PersistencePriority.HIGH)// + .accessMode(AccessMode.READ_WRITE) // + .text("Last Triggered time in Human readable form")// + + ); + + private final Doc doc; + + private ChannelId(Doc doc) { + this.doc = doc; + } + + @Override + public Doc doc() { + return this.doc; + } + } + + /** + * Gets the Channel for {@link ChannelId#SUPPORT_DURATIN}. + * + * @return the Channel + */ + public default Channel getSupportDurationChannel() { + return this.channel(ChannelId.SUPPORT_DURATIN); + } + + /** + * Gets the SupportDuration, see {@link ChannelId#SUPPORT_DURATIN}. + * + * @return the Channel {@link Value} + */ + public default SupportDuration getSupportDuration() { + return this.getSupportDurationChannel().value().asEnum(); + } + + /** + * Gets the Channel for {@link ChannelId#ACTIVATION_TIME}. + * + * @return the Channel + */ + public default Channel getActivationTimeChannel() { + return this.channel(ChannelId.ACTIVATION_TIME); + } + + /** + * Gets the ActivationTime, see {@link ChannelId#ACTIVATION_TIME}. + * + * @return the Channel {@link Value} + */ + public default ActivationTime getActivationTime() { + return this.getActivationTimeChannel().value().asEnum(); + } + + /** + * Gets the WriteChannel {@link ChannelId#LAST_TRIGGERED_TIME}. + * + * @return the WriteChannel + */ + public default WriteChannel getLastTriggeredTimeChannel() { + return this.channel(ChannelId.LAST_TRIGGERED_TIME); + } + + /** + * Gets the getLastTriggeredTime, see {@link ChannelId#LAST_TRIGGERED_TIME}. + * + * @return the Channel {@link Value} + */ + public default Value getLastTriggeredTime() { + return this.getLastTriggeredTimeChannel().value(); + } + + /** + * Sets the LastTriggeredTimseStamp, see {@link ChannelId#LAST_TRIGGERED_TIME}. + * + * @param value the value to be set + */ + public default void setLastTriggeredTime(String value) { + this.getLastTriggeredTimeChannel().setNextValue(value); + } + + /** + * Gets the Channel {@link ChannelId#SCHEDULE_PARSE_FAILED}. + * + * @return the Channel + */ + public default StateChannel getScheduleParseFailedChannel() { + return this.channel(ChannelId.SCHEDULE_PARSE_FAILED); + } + + /** + * Internal method to set the 'nextValue' on + * {@link ChannelId#SCHEDULE_PARSE_FAILED} Channel. + * + * @param value the next value + */ + public default void _setScheduleParseFailed(boolean value) { + this.getScheduleParseFailedChannel().setNextValue(value); + } + + /** + * Gets the Channel {@link ChannelId#DISCHARGE_POWER_SET_POINT}. + * + * @return the Channel + */ + public default WriteChannel getDischargePowerSetPointChannel() { + return this.channel(ChannelId.DISCHARGE_POWER_SET_POINT); + } + + /** + * Gets the getDischargeActivePowerSetPoint, see + * {@link ChannelId#DISCHARGE_POWER_SET_POINT}. + * + * @return the Channel {@link Value} + */ + public default Value getDischargePowerSetPoint() { + return this.getDischargePowerSetPointChannel().value(); + } + + /** + * Gets the WriteChannel {@link ChannelId#FREQUENCY_LIMIT}. + * + * @return the WriteChannel + */ + public default WriteChannel getFrequencyLimitChannel() { + return this.channel(ChannelId.FREQUENCY_LIMIT); + } + + /** + * Gets the getFrequencyLimit, see {@link ChannelId#FREQUENCY_LIMIT}. + * + * @return the Channel {@link Value} + */ + public default Value getFrequencyLimit() { + return this.getFrequencyLimitChannel().value(); + } + + /** + * Gets the WriteChannel {@link ChannelId#DURATION}. + * + * @return the WriteChannel + */ + public default WriteChannel getDurationChannel() { + return this.channel(ChannelId.DURATION); + } + + /* + * Gets the getDuration, see {@link ChannelId#DURATION}. + * + * @return the Channel {@link Value} + */ + public default Value getDuration() { + return this.getDurationChannel().value(); + } + + /** + * Gets the WriteChannel {@link ChannelId#START_TIMESTAMP}. + * + * @return the WriteChannel + */ + public default WriteChannel getStartTimestampChannel() { + return this.channel(ChannelId.START_TIMESTAMP); + } + + /** + * Gets the getStartTimestamp, see {@link ChannelId#START_TIMESTAMP}. + * + * @return the Channel {@link Value} + */ + public default Value getStartTimestamp() { + return this.getStartTimestampChannel().value(); + } + + /** + * Gets the Channel for {@link ChannelId#STATE_MACHINE}. + * + * @return the Channel + */ + public default Channel getStateMachineChannel() { + return this.channel(ChannelId.STATE_MACHINE); + } + + /** + * Gets the {@link StateChannel} for {@link ChannelId#STATE_MACHINE}. + * + * @return the Channel {@link Value} + */ + public default Value getStateMachine() { + return this.getStateMachineChannel().value(); + } + + /** + * Internal method to set the 'nextValue' on {@link ChannelId#STATE_MACHINE} + * Channel. + * + * @param value the next value + */ + public default void _setStateMachine(State value) { + this.getStateMachineChannel().setNextValue(value); + } + + /** + * Gets the Channel for {@link ChannelId#CONTROL_MODE}. + * + * @return the Channel + */ + public default Channel getControlModeChannel() { + return this.channel(ChannelId.CONTROL_MODE); + } + + /** + * Gets the {@link StateChannel} for {@link ChannelId#CONTROL_MODE}. + * + * @return the Channel {@link Value} + */ + public default Value getControlMode() { + return this.getControlModeChannel().value(); + } + + /** + * Internal method to set the 'nextValue' on {@link ChannelId#CONTROL_MODE} + * Channel. + * + * @param value the next value + */ + public default void _setControlMode(ControlMode value) { + this.getControlModeChannel().setNextValue(value); + } +} diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/ControllerFastFrequencyReserveImpl.java b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/ControllerFastFrequencyReserveImpl.java new file mode 100644 index 00000000000..5e9bf35f124 --- /dev/null +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/ControllerFastFrequencyReserveImpl.java @@ -0,0 +1,312 @@ +package io.openems.edge.controller.ess.fastfrequencyreserve; + +import java.time.Instant; +import java.time.ZoneId; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Function; +import java.util.stream.Stream; + +import org.osgi.service.cm.ConfigurationAdmin; +import org.osgi.service.component.ComponentContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.ConfigurationPolicy; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; +import org.osgi.service.component.annotations.ReferencePolicyOption; +import org.osgi.service.metatype.annotations.Designate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.JsonArray; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.jsonrpc.base.GenericJsonrpcResponseSuccess; +import io.openems.common.session.Role; +import io.openems.common.utils.JsonUtils; +import io.openems.edge.common.channel.WriteChannel; +import io.openems.edge.common.channel.value.Value; +import io.openems.edge.common.component.AbstractOpenemsComponent; +import io.openems.edge.common.component.ComponentManager; +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.common.jsonapi.ComponentJsonApi; +import io.openems.edge.common.jsonapi.EdgeGuards; +import io.openems.edge.common.jsonapi.JsonApiBuilder; +import io.openems.edge.controller.api.Controller; +import io.openems.edge.controller.ess.fastfrequencyreserve.enums.ActivationTime; +import io.openems.edge.controller.ess.fastfrequencyreserve.enums.SupportDuration; +import io.openems.edge.controller.ess.fastfrequencyreserve.jsonrpc.SetActivateFastFreqReserveRequest; +import io.openems.edge.controller.ess.fastfrequencyreserve.jsonrpc.SetActivateFastFreqReserveRequest.ActivateFastFreqReserveSchedule; +import io.openems.edge.controller.ess.fastfrequencyreserve.statemachine.Context; +import io.openems.edge.controller.ess.fastfrequencyreserve.statemachine.StateMachine; +import io.openems.edge.controller.ess.fastfrequencyreserve.statemachine.StateMachine.State; +import io.openems.edge.ess.api.ManagedSymmetricEss; +import io.openems.edge.meter.api.ElectricityMeter; + +@Designate(ocd = Config.class, factory = true) +@Component(// + name = "Controller.Ess.FastFrequencyReserve", // + immediate = true, // + configurationPolicy = ConfigurationPolicy.REQUIRE // +) +public class ControllerFastFrequencyReserveImpl extends AbstractOpenemsComponent + implements ControllerFastFrequencyReserve, Controller, OpenemsComponent, ComponentJsonApi { + + private final Logger log = LoggerFactory.getLogger(ControllerFastFrequencyReserveImpl.class); + private final StateMachine stateMachine = new StateMachine(State.UNDEFINED); + + private Config config = null; + private List schedule = new CopyOnWriteArrayList<>(); + + private static final Function OBTAIN_DICHARGE_POWER = ActivateFastFreqReserveSchedule::dischargePowerSetPoint; + private static final Function OBTAIN_FREQ_LIMIT = ActivateFastFreqReserveSchedule::frequencyLimit; + private static final Function OBTAIN_STARTTIME_STAMP = ActivateFastFreqReserveSchedule::startTimestamp; + private static final Function OBTAIN_DURATION = ActivateFastFreqReserveSchedule::duration; + private static final Function OBTAIN_ACTIVATION_TIME = ActivateFastFreqReserveSchedule::activationRunTime; + private static final Function OBTAIN_SUPPORT_DURATION = ActivateFastFreqReserveSchedule::supportDuration; + + @Reference + private ConfigurationAdmin cm; + + @Reference + private ComponentManager componentManager; + + @Reference(policy = ReferencePolicy.STATIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.MANDATORY) + private ManagedSymmetricEss ess; + + @Reference(policy = ReferencePolicy.STATIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.MANDATORY) + private ElectricityMeter meter; + + public ControllerFastFrequencyReserveImpl() { + super(// + OpenemsComponent.ChannelId.values(), // + Controller.ChannelId.values(), // + ControllerFastFrequencyReserve.ChannelId.values() // + ); + } + + @Activate + private void activate(ComponentContext context, Config config) throws OpenemsNamedException { + this.config = config; + super.activate(context, config.id(), config.alias(), config.enabled()); + this.updateConfig(); + } + + @Override + public void buildJsonApiRoutes(JsonApiBuilder builder) { + builder.handleRequest(SetActivateFastFreqReserveRequest.METHOD, // + endpoint -> { + endpoint.setGuards(EdgeGuards.roleIsAtleast(Role.OWNER)); + }, call -> { + this.handleSetActivateFastFreqReserveRequest( + SetActivateFastFreqReserveRequest.from(call.getRequest())); + + return new GenericJsonrpcResponseSuccess(call.getRequest().getId(), JsonUtils.buildJsonObject() // + .addProperty("startTimestamp", "recieved") // + .build()); + }); + } + + /** + * Updates the configuration for the component, setting control mode, + * references, and activation schedule. + * + * @throws OpenemsNamedException On Exception. + */ + private void updateConfig() throws OpenemsNamedException { + this._setControlMode(this.config.controlMode()); + + if (OpenemsComponent.updateReferenceFilter(this.cm, this.servicePid(), "ess", // + this.config.ess_id())) { + return; + } + if (OpenemsComponent.updateReferenceFilter(this.cm, this.servicePid(), "meter", // + this.config.meter_id())) { + return; + } + try { + if (!this.config.activationScheduleJson().trim().isEmpty()) { + final var scheduleElement = JsonUtils.parse(this.config.activationScheduleJson()); + final var scheduleArray = JsonUtils.getAsJsonArray(scheduleElement); + this.applySchedule(scheduleArray); + this._setScheduleParseFailed(false); + } + } catch (IllegalStateException | OpenemsNamedException e) { + this._setScheduleParseFailed(true); + this.logError(this.log, "Unable to parse Schedule: " + e.getMessage()); + } + } + + /** + * Updates the configuration for activating fast frequency reserve based on the + * provided request. + * + * @param request The request containing the schedule information. + */ + private void updateConfig(SetActivateFastFreqReserveRequest request) { + var scheduleString = SetActivateFastFreqReserveRequest.listToString(request.getSchedule()); + OpenemsComponent.updateConfigurationProperty(this.cm, this.servicePid(), "activationScheduleJson", + scheduleString); + } + + private void applySchedule(JsonArray jsonArray) throws OpenemsNamedException { + this.schedule = SetActivateFastFreqReserveRequest.ActivateFastFreqReserveSchedule.from(jsonArray); + } + + @Deactivate + protected void deactivate() { + super.deactivate(); + } + + @Override + public void run() throws OpenemsNamedException { + switch (this.config.controlMode()) { + case MANUAL_ON -> { + this.getConfigParams(); + this.handleStatemachine(); + } + case MANUAL_OFF -> { + // Do nothing + } + } + this._setControlMode(this.config.controlMode()); + } + + private void getConfigParams() { + + this.setChannelValueIfNeeded(ControllerFastFrequencyReserve.ChannelId.DISCHARGE_POWER_SET_POINT, + OBTAIN_DICHARGE_POWER); + final var channelValue = this.getDischargePowerSetPoint(); + + // Avoid calling + if (!channelValue.isDefined()) { + return; + } + this.setChannelValueIfNeeded(ControllerFastFrequencyReserve.ChannelId.FREQUENCY_LIMIT, // + OBTAIN_FREQ_LIMIT); + this.setChannelValueIfNeeded(ControllerFastFrequencyReserve.ChannelId.DURATION, // + OBTAIN_DURATION); + this.setChannelValueIfNeeded(ControllerFastFrequencyReserve.ChannelId.START_TIMESTAMP, // + OBTAIN_STARTTIME_STAMP); + // TODO get it for the activation time and support time, But currently this + // tested for long activation time and long support time, other enums are for + // future + this.setChannelValueIfNeeded(ControllerFastFrequencyReserve.ChannelId.ACTIVATION_TIME, // + OBTAIN_ACTIVATION_TIME); + this.setChannelValueIfNeeded(ControllerFastFrequencyReserve.ChannelId.SUPPORT_DURATIN, // + OBTAIN_SUPPORT_DURATION); + } + + /** + * Sets the value for the specified {@code FastFrequencyReserve.ChannelId} based + * on the provided {@code Function}, only if needed. + * + * @param channelId The channel to set the value for. + * @param obtainFunction A {@code Function} to retrieve the corresponding value + * based on the provided schedule entry. + */ + private void setChannelValueIfNeeded(ControllerFastFrequencyReserve.ChannelId channelId, + Function obtainFunction) { + WriteChannel channel = this.channel(channelId); + var setPointFromChannel = channel.value(); + if (setPointFromChannel.isDefined()) { + return; + } + + var currentTime = this.componentManager.getClock().withZone(ZoneId.systemDefault()); + var now = Instant.now(currentTime).getEpochSecond(); + + for (var scheduleEntry : this.schedule) { + var endTime = scheduleEntry.startTimestamp() + scheduleEntry.duration(); + + // Configurable minutes, and convert into seconds + var preActivationTimeBeforeStartTime = this.config.preActivationTime() * 60; + if (now >= scheduleEntry.startTimestamp() - preActivationTimeBeforeStartTime && now <= endTime) { + channel.setNextValue(obtainFunction.apply(scheduleEntry)); + return; + } + } + channel.setNextValue(null); + return; + } + + private void handleStatemachine() { + if (this.checkGridMode()) { + return; + } + + var state = this.stateMachine.getCurrentState(); + this._setStateMachine(state); + + if (!this.areChannelsDefined()) { + return; + } + + var context = new Context(this, // + this.componentManager.getClock(), // + this.ess, // + this.meter, // + this.getStartTimestamp().get(), // + this.getDuration().get(), // + this.getDischargePowerSetPoint().get(), // + this.getFrequencyLimit().get(), // + // TODO if other version of FFR needed, need to test first with the Inverter + // Capabilities + this.getActivationTime(), // + this.getSupportDuration()); + + try { + this.stateMachine.run(context); + } catch (OpenemsNamedException e) { + this.logError(this.log, "StateMachine failed: " + e.getMessage()); + } + } + + /** + * Checks the grid mode and returns a boolean value based on the grid mode + * state. If the grid mode is "ON_GRID" or "UNDEFINED," it returns false and + * logs a warning message when the grid mode is "UNDEFINED." If the grid mode is + * "OFF_GRID," it returns true. + * + * @return true if the grid mode is "OFF_GRID," false otherwise. + */ + private boolean checkGridMode() { + return switch (this.ess.getGridMode()) { + case ON_GRID -> false; + case UNDEFINED -> { + this.logWarn(this.log, "Grid-Mode is [UNDEFINED]"); + yield false; + } + case OFF_GRID -> true; + }; + } + + private boolean areChannelsDefined() { + return Stream.of(// + this.getDischargePowerSetPoint(), // + this.getFrequencyLimit(), // + this.getDuration(), // + this.getStartTimestamp())// + .allMatch(Value::isDefined); + } + + private void handleSetActivateFastFreqReserveRequest(SetActivateFastFreqReserveRequest request) + throws OpenemsNamedException { + this.schedule = request.getSchedule(); + + // get current schedule + var currentSchedule = (String) this.getComponentContext()// + .getProperties()// + .get("activationScheduleJson"); + var currentScheduleArray = JsonUtils.getAsJsonArray(JsonUtils.parse(currentSchedule).getAsJsonArray()); + var currentScheduleList = ActivateFastFreqReserveSchedule.from(currentScheduleArray); + + if (this.schedule.size() == currentScheduleArray.size() && currentScheduleList.equals(this.schedule)) { + return; + } + this.updateConfig(request); + } +} diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/enums/ActivationTime.java b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/enums/ActivationTime.java new file mode 100644 index 00000000000..5d483a3ef44 --- /dev/null +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/enums/ActivationTime.java @@ -0,0 +1,30 @@ +package io.openems.edge.controller.ess.fastfrequencyreserve.enums; + +import io.openems.common.types.OptionsEnum; + +public enum ActivationTime implements OptionsEnum { + SHORT_ACTIVATION_RUN(700, "Short activation time run, 700 in milliseconds"), // + MEDIUM_ACTIVATION_RUN(1000, "Medium activation time run, 1000 in milliseconds"), // + LONG_ACTIVATION_RUN(1300, "Long activation time run, 1300 in milliseconds"); + + private final int value; + private final String name; + + private ActivationTime(int value, String name) { + this.value = value; + this.name = name; + } + + public int getValue() { + return this.value; + } + + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return LONG_ACTIVATION_RUN; + } +} diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/enums/ControlMode.java b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/enums/ControlMode.java new file mode 100644 index 00000000000..e203ad4afdd --- /dev/null +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/enums/ControlMode.java @@ -0,0 +1,32 @@ +package io.openems.edge.controller.ess.fastfrequencyreserve.enums; + +import io.openems.common.types.OptionsEnum; + +public enum ControlMode implements OptionsEnum { + MANUAL_ON(0, "Manual control for the ON signal, FFR is swtiched on"), // + MANUAL_OFF(1, "Manual control for the OFF signal, FFR is swtiched off") // + ; // + + private final int value; + private final String name; + + private ControlMode(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return MANUAL_OFF; + } +} diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/enums/SupportDuration.java b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/enums/SupportDuration.java new file mode 100644 index 00000000000..040806aa262 --- /dev/null +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/enums/SupportDuration.java @@ -0,0 +1,32 @@ +package io.openems.edge.controller.ess.fastfrequencyreserve.enums; + +import io.openems.common.types.OptionsEnum; + +public enum SupportDuration implements OptionsEnum { + SHORT_SUPPORT_DURATION(5, "long support duration 5 seconds"), + LONG_SUPPORT_DURATION(30, "long support duration 30 seconds"); + + private final int value; + private final String name; + + private SupportDuration(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return LONG_SUPPORT_DURATION; + } + +} diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/jsonrpc/SetActivateFastFreqReserveRequest.java b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/jsonrpc/SetActivateFastFreqReserveRequest.java new file mode 100644 index 00000000000..4c422a365bc --- /dev/null +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/jsonrpc/SetActivateFastFreqReserveRequest.java @@ -0,0 +1,228 @@ +package io.openems.edge.controller.ess.fastfrequencyreserve.jsonrpc; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.jsonrpc.base.JsonrpcRequest; +import io.openems.common.utils.JsonUtils; +import io.openems.edge.controller.ess.fastfrequencyreserve.enums.ActivationTime; +import io.openems.edge.controller.ess.fastfrequencyreserve.enums.SupportDuration; + +/** + * Represents a JSON-RPC Request for 'setActivateFastFreqReserve'. + * + *

+ {
+ 	"jsonrpc": "2.0",
+ 	"id": "UUID",
+ 	"method": "setActivateFastFreqReserve",
+ 	"params": { 		
+ 		"schedule": [{
+ 			"startTimestamp": 1542464697,
+ 			"duration": 900,
+ 			"dischargeActivePowerSetPoint": 92000,
+ 			"frequencyLimit": 49500
+ 			"activationRunTime": "LONG_ACTIVATION_RUN",
+ 			"supportDuration": "LONG_SUPPORT_DURATION"
+ 		}]
+ 	}
+ }
+ * 
+ */ +public class SetActivateFastFreqReserveRequest extends JsonrpcRequest { + + /** + * Create {@link SetActivateFastFreqReserveRequest} from a template + * {@link JsonrpcRequest}. + * + * @param request the template {@link JsonrpcRequest} + * @return the {@link SetActivateFastFreqReserveRequest} + * @throws OpenemsNamedException on parse error + */ + public static SetActivateFastFreqReserveRequest from(JsonrpcRequest request) throws OpenemsNamedException { + final var params = request.getParams(); + final var edgeId = JsonUtils.getAsString(params, "id"); + final var scheduleArray = JsonUtils.getAsJsonArray(params, "schedule"); + final var schedule = ActivateFastFreqReserveSchedule.from(scheduleArray); + return new SetActivateFastFreqReserveRequest(request, edgeId, schedule); + } + + public static final String METHOD = "setActivateFastFreqReserve"; + + private final String edgeId; + private final List schedule; + + public SetActivateFastFreqReserveRequest(String edgeId) { + this(edgeId, new ArrayList<>()); + } + + private SetActivateFastFreqReserveRequest(String edgeId, List schedule) { + super(SetActivateFastFreqReserveRequest.METHOD); + this.edgeId = edgeId; + this.schedule = schedule; + } + + private SetActivateFastFreqReserveRequest(JsonrpcRequest request, String edgeId, + List schedule) { + super(request, SetActivateFastFreqReserveRequest.METHOD); + this.edgeId = edgeId; + this.schedule = schedule; + } + + /** + * Adds a new schedule entry for activating Fast Frequency Reserve. + * + * @param scheduleEntry The schedule entry to be added. + */ + public void addScheduleEntry(ActivateFastFreqReserveSchedule scheduleEntry) { + this.schedule.add(scheduleEntry); + } + + @Override + public JsonObject getParams() { + var schedule = new JsonArray(); + for (var se : this.schedule) { + schedule.add(se.toJson()); + } + return JsonUtils.buildJsonObject() // + .addProperty("id", this.getEdgeId()) // + .add("schedule", schedule) // + .build(); + } + + /** + * Gets the Edge-ID. + * + * @return Edge-ID + */ + public String getEdgeId() { + return this.edgeId; + } + + public List getSchedule() { + return this.schedule; + } + + /** + * Converts a list of ActivateFastFreqReserveSchedule objects to a formatted + * string. + * + * @param scheduleList The list of ActivateFastFreqReserveSchedule objects to + * convert. + * @return A string representation of the schedule list. + * @see ActivateFastFreqReserveSchedule#toString() + */ + public static String listToString(List scheduleList) { + return "["// + + scheduleList.stream()// + .map(ActivateFastFreqReserveSchedule::toString)// + .collect(Collectors.joining(", "))// + + "]"; + } + + public record ActivateFastFreqReserveSchedule(long startTimestamp, int duration, int dischargePowerSetPoint, + int frequencyLimit, ActivationTime activationRunTime, SupportDuration supportDuration) { + + /** + * Builds a list of ActivateFastFreqReserveSchedule from a JsonArray. + * + * @param jsonArray JsonArray + * @return list of {@link ActivateFastFreqReserveSchedule} + * @throws OpenemsNamedException on error + */ + public static List from(JsonArray jsonArray) throws OpenemsNamedException { + List schedule = new ArrayList<>(); + for (var jsonElement : jsonArray) { + var newSchedule = new ActivateFastFreqReserveSchedule( + JsonUtils.getAsLong(jsonElement, "startTimestamp"), // + JsonUtils.getAsInt(jsonElement, "duration"), + JsonUtils.getAsInt(jsonElement, "dischargePowerSetPoint"), + JsonUtils.getAsInt(jsonElement, "frequencyLimit"), + JsonUtils.getAsEnum(ActivationTime.class, jsonElement, "activationRunTime"), + JsonUtils.getAsEnum(SupportDuration.class, jsonElement, "supportDuration")); + + // Check for overlap with existing schedules before adding + if (!overlapsExistingSchedule(schedule, newSchedule)) { + schedule.add(newSchedule); + } + } + + schedule.sort(Comparator.comparing(ActivateFastFreqReserveSchedule::startTimestamp)); + return schedule; + } + + /** + * Checks whether a new schedule overlaps with existing schedules or is an exact + * duplicate. + * + * @param schedule List of existing schedules to compare against + * @param newSchedule The new schedule to check for overlap or duplication + * @return {@code true} if the new schedule overlaps with existing schedules or + * is an exact duplicate, {@code false} otherwise + */ + private static boolean overlapsExistingSchedule(List schedule, + ActivateFastFreqReserveSchedule newSchedule) { + for (ActivateFastFreqReserveSchedule existingSchedule : schedule) { + if (newSchedule.equals(existingSchedule)) { + // duplicate found + return true; + } + // Check for overlap + if (newSchedule.startTimestamp < (existingSchedule.startTimestamp + existingSchedule.duration) + && (newSchedule.startTimestamp + newSchedule.duration) > existingSchedule.startTimestamp) { + return true; + } + } + // No overlap or exact duplicate found + return false; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + var that = (ActivateFastFreqReserveSchedule) o; + return this.startTimestamp == that.startTimestamp // + && this.duration == that.duration // + && this.dischargePowerSetPoint == that.dischargePowerSetPoint // + && this.frequencyLimit == that.frequencyLimit // + && this.activationRunTime.equals(that.activationRunTime) // + && this.supportDuration.equals(that.supportDuration); + } + + @Override + public String toString() { + return String.format( + "{\"startTimestamp\":%d, \"duration\":%d, \"dischargePowerSetPoint\":%d, \"frequencyLimit\":%d, \"activationRunTime\":\"%s\", \"supportDuration\":\"%s\"}", + this.startTimestamp, this.duration, this.dischargePowerSetPoint, this.frequencyLimit, + this.activationRunTime, this.supportDuration); + } + + /** + * Converts this ActivateFastFreqReserveSchedule object to a JsonObject. + * + * @return A JsonObject representing this schedule, where each field is mapped + * to a corresponding property with its value. + */ + public JsonObject toJson() { + return JsonUtils.buildJsonObject() // + .addProperty("startTimestamp", this.startTimestamp()) // + .addProperty("duration", this.duration()) // + .addProperty("dischargeActivePowerSetPoint", this.dischargePowerSetPoint()) // + .addProperty("frequencyLimit", this.frequencyLimit()) // + .addProperty("activationRunTime", this.activationRunTime()) // + .addProperty("supportDuration", this.supportDuration()) // + .build(); + } + } +} diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/ActivationTimeHandler.java b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/ActivationTimeHandler.java new file mode 100644 index 00000000000..57af0c89ba9 --- /dev/null +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/ActivationTimeHandler.java @@ -0,0 +1,106 @@ +package io.openems.edge.controller.ess.fastfrequencyreserve.statemachine; + +import java.time.Duration; +import java.time.Instant; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.exceptions.OpenemsException; +import io.openems.common.utils.EnumUtils; +import io.openems.edge.common.statemachine.StateHandler; +import io.openems.edge.controller.ess.fastfrequencyreserve.statemachine.StateMachine.State; + +public class ActivationTimeHandler extends StateHandler { + + private static final int ZERO_WATT_POWER = 0; // [0 W] + + protected Instant dipDetectedStartTime; + protected ActivationTimeState activationTimeState; + + private static enum SubState { + INSIDE_TIME_FRAME, // + HANDLE_WAITING_FREQ_DIP, // + HANDLE_FREQ_DIP, // + FINISH_ACTIVATION + } + + protected static record ActivationTimeState(SubState subState, Instant lastChange) { + } + + @Override + protected void onEntry(Context context) throws OpenemsNamedException { + context.ess.setActivePowerEquals(ZERO_WATT_POWER); + this.activationTimeState = new ActivationTimeState(SubState.INSIDE_TIME_FRAME, Instant.now(context.clock)); + } + + @Override + protected State runAndGetNextState(Context context) throws OpenemsNamedException { + final var nextSubState = this.getNextSubState(context); + if (nextSubState != this.activationTimeState.subState) { + this.activationTimeState = new ActivationTimeState(nextSubState, Instant.now(context.clock)); + } + if (nextSubState == SubState.FINISH_ACTIVATION) { + return State.SUPPORT_DURATION; + } + + return State.ACTIVATION_TIME; + } + + private SubState getNextSubState(Context context) throws OpenemsNamedException { + return switch (this.activationTimeState.subState) { + case INSIDE_TIME_FRAME -> + this.isInsideTimeFrame(context) ? SubState.FINISH_ACTIVATION : SubState.HANDLE_WAITING_FREQ_DIP; + case HANDLE_WAITING_FREQ_DIP -> { + if (this.isFrequencyDipped(context)) { + context.ess.setActivePowerEquals(context.dischargePower); + var time = Instant.now(context.clock); + this.clockActivationTime(context, time); + this.dipDetectedStartTime = time; + yield SubState.HANDLE_FREQ_DIP; + } + context.ess.setActivePowerEquals(ZERO_WATT_POWER); + yield SubState.HANDLE_WAITING_FREQ_DIP; + } + case HANDLE_FREQ_DIP -> { + context.ess.setActivePowerEquals(context.dischargePower); + var activationExpirationTime = Duration.between(this.dipDetectedStartTime, Instant.now(context.clock))// + .toMillis(); // + if (activationExpirationTime >= context.activationRunTime.getValue()) { + yield SubState.FINISH_ACTIVATION; + } + yield SubState.HANDLE_FREQ_DIP; + } + case FINISH_ACTIVATION -> SubState.FINISH_ACTIVATION; + }; + } + + /** + * Clocks the activation time and sets it in the context. + * + * @param context the context. + * @param time time in instant + */ + private void clockActivationTime(Context context, Instant time) { + context.setCycleStart(time); + } + + private boolean isFrequencyDipped(Context context) throws OpenemsException { + var meterFrequency = context.meter.getFrequency(); + if (!meterFrequency.isDefined()) { + throw new OpenemsException("meter has no frequency channel defined."); + } + return (meterFrequency.get() < context.freqLimit); + } + + private boolean isInsideTimeFrame(Context context) { + final var now = Instant.now(context.clock).getEpochSecond(); + final var startTimestamp = context.startTimestamp; + final var duration = context.duration; + return now >= startTimestamp + duration; + } + + @Override + protected String debugLog() { + return State.ACTIVATION_TIME.asCamelCase() + "-" + + EnumUtils.nameAsCamelCase(this.activationTimeState.subState()); + } +} diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/BufferedTimeBeforeRecoveryHandler.java b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/BufferedTimeBeforeRecoveryHandler.java new file mode 100644 index 00000000000..23d9ae25b3d --- /dev/null +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/BufferedTimeBeforeRecoveryHandler.java @@ -0,0 +1,103 @@ +package io.openems.edge.controller.ess.fastfrequencyreserve.statemachine; + +import java.time.Duration; +import java.time.Instant; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.utils.EnumUtils; +import io.openems.edge.common.statemachine.StateHandler; +import io.openems.edge.controller.ess.fastfrequencyreserve.statemachine.StateMachine.State; +import io.openems.edge.ess.api.ManagedSymmetricEss; +import io.openems.edge.ess.power.api.Phase; +import io.openems.edge.ess.power.api.Pwr; + +public class BufferedTimeBeforeRecoveryHandler extends StateHandler { + + private static final int ZERO_WATT_POWER = 0; + private static final int BUFFER_DURATION_THRESHOLD_SECONDS = 15; // [s] + private static final int RECOVERY_DURATION_THRESHOLD_MINUTES = 4; // [minute] + private static final double EIGHTEENX_PERCENT_OF_MAX_POWER = 0.18; // [%] + + protected Instant bufferedTimeBeforeRecoveryStartTime = Instant.MIN; + + protected static record BufferedTimeBeforeRecoveryState(SubState subState, Instant lastChange) { + } + + protected BufferedTimeBeforeRecoveryState bufferedTimeBeforeRecoveryState; + + private static enum SubState { + HOLD_BUFFERED_TIME_BEFORE_RECOVERY, // + BUFFERED_TIME_RECOVERY, // + FINISH_BUFFERED_TIME_BEFORE_RECOVERY + } + + @Override + protected void onEntry(Context context) throws OpenemsNamedException { + context.ess.setActivePowerEquals(ZERO_WATT_POWER); + final var now = Instant.now(context.clock); + this.bufferedTimeBeforeRecoveryStartTime = now; + this.bufferedTimeBeforeRecoveryState = new BufferedTimeBeforeRecoveryState( + SubState.HOLD_BUFFERED_TIME_BEFORE_RECOVERY, now); + + } + + @Override + protected State runAndGetNextState(Context context) throws OpenemsNamedException { + final var nextSubState = this.getNextSubState(context); + + if (nextSubState != this.bufferedTimeBeforeRecoveryState.subState) { + this.bufferedTimeBeforeRecoveryState = new BufferedTimeBeforeRecoveryState(nextSubState, + Instant.now(context.clock)); + } + + if (nextSubState == SubState.FINISH_BUFFERED_TIME_BEFORE_RECOVERY) { + return State.RECOVERY_TIME; + } + + return State.BUFFERED_TIME_BEFORE_RECOVERY; + } + + private SubState getNextSubState(Context context) throws OpenemsNamedException { + return switch (this.bufferedTimeBeforeRecoveryState.subState) { + case HOLD_BUFFERED_TIME_BEFORE_RECOVERY -> { + context.ess.setActivePowerEquals(ZERO_WATT_POWER); + var bufferedDurationExpiration = this.calculateBufferedDurationExpiration(context); + if (bufferedDurationExpiration >= BUFFER_DURATION_THRESHOLD_SECONDS) { + yield SubState.BUFFERED_TIME_RECOVERY; + } + yield SubState.HOLD_BUFFERED_TIME_BEFORE_RECOVERY; + } + case BUFFERED_TIME_RECOVERY -> { + var minPowerEss = this.calculateMinPower(context.ess); + context.ess.setActivePowerEquals(minPowerEss); + var bufferedRecoveryExpiration = this.calculateBufferedRecoveryExpiration(context); + if (bufferedRecoveryExpiration >= RECOVERY_DURATION_THRESHOLD_MINUTES) { + yield SubState.FINISH_BUFFERED_TIME_BEFORE_RECOVERY; + } + yield SubState.BUFFERED_TIME_RECOVERY; + } + case FINISH_BUFFERED_TIME_BEFORE_RECOVERY -> SubState.FINISH_BUFFERED_TIME_BEFORE_RECOVERY; + }; + } + + private long calculateBufferedDurationExpiration(Context context) { + return Duration.between(this.bufferedTimeBeforeRecoveryStartTime, Instant.now(context.clock))// + .toSeconds(); + } + + private long calculateBufferedRecoveryExpiration(Context context) { + return Duration// + .between(context.getCycleStart(), Instant.now(context.clock))// + .toMinutes(); + } + + private int calculateMinPower(ManagedSymmetricEss ess) { + return (int) (ess.getPower().getMinPower(ess, Phase.ALL, Pwr.ACTIVE) * EIGHTEENX_PERCENT_OF_MAX_POWER); + } + + @Override + protected String debugLog() { + return State.BUFFERED_TIME_BEFORE_RECOVERY.asCamelCase() + "-" + + EnumUtils.nameAsCamelCase(this.bufferedTimeBeforeRecoveryState.subState()); + } +} diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/Context.java b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/Context.java new file mode 100644 index 00000000000..cbae7c9ec6e --- /dev/null +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/Context.java @@ -0,0 +1,64 @@ +package io.openems.edge.controller.ess.fastfrequencyreserve.statemachine; + +import java.time.Clock; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; + +import io.openems.edge.common.statemachine.AbstractContext; +import io.openems.edge.controller.ess.fastfrequencyreserve.ControllerFastFrequencyReserve; +import io.openems.edge.controller.ess.fastfrequencyreserve.ControllerFastFrequencyReserveImpl; +import io.openems.edge.controller.ess.fastfrequencyreserve.enums.ActivationTime; +import io.openems.edge.controller.ess.fastfrequencyreserve.enums.SupportDuration; +import io.openems.edge.ess.api.ManagedSymmetricEss; +import io.openems.edge.meter.api.ElectricityMeter; + +public class Context extends AbstractContext { + + protected final Clock clock; + protected final int dischargePower; + protected final long startTimestamp; + protected final int duration; + protected final int freqLimit; + protected final ActivationTime activationRunTime; + protected final SupportDuration supportDuration; + protected final ControllerFastFrequencyReserve parentController; + protected final ManagedSymmetricEss ess; + protected final ElectricityMeter meter; + + protected static Instant _cycleStart; + + public Context(ControllerFastFrequencyReserve fastFrequencyReserve, // + Clock clock, // + ManagedSymmetricEss ess, // + ElectricityMeter meter, // + long startTimestamp, // + int duration, // + int dischargePower, // + int freqLimit, // + ActivationTime activationRunTime, // + SupportDuration supportDuration) { + this.clock = clock; + this.parentController = fastFrequencyReserve; + this.startTimestamp = startTimestamp; + this.duration = duration; + this.dischargePower = dischargePower; + this.freqLimit = freqLimit; + this.ess = ess; + this.meter = meter; + this.activationRunTime = activationRunTime; + this.supportDuration = supportDuration; + } + + public Instant getCycleStart() { + return _cycleStart; + } + + public void setCycleStart(Instant cycleStart) { + LocalDateTime lastTriggered = LocalDateTime.ofInstant(cycleStart, ZoneId.systemDefault()); + String formattedDateTime = lastTriggered.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + this.parentController.setLastTriggeredTime(formattedDateTime); + _cycleStart = cycleStart; + } +} diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/DeactivationTimeHandler.java b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/DeactivationTimeHandler.java new file mode 100644 index 00000000000..98dd659b20c --- /dev/null +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/DeactivationTimeHandler.java @@ -0,0 +1,81 @@ +package io.openems.edge.controller.ess.fastfrequencyreserve.statemachine; + +import java.time.Duration; +import java.time.Instant; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.utils.EnumUtils; +import io.openems.edge.common.statemachine.StateHandler; +import io.openems.edge.controller.ess.fastfrequencyreserve.statemachine.StateMachine.State; + +public class DeactivationTimeHandler extends StateHandler { + + private static final int ZERO_WATT_POWER = 0; // [0 W] + protected Instant deactivationStateStartTime; + + private static enum SubState { + HOLD_DEACTIVATION, // + FINISH_DEACTIVATION_DURATION + } + + protected static record DeactivationTimeState(SubState subState, Instant lastChange) { + } + + protected DeactivationTimeState deactivationTimeState; + + @Override + protected void onEntry(Context context) throws OpenemsNamedException { + context.ess.setActivePowerEquals(ZERO_WATT_POWER); + this.deactivationStateStartTime = Instant.now(context.clock); + this.deactivationTimeState = new DeactivationTimeState(SubState.HOLD_DEACTIVATION, Instant.now(context.clock)); + } + + @Override + protected State runAndGetNextState(Context context) throws OpenemsNamedException { + var nextSubState = this.getNextSubState(context); + if (nextSubState != this.deactivationTimeState.subState) { + this.deactivationTimeState = new DeactivationTimeState(nextSubState, Instant.now(context.clock)); + } + if (nextSubState == SubState.FINISH_DEACTIVATION_DURATION) { + return State.BUFFERED_TIME_BEFORE_RECOVERY; + } + return State.DEACTIVATION_TIME; + } + + private SubState getNextSubState(Context context) throws OpenemsNamedException { + return switch (this.deactivationTimeState.subState) { + + case HOLD_DEACTIVATION -> { + context.ess.setActivePowerEquals(ZERO_WATT_POWER); + var deactivationDurationExpiration = this.calculateDeactivationDurationExpiration(context); + if (deactivationDurationExpiration >= context.activationRunTime.getValue()) { + yield SubState.FINISH_DEACTIVATION_DURATION; + } + yield SubState.HOLD_DEACTIVATION; + } + case FINISH_DEACTIVATION_DURATION -> SubState.FINISH_DEACTIVATION_DURATION; + }; + } + + /** + * Calculates the expiration duration for the deactivation state. The expiration + * duration is the time elapsed between the deactivation state start time and + * the current time, measured in milliseconds. + * + * @param context the Context + * @return The expiration duration in milliseconds. + */ + private long calculateDeactivationDurationExpiration(Context context) { + return Duration.between(// + this.deactivationStateStartTime, // + Instant.now(context.clock))// + .toMillis(); + } + + @Override + protected String debugLog() { + return State.DEACTIVATION_TIME.asCamelCase() + "-" + + EnumUtils.nameAsCamelCase(this.deactivationTimeState.subState()); + } + +} diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/PreActivationHandler.java b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/PreActivationHandler.java new file mode 100644 index 00000000000..86b9d62083e --- /dev/null +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/PreActivationHandler.java @@ -0,0 +1,53 @@ +package io.openems.edge.controller.ess.fastfrequencyreserve.statemachine; + +import java.time.ZonedDateTime; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.edge.common.statemachine.StateHandler; +import io.openems.edge.controller.ess.fastfrequencyreserve.statemachine.StateMachine.State; +import io.openems.edge.ess.api.ManagedSymmetricEss; +import io.openems.edge.ess.power.api.Phase; +import io.openems.edge.ess.power.api.Pwr; + +public class PreActivationHandler extends StateHandler { + + private static final double EIGHTEENX_PERCENT_OF_MAX_POWER = 0.18; // [%] + + @Override + protected State runAndGetNextState(Context context) throws OpenemsNamedException { + var ess = context.ess; + int minPowerEss = this.calculateMinPower(ess); + + if (this.isActivationTime(context)) { + return State.ACTIVATION_TIME; + } else { + ess.setActivePowerEquals(minPowerEss); + return State.PRE_ACTIVATION_STATE; + } + } + + /** + * Calculates 18% of the minimum power of the given ess. + * + * @param ess The managed symmetric ess. + * @return 18% of the minimum power of the ess. + */ + private int calculateMinPower(ManagedSymmetricEss ess) { + return (int) (ess.getPower().getMinPower(ess, Phase.ALL, Pwr.ACTIVE) * EIGHTEENX_PERCENT_OF_MAX_POWER); + } + + /** + * Checks if the current time, as adjusted by the component manager's clock, is + * within the activation time window. + * + * + * @param context the context + * @return {@code true} if the current time is within the activation time + * window, {@code false} otherwise. + */ + private boolean isActivationTime(Context context) { + var currentEpochSecond = ZonedDateTime.now(context.clock).toEpochSecond(); + return currentEpochSecond >= context.startTimestamp + && currentEpochSecond <= context.startTimestamp + context.duration; + } +} \ No newline at end of file diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/RecoveryTimeHandler.java b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/RecoveryTimeHandler.java new file mode 100644 index 00000000000..d073c55f9cf --- /dev/null +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/RecoveryTimeHandler.java @@ -0,0 +1,29 @@ +package io.openems.edge.controller.ess.fastfrequencyreserve.statemachine; + +import java.time.Duration; +import java.time.Instant; +import java.time.ZonedDateTime; + +import io.openems.edge.common.statemachine.StateHandler; +import io.openems.edge.controller.ess.fastfrequencyreserve.statemachine.StateMachine.State; + +public class RecoveryTimeHandler extends StateHandler { + + public static final int RECOVERY_DURATION_SECONDS = 15 * 60; + + @Override + protected State runAndGetNextState(Context context) { + if (this.isItWithinDuration(context)) { + return State.ACTIVATION_TIME; + } + return State.RECOVERY_TIME; + } + + private boolean isItWithinDuration(Context context) { + var now = Instant.now(context.clock).getEpochSecond(); + var expiration = Duration// + .between(context.getCycleStart(), ZonedDateTime.now(context.clock))// + .toSeconds(); + return expiration > RECOVERY_DURATION_SECONDS || now >= context.startTimestamp + context.duration; + } +} \ No newline at end of file diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/StateMachine.java b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/StateMachine.java new file mode 100644 index 00000000000..c044760a26f --- /dev/null +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/StateMachine.java @@ -0,0 +1,62 @@ +package io.openems.edge.controller.ess.fastfrequencyreserve.statemachine; + +import io.openems.common.types.OptionsEnum; +import io.openems.edge.common.statemachine.AbstractStateMachine; +import io.openems.edge.common.statemachine.StateHandler; + +public class StateMachine extends AbstractStateMachine { + + public enum State implements io.openems.edge.common.statemachine.State, OptionsEnum { + UNDEFINED(-1), // + PRE_ACTIVATION_STATE(10), // + ACTIVATION_TIME(20), // + SUPPORT_DURATION(30), // + DEACTIVATION_TIME(40), // + BUFFERED_TIME_BEFORE_RECOVERY(50), // + RECOVERY_TIME(60);// + + private final int value; + + private State(int value) { + this.value = value; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } + + @Override + public String getName() { + return this.name(); + } + + @Override + public State[] getStates() { + return State.values(); + } + + } + + public StateMachine(State initialState) { + super(initialState); + } + + @Override + public StateHandler getStateHandler(State state) { + return switch (state) { + case ACTIVATION_TIME -> new ActivationTimeHandler(); + case BUFFERED_TIME_BEFORE_RECOVERY -> new BufferedTimeBeforeRecoveryHandler(); + case DEACTIVATION_TIME -> new DeactivationTimeHandler(); + case PRE_ACTIVATION_STATE -> new PreActivationHandler(); + case RECOVERY_TIME -> new RecoveryTimeHandler(); + case SUPPORT_DURATION -> new SupportDurationTimeHandler(); + case UNDEFINED -> new UndefinedHandler(); + }; + } +} \ No newline at end of file diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/SupportDurationTimeHandler.java b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/SupportDurationTimeHandler.java new file mode 100644 index 00000000000..a5d5131c8e0 --- /dev/null +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/SupportDurationTimeHandler.java @@ -0,0 +1,64 @@ +package io.openems.edge.controller.ess.fastfrequencyreserve.statemachine; + +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDateTime; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.edge.common.statemachine.StateHandler; +import io.openems.edge.controller.ess.fastfrequencyreserve.statemachine.StateMachine.State; + +public class SupportDurationTimeHandler extends StateHandler { + + protected LocalDateTime supportDurationStartTime; + + private static enum SubState { + HOLD_SUPPORT, // + FINISH_SUPPORT_DURATION + } + + protected static record SupportDurationTimeState(SubState subState, Instant lastChange) { + } + + protected SupportDurationTimeState supportDurationTimeState; + + @Override + protected void onEntry(Context context) throws OpenemsNamedException { + context.ess.setActivePowerEquals(context.dischargePower); + this.supportDurationStartTime = LocalDateTime.now(context.clock); + this.supportDurationTimeState = new SupportDurationTimeState(SubState.HOLD_SUPPORT, Instant.now(context.clock)); + } + + @Override + protected State runAndGetNextState(Context context) throws OpenemsNamedException { + final var nextSubState = this.getNextSubState(context); + if (nextSubState != this.supportDurationTimeState.subState) { + this.supportDurationTimeState = new SupportDurationTimeState(nextSubState, Instant.now(context.clock)); + } + if (nextSubState == SubState.FINISH_SUPPORT_DURATION) { + return State.DEACTIVATION_TIME; + } + return State.SUPPORT_DURATION; + } + + private SubState getNextSubState(Context context) throws OpenemsNamedException { + return switch (this.supportDurationTimeState.subState) { + case HOLD_SUPPORT -> { + context.ess.setActivePowerEquals(context.dischargePower); + var supportDurationExpiration = this.calculateSupportDurationExpiration(context); + if (supportDurationExpiration >= context.supportDuration.getValue()) { + yield SubState.FINISH_SUPPORT_DURATION; + } + yield SubState.HOLD_SUPPORT; + } + case FINISH_SUPPORT_DURATION -> SubState.FINISH_SUPPORT_DURATION; + }; + } + + private long calculateSupportDurationExpiration(Context context) { + return Duration.between(// + this.supportDurationStartTime, // + LocalDateTime.now(context.clock))// + .getSeconds(); + } +} diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/UndefinedHandler.java b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/UndefinedHandler.java new file mode 100644 index 00000000000..513a119914a --- /dev/null +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/UndefinedHandler.java @@ -0,0 +1,40 @@ +package io.openems.edge.controller.ess.fastfrequencyreserve.statemachine; + +import java.time.ZonedDateTime; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.edge.common.statemachine.StateHandler; +import io.openems.edge.controller.ess.fastfrequencyreserve.statemachine.StateMachine.State; + +public class UndefinedHandler extends StateHandler { + + public static final int FIFTEEN_MINUTES_IN_SECONDS = 15 * 60; // 15 minutes in seconds + + @Override + protected State runAndGetNextState(Context context) throws OpenemsNamedException { + if (this.isPreActivationTime(context)) { + return State.PRE_ACTIVATION_STATE; + } else { + return State.UNDEFINED; + } + } + + /** + * Checks if the current time, as adjusted by the component manager's clock, is + * within the pre-activation time window. pre-activation time window is 15 + * minutes before the start time. + * + * @param context the context + * @return {@code true} if the current time is within the activation time + * window, {@code false} otherwise. + */ + private boolean isPreActivationTime(Context context) { + var currentDateTime = ZonedDateTime.now(context.clock); + var currentEpochSecond = currentDateTime.toEpochSecond(); + if (currentEpochSecond >= context.startTimestamp - FIFTEEN_MINUTES_IN_SECONDS + && currentEpochSecond <= context.startTimestamp + context.duration) { + return true; + } + return false; + } +} diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/test/.gitignore b/io.openems.edge.controller.ess.fastfrequencyreserve/test/.gitignore new file mode 100644 index 00000000000..e69de29bb2d diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/ControllerFastFrequencyReserveImplTest.java b/io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/ControllerFastFrequencyReserveImplTest.java new file mode 100644 index 00000000000..ac8b9abaa2e --- /dev/null +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/ControllerFastFrequencyReserveImplTest.java @@ -0,0 +1,284 @@ +package io.openems.edge.controller.ess.fastfrequencyreserve; + +import static io.openems.common.test.TestUtils.createDummyClock; +import static io.openems.edge.controller.ess.fastfrequencyreserve.ControllerFastFrequencyReserve.ChannelId.STATE_MACHINE; +import static io.openems.edge.controller.ess.fastfrequencyreserve.enums.ActivationTime.LONG_ACTIVATION_RUN; +import static io.openems.edge.controller.ess.fastfrequencyreserve.enums.SupportDuration.LONG_SUPPORT_DURATION; +import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_EQUALS; +import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.FREQUENCY; +import static java.time.temporal.ChronoUnit.DAYS; +import static java.time.temporal.ChronoUnit.HOURS; +import static java.time.temporal.ChronoUnit.MILLIS; +import static java.time.temporal.ChronoUnit.MINUTES; +import static java.time.temporal.ChronoUnit.SECONDS; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.time.Instant; +import java.time.ZoneOffset; +import java.util.List; + +import org.junit.Test; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.test.TimeLeapClock; +import io.openems.common.utils.JsonUtils; +import io.openems.edge.common.sum.GridMode; +import io.openems.edge.common.test.AbstractComponentTest.TestCase; +import io.openems.edge.common.test.DummyComponentManager; +import io.openems.edge.common.test.DummyConfigurationAdmin; +import io.openems.edge.controller.ess.fastfrequencyreserve.enums.ControlMode; +import io.openems.edge.controller.ess.fastfrequencyreserve.jsonrpc.SetActivateFastFreqReserveRequest.ActivateFastFreqReserveSchedule; +import io.openems.edge.controller.ess.fastfrequencyreserve.statemachine.StateMachine.State; +import io.openems.edge.controller.test.ControllerTest; +import io.openems.edge.ess.test.DummyManagedSymmetricEss; +import io.openems.edge.meter.test.DummyElectricityMeter; + +public class ControllerFastFrequencyReserveImplTest { + + @Test + public void testFfrController() throws Exception { + final var clock = new TimeLeapClock(Instant.parse("2023-07-13T08:45:00.00Z"), ZoneOffset.UTC); + new ControllerTest(new ControllerFastFrequencyReserveImpl()) // + .addReference("componentManager", new DummyComponentManager(clock)) // + .addReference("cm", new DummyConfigurationAdmin()) // + .addReference("ess", new DummyManagedSymmetricEss("ess0") // + .withGridMode(GridMode.ON_GRID)// + .withMaxApparentPower(92000)// + .withAllowedChargePower(-92000)// + .withAllowedDischargePower(92000)) // + .addReference("meter", new DummyElectricityMeter("meter0")) // + .activate(MyConfig.create() // + .setEssId("ess0") // + .setId("ctrl0") // + .setMeterId("meter0") // + .setMode(ControlMode.MANUAL_ON) // + .setPreActivationTime(15)// + .setactivationScheduleJson(JsonUtils.buildJsonArray() // + .add(JsonUtils.buildJsonObject() // + .addProperty("startTimestamp", 1689242400) // Thu Jul 13 2023 10:00:00 GMT+0000 + .addProperty("duration", 86400) // + .addProperty("dischargePowerSetPoint", 92000) // + .addProperty("frequencyLimit", 49500) // + .addProperty("activationRunTime", LONG_ACTIVATION_RUN) // + .addProperty("supportDuration", LONG_SUPPORT_DURATION) // + .build()) + .add(JsonUtils.buildJsonObject() // + .addProperty("startTimestamp", 1689331500) // Fri Jul 14 2023 10:45:00 GMT+0000 + .addProperty("duration", 86400) // + .addProperty("dischargePowerSetPoint", 92000) // + .addProperty("frequencyLimit", 49500) // + .addProperty("activationRunTime", LONG_ACTIVATION_RUN) // + .addProperty("supportDuration", LONG_SUPPORT_DURATION) // + .build()) + .build()// + .toString())// + .build()) + .next(new TestCase("1") // + .input("meter0", FREQUENCY, 50000)// + .output(STATE_MACHINE, State.UNDEFINED) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, null))// + .next(new TestCase("2") // + .input("meter0", FREQUENCY, 50000)// + .output(STATE_MACHINE, State.UNDEFINED) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, null))// + .next(new TestCase("3") // + .timeleap(clock, 1, HOURS) // + .input("meter0", FREQUENCY, 50000))// + .next(new TestCase("4"))// + .next(new TestCase("5") // + .output(STATE_MACHINE, State.UNDEFINED)) + .next(new TestCase("6") // + .timeleap(clock, 10, MINUTES)// + .output(STATE_MACHINE, State.PRE_ACTIVATION_STATE)) + .next(new TestCase("7") // + .timeleap(clock, 10, MINUTES)) + .next(new TestCase("8") // + .output(STATE_MACHINE, State.ACTIVATION_TIME)// + .output("ess0", SET_ACTIVE_POWER_EQUALS, 0)) + .next(new TestCase("9") // + .input("meter0", FREQUENCY, 50000)// + .output(STATE_MACHINE, State.ACTIVATION_TIME)// + .output("ess0", SET_ACTIVE_POWER_EQUALS, 0)) + .next(new TestCase("10") // + .input("meter0", FREQUENCY, 49400)// + .output(STATE_MACHINE, State.ACTIVATION_TIME)// + .output("ess0", SET_ACTIVE_POWER_EQUALS, 92000)) + .next(new TestCase("11") // + .timeleap(clock, LONG_ACTIVATION_RUN.getValue(), MILLIS)) // + .next(new TestCase("12") // + .output(STATE_MACHINE, State.SUPPORT_DURATION)// + .output("ess0", SET_ACTIVE_POWER_EQUALS, 92000)) // + .next(new TestCase("13") // + .timeleap(clock, LONG_SUPPORT_DURATION.getValue(), SECONDS) // + .output(STATE_MACHINE, State.SUPPORT_DURATION)) + .next(new TestCase("14") // + .output(STATE_MACHINE, State.DEACTIVATION_TIME) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 0)) + .next(new TestCase("15") // + .timeleap(clock, LONG_ACTIVATION_RUN.getValue(), MILLIS)) // + .next(new TestCase("16") // + .output(STATE_MACHINE, State.BUFFERED_TIME_BEFORE_RECOVERY) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 0)) + .next(new TestCase("17") // + .timeleap(clock, 16, SECONDS) // + .output(STATE_MACHINE, State.BUFFERED_TIME_BEFORE_RECOVERY)) + .next(new TestCase("18") // + .timeleap(clock, 12, MINUTES)) // + .next(new TestCase("19") // + .output(STATE_MACHINE, State.RECOVERY_TIME) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -16560)) + .next(new TestCase("20") // + .output(STATE_MACHINE, State.RECOVERY_TIME)) + .next(new TestCase("21") // + .output(STATE_MACHINE, State.RECOVERY_TIME)) + .next(new TestCase("22") // + .timeleap(clock, LONG_SUPPORT_DURATION.getValue(), MINUTES)) + .next(new TestCase("23") // + .input("meter0", FREQUENCY, 50000)// + .output(STATE_MACHINE, State.ACTIVATION_TIME)) + .next(new TestCase("24") // + .timeleap(clock, 1, DAYS)) // + .next(new TestCase("25") // + .output(STATE_MACHINE, State.ACTIVATION_TIME)) + .next(new TestCase("26") // + .timeleap(clock, 4, HOURS)) // + .next(new TestCase("27") // + .output(STATE_MACHINE, State.ACTIVATION_TIME)) + .next(new TestCase("28")// + .input("meter0", FREQUENCY, 49400)) + .next(new TestCase("29") // + .timeleap(clock, LONG_ACTIVATION_RUN.getValue(), MILLIS)) // + .next(new TestCase("30") // + .output(STATE_MACHINE, State.SUPPORT_DURATION)// + .output("ess0", SET_ACTIVE_POWER_EQUALS, 92000)) // + .next(new TestCase("31") // + .timeleap(clock, LONG_ACTIVATION_RUN.getValue(), MILLIS)) // + .next(new TestCase("32") // + .output(STATE_MACHINE, State.SUPPORT_DURATION)// + .output("ess0", SET_ACTIVE_POWER_EQUALS, 92000)) // + .next(new TestCase("33") // + .timeleap(clock, LONG_SUPPORT_DURATION.getValue(), SECONDS) // + .output(STATE_MACHINE, State.SUPPORT_DURATION)) + .next(new TestCase("34") // + .output(STATE_MACHINE, State.DEACTIVATION_TIME).output("ess0", SET_ACTIVE_POWER_EQUALS, 0)) + .next(new TestCase("35") // + .timeleap(clock, LONG_ACTIVATION_RUN.getValue(), MILLIS)) // + .next(new TestCase("36") // + .output(STATE_MACHINE, State.BUFFERED_TIME_BEFORE_RECOVERY) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 0)) + .next(new TestCase("37") // + .output(STATE_MACHINE, State.BUFFERED_TIME_BEFORE_RECOVERY)) + .next(new TestCase("38") // + .output(STATE_MACHINE, State.BUFFERED_TIME_BEFORE_RECOVERY)) + .next(new TestCase("39") // + .timeleap(clock, LONG_SUPPORT_DURATION.getValue(), MINUTES)) + .next(new TestCase("40") // + .input("meter0", FREQUENCY, 50000)// + .output(STATE_MACHINE, State.BUFFERED_TIME_BEFORE_RECOVERY)) + .next(new TestCase("41") // + .input("meter0", FREQUENCY, 50000)// + .output(STATE_MACHINE, State.RECOVERY_TIME)) + .next(new TestCase("42") // + .timeleap(clock, 1, DAYS)) // + .deactivate(); + } + + @Test + public void testInvalidJsonSchedule() throws Exception { + final var clock = createDummyClock(); + new ControllerTest(new ControllerFastFrequencyReserveImpl()) // + .addReference("componentManager", new DummyComponentManager(clock)) // + .addReference("cm", new DummyConfigurationAdmin()) // + .addReference("ess", new DummyManagedSymmetricEss("ess0") // + .withGridMode(GridMode.ON_GRID)// + .withMaxApparentPower(92000)// + .withAllowedChargePower(-92000)// + .withAllowedDischargePower(92000)) // + .addReference("meter", new DummyElectricityMeter("meter0")) // + .activate(MyConfig.create() // + .setEssId("ess0") // + .setId("ctrl0") // + .setMeterId("meter0") // + .setMode(ControlMode.MANUAL_ON) // + .setPreActivationTime(15)// + .setactivationScheduleJson("foo")// + .build()) // + .deactivate(); + } + + @Test + public void testInvalidJsonSchedule1() throws Exception { + final var clock = createDummyClock(); + new ControllerTest(new ControllerFastFrequencyReserveImpl()) // + .addReference("componentManager", new DummyComponentManager(clock)) // + .addReference("cm", new DummyConfigurationAdmin()) // + .addReference("ess", new DummyManagedSymmetricEss("ess0") // + .withGridMode(GridMode.ON_GRID)// + .withMaxApparentPower(92000)// + .withAllowedChargePower(-92000)// + .withAllowedDischargePower(92000)) // + .addReference("meter", new DummyElectricityMeter("meter0")) // + .activate(MyConfig.create() // + .setEssId("ess0") // + .setId("ctrl0") // + .setMeterId("meter0") // + .setMode(ControlMode.MANUAL_ON) // + .setPreActivationTime(15)// + .setactivationScheduleJson("[foo]")// + .build()) // + .deactivate(); + } + + public static final String MY_JSON = """ + [ + { + "startTimestamp":"1701738000", + "duration":"10800", + "dischargePowerSetPoint":"92000", + "frequencyLimit":"50000", + "activationRunTime":"LONG_ACTIVATION_RUN", + "supportDuration":"LONG_SUPPORT_DURATION" + }, + { + "startTimestamp":"1701738000", + "duration":"10800", + "dischargePowerSetPoint":"92000", + "frequencyLimit":"50000", + "activationRunTime":"LONG_ACTIVATION_RUN", + "supportDuration":"LONG_SUPPORT_DURATION" + }, + { + "startTimestamp":"1701752400", + "duration":"10800", + "dischargePowerSetPoint":"92000", + "frequencyLimit":"50000", + "activationRunTime":"LONG_ACTIVATION_RUN", + "supportDuration":"LONG_SUPPORT_DURATION" + }, + { + "startTimestamp":"1701777600", + "duration":"10800", + "dischargePowerSetPoint":"92000", + "frequencyLimit":"50000", + "activationRunTime":"LONG_ACTIVATION_RUN", + "supportDuration":"LONG_SUPPORT_DURATION" + } + ] + """; + + @Test + public void testFromMethod() throws OpenemsNamedException { + var scheduleArray = JsonUtils.parseToJsonArray(MY_JSON); + try { + List scheduleList = ActivateFastFreqReserveSchedule.from(scheduleArray); + + assertEquals(3, scheduleList.size()); + assertTrue(scheduleList.get(0).startTimestamp() <= scheduleList.get(1).startTimestamp()); + assertTrue(scheduleList.get(1).startTimestamp() <= scheduleList.get(2).startTimestamp()); + + } catch (OpenemsNamedException e) { + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/ControllerFastFrequencyReserveImplTest2.java b/io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/ControllerFastFrequencyReserveImplTest2.java new file mode 100644 index 00000000000..d1bbb7c9da1 --- /dev/null +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/ControllerFastFrequencyReserveImplTest2.java @@ -0,0 +1,182 @@ +package io.openems.edge.controller.ess.fastfrequencyreserve; + +import static io.openems.common.utils.JsonUtils.buildJsonArray; +import static io.openems.common.utils.JsonUtils.buildJsonObject; +import static io.openems.edge.controller.ess.fastfrequencyreserve.ControllerFastFrequencyReserve.ChannelId.STATE_MACHINE; +import static io.openems.edge.controller.ess.fastfrequencyreserve.enums.ActivationTime.LONG_ACTIVATION_RUN; +import static io.openems.edge.controller.ess.fastfrequencyreserve.enums.SupportDuration.LONG_SUPPORT_DURATION; +import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_EQUALS; +import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.FREQUENCY; +import static java.time.temporal.ChronoUnit.DAYS; +import static java.time.temporal.ChronoUnit.HOURS; +import static java.time.temporal.ChronoUnit.MILLIS; +import static java.time.temporal.ChronoUnit.MINUTES; +import static java.time.temporal.ChronoUnit.SECONDS; + +import java.time.Instant; +import java.time.ZoneOffset; + +import org.junit.Test; + +import io.openems.common.test.TimeLeapClock; +import io.openems.edge.common.sum.GridMode; +import io.openems.edge.common.test.AbstractComponentTest.TestCase; +import io.openems.edge.common.test.DummyComponentManager; +import io.openems.edge.common.test.DummyConfigurationAdmin; +import io.openems.edge.controller.ess.fastfrequencyreserve.enums.ControlMode; +import io.openems.edge.controller.ess.fastfrequencyreserve.statemachine.StateMachine.State; +import io.openems.edge.controller.test.ControllerTest; +import io.openems.edge.ess.test.DummyManagedSymmetricEss; +import io.openems.edge.meter.test.DummyElectricityMeter; + +public class ControllerFastFrequencyReserveImplTest2 { + + @Test + public void test1() throws Exception { + final var clock = new TimeLeapClock(Instant.parse("2023-07-13T08:45:00.00Z"), ZoneOffset.UTC); + new ControllerTest(new ControllerFastFrequencyReserveImpl()) // + .addReference("componentManager", new DummyComponentManager(clock)) // + .addReference("cm", new DummyConfigurationAdmin()) // + .addReference("ess", new DummyManagedSymmetricEss("ess0") // + .withGridMode(GridMode.ON_GRID)// + .withMaxApparentPower(92000)// + .withAllowedChargePower(-92000)// + .withAllowedDischargePower(92000)) // + .addReference("meter", new DummyElectricityMeter("meter0")) // + .activate(MyConfig.create() // + .setEssId("ess0") // + .setId("ctrl0") // + .setMeterId("meter0") // + .setMode(ControlMode.MANUAL_ON) // + .setPreActivationTime(15)// + .setactivationScheduleJson(buildJsonArray() // + .add(buildJsonObject() // + .addProperty("startTimestamp", 1689242400) // Thu Jul 13 2023 10:00:00 GMT+0000 + .addProperty("duration", 86400) // + .addProperty("dischargePowerSetPoint", 92000) // + .addProperty("frequencyLimit", 49500) // + .addProperty("activationRunTime", LONG_ACTIVATION_RUN) // + .addProperty("supportDuration", LONG_SUPPORT_DURATION) // + .build()) + .add(buildJsonObject() // + .addProperty("startTimestamp", 1689331500) // Fri Jul 14 2023 10:45:00 GMT+0000 + .addProperty("duration", 86400) // + .addProperty("dischargePowerSetPoint", 92000) // + .addProperty("frequencyLimit", 49500) // + .addProperty("activationRunTime", LONG_ACTIVATION_RUN) // + .addProperty("supportDuration", LONG_SUPPORT_DURATION) // + .build()) + .build()// + .toString())// + .build()) + .next(new TestCase("1") // + .input("meter0", FREQUENCY, 50000)// + .output(ControllerFastFrequencyReserve.ChannelId.STATE_MACHINE, State.UNDEFINED) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, null))// + .next(new TestCase("2") // + .input("meter0", FREQUENCY, 50000)// + .output(STATE_MACHINE, State.UNDEFINED) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, null))// + .next(new TestCase("3") // + .timeleap(clock, 1, HOURS) // + .input("meter0", FREQUENCY, 50000))// + .next(new TestCase("4"))// + .next(new TestCase("5") // + .output(STATE_MACHINE, State.UNDEFINED)) + .next(new TestCase("6") // + .timeleap(clock, 10, MINUTES)// + .output(STATE_MACHINE, State.PRE_ACTIVATION_STATE)) + .next(new TestCase("7") // + .timeleap(clock, 10, MINUTES)) + .next(new TestCase("8") // + .output(STATE_MACHINE, State.ACTIVATION_TIME)// + .output("ess0", SET_ACTIVE_POWER_EQUALS, 0)) + .next(new TestCase("9") // + .input("meter0", FREQUENCY, 50000)// + .output(STATE_MACHINE, State.ACTIVATION_TIME)// + .output("ess0", SET_ACTIVE_POWER_EQUALS, 0)) + .next(new TestCase("10") // + .input("meter0", FREQUENCY, 49400)// + .output(STATE_MACHINE, State.ACTIVATION_TIME)// + .output("ess0", SET_ACTIVE_POWER_EQUALS, 92000)) + .next(new TestCase("11") // + .timeleap(clock, LONG_ACTIVATION_RUN.getValue(), MILLIS)) // + .next(new TestCase("12") // + .output(STATE_MACHINE, State.SUPPORT_DURATION)// + .output("ess0", SET_ACTIVE_POWER_EQUALS, 92000)) // + .next(new TestCase("13") // + .timeleap(clock, LONG_SUPPORT_DURATION.getValue(), SECONDS) // + .output(STATE_MACHINE, State.SUPPORT_DURATION)) + .next(new TestCase("14") // + .output(STATE_MACHINE, State.DEACTIVATION_TIME).output("ess0", SET_ACTIVE_POWER_EQUALS, 0)) + .next(new TestCase("15") // + .timeleap(clock, LONG_ACTIVATION_RUN.getValue(), MILLIS)) // + .next(new TestCase("16") // + .output(STATE_MACHINE, State.BUFFERED_TIME_BEFORE_RECOVERY) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 0)) + .next(new TestCase("17") // + .timeleap(clock, 16, SECONDS) // + .output(STATE_MACHINE, State.BUFFERED_TIME_BEFORE_RECOVERY)) + .next(new TestCase("18") // + .timeleap(clock, 12, MINUTES)) // + .next(new TestCase("19") // + .output(STATE_MACHINE, State.RECOVERY_TIME) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -16560)) + .next(new TestCase("20") // + .output(STATE_MACHINE, State.RECOVERY_TIME)) + .next(new TestCase("21") // + .output(STATE_MACHINE, State.RECOVERY_TIME)) + .next(new TestCase("22") // + .timeleap(clock, LONG_SUPPORT_DURATION.getValue(), MINUTES)) + .next(new TestCase("23") // + .input("meter0", FREQUENCY, 50000)// + .output(STATE_MACHINE, State.ACTIVATION_TIME)) + .next(new TestCase("24") // + .timeleap(clock, 1, DAYS)) // + .next(new TestCase("25") // + .output(STATE_MACHINE, State.ACTIVATION_TIME)) + .next(new TestCase("26") // + .timeleap(clock, 4, HOURS)) // + .next(new TestCase("27") // + .output(STATE_MACHINE, State.ACTIVATION_TIME)) + .next(new TestCase("28")// + .input("meter0", FREQUENCY, 49400)) + .next(new TestCase("29") // + .timeleap(clock, LONG_ACTIVATION_RUN.getValue(), MILLIS)) // + .next(new TestCase("30") // + .output(STATE_MACHINE, State.SUPPORT_DURATION)// + .output("ess0", SET_ACTIVE_POWER_EQUALS, 92000)) // + .next(new TestCase("31") // + .timeleap(clock, LONG_ACTIVATION_RUN.getValue(), MILLIS)) // + .next(new TestCase("32") // + .output(STATE_MACHINE, State.SUPPORT_DURATION)// + .output("ess0", SET_ACTIVE_POWER_EQUALS, 92000)) // + .next(new TestCase("33") // + .timeleap(clock, LONG_SUPPORT_DURATION.getValue(), SECONDS) // + .output(STATE_MACHINE, State.SUPPORT_DURATION)) + .next(new TestCase("34") // + .output(STATE_MACHINE, State.DEACTIVATION_TIME) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 0)) + .next(new TestCase("35") // + .timeleap(clock, LONG_ACTIVATION_RUN.getValue(), MILLIS)) // + .next(new TestCase("36") // + .output(STATE_MACHINE, State.BUFFERED_TIME_BEFORE_RECOVERY) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 0)) + .next(new TestCase("37") // + .output(STATE_MACHINE, State.BUFFERED_TIME_BEFORE_RECOVERY)) + .next(new TestCase("38") // + .output(STATE_MACHINE, State.BUFFERED_TIME_BEFORE_RECOVERY)) + .next(new TestCase("39") // + .timeleap(clock, LONG_SUPPORT_DURATION.getValue(), MINUTES)) + .next(new TestCase("40") // + .input("meter0", FREQUENCY, 50000)// + .output(STATE_MACHINE, State.BUFFERED_TIME_BEFORE_RECOVERY)) + .next(new TestCase("41") // + .input("meter0", FREQUENCY, 50000)// + .output(STATE_MACHINE, State.RECOVERY_TIME)) + .next(new TestCase("42") // + .timeleap(clock, 1, DAYS)) // + .deactivate(); + } + +} diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/JsonRpcTest.java b/io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/JsonRpcTest.java new file mode 100644 index 00000000000..c0db3e9a28d --- /dev/null +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/JsonRpcTest.java @@ -0,0 +1,68 @@ +package io.openems.edge.controller.ess.fastfrequencyreserve; + +import java.net.URI; +//import org.junit.Test; +import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; + +import io.openems.edge.controller.ess.fastfrequencyreserve.enums.ActivationTime; +import io.openems.edge.controller.ess.fastfrequencyreserve.enums.SupportDuration; +import io.openems.edge.controller.ess.fastfrequencyreserve.jsonrpc.SetActivateFastFreqReserveRequest; +import io.openems.edge.controller.ess.fastfrequencyreserve.jsonrpc.SetActivateFastFreqReserveRequest.ActivateFastFreqReserveSchedule; + +/** + * This Test demonstrates the usage of the OpenEMS Backend-to-Backend API + * interface. To start the tests make sure to start OpenEMS Backend and activate + * the B2bWebsocket component via Apache Felix. Afterwards uncomment the "@Test" + * annotations below and execute the Tests. + */ +public class JsonRpcTest { + + private static final String URI = "ws://localhost:8076"; + private static final String USERNAME = "user"; + private static final String PASSWORD = "password"; + + private static TestClient prepareTestClient() throws URISyntaxException, InterruptedException { + Map httpHeaders = new HashMap<>(); + var auth = new String( + Base64.getEncoder().encode((JsonRpcTest.USERNAME + ":" + JsonRpcTest.PASSWORD).getBytes()), + StandardCharsets.UTF_8); + httpHeaders.put("Authorization", "Basic " + auth); + var client = new TestClient(new URI(JsonRpcTest.URI), httpHeaders); + client.startBlocking(); + return client; + } + + /** + * Tests the activation of Fast Frequency Reserve schedule. + * + * @throws URISyntaxException String could not be parsed as a URI reference. + * @throws InterruptedException interrupted exception. + */ + // @Test + public void testActivateFastFreqReserveSchedule() throws URISyntaxException, InterruptedException { + var client = JsonRpcTest.prepareTestClient(); + + var request = new SetActivateFastFreqReserveRequest("edge0"); + var now = System.currentTimeMillis() / 1000; + ActivateFastFreqReserveSchedule newEntry = new ActivateFastFreqReserveSchedule(// + now, // + 1000, // + 92000, // + 50000, // + ActivationTime.LONG_ACTIVATION_RUN, // + SupportDuration.LONG_SUPPORT_DURATION); + request.addScheduleEntry(newEntry); + + try { + var responseFuture = client.sendRequest(request); + System.out.println(responseFuture.get().toString()); + } catch (Exception e) { + System.out.println(e.getMessage()); + } + } + +} diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/MyConfig.java b/io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/MyConfig.java new file mode 100644 index 00000000000..948d3e0fb3a --- /dev/null +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/MyConfig.java @@ -0,0 +1,105 @@ +package io.openems.edge.controller.ess.fastfrequencyreserve; + +import io.openems.common.test.AbstractComponentConfig; +import io.openems.common.utils.ConfigUtils; +import io.openems.edge.controller.ess.fastfrequencyreserve.enums.ControlMode; + +@SuppressWarnings("all") +public class MyConfig extends AbstractComponentConfig implements Config { + + protected static class Builder { + private String id; + private String essId; + private String meterId; + private ControlMode mode; + private String activationScheduleJson; + private int preActivationTime; + + public Builder setId(String id) { + this.id = id; + return this; + } + + public Builder setEssId(String essId) { + this.essId = essId; + return this; + } + + public Builder setMeterId(String meterId) { + this.meterId = meterId; + return this; + } + + public Builder setMode(ControlMode mode) { + this.mode = mode; + return this; + } + + public Builder setactivationScheduleJson(String schedule) { + this.activationScheduleJson = schedule; + return this; + } + + public Builder setPreActivationTime(int preActivationTime) { + this.preActivationTime = preActivationTime; + return this; + } + + public MyConfig build() { + return new MyConfig(this); + } + + } + + /** + * Create a Config builder. + * + * @return a {@link Builder} + */ + public static Builder create() { + return new Builder(); + } + + private final Builder builder; + + private MyConfig(Builder builder) { + super(Config.class, builder.id); + this.builder = builder; + } + + @Override + public String ess_id() { + return this.builder.essId; + } + + @Override + public String meter_id() { + return this.builder.meterId; + } + + @Override + public String ess_target() { + return ConfigUtils.generateReferenceTargetFilter(this.id(), this.ess_id()); + } + + @Override + public String meter_target() { + return ConfigUtils.generateReferenceTargetFilter(this.id(), this.meter_id()); + } + + @Override + public ControlMode controlMode() { + return this.builder.mode; + } + + @Override + public String activationScheduleJson() { + return this.builder.activationScheduleJson; + } + + @Override + public int preActivationTime() { + return this.builder.preActivationTime; + } + +} diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/TestClient.java b/io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/TestClient.java new file mode 100644 index 00000000000..991887efda7 --- /dev/null +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/TestClient.java @@ -0,0 +1,122 @@ +package io.openems.edge.controller.ess.fastfrequencyreserve; + +import java.net.URI; +import java.util.Map; + +import org.java_websocket.WebSocket; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.openems.common.websocket.AbstractWebsocketClient; +import io.openems.common.websocket.OnClose; +import io.openems.common.websocket.OnError; +import io.openems.common.websocket.OnNotification; +import io.openems.common.websocket.OnOpen; +import io.openems.common.websocket.OnRequest; +import io.openems.common.websocket.WsData; + +public class TestClient extends AbstractWebsocketClient { + + private final Logger log = LoggerFactory.getLogger(TestClient.class); + + private OnOpen onOpen; + private OnRequest onRequest; + private OnNotification onNotification; + private OnError onError; + private OnClose onClose; + + protected TestClient(URI serverUri, Map httpHeaders) { + super("JsonTest.Unittest", serverUri, httpHeaders); + this.onOpen = (ws, handshake) -> { + return null; + }; + this.onRequest = (ws, request) -> { + this.log.info("OnRequest: " + request); + return null; + }; + this.onNotification = (ws, notification) -> { + this.log.info("OnNotification: " + notification); + }; + this.onError = (ws, ex) -> { + this.log.info("onError: " + ex.getMessage()); + }; + this.onClose = (ws, code, reason, remote) -> { + this.log.info("onClose: " + reason); + }; + } + + @Override + public OnOpen getOnOpen() { + return this.onOpen; + } + + public void setOnOpen(OnOpen onOpen) { + this.onOpen = onOpen; + } + + @Override + public OnRequest getOnRequest() { + return this.onRequest; + } + + public void setOnRequest(OnRequest onRequest) { + this.onRequest = onRequest; + } + + @Override + public OnError getOnError() { + return this.onError; + } + + public void setOnError(OnError onError) { + this.onError = onError; + } + + @Override + public OnClose getOnClose() { + return this.onClose; + } + + public void setOnClose(OnClose onClose) { + this.onClose = onClose; + } + + @Override + protected OnNotification getOnNotification() { + return this.onNotification; + } + + public void setOnNotification(OnNotification onNotification) { + this.onNotification = onNotification; + } + + @Override + protected WsData createWsData(WebSocket ws) { + return new WsData(ws) { + @Override + public String toString() { + return "TestClient.WsData []"; + } + }; + } + + @Override + protected void logInfo(Logger log, String message) { + log.info(message); + } + + @Override + protected void logWarn(Logger log, String message) { + log.warn(message); + } + + @Override + protected void logError(Logger log, String message) { + log.error(message); + } + + @Override + protected void execute(Runnable command) { + command.run(); + } +} diff --git a/io.openems.edge.controller.ess.fixactivepower/.classpath b/io.openems.edge.controller.ess.fixactivepower/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.controller.ess.fixactivepower/.classpath +++ b/io.openems.edge.controller.ess.fixactivepower/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.controller.ess.fixactivepower/bnd.bnd b/io.openems.edge.controller.ess.fixactivepower/bnd.bnd index 9b0e7d57001..38c5412d005 100644 --- a/io.openems.edge.controller.ess.fixactivepower/bnd.bnd +++ b/io.openems.edge.controller.ess.fixactivepower/bnd.bnd @@ -8,8 +8,9 @@ Bundle-Version: 1.0.0.${tstamp} io.openems.common,\ io.openems.edge.common,\ io.openems.edge.controller.api,\ + io.openems.edge.energy.api,\ io.openems.edge.ess.api,\ io.openems.edge.timedata.api,\ - + -testpath: \ ${testpath} diff --git a/io.openems.edge.controller.ess.fixactivepower/src/io/openems/edge/controller/ess/fixactivepower/ControllerEssFixActivePowerImpl.java b/io.openems.edge.controller.ess.fixactivepower/src/io/openems/edge/controller/ess/fixactivepower/ControllerEssFixActivePowerImpl.java index ad3f5905e0e..32c9fb30834 100644 --- a/io.openems.edge.controller.ess.fixactivepower/src/io/openems/edge/controller/ess/fixactivepower/ControllerEssFixActivePowerImpl.java +++ b/io.openems.edge.controller.ess.fixactivepower/src/io/openems/edge/controller/ess/fixactivepower/ControllerEssFixActivePowerImpl.java @@ -1,5 +1,9 @@ package io.openems.edge.controller.ess.fixactivepower; +import static io.openems.edge.energy.api.EnergyUtils.toEnergy; + +import java.util.function.Supplier; + import org.osgi.service.cm.ConfigurationAdmin; import org.osgi.service.component.ComponentContext; import org.osgi.service.component.annotations.Activate; @@ -17,10 +21,13 @@ import io.openems.edge.common.component.AbstractOpenemsComponent; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.controller.api.Controller; +import io.openems.edge.energy.api.EnergySchedulable; +import io.openems.edge.energy.api.EnergyScheduleHandler; import io.openems.edge.ess.api.HybridEss; import io.openems.edge.ess.api.ManagedSymmetricEss; import io.openems.edge.ess.api.PowerConstraint; import io.openems.edge.ess.power.api.Pwr; +import io.openems.edge.ess.power.api.Relationship; import io.openems.edge.timedata.api.Timedata; import io.openems.edge.timedata.api.TimedataProvider; import io.openems.edge.timedata.api.utils.CalculateActiveTime; @@ -32,10 +39,11 @@ configurationPolicy = ConfigurationPolicy.REQUIRE // ) public class ControllerEssFixActivePowerImpl extends AbstractOpenemsComponent - implements ControllerEssFixActivePower, Controller, OpenemsComponent, TimedataProvider { + implements ControllerEssFixActivePower, EnergySchedulable, Controller, OpenemsComponent, TimedataProvider { private final CalculateActiveTime calculateCumulatedActiveTime = new CalculateActiveTime(this, ControllerEssFixActivePower.ChannelId.CUMULATED_ACTIVE_TIME); + private final EnergyScheduleHandler energyScheduleHandler; @Reference private ConfigurationAdmin cm; @@ -54,6 +62,13 @@ public ControllerEssFixActivePowerImpl() { Controller.ChannelId.values(), // ControllerEssFixActivePower.ChannelId.values() // ); + this.energyScheduleHandler = buildEnergyScheduleHandler(() -> new EshContext(// + this.config.mode(), // + toEnergy(switch (this.config.phase()) { + case ALL -> this.config.power(); + case L1, L2, L3 -> this.config.power() * 3; + }), // + this.config.relationship())); } @Activate @@ -70,6 +85,7 @@ private void modified(ComponentContext context, Config config) { if (this.applyConfig(context, config)) { return; } + this.energyScheduleHandler.triggerReschedule("ControllerEssFixActivePowerImpl::modified()"); } private boolean applyConfig(ComponentContext context, Config config) { @@ -137,4 +153,41 @@ protected static Integer getAcPower(ManagedSymmetricEss ess, HybridEssMode hybri public Timedata getTimedata() { return this.timedata; } + + /** + * Builds the {@link EnergyScheduleHandler}. + * + *

+ * This is public so that it can be used by the EnergyScheduler integration + * test. + * + * @param context a supplier for the configured {@link EshContext} + * @return a {@link EnergyScheduleHandler} + */ + public static EnergyScheduleHandler buildEnergyScheduleHandler(Supplier context) { + return EnergyScheduleHandler.WithOnlyOneState.create() // + .setContextFunction(simContext -> context.get()) // + .setSimulator((simContext, period, energyFlow, ctrlContext) -> { + switch (ctrlContext.mode) { + case MANUAL_ON: + switch (ctrlContext.relationship) { + case EQUALS -> energyFlow.setEss(ctrlContext.energy); + case GREATER_OR_EQUALS -> energyFlow.setEssMaxCharge(-ctrlContext.energy); + case LESS_OR_EQUALS -> energyFlow.setEssMaxDischarge(ctrlContext.energy); + } + break; + case MANUAL_OFF: + break; + } + }) // + .build(); + } + + public static record EshContext(Mode mode, int energy, Relationship relationship) { + } + + @Override + public EnergyScheduleHandler getEnergyScheduleHandler() { + return this.energyScheduleHandler; + } } \ No newline at end of file diff --git a/io.openems.edge.controller.ess.fixactivepower/src/io/openems/edge/controller/ess/fixactivepower/package-info.java b/io.openems.edge.controller.ess.fixactivepower/src/io/openems/edge/controller/ess/fixactivepower/package-info.java new file mode 100644 index 00000000000..aab27855be8 --- /dev/null +++ b/io.openems.edge.controller.ess.fixactivepower/src/io/openems/edge/controller/ess/fixactivepower/package-info.java @@ -0,0 +1,3 @@ +@org.osgi.annotation.versioning.Version("1.0.0") +@org.osgi.annotation.bundle.Export +package io.openems.edge.controller.ess.fixactivepower; diff --git a/io.openems.edge.controller.ess.fixactivepower/test/io/openems/edge/controller/ess/fixactivepower/ControllerEssFixActivePowerImplTest.java b/io.openems.edge.controller.ess.fixactivepower/test/io/openems/edge/controller/ess/fixactivepower/ControllerEssFixActivePowerImplTest.java index d26fa505e44..ebcde754b1d 100644 --- a/io.openems.edge.controller.ess.fixactivepower/test/io/openems/edge/controller/ess/fixactivepower/ControllerEssFixActivePowerImplTest.java +++ b/io.openems.edge.controller.ess.fixactivepower/test/io/openems/edge/controller/ess/fixactivepower/ControllerEssFixActivePowerImplTest.java @@ -1,5 +1,6 @@ package io.openems.edge.controller.ess.fixactivepower; +import static io.openems.edge.controller.ess.fixactivepower.ControllerEssFixActivePowerImpl.getAcPower; import static org.junit.Assert.assertEquals; import org.junit.Test; @@ -14,45 +15,44 @@ public class ControllerEssFixActivePowerImplTest { - private static final String CTRL_ID = "ctrl0"; - private static final String ESS_ID = "ess0"; - @Test public void testOn() throws OpenemsException, Exception { - final var ess = new DummyManagedAsymmetricEss(ESS_ID); + final var ess = new DummyManagedAsymmetricEss("ess0"); new ControllerTest(new ControllerEssFixActivePowerImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("ess", ess) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setMode(Mode.MANUAL_ON) // .setHybridEssMode(HybridEssMode.TARGET_DC) // .setPower(1234) // .setPhase(Phase.ALL) // .setRelationship(Relationship.EQUALS) // - .build()); // + .build()) // + .deactivate(); } @Test public void testOff() throws OpenemsException, Exception { new ControllerTest(new ControllerEssFixActivePowerImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("ess", new DummyManagedAsymmetricEss(ESS_ID)) // + .addReference("ess", new DummyManagedAsymmetricEss("ess0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setMode(Mode.MANUAL_OFF) // .setHybridEssMode(HybridEssMode.TARGET_DC) // .setPower(1234) // .setPhase(Phase.ALL) // .setRelationship(Relationship.EQUALS) // - .build()); // + .build()) // + .deactivate(); } @Test public void testGetAcPower() throws OpenemsException, Exception { - var hybridEss = new DummyHybridEss(ESS_ID) // + var hybridEss = new DummyHybridEss("ess0") // .withActivePower(7000) // .withMaxApparentPower(10000) // .withAllowedChargePower(-5000) // @@ -60,9 +60,9 @@ public void testGetAcPower() throws OpenemsException, Exception { .withDcDischargePower(3000); // assertEquals(Integer.valueOf(5000), // - ControllerEssFixActivePowerImpl.getAcPower(hybridEss, HybridEssMode.TARGET_AC, 5000)); + getAcPower(hybridEss, HybridEssMode.TARGET_AC, 5000)); assertEquals(Integer.valueOf(9000), // - ControllerEssFixActivePowerImpl.getAcPower(hybridEss, HybridEssMode.TARGET_DC, 5000)); + getAcPower(hybridEss, HybridEssMode.TARGET_DC, 5000)); } } diff --git a/io.openems.edge.controller.ess.fixstateofcharge/.classpath b/io.openems.edge.controller.ess.fixstateofcharge/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.controller.ess.fixstateofcharge/.classpath +++ b/io.openems.edge.controller.ess.fixstateofcharge/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.controller.ess.fixstateofcharge/test/io/openems/edge/controller/ess/fixstateofcharge/ControllerEssFixStateOfChargeImplTest.java b/io.openems.edge.controller.ess.fixstateofcharge/test/io/openems/edge/controller/ess/fixstateofcharge/ControllerEssFixStateOfChargeImplTest.java index 48553f06491..70d0b636428 100644 --- a/io.openems.edge.controller.ess.fixstateofcharge/test/io/openems/edge/controller/ess/fixstateofcharge/ControllerEssFixStateOfChargeImplTest.java +++ b/io.openems.edge.controller.ess.fixstateofcharge/test/io/openems/edge/controller/ess/fixstateofcharge/ControllerEssFixStateOfChargeImplTest.java @@ -1,10 +1,23 @@ package io.openems.edge.controller.ess.fixstateofcharge; +import static io.openems.edge.controller.ess.fixstateofcharge.api.AbstractFixStateOfCharge.DEFAULT_POWER_FACTOR; +import static io.openems.edge.controller.ess.fixstateofcharge.api.EndCondition.CAPACITY_CHANGED; +import static io.openems.edge.controller.ess.fixstateofcharge.api.FixStateOfCharge.ChannelId.DEBUG_SET_ACTIVE_POWER; +import static io.openems.edge.controller.ess.fixstateofcharge.api.FixStateOfCharge.ChannelId.DEBUG_SET_ACTIVE_POWER_RAW; +import static io.openems.edge.controller.ess.fixstateofcharge.api.FixStateOfCharge.ChannelId.STATE_MACHINE; +import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_EQUALS; +import static io.openems.edge.ess.api.SymmetricEss.ChannelId.CAPACITY; +import static io.openems.edge.ess.api.SymmetricEss.ChannelId.MAX_APPARENT_POWER; +import static io.openems.edge.ess.api.SymmetricEss.ChannelId.SOC; +import static java.lang.Math.min; +import static java.lang.Math.round; +import static java.time.temporal.ChronoUnit.HOURS; +import static java.time.temporal.ChronoUnit.MINUTES; + import java.time.Instant; import java.time.ZoneId; import java.time.ZoneOffset; import java.time.ZonedDateTime; -import java.time.temporal.ChronoUnit; import org.junit.Test; @@ -14,54 +27,30 @@ import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyComponentManager; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.controller.ess.fixstateofcharge.api.AbstractFixStateOfCharge; -import io.openems.edge.controller.ess.fixstateofcharge.api.EndCondition; -import io.openems.edge.controller.ess.fixstateofcharge.statemachine.StateMachine; +import io.openems.edge.controller.ess.fixstateofcharge.statemachine.StateMachine.State; import io.openems.edge.controller.test.ControllerTest; import io.openems.edge.ess.test.DummyManagedSymmetricEss; import io.openems.edge.timedata.test.DummyTimedata; public class ControllerEssFixStateOfChargeImplTest { - // Ids - private static final String CTRL_ID = "ctrlFixStateOfCharge0"; - private static final String ESS_ID = "ess0"; - - // Components - private static final DummyManagedSymmetricEss ESS = new DummyManagedSymmetricEss(ESS_ID) // + private static final DummyManagedSymmetricEss ESS = new DummyManagedSymmetricEss("ess0") // .withMaxApparentPower(10_000); - - // Defaults private static final String DEFAULT_TARGET_TIME = "2022-10-27T10:30:00+01:00"; - - // Ess channels - private static final ChannelAddress ESS_CAPACITY = new ChannelAddress(ESS_ID, "Capacity"); - private static final ChannelAddress ESS_SOC = new ChannelAddress(ESS_ID, "Soc"); - private static final ChannelAddress ESS_MAX_APPARENT_POWER = new ChannelAddress(ESS_ID, "MaxApparentPower"); - private static final ChannelAddress ESS_SET_ACTIVE_POWER_EQUALS = new ChannelAddress(ESS_ID, - "SetActivePowerEquals"); - - // Controller channels - private static final ChannelAddress STATE_MACHINE = new ChannelAddress(CTRL_ID, "StateMachine"); - private static final ChannelAddress DEBUG_SET_ACTIVE_POWER = new ChannelAddress(CTRL_ID, "DebugSetActivePower"); - private static final ChannelAddress DEBUG_SET_ACTIVE_POWER_RAW = new ChannelAddress(CTRL_ID, - "DebugSetActivePowerRaw"); - private static final ChannelAddress CTRL_ESS_CAPACITY = new ChannelAddress(CTRL_ID, "EssCapacity"); + private static final ChannelAddress CTRL_ESS_CAPACITY = new ChannelAddress("ctrl0", "EssCapacity"); @Test public void testNotRunning() throws Exception { final var clock = new TimeLeapClock(Instant.parse("2022-01-01T08:00:00.00Z"), ZoneOffset.UTC); - final var componentManager = new DummyComponentManager(clock); - new ControllerTest(new ControllerEssFixStateOfChargeImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("componentManager", componentManager) // + .addReference("componentManager", new DummyComponentManager(clock)) // .addReference("timedata", new DummyTimedata("timedata0")) // .addReference("sum", new DummySum()) // .addReference("ess", ESS) // .activate(FixStateOfChargeConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setRunning(false) // .setTargetSoc(30) // .setSpecifyTargetTime(true) // @@ -69,29 +58,30 @@ public void testNotRunning() throws Exception { .setSelfTermination(false) // .setTerminationBuffer(720) // .setConditionalTermination(false) // - .setEndCondition(EndCondition.CAPACITY_CHANGED) // + .setEndCondition(CAPACITY_CHANGED) // .build()) .next(new TestCase()) // .next(new TestCase() // - .output(ESS_SET_ACTIVE_POWER_EQUALS, null) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, null) // .output(DEBUG_SET_ACTIVE_POWER, null) // .output(DEBUG_SET_ACTIVE_POWER_RAW, null) // - .output(STATE_MACHINE, StateMachine.State.IDLE) // - ); + .output(STATE_MACHINE, State.IDLE)) // + .deactivate(); } @Test public void testAllStates() throws Exception { + final var clock = new TimeLeapClock(Instant.parse("2023-01-01T08:00:00.00Z"), ZoneOffset.UTC); new ControllerTest(new ControllerEssFixStateOfChargeImpl()) // - .addReference("componentManager", new DummyComponentManager()) // + .addReference("componentManager", new DummyComponentManager(clock)) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("sum", new DummySum()) // .addReference("timedata", new DummyTimedata("timedata0")) // - .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) // + .addReference("ess", new DummyManagedSymmetricEss("ess0")) // .activate(FixStateOfChargeConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setRunning(true) // .setTargetSoc(30) // .setSpecifyTargetTime(true) // @@ -99,39 +89,39 @@ public void testAllStates() throws Exception { .setSelfTermination(false) // .setTerminationBuffer(720) // .setConditionalTermination(true) // - .setEndCondition(EndCondition.CAPACITY_CHANGED) // + .setEndCondition(CAPACITY_CHANGED) // .build()) .next(new TestCase() // - .input(ESS_SOC, 22) // - .input(ESS_MAX_APPARENT_POWER, 10000) // - .output(STATE_MACHINE, StateMachine.State.IDLE)) // + .input("ess0", SOC, 22) // + .input("ess0", MAX_APPARENT_POWER, 10000) // + .output(STATE_MACHINE, State.IDLE)) // .next(new TestCase() // - .input(ESS_SOC, 22) // - .input(ESS_MAX_APPARENT_POWER, 10000) // - .output(STATE_MACHINE, StateMachine.State.IDLE)) // + .input("ess0", SOC, 22) // + .input("ess0", MAX_APPARENT_POWER, 10000) // + .output(STATE_MACHINE, State.IDLE)) // .next(new TestCase() // - .input(ESS_SOC, 21) // - .output(STATE_MACHINE, StateMachine.State.NOT_STARTED)) // + .input("ess0", SOC, 21) // + .output(STATE_MACHINE, State.NOT_STARTED)) // .next(new TestCase() // - .input(ESS_SOC, 20) // - .output(STATE_MACHINE, StateMachine.State.BELOW_TARGET_SOC)) // + .input("ess0", SOC, 20) // + .output(STATE_MACHINE, State.BELOW_TARGET_SOC)) // .next(new TestCase() // - .input(ESS_SOC, 25) // - .output(STATE_MACHINE, StateMachine.State.BELOW_TARGET_SOC)) // + .input("ess0", SOC, 25) // + .output(STATE_MACHINE, State.BELOW_TARGET_SOC)) // .next(new TestCase() // - .input(ESS_SOC, 30)) // + .input("ess0", SOC, 30)) // .next(new TestCase() // - .input(ESS_SOC, 30) /// - .output(STATE_MACHINE, StateMachine.State.AT_TARGET_SOC)) // + .input("ess0", SOC, 30) /// + .output(STATE_MACHINE, State.AT_TARGET_SOC)) // .next(new TestCase() // - .input(ESS_SOC, 30) // - .output(STATE_MACHINE, StateMachine.State.AT_TARGET_SOC)) // - ; + .input("ess0", SOC, 30) // + .output(STATE_MACHINE, State.AT_TARGET_SOC)) // + .deactivate(); } @Test public void testCapacityCondition() throws Exception { - + final var clock = new TimeLeapClock(Instant.parse("2023-01-01T08:00:00.00Z"), ZoneOffset.UTC); var timedata = new DummyTimedata("timedata0"); var start = ZonedDateTime.of(2022, 05, 05, 0, 0, 0, 0, ZoneId.of("UTC")); @@ -139,15 +129,15 @@ public void testCapacityCondition() throws Exception { timedata.add(start.plusMinutes(60), CTRL_ESS_CAPACITY, 8_000); timedata.add(start.plusMinutes(90), CTRL_ESS_CAPACITY, 8_000); - var test = new ControllerTest(new ControllerEssFixStateOfChargeImpl()) // - .addReference("componentManager", new DummyComponentManager()) // + new ControllerTest(new ControllerEssFixStateOfChargeImpl()) // + .addReference("componentManager", new DummyComponentManager(clock)) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("sum", new DummySum()) // .addReference("timedata", timedata) // - .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) // + .addReference("ess", new DummyManagedSymmetricEss("ess0")) // .activate(FixStateOfChargeConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setRunning(true) // .setTargetSoc(30) // .setSpecifyTargetTime(true) // @@ -155,70 +145,66 @@ public void testCapacityCondition() throws Exception { .setSelfTermination(false) // .setTerminationBuffer(720) // .setConditionalTermination(true) // - .setEndCondition(EndCondition.CAPACITY_CHANGED) // + .setEndCondition(CAPACITY_CHANGED) // .build()) .next(new TestCase() // - .input(ESS_SOC, 22) // - .input(ESS_MAX_APPARENT_POWER, 10000) // - .output(STATE_MACHINE, StateMachine.State.IDLE)) // + .input("ess0", SOC, 22) // + .input("ess0", MAX_APPARENT_POWER, 10000) // + .output(STATE_MACHINE, State.IDLE)) // .next(new TestCase() // - .input(ESS_SOC, 22) // - .input(ESS_MAX_APPARENT_POWER, 10000) // - .output(STATE_MACHINE, StateMachine.State.IDLE)) // + .input("ess0", SOC, 22) // + .input("ess0", MAX_APPARENT_POWER, 10000) // + .output(STATE_MACHINE, State.IDLE)) // .next(new TestCase() // - .input(ESS_SOC, 21) // - .output(STATE_MACHINE, StateMachine.State.NOT_STARTED)) // + .input("ess0", SOC, 21) // + .output(STATE_MACHINE, State.NOT_STARTED)) // .next(new TestCase() // - .input(ESS_SOC, 20) // - .output(STATE_MACHINE, StateMachine.State.BELOW_TARGET_SOC)) // + .input("ess0", SOC, 20) // + .output(STATE_MACHINE, State.BELOW_TARGET_SOC)) // .next(new TestCase() // - .input(ESS_SOC, 25) // + .input("ess0", SOC, 25) // .input(CTRL_ESS_CAPACITY, 8_000) // - .input(ESS_CAPACITY, 8_000) // + .input("ess0", CAPACITY, 8_000) // .output(CTRL_ESS_CAPACITY, 8_000) // - .output(STATE_MACHINE, StateMachine.State.BELOW_TARGET_SOC)) // + .output(STATE_MACHINE, State.BELOW_TARGET_SOC)) // .next(new TestCase() // - .input(ESS_CAPACITY, 8_000) // - .input(ESS_SOC, 30) // + .input("ess0", CAPACITY, 8_000) // + .input("ess0", SOC, 30) // .output(CTRL_ESS_CAPACITY, 8_000)) // .next(new TestCase() // - .input(ESS_SOC, 30) /// - .input(ESS_CAPACITY, 8_000) // + .input("ess0", SOC, 30) /// + .input("ess0", CAPACITY, 8_000) // .output(CTRL_ESS_CAPACITY, 8_000) // - .output(STATE_MACHINE, StateMachine.State.AT_TARGET_SOC)) // - ; + .output(STATE_MACHINE, State.AT_TARGET_SOC)) // - // EMS restart - test.next(new TestCase() // - .input(ESS_CAPACITY, null) // - .input(CTRL_ESS_CAPACITY, null) // - .output(CTRL_ESS_CAPACITY, null)); // + .next(new TestCase("EMS restart") // + .input("ess0", CAPACITY, null) // + .input(CTRL_ESS_CAPACITY, null) // + .output(CTRL_ESS_CAPACITY, null)) // - // New Ess.Capacity (Ctrl is taking the last one from timedata) - test.next(new TestCase() // - .input(ESS_CAPACITY, 10_000) // - .input(CTRL_ESS_CAPACITY, null) // - .output(CTRL_ESS_CAPACITY, 8_000)); // + .next(new TestCase("New Ess.Capacity (Ctrl is taking the last one from timedata)") // + .input("ess0", CAPACITY, 10_000) // + .input(CTRL_ESS_CAPACITY, null) // + .output(CTRL_ESS_CAPACITY, 8_000)) // - test.next(new TestCase() // - .output(STATE_MACHINE, StateMachine.State.IDLE)) // - ; + .next(new TestCase() // + .output(STATE_MACHINE, State.IDLE)) // + + .deactivate(); } @Test public void testAboveLimit() throws Exception { final var clock = new TimeLeapClock(Instant.parse("2022-01-01T08:00:00.00Z"), ZoneOffset.UTC); - final var componentManager = new DummyComponentManager(clock); - new ControllerTest(new ControllerEssFixStateOfChargeImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("componentManager", componentManager) // + .addReference("componentManager", new DummyComponentManager(clock)) // .addReference("sum", new DummySum()) // .addReference("timedata", new DummyTimedata("timedata0")) // .addReference("ess", ESS) // .activate(FixStateOfChargeConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setRunning(true) // .setTargetSoc(30) // .setSpecifyTargetTime(false) // @@ -226,44 +212,42 @@ public void testAboveLimit() throws Exception { .setSelfTermination(false) // .setTerminationBuffer(720) // .setConditionalTermination(false) // - .setEndCondition(EndCondition.CAPACITY_CHANGED) // + .setEndCondition(CAPACITY_CHANGED) // .build()) .next(new TestCase() // - .input(ESS_SOC, 50) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.IDLE)) // + .input("ess0", SOC, 50) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.IDLE)) // .next(new TestCase() // - .input(ESS_SOC, 50) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.IDLE)) // + .input("ess0", SOC, 50) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.IDLE)) // .next(new TestCase() // - .input(ESS_SOC, 50) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.NOT_STARTED)) // + .input("ess0", SOC, 50) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.NOT_STARTED)) // .next(new TestCase() // - .input(ESS_SOC, 50) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.ABOVE_TARGET_SOC) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 500) // + .input("ess0", SOC, 50) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.ABOVE_TARGET_SOC) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 500) // .output(DEBUG_SET_ACTIVE_POWER_RAW, 500) // - .output(DEBUG_SET_ACTIVE_POWER, 500) // Would increase till 10_000 - ); + .output(DEBUG_SET_ACTIVE_POWER, 500)) // Would increase till 10_000 + .deactivate(); } @Test public void testBelowLimit() throws Exception { final var clock = new TimeLeapClock(Instant.parse("2022-01-01T08:00:00.00Z"), ZoneOffset.UTC); - final var componentManager = new DummyComponentManager(clock); - new ControllerTest(new ControllerEssFixStateOfChargeImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("componentManager", componentManager) // + .addReference("componentManager", new DummyComponentManager(clock)) // .addReference("sum", new DummySum()) // .addReference("timedata", new DummyTimedata("timedata0")) // .addReference("ess", ESS) // .activate(FixStateOfChargeConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setRunning(true) // .setTargetSoc(30) // .setSpecifyTargetTime(false) // @@ -271,44 +255,42 @@ public void testBelowLimit() throws Exception { .setSelfTermination(false) // .setTerminationBuffer(720) // .setConditionalTermination(false) // - .setEndCondition(EndCondition.CAPACITY_CHANGED) // + .setEndCondition(CAPACITY_CHANGED) // .build()) .next(new TestCase() // - .input(ESS_SOC, 10) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.IDLE)) // + .input("ess0", SOC, 10) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.IDLE)) // .next(new TestCase() // - .input(ESS_SOC, 10) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.IDLE)) // + .input("ess0", SOC, 10) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.IDLE)) // .next(new TestCase() // - .input(ESS_SOC, 10) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.NOT_STARTED)) // + .input("ess0", SOC, 10) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.NOT_STARTED)) // .next(new TestCase() // - .input(ESS_SOC, 10) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.BELOW_TARGET_SOC) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -500) // + .input("ess0", SOC, 10) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.BELOW_TARGET_SOC) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -500) // .output(DEBUG_SET_ACTIVE_POWER_RAW, -500) // - .output(DEBUG_SET_ACTIVE_POWER, -500) // Would increase till 10_000 - ); + .output(DEBUG_SET_ACTIVE_POWER, -500)) // Would increase till 10_000 + .deactivate(); } @Test public void testAtLimit() throws Exception { final var clock = new TimeLeapClock(Instant.parse("2022-10-27T08:00:00.00Z"), ZoneOffset.UTC); - final var componentManager = new DummyComponentManager(clock); - new ControllerTest(new ControllerEssFixStateOfChargeImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("componentManager", componentManager) // + .addReference("componentManager", new DummyComponentManager(clock)) // .addReference("sum", new DummySum()) // .addReference("timedata", new DummyTimedata("timedata0")) // .addReference("ess", ESS) // .activate(FixStateOfChargeConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setRunning(true) // .setTargetSoc(30) // .setSpecifyTargetTime(false) // @@ -316,85 +298,85 @@ public void testAtLimit() throws Exception { .setSelfTermination(false) // .setTerminationBuffer(720) // .setConditionalTermination(false) // - .setEndCondition(EndCondition.CAPACITY_CHANGED) // + .setEndCondition(CAPACITY_CHANGED) // .build()) .next(new TestCase() // - .input(ESS_SOC, 10) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.IDLE)) // + .input("ess0", SOC, 10) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.IDLE)) // .next(new TestCase() // - .input(ESS_SOC, 10) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.IDLE)) // + .input("ess0", SOC, 10) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.IDLE)) // .next(new TestCase() // - .input(ESS_SOC, 10) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.NOT_STARTED)) // + .input("ess0", SOC, 10) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.NOT_STARTED)) // .next(new TestCase() // - .input(ESS_SOC, 10) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.BELOW_TARGET_SOC) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -500) // + .input("ess0", SOC, 10) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.BELOW_TARGET_SOC) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -500) // .output(DEBUG_SET_ACTIVE_POWER_RAW, -500) // .output(DEBUG_SET_ACTIVE_POWER, -500)) // .next(new TestCase() // - .input(ESS_SOC, 10) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -1000)) // + .input("ess0", SOC, 10) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -1000)) // .next(new TestCase() // - .input(ESS_SOC, 10) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -1500)) // + .input("ess0", SOC, 10) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -1500)) // .next(new TestCase() // - .input(ESS_SOC, 10) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -2000)) // + .input("ess0", SOC, 10) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -2000)) // .next(new TestCase() // - .input(ESS_SOC, 10) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -2500)) // + .input("ess0", SOC, 10) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -2500)) // .next(new TestCase() // - .input(ESS_SOC, 10) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -3000)) // + .input("ess0", SOC, 10) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -3000)) // .next(new TestCase() // - .input(ESS_SOC, 10) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -3500)) // + .input("ess0", SOC, 10) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -3500)) // .next(new TestCase() // - .input(ESS_SOC, 10) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -4000)) // + .input("ess0", SOC, 10) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -4000)) // .next(new TestCase()) // .next(new TestCase() // - .input(ESS_SOC, 10) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -5000)) // + .input("ess0", SOC, 10) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -5000)) // .next(new TestCase()) // .next(new TestCase() // - .input(ESS_SOC, 10) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -6000)) // + .input("ess0", SOC, 10) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -6000)) // .next(new TestCase()) // .next(new TestCase() // - .input(ESS_SOC, 10) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -7000)) // + .input("ess0", SOC, 10) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -7000)) // .next(new TestCase()) // .next(new TestCase() // - .input(ESS_SOC, 10) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -8000)) // + .input("ess0", SOC, 10) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -8000)) // .next(new TestCase()) // .next(new TestCase() // - .input(ESS_SOC, 10) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -9000)) // + .input("ess0", SOC, 10) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -9000)) // .next(new TestCase()) // .next(new TestCase() // - .input(ESS_SOC, 10) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -10000)) // + .input("ess0", SOC, 10) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -10000)) // .next(new TestCase() // - .input(ESS_SOC, 30) // - .input(ESS_MAX_APPARENT_POWER, 10_000)) // + .input("ess0", SOC, 30) // + .input("ess0", MAX_APPARENT_POWER, 10_000)) // .next(new TestCase() // - .input(ESS_SOC, 30) // - .output(STATE_MACHINE, StateMachine.State.AT_TARGET_SOC) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -9000)) // + .input("ess0", SOC, 30) // + .output(STATE_MACHINE, State.AT_TARGET_SOC) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -9000)) // .next(new TestCase() // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -8000)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -8000)) // .next(new TestCase()) // .next(new TestCase()) // .next(new TestCase()) // @@ -402,26 +384,24 @@ public void testAtLimit() throws Exception { .next(new TestCase()) // .next(new TestCase()) // .next(new TestCase() // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -1000)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -1000)) // .next(new TestCase() // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 0)) // - ; + .output("ess0", SET_ACTIVE_POWER_EQUALS, 0)) // + .deactivate(); } @Test public void testAtLimitDeadBand() throws Exception { final var clock = new TimeLeapClock(Instant.parse("2022-10-27T08:00:00.00Z"), ZoneOffset.UTC); - final var componentManager = new DummyComponentManager(clock); - new ControllerTest(new ControllerEssFixStateOfChargeImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("componentManager", componentManager) // + .addReference("componentManager", new DummyComponentManager(clock)) // .addReference("sum", new DummySum()) // .addReference("timedata", new DummyTimedata("timedata0")) // .addReference("ess", ESS) // .activate(FixStateOfChargeConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setRunning(true) // .setTargetSoc(30) // .setSpecifyTargetTime(false) // @@ -429,78 +409,76 @@ public void testAtLimitDeadBand() throws Exception { .setSelfTermination(false) // .setTerminationBuffer(720) // .setConditionalTermination(false) // - .setEndCondition(EndCondition.CAPACITY_CHANGED) // + .setEndCondition(CAPACITY_CHANGED) // .build()) .next(new TestCase() // - .input(ESS_SOC, 30) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .input(ESS_CAPACITY, 10_000) // - .output(STATE_MACHINE, StateMachine.State.IDLE)) // + .input("ess0", SOC, 30) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .input("ess0", CAPACITY, 10_000) // + .output(STATE_MACHINE, State.IDLE)) // .next(new TestCase() // - .input(ESS_SOC, 30) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.IDLE)) // + .input("ess0", SOC, 30) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.IDLE)) // .next(new TestCase() // - .input(ESS_SOC, 30) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.NOT_STARTED)) // + .input("ess0", SOC, 30) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.NOT_STARTED)) // .next(new TestCase() // - .input(ESS_SOC, 30) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.AT_TARGET_SOC) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 0) // + .input("ess0", SOC, 30) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.AT_TARGET_SOC) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 0) // .output(DEBUG_SET_ACTIVE_POWER_RAW, 0) // .output(DEBUG_SET_ACTIVE_POWER, 0)) // .next(new TestCase() // - .input(ESS_SOC, 30) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 0)) // + .input("ess0", SOC, 30) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 0)) // .next(new TestCase() // - .input(ESS_SOC, 31) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 0)) // + .input("ess0", SOC, 31) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 0)) // .next(new TestCase() // - .input(ESS_SOC, 29) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 0)) // + .input("ess0", SOC, 29) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 0)) // .next(new TestCase() // - .input(ESS_SOC, 28)) // + .input("ess0", SOC, 28)) // .next(new TestCase() // - .input(ESS_SOC, 28) // - .output(STATE_MACHINE, StateMachine.State.WITHIN_LOWER_TARGET_SOC_BOUNDARIES) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -500)) // + .input("ess0", SOC, 28) // + .output(STATE_MACHINE, State.WITHIN_LOWER_TARGET_SOC_BOUNDARIES) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -500)) // .next(new TestCase() // - .input(ESS_SOC, 28) // - .output(STATE_MACHINE, StateMachine.State.WITHIN_LOWER_TARGET_SOC_BOUNDARIES) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -1000)) // + .input("ess0", SOC, 28) // + .output(STATE_MACHINE, State.WITHIN_LOWER_TARGET_SOC_BOUNDARIES) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -1000)) // .next(new TestCase() // - .input(ESS_SOC, 28) // - .output(STATE_MACHINE, StateMachine.State.WITHIN_LOWER_TARGET_SOC_BOUNDARIES) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -1500)) // + .input("ess0", SOC, 28) // + .output(STATE_MACHINE, State.WITHIN_LOWER_TARGET_SOC_BOUNDARIES) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -1500)) // .next(new TestCase() // - .input(ESS_SOC, 28) // - .output(STATE_MACHINE, StateMachine.State.WITHIN_LOWER_TARGET_SOC_BOUNDARIES) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -1 * Math.round(// - Math.min(10_000/* maxApparentPower */ * AbstractFixStateOfCharge.DEFAULT_POWER_FACTOR, + .input("ess0", SOC, 28) // + .output(STATE_MACHINE, State.WITHIN_LOWER_TARGET_SOC_BOUNDARIES) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -1 * round(// + min(10_000/* maxApparentPower */ * DEFAULT_POWER_FACTOR, 10_000 /* capacity */ * (1f / 6f))) // 1467 - ))// - ; + )) // + .deactivate(); } @Test public void testBoundaries() throws Exception { final var clock = new TimeLeapClock(Instant.parse("2022-10-27T08:00:00.00Z"), ZoneOffset.UTC); - final var componentManager = new DummyComponentManager(clock); - /* * Below target SoC */ new ControllerTest(new ControllerEssFixStateOfChargeImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("componentManager", componentManager) // + .addReference("componentManager", new DummyComponentManager(clock)) // .addReference("sum", new DummySum()) // .addReference("timedata", new DummyTimedata("timedata0")) // .addReference("ess", ESS) // .activate(FixStateOfChargeConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setRunning(true) // .setTargetSoc(30) // .setSpecifyTargetTime(false) // @@ -508,67 +486,67 @@ public void testBoundaries() throws Exception { .setSelfTermination(false) // .setTerminationBuffer(720) // .setConditionalTermination(false) // - .setEndCondition(EndCondition.CAPACITY_CHANGED) // + .setEndCondition(CAPACITY_CHANGED) // .build()) .next(new TestCase() // - .input(ESS_SOC, 20) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.IDLE)) // + .input("ess0", SOC, 20) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.IDLE)) // .next(new TestCase() // - .input(ESS_SOC, 20) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.IDLE)) // + .input("ess0", SOC, 20) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.IDLE)) // .next(new TestCase() // - .input(ESS_SOC, 20) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.NOT_STARTED)) // + .input("ess0", SOC, 20) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.NOT_STARTED)) // .next(new TestCase() // - .input(ESS_SOC, 20) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .input(ESS_CAPACITY, 10_000) // - .output(STATE_MACHINE, StateMachine.State.BELOW_TARGET_SOC) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -500) // + .input("ess0", SOC, 20) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .input("ess0", CAPACITY, 10_000) // + .output(STATE_MACHINE, State.BELOW_TARGET_SOC) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -500) // .output(DEBUG_SET_ACTIVE_POWER_RAW, -500) // .output(DEBUG_SET_ACTIVE_POWER, -500)) // .next(new TestCase() // - .input(ESS_SOC, 22) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -1000)) // + .input("ess0", SOC, 22) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -1000)) // // Skip Ramp .next(new TestCase(), 17) // .next(new TestCase() // - .input(ESS_SOC, 27) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -10000)) // + .input("ess0", SOC, 27) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -10000)) // .next(new TestCase() // - .input(ESS_SOC, 28)) // + .input("ess0", SOC, 28)) // .next(new TestCase() // - .input(ESS_SOC, 28) // - .output(STATE_MACHINE, StateMachine.State.WITHIN_LOWER_TARGET_SOC_BOUNDARIES) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -9500)) // + .input("ess0", SOC, 28) // + .output(STATE_MACHINE, State.WITHIN_LOWER_TARGET_SOC_BOUNDARIES) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -9500)) // .next(new TestCase() // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -9000)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -9000)) // // Skip ramp .next(new TestCase(), 13) // .next(new TestCase() // - .input(ESS_SOC, 29) // - .output(STATE_MACHINE, StateMachine.State.WITHIN_LOWER_TARGET_SOC_BOUNDARIES) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -2000)) // + .input("ess0", SOC, 29) // + .output(STATE_MACHINE, State.WITHIN_LOWER_TARGET_SOC_BOUNDARIES) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -2000)) // .next(new TestCase() // - .input(ESS_SOC, 29) // - .output(STATE_MACHINE, StateMachine.State.WITHIN_LOWER_TARGET_SOC_BOUNDARIES) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -1 * Math.round(// - Math.min(10_000/* maxApparentPower */ * AbstractFixStateOfCharge.DEFAULT_POWER_FACTOR, + .input("ess0", SOC, 29) // + .output(STATE_MACHINE, State.WITHIN_LOWER_TARGET_SOC_BOUNDARIES) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -1 * round(// + min(10_000/* maxApparentPower */ * DEFAULT_POWER_FACTOR, 10_000 /* capacity */ * (1f / 6f))) // ))// 1667 .next(new TestCase() // - .input(ESS_SOC, 30)) // + .input("ess0", SOC, 30)) // .next(new TestCase() // - .input(ESS_SOC, 30) // - .output(STATE_MACHINE, StateMachine.State.AT_TARGET_SOC) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -667)) // + .input("ess0", SOC, 30) // + .output(STATE_MACHINE, State.AT_TARGET_SOC) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -667)) // .next(new TestCase() // - .input(ESS_SOC, 30) // - .output(STATE_MACHINE, StateMachine.State.AT_TARGET_SOC) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 0)) // + .input("ess0", SOC, 30) // + .output(STATE_MACHINE, State.AT_TARGET_SOC) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 0)) // ; /* @@ -576,13 +554,13 @@ public void testBoundaries() throws Exception { */ new ControllerTest(new ControllerEssFixStateOfChargeImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("componentManager", componentManager) // + .addReference("componentManager", new DummyComponentManager(clock)) // .addReference("sum", new DummySum()) // .addReference("timedata", new DummyTimedata("timedata0")) // .addReference("ess", ESS) // .activate(FixStateOfChargeConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setRunning(true) // .setTargetSoc(30) // .setSpecifyTargetTime(false) // @@ -590,66 +568,66 @@ public void testBoundaries() throws Exception { .setSelfTermination(false) // .setTerminationBuffer(720) // .setConditionalTermination(false) // - .setEndCondition(EndCondition.CAPACITY_CHANGED) // + .setEndCondition(CAPACITY_CHANGED) // .build()) .next(new TestCase() // - .input(ESS_SOC, 40) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.IDLE)) // + .input("ess0", SOC, 40) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.IDLE)) // .next(new TestCase() // - .input(ESS_SOC, 40) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.IDLE)) // + .input("ess0", SOC, 40) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.IDLE)) // .next(new TestCase() // - .input(ESS_SOC, 40) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.NOT_STARTED)) // + .input("ess0", SOC, 40) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.NOT_STARTED)) // .next(new TestCase() // - .input(ESS_SOC, 40) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.ABOVE_TARGET_SOC) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 500) // + .input("ess0", SOC, 40) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.ABOVE_TARGET_SOC) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 500) // .output(DEBUG_SET_ACTIVE_POWER_RAW, 500) // .output(DEBUG_SET_ACTIVE_POWER, 500)) // .next(new TestCase() // - .input(ESS_SOC, 40) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 1000)) // + .input("ess0", SOC, 40) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 1000)) // // Skip ramp .next(new TestCase(), 18) // .next(new TestCase() // - .input(ESS_SOC, 33)) // + .input("ess0", SOC, 33)) // .next(new TestCase() // - .input(ESS_SOC, 32)) // + .input("ess0", SOC, 32)) // .next(new TestCase() // - .input(ESS_SOC, 32) // - .output(STATE_MACHINE, StateMachine.State.WITHIN_UPPER_TARGET_SOC_BOUNDARIES) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 9500)) // + .input("ess0", SOC, 32) // + .output(STATE_MACHINE, State.WITHIN_UPPER_TARGET_SOC_BOUNDARIES) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 9500)) // .next(new TestCase() // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 9000)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 9000)) // // Skip ramp .next(new TestCase(), 13) // .next(new TestCase() // - .input(ESS_SOC, 31) // - .output(STATE_MACHINE, StateMachine.State.WITHIN_UPPER_TARGET_SOC_BOUNDARIES) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 2000)) // + .input("ess0", SOC, 31) // + .output(STATE_MACHINE, State.WITHIN_UPPER_TARGET_SOC_BOUNDARIES) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 2000)) // .next(new TestCase() // - .input(ESS_SOC, 31) // - .output(STATE_MACHINE, StateMachine.State.WITHIN_UPPER_TARGET_SOC_BOUNDARIES) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, Math.round(// - Math.min(10_000/* maxApparentPower */ * AbstractFixStateOfCharge.DEFAULT_POWER_FACTOR, + .input("ess0", SOC, 31) // + .output(STATE_MACHINE, State.WITHIN_UPPER_TARGET_SOC_BOUNDARIES) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, round(// + min(10_000/* maxApparentPower */ * DEFAULT_POWER_FACTOR, 10_000 /* capacity */ * (1f / 6f))) // ))// 1667 .next(new TestCase() // - .input(ESS_SOC, 30)) // + .input("ess0", SOC, 30)) // .next(new TestCase() // - .input(ESS_SOC, 30) // - .output(STATE_MACHINE, StateMachine.State.AT_TARGET_SOC) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 667)) // + .input("ess0", SOC, 30) // + .output(STATE_MACHINE, State.AT_TARGET_SOC) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 667)) // .next(new TestCase() // - .input(ESS_SOC, 30) // - .output(STATE_MACHINE, StateMachine.State.AT_TARGET_SOC) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 0)) // - ; + .input("ess0", SOC, 30) // + .output(STATE_MACHINE, State.AT_TARGET_SOC) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 0)) // + .deactivate(); } @Test @@ -665,8 +643,8 @@ public void testLimitWithSpecifiedTimeBelowLimit() throws Exception { .addReference("timedata", new DummyTimedata("timedata0")) // .addReference("ess", ESS) // .activate(FixStateOfChargeConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setRunning(true) // .setTargetSoc(30) // .setSpecifyTargetTime(true) // @@ -674,35 +652,35 @@ public void testLimitWithSpecifiedTimeBelowLimit() throws Exception { .setSelfTermination(false) // .setTerminationBuffer(720) // .setConditionalTermination(false) // - .setEndCondition(EndCondition.CAPACITY_CHANGED) // + .setEndCondition(CAPACITY_CHANGED) // .build()) .next(new TestCase() // - .input(ESS_SOC, 10) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.IDLE)) // + .input("ess0", SOC, 10) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.IDLE)) // .next(new TestCase() // - .input(ESS_SOC, 10) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.IDLE)) // + .input("ess0", SOC, 10) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.IDLE)) // .next(new TestCase() // - .input(ESS_SOC, 10) // - .input(ESS_CAPACITY, 30_000) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.NOT_STARTED)) // + .input("ess0", SOC, 10) // + .input("ess0", CAPACITY, 30_000) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.NOT_STARTED)) // // Start time = 2022-10-27T09:14:24+01:00, Current: 2022-10-27T09:00:00+01:00 .next(new TestCase() // - .input(ESS_SOC, 10) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .input(ESS_CAPACITY, 30_000) // - .output(STATE_MACHINE, StateMachine.State.NOT_STARTED) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, null) // + .input("ess0", SOC, 10) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .input("ess0", CAPACITY, 30_000) // + .output(STATE_MACHINE, State.NOT_STARTED) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, null) // .output(DEBUG_SET_ACTIVE_POWER_RAW, null) // .output(DEBUG_SET_ACTIVE_POWER, null)) // .next(new TestCase() // - .timeleap(clock, 15, ChronoUnit.MINUTES))// + .timeleap(clock, 15, MINUTES))// .next(new TestCase() // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -500)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -500)) // .next(new TestCase()) // .next(new TestCase()) // .next(new TestCase()) // @@ -712,33 +690,33 @@ public void testLimitWithSpecifiedTimeBelowLimit() throws Exception { .next(new TestCase()) // .next(new TestCase()) // .next(new TestCase() // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -5000)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -5000)) // .next(new TestCase() // - .input(ESS_SOC, 10) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .input(ESS_CAPACITY, 30_000) // - .output(STATE_MACHINE, StateMachine.State.BELOW_TARGET_SOC) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -5040) // + .input("ess0", SOC, 10) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .input("ess0", CAPACITY, 30_000) // + .output(STATE_MACHINE, State.BELOW_TARGET_SOC) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -5040) // .output(DEBUG_SET_ACTIVE_POWER_RAW, -5040) // .output(DEBUG_SET_ACTIVE_POWER, -5040)) // .next(new TestCase() // - .input(ESS_SOC, 30) // - .input(ESS_MAX_APPARENT_POWER, 10_000)) // + .input("ess0", SOC, 30) // + .input("ess0", MAX_APPARENT_POWER, 10_000)) // .next(new TestCase() // - .input(ESS_SOC, 30) // - .output(STATE_MACHINE, StateMachine.State.AT_TARGET_SOC) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -4040)) // + .input("ess0", SOC, 30) // + .output(STATE_MACHINE, State.AT_TARGET_SOC) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -4040)) // .next(new TestCase() // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -3040)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -3040)) // .next(new TestCase() // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -2040)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -2040)) // .next(new TestCase() // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -1040)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -1040)) // .next(new TestCase() // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -40)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -40)) // .next(new TestCase() // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 0)) // - ; + .output("ess0", SET_ACTIVE_POWER_EQUALS, 0)) // + .deactivate(); } @Test @@ -753,8 +731,8 @@ public void testLimitWithSpecifiedTimeAboveLimit() throws Exception { .addReference("timedata", new DummyTimedata("timedata0")) // .addReference("ess", ESS) // .activate(FixStateOfChargeConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setRunning(true) // .setTargetSoc(30) // .setSpecifyTargetTime(true) // @@ -763,85 +741,85 @@ public void testLimitWithSpecifiedTimeAboveLimit() throws Exception { .setSelfTermination(false) // .setTerminationBuffer(720) // .setConditionalTermination(false) // - .setEndCondition(EndCondition.CAPACITY_CHANGED) // + .setEndCondition(CAPACITY_CHANGED) // .build()) .next(new TestCase() // - .input(ESS_SOC, 80) // - .input(ESS_CAPACITY, 30_000) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.IDLE)) // + .input("ess0", SOC, 80) // + .input("ess0", CAPACITY, 30_000) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.IDLE)) // .next(new TestCase() // - .input(ESS_SOC, 80) // - .input(ESS_CAPACITY, 30_000) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.IDLE)) // + .input("ess0", SOC, 80) // + .input("ess0", CAPACITY, 30_000) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.IDLE)) // .next(new TestCase() // - .input(ESS_SOC, 80) // - .input(ESS_CAPACITY, 30_000) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.NOT_STARTED)) // + .input("ess0", SOC, 80) // + .input("ess0", CAPACITY, 30_000) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.NOT_STARTED)) // // Start time = 2022-10-27T06:26:24, Current: 2022-10-26T23:00 .next(new TestCase() // - .input(ESS_SOC, 80) // - .input(ESS_CAPACITY, 30_000) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.NOT_STARTED) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, null) // + .input("ess0", SOC, 80) // + .input("ess0", CAPACITY, 30_000) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.NOT_STARTED) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, null) // .output(DEBUG_SET_ACTIVE_POWER_RAW, null) // .output(DEBUG_SET_ACTIVE_POWER, null)) // .next(new TestCase() // - .timeleap(clock, 7, ChronoUnit.HOURS)) // + .timeleap(clock, 7, HOURS)) // .next(new TestCase() // - .timeleap(clock, 31, ChronoUnit.MINUTES)) // + .timeleap(clock, 31, MINUTES)) // .next(new TestCase() // - .input(ESS_SOC, 80) // - .input(ESS_CAPACITY, 30_000) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.ABOVE_TARGET_SOC) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 500) // + .input("ess0", SOC, 80) // + .input("ess0", CAPACITY, 30_000) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.ABOVE_TARGET_SOC) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 500) // .output(DEBUG_SET_ACTIVE_POWER_RAW, 500) // .output(DEBUG_SET_ACTIVE_POWER, 500)) // .next(new TestCase() // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 1000)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 1000)) // .next(new TestCase()) // .next(new TestCase() // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 2000)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 2000)) // .next(new TestCase()) // .next(new TestCase() // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 3000)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 3000)) // .next(new TestCase()) // .next(new TestCase() // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 4000)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 4000)) // .next(new TestCase()) // .next(new TestCase() // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 5000)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 5000)) // .next(new TestCase()) // .next(new TestCase() // - .input(ESS_SOC, 80) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .input(ESS_CAPACITY, 30_000) // - .output(STATE_MACHINE, StateMachine.State.ABOVE_TARGET_SOC) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 5128) // + .input("ess0", SOC, 80) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .input("ess0", CAPACITY, 30_000) // + .output(STATE_MACHINE, State.ABOVE_TARGET_SOC) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 5128) // .output(DEBUG_SET_ACTIVE_POWER_RAW, 5128) // .output(DEBUG_SET_ACTIVE_POWER, 5128)) // .next(new TestCase() // - .input(ESS_SOC, 30) // - .input(ESS_MAX_APPARENT_POWER, 10_000)) // + .input("ess0", SOC, 30) // + .input("ess0", MAX_APPARENT_POWER, 10_000)) // .next(new TestCase() // - .input(ESS_SOC, 30) // - .output(STATE_MACHINE, StateMachine.State.AT_TARGET_SOC) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 4128)) // + .input("ess0", SOC, 30) // + .output(STATE_MACHINE, State.AT_TARGET_SOC) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 4128)) // .next(new TestCase() // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 3128)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 3128)) // .next(new TestCase() // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 2128)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 2128)) // .next(new TestCase() // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 1128)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 1128)) // .next(new TestCase() // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 128)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 128)) // .next(new TestCase() // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 0)) // - ; + .output("ess0", SET_ACTIVE_POWER_EQUALS, 0)) // + .deactivate(); } } diff --git a/io.openems.edge.controller.ess.gridoptimizedcharge/.classpath b/io.openems.edge.controller.ess.gridoptimizedcharge/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.controller.ess.gridoptimizedcharge/.classpath +++ b/io.openems.edge.controller.ess.gridoptimizedcharge/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.controller.ess.gridoptimizedcharge/bnd.bnd b/io.openems.edge.controller.ess.gridoptimizedcharge/bnd.bnd index ed3817a1757..7694818e8c2 100644 --- a/io.openems.edge.controller.ess.gridoptimizedcharge/bnd.bnd +++ b/io.openems.edge.controller.ess.gridoptimizedcharge/bnd.bnd @@ -8,10 +8,13 @@ Bundle-Version: 1.0.0.${tstamp} io.openems.common,\ io.openems.edge.common,\ io.openems.edge.controller.api,\ + io.openems.edge.energy.api,\ io.openems.edge.ess.api,\ io.openems.edge.meter.api,\ io.openems.edge.predictor.api,\ io.openems.edge.timedata.api,\ -testpath: \ - ${testpath} + ${testpath},\ + io.openems.edge.evcs.api,\ + org.apache.commons.math3,\ diff --git a/io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/ControllerEssGridOptimizedCharge.java b/io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/ControllerEssGridOptimizedCharge.java index 2ee38fba17a..05dc14dc251 100644 --- a/io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/ControllerEssGridOptimizedCharge.java +++ b/io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/ControllerEssGridOptimizedCharge.java @@ -192,6 +192,12 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { CONFIGURED_ESS_IS_NOT_MANAGED(Doc.of(Level.FAULT) // .text("The Energy Storage System is in read-only mode and does not allow to be controlled.")), // + /** + * Production values for prediction not available. + */ + NO_VALID_PRODUCTION_PREDICTION(Doc.of(Level.WARNING) // + .translationKey(ControllerEssGridOptimizedCharge.class, "noValidProductionPrediction")), // + /** * Cumulated seconds of the state delay charge. */ @@ -696,6 +702,25 @@ public default void _setConfiguredEssIsNotManaged(Boolean value) { this.getConfiguredEssIsNotManagedChannel().setNextValue(value); } + /** + * Gets the Channel for {@link ChannelId#NO_VALID_PRODUCTION_PREDICTION}. + * + * @return the Channel + */ + public default StateChannel noValidProductionPredictionChannel() { + return this.channel(ChannelId.NO_VALID_PRODUCTION_PREDICTION); + } + + /** + * Internal method to set the 'nextValue' on + * {@link ChannelId#NO_VALID_PRODUCTION_PREDICTION} Channel. + * + * @param value the next value + */ + public default void _setNoValidProductionPredictionChannel(Boolean value) { + this.noValidProductionPredictionChannel().setNextValue(value); + } + /** * Gets the Channel for {@link ChannelId#DELAY_CHARGE_TIME}. * diff --git a/io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/ControllerEssGridOptimizedChargeImpl.java b/io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/ControllerEssGridOptimizedChargeImpl.java index aa727e503b4..fb735889d69 100644 --- a/io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/ControllerEssGridOptimizedChargeImpl.java +++ b/io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/ControllerEssGridOptimizedChargeImpl.java @@ -1,11 +1,19 @@ package io.openems.edge.controller.ess.gridoptimizedcharge; +import static java.util.stream.Collectors.groupingBy; + +import java.time.Duration; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; +import java.time.ZonedDateTime; import java.time.temporal.ChronoField; +import java.time.temporal.ChronoUnit; import java.util.ArrayList; +import java.util.Collections; import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.function.Supplier; import org.osgi.service.cm.ConfigurationAdmin; import org.osgi.service.component.ComponentContext; @@ -22,6 +30,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.collect.ImmutableSortedMap; + import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.exceptions.OpenemsException; import io.openems.edge.common.channel.IntegerReadChannel; @@ -33,6 +43,8 @@ import io.openems.edge.common.filter.RampFilter; import io.openems.edge.common.sum.Sum; import io.openems.edge.controller.api.Controller; +import io.openems.edge.energy.api.EnergySchedulable; +import io.openems.edge.energy.api.EnergyScheduleHandler; import io.openems.edge.ess.api.ManagedSymmetricEss; import io.openems.edge.meter.api.ElectricityMeter; import io.openems.edge.predictor.api.manager.PredictorManager; @@ -46,8 +58,9 @@ immediate = true, // configurationPolicy = ConfigurationPolicy.REQUIRE // ) -public class ControllerEssGridOptimizedChargeImpl extends AbstractOpenemsComponent implements - ControllerEssGridOptimizedCharge, Controller, OpenemsComponent, TimedataProvider, ComponentManagerProvider { +public class ControllerEssGridOptimizedChargeImpl extends AbstractOpenemsComponent + implements ControllerEssGridOptimizedCharge, EnergySchedulable, Controller, OpenemsComponent, TimedataProvider, + ComponentManagerProvider { /** * Buffer in watt taken into account in the calculation of the first and last @@ -58,6 +71,7 @@ public class ControllerEssGridOptimizedChargeImpl extends AbstractOpenemsCompone protected final RampFilter rampFilter = new RampFilter(); private final Logger log = LoggerFactory.getLogger(ControllerEssGridOptimizedChargeImpl.class); + private final EnergyScheduleHandler energyScheduleHandler; /* * Time counter for the important states @@ -107,6 +121,9 @@ public ControllerEssGridOptimizedChargeImpl() { Controller.ChannelId.values(), // ControllerEssGridOptimizedCharge.ChannelId.values() // ); + this.energyScheduleHandler = buildEnergyScheduleHandler(// + () -> this.config.mode(), // + () -> DelayCharge.parseTime(this.config.manualTargetTime())); } @Activate @@ -145,12 +162,19 @@ private void updateConfig(Config config) { @Override public void run() throws OpenemsNamedException { + if (!this.ess.isManaged() && this.config.mode() != Mode.OFF) { this._setConfiguredEssIsNotManaged(true); return; } this._setConfiguredEssIsNotManaged(false); + if (!this.sum.getProductionActivePower().isDefined()) { + this._setNoValidProductionPredictionChannel(true); + return; + } + this._setNoValidProductionPredictionChannel(false); + // Updates the time channels. this.calculateTime(); @@ -435,4 +459,92 @@ public Timedata getTimedata() { return this.timedata; } + /** + * Builds the {@link EnergyScheduleHandler}. + * + *

+ * This is public so that it can be used by the EnergyScheduler integration + * test. + * + * @param mode a supplier for the configured {@link Mode} + * @param manualTargetTime a supplier for the configured manualTargetTime + * @return a {@link EnergyScheduleHandler} + */ + public static EnergyScheduleHandler buildEnergyScheduleHandler(Supplier mode, + Supplier manualTargetTime) { + return EnergyScheduleHandler.WithOnlyOneState.create() // + .setContextFunction(simContext -> { + // TODO try to reuse existing logic for parsing, calculating limits, etc.; for + // now this only works for current day and MANUAL mode + final var limits = ImmutableSortedMap.naturalOrder(); + final var periodsPerDay = simContext.periods().stream() // + .collect(groupingBy(p -> p.time().truncatedTo(ChronoUnit.DAYS))); + if (!periodsPerDay.isEmpty()) { + final var firstDayMignight = Collections.min(periodsPerDay.keySet()); + + for (var entry : periodsPerDay.entrySet()) { + // Find target time for this day + var midnight = entry.getKey(); // beginning of this day + var periods = entry.getValue(); // periods of this day + ZonedDateTime targetTime = switch (mode.get()) { + case OFF -> midnight; // Can not happen + case MANUAL -> midnight // + .withHour(manualTargetTime.get().getHour()) // + .withMinute(manualTargetTime.get().getMinute()); + case AUTOMATIC -> midnight; // TODO + }; + // Find first period with Production > Consumption + var firstExcessEnergyOpt = periods.stream() // + .filter(p -> p.production() > p.consumption()) // + .findFirst(); + if (firstExcessEnergyOpt.isEmpty() + || targetTime.isBefore(firstExcessEnergyOpt.get().time())) { + // Production exceeds Consumption never or too late on this day + // -> set no limit for this day + limits.put(midnight, OptionalInt.empty()); + continue; + } + var firstExcessEnergy = firstExcessEnergyOpt.get().time(); + + // Set no limit for early hours of the day + if (firstExcessEnergy.isAfter(midnight)) { + limits.put(midnight, OptionalInt.empty()); + } + + // Calculate actual charge limit + var noOfQuarters = (int) Duration.between(firstExcessEnergy, targetTime).toMinutes() / 15; + final var totalEnergy = midnight == firstDayMignight // + ? // use actual data for first day + simContext.ess().totalEnergy() - simContext.ess().currentEnergy() + : // assume full charge from second day + simContext.ess().totalEnergy(); + limits.put(firstExcessEnergy, OptionalInt.of(totalEnergy / noOfQuarters)); + + // No limit after targetTime + limits.put(targetTime, OptionalInt.empty()); + } + } + + return new EshContext(limits.build()); + }) // + .setSimulator((simContext, period, energyFlow, ctrlContext) -> { + var limitEntry = ctrlContext.limits.floorEntry(period.time()); + if (limitEntry == null) { + return; + } + var limit = limitEntry.getValue(); + if (limit.isPresent()) { + energyFlow.setEssMaxCharge(limit.getAsInt()); + } + }) // + .build(); + } + + private static record EshContext(ImmutableSortedMap limits) { + } + + @Override + public EnergyScheduleHandler getEnergyScheduleHandler() { + return this.energyScheduleHandler; + } } diff --git a/io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/package-info.java b/io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/package-info.java new file mode 100644 index 00000000000..9736f9da6be --- /dev/null +++ b/io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/package-info.java @@ -0,0 +1,3 @@ +@org.osgi.annotation.versioning.Version("1.0.0") +@org.osgi.annotation.bundle.Export +package io.openems.edge.controller.ess.gridoptimizedcharge; diff --git a/io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/translation_de.properties b/io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/translation_de.properties new file mode 100644 index 00000000000..2660bc97b77 --- /dev/null +++ b/io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/translation_de.properties @@ -0,0 +1,2 @@ +# ControllerEssGridOptimizedCharge +noValidProductionPrediction = Keine Erzeugungsprognose möglich. Bitte erfassen Sie die Erzeugung Ihrer Anlage über eine App oder wählen Sie in der netzdienlichen Beladung den Modus 'AUS'. diff --git a/io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/translation_en.properties b/io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/translation_en.properties new file mode 100644 index 00000000000..e2195067037 --- /dev/null +++ b/io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/translation_en.properties @@ -0,0 +1,2 @@ +# ControllerEssGridOptimizedCharge +noValidProductionPrediction = No production forecast available. Please log the production via an app or set the grid-optimized charge mode to 'OFF'. diff --git a/io.openems.edge.controller.ess.gridoptimizedcharge/test/io/openems/edge/controller/ess/gridoptimizedcharge/ControllerEssGridOptimizedChargeImplTest.java b/io.openems.edge.controller.ess.gridoptimizedcharge/test/io/openems/edge/controller/ess/gridoptimizedcharge/ControllerEssGridOptimizedChargeImplTest.java index 03f197139e3..862b0955d56 100644 --- a/io.openems.edge.controller.ess.gridoptimizedcharge/test/io/openems/edge/controller/ess/gridoptimizedcharge/ControllerEssGridOptimizedChargeImplTest.java +++ b/io.openems.edge.controller.ess.gridoptimizedcharge/test/io/openems/edge/controller/ess/gridoptimizedcharge/ControllerEssGridOptimizedChargeImplTest.java @@ -1,5 +1,20 @@ package io.openems.edge.controller.ess.gridoptimizedcharge; +import static io.openems.edge.common.sum.Sum.ChannelId.PRODUCTION_DC_ACTUAL_POWER; +import static io.openems.edge.controller.ess.gridoptimizedcharge.ControllerEssGridOptimizedCharge.ChannelId.DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT; +import static io.openems.edge.controller.ess.gridoptimizedcharge.ControllerEssGridOptimizedCharge.ChannelId.DELAY_CHARGE_STATE; +import static io.openems.edge.controller.ess.gridoptimizedcharge.ControllerEssGridOptimizedCharge.ChannelId.PREDICTED_TARGET_MINUTE; +import static io.openems.edge.controller.ess.gridoptimizedcharge.ControllerEssGridOptimizedCharge.ChannelId.PREDICTED_TARGET_MINUTE_ADJUSTED; +import static io.openems.edge.controller.ess.gridoptimizedcharge.ControllerEssGridOptimizedCharge.ChannelId.RAW_DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT; +import static io.openems.edge.controller.ess.gridoptimizedcharge.ControllerEssGridOptimizedCharge.ChannelId.RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT; +import static io.openems.edge.controller.ess.gridoptimizedcharge.ControllerEssGridOptimizedCharge.ChannelId.SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT; +import static io.openems.edge.controller.ess.gridoptimizedcharge.ControllerEssGridOptimizedCharge.ChannelId.SELL_TO_GRID_LIMIT_STATE; +import static io.openems.edge.controller.ess.gridoptimizedcharge.ControllerEssGridOptimizedCharge.ChannelId.START_EPOCH_SECONDS; +import static io.openems.edge.controller.ess.gridoptimizedcharge.ControllerEssGridOptimizedCharge.ChannelId.TARGET_MINUTE; +import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_LESS_OR_EQUALS; +import static io.openems.edge.ess.api.SymmetricEss.ChannelId.CAPACITY; +import static io.openems.edge.ess.api.SymmetricEss.ChannelId.MAX_APPARENT_POWER; +import static io.openems.edge.ess.api.SymmetricEss.ChannelId.SOC; import static io.openems.edge.predictor.api.prediction.Prediction.EMPTY_PREDICTION; import static java.time.temporal.ChronoUnit.DAYS; import static org.junit.Assert.assertEquals; @@ -34,8 +49,10 @@ import io.openems.edge.common.test.Plot.AxisFormat; import io.openems.edge.common.test.Plot.Data; import io.openems.edge.controller.test.ControllerTest; +import io.openems.edge.ess.api.SymmetricEss; import io.openems.edge.ess.test.DummyHybridEss; import io.openems.edge.ess.test.DummyManagedSymmetricEss; +import io.openems.edge.meter.api.ElectricityMeter; import io.openems.edge.meter.test.DummyElectricityMeter; import io.openems.edge.predictor.api.prediction.Prediction; import io.openems.edge.predictor.api.test.DummyPredictor; @@ -43,50 +60,13 @@ public class ControllerEssGridOptimizedChargeImplTest { - // Ids - private static final String CTRL_ID = "ctrlGridOptimizedCharge0"; - private static final String PREDICTOR_ID = "predictor0"; - private static final String ESS_ID = "ess0"; - private static final String METER_ID = "meter0"; - // Components - private static final DummyManagedSymmetricEss ESS = new DummyManagedSymmetricEss(ESS_ID); - private static final DummyElectricityMeter METER = new DummyElectricityMeter(METER_ID); - private static final DummyHybridEss HYBRID_ESS = new DummyHybridEss(ESS_ID); - private static final DummyManagedSymmetricEss ESS_WITH_NONE_APPARENT_POWER = new DummyManagedSymmetricEss(ESS_ID) // + private static final DummyManagedSymmetricEss ESS = new DummyManagedSymmetricEss("ess0"); + private static final DummyElectricityMeter METER = new DummyElectricityMeter("meter0"); + private static final DummyHybridEss HYBRID_ESS = new DummyHybridEss("ess0"); + private static final DummyManagedSymmetricEss ESS_WITH_NONE_APPARENT_POWER = new DummyManagedSymmetricEss("ess0") // .withMaxApparentPower(0); - // Ess channels - private static final ChannelAddress ESS_CAPACITY = new ChannelAddress(ESS_ID, "Capacity"); - private static final ChannelAddress ESS_SOC = new ChannelAddress(ESS_ID, "Soc"); - private static final ChannelAddress ESS_MAX_APPARENT_POWER = new ChannelAddress(ESS_ID, "MaxApparentPower"); - private static final ChannelAddress ESS_ACTIVE_POWER = new ChannelAddress(ESS_ID, "ActivePower"); - private static final ChannelAddress ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS = new ChannelAddress(ESS_ID, - "SetActivePowerLessOrEquals"); - - // Meter channels - private static final ChannelAddress METER_ACTIVE_POWER = new ChannelAddress(METER_ID, "ActivePower"); - - // Controller channels - private static final ChannelAddress PREDICTED_TARGET_MINUTE = new ChannelAddress(CTRL_ID, "PredictedTargetMinute"); - private static final ChannelAddress PREDICTED_TARGET_MINUTE_ADJUSTED = new ChannelAddress(CTRL_ID, - "PredictedTargetMinuteAdjusted"); - private static final ChannelAddress TARGET_MINUTE = new ChannelAddress(CTRL_ID, "TargetMinute"); - private static final ChannelAddress DELAY_CHARGE_STATE = new ChannelAddress(CTRL_ID, "DelayChargeState"); - private static final ChannelAddress SELL_TO_GRID_LIMIT_STATE = new ChannelAddress(CTRL_ID, "SellToGridLimitState"); - private static final ChannelAddress DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT = new ChannelAddress(CTRL_ID, - "DelayChargeMaximumChargeLimit"); - private static final ChannelAddress RAW_DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT = new ChannelAddress(CTRL_ID, - "RawDelayChargeMaximumChargeLimit"); - private static final ChannelAddress SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT = new ChannelAddress(CTRL_ID, - "SellToGridLimitMinimumChargeLimit"); - private static final ChannelAddress RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT = new ChannelAddress(CTRL_ID, - "RawSellToGridLimitChargeLimit"); - private static final ChannelAddress START_EPOCH_SECONDS = new ChannelAddress(CTRL_ID, "StartEpochSeconds"); - - // Sum channels - private static final ChannelAddress SUM_PRODUCTION_DC_ACTUAL_POWER = new ChannelAddress("_sum", - "ProductionDcActualPower"); private static final ChannelAddress SUM_PRODUCTION_ACTIVE_POWER = new ChannelAddress("_sum", "ProductionActivePower"); private static final ChannelAddress SUM_CONSUMPTION_ACTIVE_POWER = new ChannelAddress("_sum", @@ -157,11 +137,11 @@ public void automatic_default_predictions_at_midnight_test() throws Exception { final var sum = new DummySum(); final var predictorManager = new DummyPredictorManager( // Production - new DummyPredictor(PREDICTOR_ID, cm, + new DummyPredictor("predictor0", cm, Prediction.from(sum, SUM_PRODUCTION_ACTIVE_POWER, now, DEFAULT_PRODUCTION_PREDICTION), SUM_PRODUCTION_ACTIVE_POWER), // Consumption - new DummyPredictor(PREDICTOR_ID, cm, + new DummyPredictor("predictor0", cm, Prediction.from(sum, SUM_CONSUMPTION_ACTIVE_POWER, now, DEFAULT_CONSUMPTION_PREDICTION), SUM_CONSUMPTION_ACTIVE_POWER)); @@ -173,10 +153,10 @@ public void automatic_default_predictions_at_midnight_test() throws Exception { .addReference("meter", METER) // .addReference("sum", new DummySum()) // .activate(MyConfig.create() // - .setEssId(ESS_ID) // - .setId(CTRL_ID) // + .setEssId("ess0") // + .setId("ctrlGridOptimizedCharge0") // .setMaximumSellToGridPower(7_000) // - .setMeterId(METER_ID) // + .setMeterId("meter0") // .setDelayChargeRiskLevel(DelayChargeRiskLevel.MEDIUM) // .setMode(Mode.AUTOMATIC) // .setSellToGridLimitEnabled(true) // @@ -184,18 +164,20 @@ public void automatic_default_predictions_at_midnight_test() throws Exception { .setManualTargetTime("") // .build()) // .next(new TestCase() // - .input(METER_ACTIVE_POWER, 0) // - .input(ESS_ACTIVE_POWER, 0) // - .input(ESS_CAPACITY, 10_000) // - .input(ESS_SOC, 20) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 0) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) // + .input("ess0", CAPACITY, 10_000) // + .input("ess0", SOC, 20) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // .input(START_EPOCH_SECONDS, 1630566000) // .output(PREDICTED_TARGET_MINUTE, /* QuarterHour */ 68 * 15) // .output(PREDICTED_TARGET_MINUTE_ADJUSTED, /* QuarterHour */ 68 * 15 - 120) // .output(DELAY_CHARGE_STATE, DelayChargeState.AVOID_LOW_CHARGING) // .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, -6650) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, 6650) // - .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 0)); // Avoid low charge power + .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 0)) // Avoid low charge power + .deactivate(); } @Test @@ -206,11 +188,11 @@ public void automatic_default_predictions_at_midday_test() throws Exception { final var sum = new DummySum(); final var predictorManager = new DummyPredictorManager( // Production - new DummyPredictor(PREDICTOR_ID, cm, + new DummyPredictor("predictor0", cm, Prediction.from(sum, SUM_PRODUCTION_ACTIVE_POWER, midnight, DEFAULT_PRODUCTION_PREDICTION), SUM_PRODUCTION_ACTIVE_POWER), // Consumption - new DummyPredictor(PREDICTOR_ID, cm, + new DummyPredictor("predictor0", cm, Prediction.from(sum, SUM_CONSUMPTION_ACTIVE_POWER, midnight, DEFAULT_CONSUMPTION_PREDICTION), SUM_CONSUMPTION_ACTIVE_POWER)); @@ -222,10 +204,10 @@ public void automatic_default_predictions_at_midday_test() throws Exception { .addReference("meter", METER) // .addReference("sum", new DummySum()) // .activate(MyConfig.create() // - .setEssId(ESS_ID) // - .setId(CTRL_ID) // + .setEssId("ess0") // + .setId("ctrlGridOptimizedCharge0") // .setMaximumSellToGridPower(7_000) // - .setMeterId(METER_ID) // + .setMeterId("meter0") // .setDelayChargeRiskLevel(DelayChargeRiskLevel.MEDIUM) // .setMode(Mode.AUTOMATIC) // .setSellToGridLimitEnabled(true) // @@ -233,11 +215,12 @@ public void automatic_default_predictions_at_midday_test() throws Exception { .setManualTargetTime("") // .build()) // .next(new TestCase() // - .input(METER_ACTIVE_POWER, 0) // - .input(ESS_ACTIVE_POWER, 0) // - .input(ESS_CAPACITY, 10_000) // - .input(ESS_SOC, 20) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 0) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) // + .input("ess0", CAPACITY, 10_000) // + .input("ess0", SOC, 20) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // .input(START_EPOCH_SECONDS, 1630566000) // .output(PREDICTED_TARGET_MINUTE, /* QuarterHour */ 68 * 15) // .output(PREDICTED_TARGET_MINUTE_ADJUSTED, /* QuarterHour */ 68 * 15 - 120) // @@ -246,7 +229,8 @@ public void automatic_default_predictions_at_midday_test() throws Exception { .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, 6650) // // If Energy calculation would be applied on medium risk level - Predicted // available Energy is not enough to reach 100% - .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 2700)); + .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 2700)) // + .deactivate(); } @Test @@ -262,11 +246,11 @@ public void automatic_default_predictions_at_midday_averaged_test() throws Excep final var sum = new DummySum(); final var predictorManager = new DummyPredictorManager( // Production - new DummyPredictor(PREDICTOR_ID, cm, + new DummyPredictor("predictor0", cm, Prediction.from(sum, SUM_PRODUCTION_ACTIVE_POWER, midnight, DEFAULT_PRODUCTION_PREDICTION), SUM_PRODUCTION_ACTIVE_POWER), // Consumption - new DummyPredictor(PREDICTOR_ID, cm, + new DummyPredictor("predictor0", cm, Prediction.from(sum, SUM_CONSUMPTION_ACTIVE_POWER, midnight, DEFAULT_CONSUMPTION_PREDICTION), SUM_CONSUMPTION_ACTIVE_POWER)); @@ -278,10 +262,10 @@ public void automatic_default_predictions_at_midday_averaged_test() throws Excep .addReference("meter", METER) // .addReference("sum", new DummySum()) // .activate(MyConfig.create() // - .setEssId(ESS_ID) // - .setId(CTRL_ID) // + .setEssId("ess0") // + .setId("ctrlGridOptimizedCharge0") // .setMaximumSellToGridPower(7_000) // - .setMeterId(METER_ID) // + .setMeterId("meter0") // .setDelayChargeRiskLevel(DelayChargeRiskLevel.MEDIUM) // .setMode(Mode.AUTOMATIC) // .setSellToGridLimitEnabled(true) // @@ -290,11 +274,12 @@ public void automatic_default_predictions_at_midday_averaged_test() throws Excep .build()) // .next(new TestCase() // .onAfterProcessImage(sleep) // - .input(METER_ACTIVE_POWER, 0) // - .input(ESS_ACTIVE_POWER, 0) // - .input(ESS_CAPACITY, 10_000) // - .input(ESS_SOC, 20) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 0) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) // + .input("ess0", CAPACITY, 10_000) // + .input("ess0", SOC, 20) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // .input(START_EPOCH_SECONDS, 1630566000) // .output(PREDICTED_TARGET_MINUTE, /* QuarterHour */ 68 * 15) // .output(PREDICTED_TARGET_MINUTE_ADJUSTED, /* QuarterHour */ 68 * 15 - 120) // @@ -305,23 +290,28 @@ public void automatic_default_predictions_at_midday_averaged_test() throws Excep .output(RAW_DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 2700)) // .next(new TestCase() // .onAfterProcessImage(sleep) // - .input(ESS_SOC, 21) // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // + .input("ess0", SOC, 21) // .input(START_EPOCH_SECONDS, 1630566000) // .output(DELAY_CHARGE_STATE, DelayChargeState.ACTIVE_LIMIT) // .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 2683) // .output(RAW_DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 2666)) // .next(new TestCase() // .onAfterProcessImage(sleep) // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 2677) // .output(RAW_DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 2666)) // .next(new TestCase() // .onAfterProcessImage(sleep) // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 2675) // .output(RAW_DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 2666)) // .next(new TestCase() // .onAfterProcessImage(sleep) // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 2673) // .output(RAW_DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 2666)) // + .deactivate(); ; } @@ -333,11 +323,11 @@ public void automatic_default_predictions_at_evening_test() throws Exception { final var sum = new DummySum(); final var predictorManager = new DummyPredictorManager( // Production - new DummyPredictor(PREDICTOR_ID, cm, + new DummyPredictor("predictor0", cm, Prediction.from(sum, SUM_PRODUCTION_ACTIVE_POWER, now, DEFAULT_PRODUCTION_PREDICTION), SUM_PRODUCTION_ACTIVE_POWER), // Consumption - new DummyPredictor(PREDICTOR_ID, cm, + new DummyPredictor("predictor0", cm, Prediction.from(sum, SUM_CONSUMPTION_ACTIVE_POWER, now, DEFAULT_CONSUMPTION_PREDICTION), SUM_CONSUMPTION_ACTIVE_POWER)); @@ -349,10 +339,10 @@ public void automatic_default_predictions_at_evening_test() throws Exception { .addReference("meter", METER) // .addReference("sum", new DummySum()) // .activate(MyConfig.create() // - .setEssId(ESS_ID) // - .setId(CTRL_ID) // + .setEssId("ess0") // + .setId("ctrlGridOptimizedCharge0") // .setMaximumSellToGridPower(7_000) // - .setMeterId(METER_ID) // + .setMeterId("meter0") // .setDelayChargeRiskLevel(DelayChargeRiskLevel.MEDIUM) // .setMode(Mode.AUTOMATIC) // .setSellToGridLimitEnabled(true) // @@ -360,11 +350,12 @@ public void automatic_default_predictions_at_evening_test() throws Exception { .setManualTargetTime("") // .build()) // .next(new TestCase() // - .input(METER_ACTIVE_POWER, 0) // - .input(ESS_ACTIVE_POWER, 0) // - .input(ESS_CAPACITY, 10_000) // - .input(ESS_SOC, 20) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 0) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) // + .input("ess0", CAPACITY, 10_000) // + .input("ess0", SOC, 20) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // .input(PREDICTED_TARGET_MINUTE, /* QuarterHour */ 68 * 15) // .input(START_EPOCH_SECONDS, 1630566000) // .input(PREDICTED_TARGET_MINUTE_ADJUSTED, /* QuarterHour */ 68 * 15 - 120) // @@ -373,6 +364,7 @@ public void automatic_default_predictions_at_evening_test() throws Exception { // Value increases steadily by 0.25% of max apparent power 10_000 .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 2025)) .next(new TestCase() // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // .input(PREDICTED_TARGET_MINUTE, /* QuarterHour */ 68 * 15) // .input(PREDICTED_TARGET_MINUTE_ADJUSTED, /* QuarterHour */ 68 * 15 - 120) // .input(START_EPOCH_SECONDS, 1630566000) // @@ -383,6 +375,7 @@ public void automatic_default_predictions_at_evening_test() throws Exception { .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, 6650) // .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 2050)) .next(new TestCase() // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // .input(PREDICTED_TARGET_MINUTE, /* QuarterHour */ 68 * 15) // .input(PREDICTED_TARGET_MINUTE_ADJUSTED, /* QuarterHour */ 68 * 15 - 120) // .input(START_EPOCH_SECONDS, 1630566000) // @@ -391,7 +384,8 @@ public void automatic_default_predictions_at_evening_test() throws Exception { .output(DELAY_CHARGE_STATE, DelayChargeState.ACTIVE_LIMIT) // .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, -6650) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, 6650) // - .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 2075)); + .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 2075)) // + .deactivate(); } @Test @@ -399,8 +393,8 @@ public void automatic_no_predictions_test() throws Exception { final var clock = new TimeLeapClock(Instant.parse("2020-01-01T00:00:00.00Z"), ZoneOffset.UTC); final var cm = new DummyComponentManager(clock); final var predictorManager = new DummyPredictorManager( - new DummyPredictor(PREDICTOR_ID, cm, EMPTY_PREDICTION, SUM_PRODUCTION_ACTIVE_POWER), // - new DummyPredictor(PREDICTOR_ID, cm, EMPTY_PREDICTION, SUM_CONSUMPTION_ACTIVE_POWER)); + new DummyPredictor("predictor0", cm, EMPTY_PREDICTION, SUM_PRODUCTION_ACTIVE_POWER), // + new DummyPredictor("predictor0", cm, EMPTY_PREDICTION, SUM_CONSUMPTION_ACTIVE_POWER)); new ControllerTest(new ControllerEssGridOptimizedChargeImpl()) // .addReference("predictorManager", predictorManager) // @@ -410,10 +404,10 @@ public void automatic_no_predictions_test() throws Exception { .addReference("meter", METER) // .addReference("sum", new DummySum()) // .activate(MyConfig.create() // - .setEssId(ESS_ID) // - .setId(CTRL_ID) // + .setEssId("ess0") // + .setId("ctrlGridOptimizedCharge0") // .setMaximumSellToGridPower(7_000) // - .setMeterId(METER_ID) // + .setMeterId("meter0") // .setDelayChargeRiskLevel(DelayChargeRiskLevel.MEDIUM) // .setMode(Mode.AUTOMATIC) // .setSellToGridLimitEnabled(true) // @@ -421,15 +415,17 @@ public void automatic_no_predictions_test() throws Exception { .setManualTargetTime("") // .build()) // .next(new TestCase() // - .input(METER_ACTIVE_POWER, 0) // - .input(ESS_ACTIVE_POWER, 0) // - .input(ESS_CAPACITY, 10_000) // - .input(ESS_SOC, 20) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 0) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) // + .input("ess0", CAPACITY, 10_000) // + .input("ess0", SOC, 20) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // .input(START_EPOCH_SECONDS, 1630566000) // .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, -6650) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, 6650) // - .output(DELAY_CHARGE_STATE, DelayChargeState.TARGET_MINUTE_NOT_CALCULATED)); + .output(DELAY_CHARGE_STATE, DelayChargeState.TARGET_MINUTE_NOT_CALCULATED)) // + .deactivate(); } @Test @@ -437,8 +433,8 @@ public void automatic_sell_to_grid_limit_test() throws Exception { final var clock = new TimeLeapClock(Instant.parse("2020-01-01T00:00:00.00Z"), ZoneOffset.UTC); final var cm = new DummyComponentManager(clock); final var predictorManager = new DummyPredictorManager( - new DummyPredictor(PREDICTOR_ID, cm, EMPTY_PREDICTION, SUM_PRODUCTION_ACTIVE_POWER), // - new DummyPredictor(PREDICTOR_ID, cm, EMPTY_PREDICTION, SUM_CONSUMPTION_ACTIVE_POWER)); + new DummyPredictor("predictor0", cm, EMPTY_PREDICTION, SUM_PRODUCTION_ACTIVE_POWER), // + new DummyPredictor("predictor0", cm, EMPTY_PREDICTION, SUM_CONSUMPTION_ACTIVE_POWER)); new ControllerTest(new ControllerEssGridOptimizedChargeImpl()) // .addReference("predictorManager", predictorManager) // @@ -448,10 +444,10 @@ public void automatic_sell_to_grid_limit_test() throws Exception { .addReference("meter", METER) // .addReference("sum", new DummySum()) // .activate(MyConfig.create() // - .setEssId(ESS_ID) // - .setId(CTRL_ID) // + .setEssId("ess0") // + .setId("ctrlGridOptimizedCharge0") // .setMaximumSellToGridPower(7_000) // - .setMeterId(METER_ID) // + .setMeterId("meter0") // .setDelayChargeRiskLevel(DelayChargeRiskLevel.MEDIUM) // .setMode(Mode.AUTOMATIC) // .setSellToGridLimitEnabled(true) // @@ -459,68 +455,76 @@ public void automatic_sell_to_grid_limit_test() throws Exception { .setManualTargetTime("") // .build()) // .next(new TestCase() // - .input(METER_ACTIVE_POWER, -7500) // - .input(ESS_CAPACITY, 10_000) // - .input(ESS_SOC, 20) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .input(ESS_ACTIVE_POWER, 0) // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -7500) // + .input("ess0", CAPACITY, 10_000) // + .input("ess0", SOC, 20) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) // .input(START_EPOCH_SECONDS, 1630566000) // .output(DELAY_CHARGE_STATE, DelayChargeState.TARGET_MINUTE_NOT_CALCULATED) // .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, null) // - .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -850) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -850) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -850) // .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 850) // .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) // .next(new TestCase() // - .input(METER_ACTIVE_POWER, -12000) // - .input(ESS_ACTIVE_POWER, -850) // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -12000) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -850) // .input(START_EPOCH_SECONDS, 1630566000) // - .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -6200) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -6200) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -6200) // .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 6200) // .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) // .next(new TestCase() // - .input(METER_ACTIVE_POWER, -7000) // - .input(ESS_ACTIVE_POWER, -6200) // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -7000) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -6200) // .input(START_EPOCH_SECONDS, 1630566000) // - .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -6550) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -6550) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -6550) // .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 6550) // .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) // .next(new TestCase() // - .input(METER_ACTIVE_POWER, -5000) // - .input(ESS_ACTIVE_POWER, -6550) // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -5000) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -6550) // .input(START_EPOCH_SECONDS, 1630566000) // - .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -6050) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -6050) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -6050) // .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 6050) // .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) // .next(new TestCase() // - .input(METER_ACTIVE_POWER, -8000) // - .input(ESS_ACTIVE_POWER, -6050) // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -8000) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -6050) // .input(START_EPOCH_SECONDS, 1630566000) // - .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -7400) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -7400) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -7400) // .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 7400) // .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) // .next(new TestCase() // // Difference between last limit and current lower than the ramp - ramp is not // applied - .input(METER_ACTIVE_POWER, -7000) // - .input(ESS_ACTIVE_POWER, -7400) // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -7000) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -7400) // .input(START_EPOCH_SECONDS, 1630566000) // - .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -7750) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -7750) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -7750) // .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 7750) // .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) // .next(new TestCase() // - .input(METER_ACTIVE_POWER, -6000) // - .input(ESS_ACTIVE_POWER, -7750) // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -6000) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -7750) // .input(START_EPOCH_SECONDS, 1630566000) // - .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -7250) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -7250) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -7250) // .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 7250) // - .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)); + .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) // + .deactivate(); } @Test @@ -528,8 +532,8 @@ public void automatic_sell_to_grid_limit_test_with_full_ess() throws Exception { final var clock = new TimeLeapClock(Instant.parse("2020-01-01T00:00:00.00Z"), ZoneOffset.UTC); final var cm = new DummyComponentManager(clock); final var predictorManager = new DummyPredictorManager( - new DummyPredictor(PREDICTOR_ID, cm, EMPTY_PREDICTION, SUM_PRODUCTION_ACTIVE_POWER), // - new DummyPredictor(PREDICTOR_ID, cm, EMPTY_PREDICTION, SUM_CONSUMPTION_ACTIVE_POWER)); + new DummyPredictor("predictor0", cm, EMPTY_PREDICTION, SUM_PRODUCTION_ACTIVE_POWER), // + new DummyPredictor("predictor0", cm, EMPTY_PREDICTION, SUM_CONSUMPTION_ACTIVE_POWER)); new ControllerTest(new ControllerEssGridOptimizedChargeImpl()) // .addReference("predictorManager", predictorManager) // @@ -539,10 +543,10 @@ public void automatic_sell_to_grid_limit_test_with_full_ess() throws Exception { .addReference("meter", METER) // .addReference("sum", new DummySum()) // .activate(MyConfig.create() // - .setEssId(ESS_ID) // - .setId(CTRL_ID) // + .setEssId("ess0") // + .setId("ctrlGridOptimizedCharge0") // .setMaximumSellToGridPower(7_000) // - .setMeterId(METER_ID) // + .setMeterId("meter0") // .setDelayChargeRiskLevel(DelayChargeRiskLevel.MEDIUM) // .setMode(Mode.AUTOMATIC) // .setSellToGridLimitEnabled(true) // @@ -550,70 +554,78 @@ public void automatic_sell_to_grid_limit_test_with_full_ess() throws Exception { .setManualTargetTime("") // .build()) // .next(new TestCase() // - .input(METER_ACTIVE_POWER, -7500) // - .input(ESS_CAPACITY, 10_000) // - .input(ESS_SOC, 100) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .input(ESS_ACTIVE_POWER, 0) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -7500) // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // + .input("ess0", CAPACITY, 10_000) // + .input("ess0", SOC, 100) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) // .input(START_EPOCH_SECONDS, 1630566000) // .output(DELAY_CHARGE_STATE, DelayChargeState.TARGET_MINUTE_NOT_CALCULATED) // .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, null) // - .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -500) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -500) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -500) // .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 500) // .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) // .next(new TestCase() // - .input(METER_ACTIVE_POWER, -12000) // - .input(ESS_ACTIVE_POWER, -1000) // - .input(ESS_SOC, 100) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -12000) // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -1000) // + .input("ess0", SOC, 100) // .input(START_EPOCH_SECONDS, 1630566000) // - .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -6000) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -6000) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -6000) // .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 6000) // .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) // .next(new TestCase() // - .input(METER_ACTIVE_POWER, -7000) // - .input(ESS_ACTIVE_POWER, -6000) // - .input(ESS_SOC, 100) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -7000) // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -6000) // + .input("ess0", SOC, 100) // .input(START_EPOCH_SECONDS, 1630566000) // - .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -6000) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -6000) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -6000) // .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 6000) // .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) // .next(new TestCase() // - .input(METER_ACTIVE_POWER, -5000) // - .input(ESS_ACTIVE_POWER, -6000) // - .input(ESS_SOC, 100) // - .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -5500) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -5000) // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -6000) // + .input("ess0", SOC, 100) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -5500) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -5500) // .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 5500) // .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) // .next(new TestCase() // - .input(METER_ACTIVE_POWER, -8000) // - .input(ESS_ACTIVE_POWER, -5500) // - .input(ESS_SOC, 100) // - .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -6500) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -8000) // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -5500) // + .input("ess0", SOC, 100) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -6500) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -6500) // .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 6500) // .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) // .next(new TestCase() // // Difference between last limit and current lower than the ramp - ramp is not // applied - .input(METER_ACTIVE_POWER, -7000) // - .input(ESS_ACTIVE_POWER, -6300) // - .input(ESS_SOC, 100) // - .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -6300) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -7000) // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -6300) // + .input("ess0", SOC, 100) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -6300) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -6300) // .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 6300) // .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) // .next(new TestCase() // - .input(METER_ACTIVE_POWER, -6000) // - .input(ESS_ACTIVE_POWER, -6000) // - .input(ESS_SOC, 100) // - .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -5800) // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -6000) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -6000) // + .input("ess0", SOC, 100) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -5800) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -5800) // .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 5800) // - .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)); + .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) // + .deactivate(); } @Test @@ -621,8 +633,8 @@ public void automatic_sell_to_grid_limit_buffer_test() throws Exception { final var clock = new TimeLeapClock(Instant.parse("2020-01-01T00:00:00.00Z"), ZoneOffset.UTC); final var cm = new DummyComponentManager(clock); final var predictorManager = new DummyPredictorManager( - new DummyPredictor(PREDICTOR_ID, cm, EMPTY_PREDICTION, SUM_PRODUCTION_ACTIVE_POWER), // - new DummyPredictor(PREDICTOR_ID, cm, EMPTY_PREDICTION, SUM_CONSUMPTION_ACTIVE_POWER)); + new DummyPredictor("predictor0", cm, EMPTY_PREDICTION, SUM_PRODUCTION_ACTIVE_POWER), // + new DummyPredictor("predictor0", cm, EMPTY_PREDICTION, SUM_CONSUMPTION_ACTIVE_POWER)); new ControllerTest(new ControllerEssGridOptimizedChargeImpl()) // .addReference("predictorManager", predictorManager) // @@ -632,10 +644,10 @@ public void automatic_sell_to_grid_limit_buffer_test() throws Exception { .addReference("meter", METER) // .addReference("sum", new DummySum()) // .activate(MyConfig.create() // - .setEssId(ESS_ID) // - .setId(CTRL_ID) // + .setEssId("ess0") // + .setId("ctrlGridOptimizedCharge0") // .setMaximumSellToGridPower(7_000) // - .setMeterId(METER_ID) // + .setMeterId("meter0") // .setDelayChargeRiskLevel(DelayChargeRiskLevel.MEDIUM) // .setMode(Mode.AUTOMATIC) // .setSellToGridLimitEnabled(true) // @@ -643,68 +655,76 @@ public void automatic_sell_to_grid_limit_buffer_test() throws Exception { .setManualTargetTime("") // .build()) // .next(new TestCase() // - .input(METER_ACTIVE_POWER, -7500) // - .input(ESS_CAPACITY, 10_000) // - .input(ESS_SOC, 20) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .input(ESS_ACTIVE_POWER, 0) // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -7500) // + .input("ess0", CAPACITY, 10_000) // + .input("ess0", SOC, 20) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) // .input(START_EPOCH_SECONDS, 1630566000) // .output(DELAY_CHARGE_STATE, DelayChargeState.TARGET_MINUTE_NOT_CALCULATED) // .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, null) // - .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -850) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -850) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -850) // .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 850) // .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) // .next(new TestCase() // - .input(METER_ACTIVE_POWER, -12000) // - .input(ESS_ACTIVE_POWER, -1000) // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -12000) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -1000) // .input(START_EPOCH_SECONDS, 1630566000) // - .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -6350) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -6350) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -6350) // .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 6350) // .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) // .next(new TestCase() // - .input(METER_ACTIVE_POWER, -7000) // - .input(ESS_ACTIVE_POWER, -6000) // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -7000) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -6000) // .input(START_EPOCH_SECONDS, 1630566000) // - .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -6350) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -6350) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -6350) // .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 6350) // .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) // .next(new TestCase() // - .input(METER_ACTIVE_POWER, -5000) // - .input(ESS_ACTIVE_POWER, -6000) // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -5000) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -6000) // .input(START_EPOCH_SECONDS, 1630566000) // - .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -5850) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -5850) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -5850) // .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 5850) // .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) // .next(new TestCase() // - .input(METER_ACTIVE_POWER, -8000) // - .input(ESS_ACTIVE_POWER, -5500) // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -8000) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -5500) // .input(START_EPOCH_SECONDS, 1630566000) // - .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -6850) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -6850) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -6850) // .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 6850) // .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) // .next(new TestCase() // // Difference between last limit and current lower than the ramp - ramp is not // applied - .input(METER_ACTIVE_POWER, -7000) // - .input(ESS_ACTIVE_POWER, -6300) // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -7000) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -6300) // .input(START_EPOCH_SECONDS, 1630566000) // - .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -6650) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -6650) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -6650) // .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 6650) // .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) // .next(new TestCase() // - .input(METER_ACTIVE_POWER, -6000) // - .input(ESS_ACTIVE_POWER, -6000) // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -6000) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -6000) // .input(START_EPOCH_SECONDS, 1630566000) // - .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -6150) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -6150) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -6150) // .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 6150) // - .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)); + .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) // + .deactivate(); } @Test @@ -715,11 +735,11 @@ public void manual_midnight_test() throws Exception { final var sum = new DummySum(); final var predictorManager = new DummyPredictorManager( // Production - new DummyPredictor(PREDICTOR_ID, cm, + new DummyPredictor("predictor0", cm, Prediction.from(sum, SUM_PRODUCTION_ACTIVE_POWER, now, DEFAULT_PRODUCTION_PREDICTION), SUM_PRODUCTION_ACTIVE_POWER), // Consumption - new DummyPredictor(PREDICTOR_ID, cm, + new DummyPredictor("predictor0", cm, Prediction.from(sum, SUM_CONSUMPTION_ACTIVE_POWER, now, DEFAULT_CONSUMPTION_PREDICTION), SUM_CONSUMPTION_ACTIVE_POWER)); @@ -731,10 +751,10 @@ public void manual_midnight_test() throws Exception { .addReference("meter", METER) // .addReference("sum", new DummySum()) // .activate(MyConfig.create() // - .setEssId(ESS_ID) // - .setId(CTRL_ID) // + .setEssId("ess0") // + .setId("ctrlGridOptimizedCharge0") // .setMaximumSellToGridPower(7_000) // - .setMeterId(METER_ID) // + .setMeterId("meter0") // .setDelayChargeRiskLevel(DelayChargeRiskLevel.MEDIUM) // .setMode(Mode.MANUAL) // .setSellToGridLimitEnabled(true) // @@ -742,19 +762,21 @@ public void manual_midnight_test() throws Exception { .setSellToGridLimitRampPercentage(5) // .build()) // .next(new TestCase() // - .input(METER_ACTIVE_POWER, 0) // - .input(ESS_ACTIVE_POWER, 0) // - .input(ESS_CAPACITY, 10_000) // - .input(ESS_SOC, 20) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 0) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) // + .input("ess0", CAPACITY, 10_000) // + .input("ess0", SOC, 20) // .input(START_EPOCH_SECONDS, 1630566000) // - .input(ESS_MAX_APPARENT_POWER, 10_000)) // + .input("ess0", MAX_APPARENT_POWER, 10_000)) // .next(new TestCase() // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // .input(START_EPOCH_SECONDS, 1630566000) // .output(TARGET_MINUTE, /* QuarterHour */ 1020) // .output(DELAY_CHARGE_STATE, DelayChargeState.AVOID_LOW_CHARGING) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, 6650) // .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, -6650) // - .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 0)); // 476 W below minimum + .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 0)) // 476 W below minimum + .deactivate(); } @Test @@ -765,11 +787,11 @@ public void manual_midday_test() throws Exception { final var sum = new DummySum(); final var predictorManager = new DummyPredictorManager( // Production - new DummyPredictor(PREDICTOR_ID, cm, + new DummyPredictor("predictor0", cm, Prediction.from(sum, SUM_PRODUCTION_ACTIVE_POWER, now, DEFAULT_PRODUCTION_PREDICTION), SUM_PRODUCTION_ACTIVE_POWER), // Consumption - new DummyPredictor(PREDICTOR_ID, cm, + new DummyPredictor("predictor0", cm, Prediction.from(sum, SUM_CONSUMPTION_ACTIVE_POWER, now, DEFAULT_CONSUMPTION_PREDICTION), SUM_CONSUMPTION_ACTIVE_POWER)); @@ -781,10 +803,10 @@ public void manual_midday_test() throws Exception { .addReference("meter", METER) // .addReference("sum", new DummySum()) // .activate(MyConfig.create() // - .setEssId(ESS_ID) // - .setId(CTRL_ID) // + .setEssId("ess0") // + .setId("ctrlGridOptimizedCharge0") // .setMaximumSellToGridPower(7_000) // - .setMeterId(METER_ID) // + .setMeterId("meter0") // .setDelayChargeRiskLevel(DelayChargeRiskLevel.MEDIUM) // .setMode(Mode.MANUAL) // .setSellToGridLimitEnabled(true) // @@ -792,17 +814,19 @@ public void manual_midday_test() throws Exception { .setSellToGridLimitRampPercentage(5) // .build()) // .next(new TestCase() // - .input(METER_ACTIVE_POWER, 0) // - .input(ESS_ACTIVE_POWER, 0) // - .input(ESS_CAPACITY, 10_000) // - .input(ESS_SOC, 20) // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 0) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) // + .input("ess0", CAPACITY, 10_000) // + .input("ess0", SOC, 20) // .input(START_EPOCH_SECONDS, 1630566000) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // .output(TARGET_MINUTE, /* QuarterHour */ 1020) // .output(DELAY_CHARGE_STATE, DelayChargeState.ACTIVE_LIMIT) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, 6650) // .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, -6650) // - .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 1620)); + .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 1620)) // + .deactivate(); } @Test @@ -813,11 +837,11 @@ public void hybridEss_manual_midday_test() throws Exception { final var sum = new DummySum(); final var predictorManager = new DummyPredictorManager( // Production - new DummyPredictor(PREDICTOR_ID, cm, + new DummyPredictor("predictor0", cm, Prediction.from(sum, SUM_PRODUCTION_ACTIVE_POWER, now, DEFAULT_PRODUCTION_PREDICTION), SUM_PRODUCTION_ACTIVE_POWER), // Consumption - new DummyPredictor(PREDICTOR_ID, cm, + new DummyPredictor("predictor0", cm, Prediction.from(sum, SUM_CONSUMPTION_ACTIVE_POWER, now, DEFAULT_CONSUMPTION_PREDICTION), SUM_CONSUMPTION_ACTIVE_POWER)); @@ -829,10 +853,10 @@ public void hybridEss_manual_midday_test() throws Exception { .addReference("meter", METER) // .addReference("sum", new DummySum()) // .activate(MyConfig.create() // - .setEssId(ESS_ID) // - .setId(CTRL_ID) // + .setEssId("ess0") // + .setId("ctrlGridOptimizedCharge0") // .setMaximumSellToGridPower(7_000) // - .setMeterId(METER_ID) // + .setMeterId("meter0") // .setDelayChargeRiskLevel(DelayChargeRiskLevel.MEDIUM) // .setMode(Mode.MANUAL) // .setSellToGridLimitEnabled(true) // @@ -840,18 +864,21 @@ public void hybridEss_manual_midday_test() throws Exception { .setSellToGridLimitRampPercentage(5) // .build()) // .next(new TestCase() // - .input(METER_ACTIVE_POWER, 0) // - .input(ESS_ACTIVE_POWER, 0) // - .input(ESS_CAPACITY, 10_000) // - .input(ESS_SOC, 20) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 0) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) // + .input("ess0", CAPACITY, 10_000) // + .input("ess0", SOC, 20) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // .input(START_EPOCH_SECONDS, 1630566000) // - .input(SUM_PRODUCTION_DC_ACTUAL_POWER, 10_000).output(TARGET_MINUTE, /* QuarterHour */ 1020) // + .input(PRODUCTION_DC_ACTUAL_POWER, 10_000) // + .output(TARGET_MINUTE, /* QuarterHour */ 1020) // .output(DELAY_CHARGE_STATE, DelayChargeState.NO_CHARGE_LIMIT) // .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 3350) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, 6650) // .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_FIXED) // - .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, null)); + .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, null)) // + .deactivate(); } @@ -863,11 +890,11 @@ public void mode_off_test() throws Exception { final var sum = new DummySum(); final var predictorManager = new DummyPredictorManager( // Production - new DummyPredictor(PREDICTOR_ID, cm, + new DummyPredictor("predictor0", cm, Prediction.from(sum, SUM_PRODUCTION_ACTIVE_POWER, now, DEFAULT_PRODUCTION_PREDICTION), SUM_PRODUCTION_ACTIVE_POWER), // Consumption - new DummyPredictor(PREDICTOR_ID, cm, + new DummyPredictor("predictor0", cm, Prediction.from(sum, SUM_CONSUMPTION_ACTIVE_POWER, now, DEFAULT_CONSUMPTION_PREDICTION), SUM_CONSUMPTION_ACTIVE_POWER)); @@ -879,10 +906,10 @@ public void mode_off_test() throws Exception { .addReference("meter", METER) // .addReference("sum", new DummySum()) // .activate(MyConfig.create() // - .setEssId(ESS_ID) // - .setId(CTRL_ID) // + .setEssId("ess0") // + .setId("ctrlGridOptimizedCharge0") // .setMaximumSellToGridPower(7_000) // - .setMeterId(METER_ID) // + .setMeterId("meter0") // .setDelayChargeRiskLevel(DelayChargeRiskLevel.MEDIUM) // .setMode(Mode.OFF) // .setSellToGridLimitEnabled(true) // @@ -890,16 +917,18 @@ public void mode_off_test() throws Exception { .setManualTargetTime("") // .build()) // .next(new TestCase() // - .input(METER_ACTIVE_POWER, -7500) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .input(ESS_ACTIVE_POWER, 0) // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -7500) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) // .input(START_EPOCH_SECONDS, 1630566000) // .output(DELAY_CHARGE_STATE, DelayChargeState.DISABLED) // .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT) // .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, null) // - .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -850) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -850) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -850) // - .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 850)); // + .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 850)) // + .deactivate(); } @Test @@ -910,11 +939,11 @@ public void no_capacity_left_test() throws Exception { final var sum = new DummySum(); final var predictorManager = new DummyPredictorManager( // Production - new DummyPredictor(PREDICTOR_ID, cm, + new DummyPredictor("predictor0", cm, Prediction.from(sum, SUM_PRODUCTION_ACTIVE_POWER, now, DEFAULT_PRODUCTION_PREDICTION), SUM_PRODUCTION_ACTIVE_POWER), // Consumption - new DummyPredictor(PREDICTOR_ID, cm, + new DummyPredictor("predictor0", cm, Prediction.from(sum, SUM_CONSUMPTION_ACTIVE_POWER, now, DEFAULT_CONSUMPTION_PREDICTION), SUM_CONSUMPTION_ACTIVE_POWER)); @@ -926,10 +955,10 @@ public void no_capacity_left_test() throws Exception { .addReference("meter", METER) // .addReference("sum", new DummySum()) // .activate(MyConfig.create() // - .setEssId(ESS_ID) // - .setId(CTRL_ID) // + .setEssId("ess0") // + .setId("ctrlGridOptimizedCharge0") // .setMaximumSellToGridPower(7_000) // - .setMeterId(METER_ID) // + .setMeterId("meter0") // .setDelayChargeRiskLevel(DelayChargeRiskLevel.MEDIUM) // .setMode(Mode.AUTOMATIC) // .setSellToGridLimitEnabled(true) // @@ -937,18 +966,20 @@ public void no_capacity_left_test() throws Exception { .setManualTargetTime("") // .build()) // .next(new TestCase() // - .input(METER_ACTIVE_POWER, 0) // - .input(ESS_ACTIVE_POWER, 0) // - .input(ESS_CAPACITY, 10_000) // - .input(ESS_SOC, 99) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 0) // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) // + .input("ess0", CAPACITY, 10_000) // + .input("ess0", SOC, 99) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // .input(START_EPOCH_SECONDS, 1630566000) // // ess.getPower().getMinPower() (Maximum allowed charge power) is '0' because // the referenced // DummyManagedSymmetricEss has an apparent power of zero. .output(DELAY_CHARGE_STATE, DelayChargeState.NO_REMAINING_CAPACITY) // - .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, null)); // + .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, null)) // + .deactivate(); } @Test @@ -964,11 +995,11 @@ public void start_production_not_enough_test() throws Exception { final var sum = new DummySum(); final var predictorManager = new DummyPredictorManager( // Production - new DummyPredictor(PREDICTOR_ID, cm, + new DummyPredictor("predictor0", cm, Prediction.from(sum, SUM_PRODUCTION_ACTIVE_POWER, now, DEFAULT_PRODUCTION_PREDICTION), SUM_PRODUCTION_ACTIVE_POWER), // Consumption - new DummyPredictor(PREDICTOR_ID, cm, + new DummyPredictor("predictor0", cm, Prediction.from(sum, SUM_CONSUMPTION_ACTIVE_POWER, now, DEFAULT_CONSUMPTION_PREDICTION), SUM_CONSUMPTION_ACTIVE_POWER)); @@ -980,10 +1011,10 @@ public void start_production_not_enough_test() throws Exception { .addReference("meter", METER) // .addReference("sum", new DummySum()) // .activate(MyConfig.create() // - .setEssId(ESS_ID) // - .setId(CTRL_ID) // + .setEssId("ess0") // + .setId("ctrlGridOptimizedCharge0") // .setMaximumSellToGridPower(7_000) // - .setMeterId(METER_ID) // + .setMeterId("meter0") // .setDelayChargeRiskLevel(DelayChargeRiskLevel.MEDIUM) // .setMode(Mode.OFF) // .setSellToGridLimitEnabled(true) // @@ -1010,7 +1041,8 @@ public void start_production_not_enough_test() throws Exception { .input(SUM_CONSUMPTION_ACTIVE_POWER, 6000) // .output(DELAY_CHARGE_STATE, DelayChargeState.NOT_STARTED) // .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.NOT_STARTED) // - .output(START_EPOCH_SECONDS, null)); // + .output(START_EPOCH_SECONDS, null)) // + .deactivate(); } @Test @@ -1026,11 +1058,11 @@ public void start_production_average_test() throws Exception { final var sum = new DummySum(); final var predictorManager = new DummyPredictorManager( // Production - new DummyPredictor(PREDICTOR_ID, cm, + new DummyPredictor("predictor0", cm, Prediction.from(sum, SUM_PRODUCTION_ACTIVE_POWER, now, DEFAULT_PRODUCTION_PREDICTION), SUM_PRODUCTION_ACTIVE_POWER), // Consumption - new DummyPredictor(PREDICTOR_ID, cm, + new DummyPredictor("predictor0", cm, Prediction.from(sum, SUM_CONSUMPTION_ACTIVE_POWER, now, DEFAULT_CONSUMPTION_PREDICTION), SUM_CONSUMPTION_ACTIVE_POWER)); @@ -1042,10 +1074,10 @@ public void start_production_average_test() throws Exception { .addReference("meter", METER) // .addReference("sum", new DummySum()) // .activate(MyConfig.create() // - .setEssId(ESS_ID) // - .setId(CTRL_ID) // + .setEssId("ess0") // + .setId("ctrlGridOptimizedCharge0") // .setMaximumSellToGridPower(7_000) // - .setMeterId(METER_ID) // + .setMeterId("meter0") // .setDelayChargeRiskLevel(DelayChargeRiskLevel.MEDIUM) // .setMode(Mode.MANUAL) // .setSellToGridLimitEnabled(true) // @@ -1151,11 +1183,11 @@ public void start_production_average_test() throws Exception { .input(SUM_PRODUCTION_ACTIVE_POWER, 2000) // Avg: 1166 .input(SUM_CONSUMPTION_ACTIVE_POWER, 1000) // .input(START_EPOCH_SECONDS, null) // - .input(METER_ACTIVE_POWER, 0) // - .input(ESS_ACTIVE_POWER, 0) // - .input(ESS_CAPACITY, 10_000) // - .input(ESS_SOC, 20) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 0) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) // + .input("ess0", CAPACITY, 10_000) // + .input("ess0", SOC, 20) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // // Epoch seconds at 2020-01-01 00:00:00: 1577836800 (Clock is not updated) .output(START_EPOCH_SECONDS, 1577836800L) // @@ -1163,12 +1195,12 @@ public void start_production_average_test() throws Exception { .output(DELAY_CHARGE_STATE, DelayChargeState.AVOID_LOW_CHARGING) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, 6650) // .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, -6650) // - .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 0)); // 506 W is not efficient + .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 0)) // 506 W is not efficient + .deactivate(); } @Test public void getCalculatedPowerLimit_middayTest() throws Exception { - /* * Initial values */ @@ -1412,80 +1444,6 @@ private void testLogic(String description, Integer[] productionPrediction, Integ }); } - private DelayChargeResultState testOneDay(String testDescription, Integer[] productionPrediction, - Integer[] consumptionPrediction, int soc, Optional targetMinuteOpt, int capacity, - int maxApparentPower, int allowedChargePower, DelayChargeRiskLevel riskLevel, Integer[] productionActual, - Integer[] consumptionActual, float resultBuffer) { - DelayChargeResultState resultState; - DelayChargeResult newLogic = ControllerEssGridOptimizedChargeImplTest.testOneDay(testDescription, - productionPrediction, consumptionPrediction, soc, targetMinuteOpt, capacity, maxApparentPower, - allowedChargePower, riskLevel, productionActual, consumptionActual, false); - - DelayChargeResult oldLogic = ControllerEssGridOptimizedChargeImplTest.testOneDay(testDescription, - productionPrediction, consumptionPrediction, soc, targetMinuteOpt, capacity, maxApparentPower, - allowedChargePower, riskLevel, productionActual, consumptionActual, true); - - if (newLogic.getFinalSoc() + resultBuffer < oldLogic.getFinalSoc()) { - resultState = DelayChargeResultState.WARNING; - } else if (newLogic.getFinalSoc() - resultBuffer > oldLogic.getFinalSoc()) { - resultState = DelayChargeResultState.PERFECT; - } else { - resultState = DelayChargeResultState.OK; - } - - float unefficientEnergy = Math - .round(newLogic.getChargedEnergyWithLowPower() / newLogic.getChargedEnergy() * 1000) / 10.0f; - float unefficientEnergyOld = Math - .round(oldLogic.getChargedEnergyWithLowPower() / oldLogic.getChargedEnergy() * 1000) / 10.0f; - System.out.println(resultState.text + "\t" + testDescription + " \t(New: " - + Math.round(newLogic.getFinalSoc() * 100) / 100.0 + " | Old: " - + Math.round(oldLogic.getFinalSoc() * 100) / 100.0 + ") \t Energy: (New: " - + newLogic.getChargedEnergy() + "[" + newLogic.getChargedEnergyWithLowPower() + " -> " - + unefficientEnergy + "%] | Old: " + oldLogic.getChargedEnergy() + "[" - + oldLogic.getChargedEnergyWithLowPower() + " -> " + unefficientEnergyOld + "%])"); - - // fail("New logic results in a lower SoC (New: " + newLogic.getFinalSoc() + " | - // Old: "+ oldLogic.getFinalSoc() + ") - " + testDescription); - return resultState; - } - - private static class DelayChargeResult { - - private float finalSoc; - private float chargedEnergy; - private float chargedEnergyWithLowPower; - - public DelayChargeResult(float finalSoc, float chargedEnergy, float chargedEnergyWithLowPower) { - this.finalSoc = finalSoc; - this.chargedEnergy = chargedEnergy; - this.chargedEnergyWithLowPower = chargedEnergyWithLowPower; - } - - public float getFinalSoc() { - return this.finalSoc; - } - - public float getChargedEnergy() { - return this.chargedEnergy; - } - - public float getChargedEnergyWithLowPower() { - return this.chargedEnergyWithLowPower; - } - } - - private static enum DelayChargeResultState { - OK("OK - SoC as bevore"), // - WARNING("WARNING - Lower SoC"), // - PERFECT("PERFECT - Higher SoC"); - - private String text; - - DelayChargeResultState(String text) { - this.text = text; - } - } - @SuppressWarnings("deprecation") private static DelayChargeResult testOneDay(String testDescription, Integer[] productionPrediction, Integer[] consumptionPrediction, int soc, Optional targetMinuteOpt, int capacity, @@ -1626,6 +1584,80 @@ private static DelayChargeResult testOneDay(String testDescription, Integer[] pr return new DelayChargeResult(socFloat, totoalActivePower * 0.25f, totoalActivePowerLessEfficiency * 0.25f); } + private DelayChargeResultState testOneDay(String testDescription, Integer[] productionPrediction, + Integer[] consumptionPrediction, int soc, Optional targetMinuteOpt, int capacity, + int maxApparentPower, int allowedChargePower, DelayChargeRiskLevel riskLevel, Integer[] productionActual, + Integer[] consumptionActual, float resultBuffer) { + DelayChargeResultState resultState; + DelayChargeResult newLogic = ControllerEssGridOptimizedChargeImplTest.testOneDay(testDescription, + productionPrediction, consumptionPrediction, soc, targetMinuteOpt, capacity, maxApparentPower, + allowedChargePower, riskLevel, productionActual, consumptionActual, false); + + DelayChargeResult oldLogic = ControllerEssGridOptimizedChargeImplTest.testOneDay(testDescription, + productionPrediction, consumptionPrediction, soc, targetMinuteOpt, capacity, maxApparentPower, + allowedChargePower, riskLevel, productionActual, consumptionActual, true); + + if (newLogic.getFinalSoc() + resultBuffer < oldLogic.getFinalSoc()) { + resultState = DelayChargeResultState.WARNING; + } else if (newLogic.getFinalSoc() - resultBuffer > oldLogic.getFinalSoc()) { + resultState = DelayChargeResultState.PERFECT; + } else { + resultState = DelayChargeResultState.OK; + } + + float unefficientEnergy = Math + .round(newLogic.getChargedEnergyWithLowPower() / newLogic.getChargedEnergy() * 1000) / 10.0f; + float unefficientEnergyOld = Math + .round(oldLogic.getChargedEnergyWithLowPower() / oldLogic.getChargedEnergy() * 1000) / 10.0f; + System.out.println(resultState.text + "\t" + testDescription + " \t(New: " + + Math.round(newLogic.getFinalSoc() * 100) / 100.0 + " | Old: " + + Math.round(oldLogic.getFinalSoc() * 100) / 100.0 + ") \t Energy: (New: " + + newLogic.getChargedEnergy() + "[" + newLogic.getChargedEnergyWithLowPower() + " -> " + + unefficientEnergy + "%] | Old: " + oldLogic.getChargedEnergy() + "[" + + oldLogic.getChargedEnergyWithLowPower() + " -> " + unefficientEnergyOld + "%])"); + + // fail("New logic results in a lower SoC (New: " + newLogic.getFinalSoc() + " | + // Old: "+ oldLogic.getFinalSoc() + ") - " + testDescription); + return resultState; + } + + private static class DelayChargeResult { + + private float finalSoc; + private float chargedEnergy; + private float chargedEnergyWithLowPower; + + public DelayChargeResult(float finalSoc, float chargedEnergy, float chargedEnergyWithLowPower) { + this.finalSoc = finalSoc; + this.chargedEnergy = chargedEnergy; + this.chargedEnergyWithLowPower = chargedEnergyWithLowPower; + } + + public float getFinalSoc() { + return this.finalSoc; + } + + public float getChargedEnergy() { + return this.chargedEnergy; + } + + public float getChargedEnergyWithLowPower() { + return this.chargedEnergyWithLowPower; + } + } + + private static enum DelayChargeResultState { + OK("OK - SoC as bevore"), // + WARNING("WARNING - Lower SoC"), // + PERFECT("PERFECT - Higher SoC"); + + private String text; + + DelayChargeResultState(String text) { + this.text = text; + } + } + @Test public void calculateAvailEnergy_test() throws Exception { final var clock = new TimeLeapClock(Instant.parse("2020-01-01T08:00:00.00Z"), ZoneOffset.UTC); @@ -1634,11 +1666,11 @@ public void calculateAvailEnergy_test() throws Exception { final var sum = new DummySum(); final var predictorManager = new DummyPredictorManager( // Production - new DummyPredictor(PREDICTOR_ID, cm, + new DummyPredictor("predictor0", cm, Prediction.from(sum, SUM_PRODUCTION_ACTIVE_POWER, midnight, DEFAULT_PRODUCTION_PREDICTION), SUM_PRODUCTION_ACTIVE_POWER), // Consumption - new DummyPredictor(PREDICTOR_ID, cm, + new DummyPredictor("predictor0", cm, Prediction.from(sum, SUM_CONSUMPTION_ACTIVE_POWER, midnight, DEFAULT_CONSUMPTION_PREDICTION), SUM_CONSUMPTION_ACTIVE_POWER)); diff --git a/io.openems.edge.controller.ess.gridoptimizedcharge/test/io/openems/edge/controller/ess/gridoptimizedcharge/EnergyScheduleHandlerTest.java b/io.openems.edge.controller.ess.gridoptimizedcharge/test/io/openems/edge/controller/ess/gridoptimizedcharge/EnergyScheduleHandlerTest.java new file mode 100644 index 00000000000..0f33e93c960 --- /dev/null +++ b/io.openems.edge.controller.ess.gridoptimizedcharge/test/io/openems/edge/controller/ess/gridoptimizedcharge/EnergyScheduleHandlerTest.java @@ -0,0 +1,40 @@ +package io.openems.edge.controller.ess.gridoptimizedcharge; + +import static org.junit.Assert.assertEquals; + +import java.time.LocalTime; + +import org.apache.commons.math3.optim.nonlinear.scalar.GoalType; +import org.junit.Test; + +import io.openems.edge.energy.api.EnergyScheduleHandler; +import io.openems.edge.energy.api.EnergyScheduleHandler.AbstractEnergyScheduleHandler; +import io.openems.edge.energy.api.simulation.Coefficient; +import io.openems.edge.energy.api.simulation.EnergyFlow; +import io.openems.edge.energy.api.simulation.GlobalSimulationsContext; +import io.openems.edge.energy.api.simulation.OneSimulationContext; +import io.openems.edge.energy.api.test.DummyGlobalSimulationsContext; + +public class EnergyScheduleHandlerTest { + + @Test + public void testManual() { + var esh = ControllerEssGridOptimizedChargeImpl.buildEnergyScheduleHandler(// + () -> Mode.MANUAL, // + () -> LocalTime.of(10, 00)); + var gsc = DummyGlobalSimulationsContext.fromHandlers(esh); + ((AbstractEnergyScheduleHandler) esh /* this is safe */).initialize(gsc); + + assertEquals(3894, getEssMaxCharge(gsc, esh, 0)); + assertEquals(1214, getEssMaxCharge(gsc, esh, 26)); + assertEquals(4000, getEssMaxCharge(gsc, esh, 40)); + } + + private static int getEssMaxCharge(GlobalSimulationsContext gsc, EnergyScheduleHandler esh, int periodIndex) { + var osc = OneSimulationContext.from(gsc); + var period = gsc.periods().get(periodIndex); + var ef = EnergyFlow.Model.from(osc, period); + ((EnergyScheduleHandler.WithOnlyOneState) esh).simulatePeriod(OneSimulationContext.from(gsc), period, ef); + return ((int) ef.getExtremeCoefficientValue(Coefficient.ESS, GoalType.MINIMIZE)) * -1; + } +} diff --git a/io.openems.edge.controller.ess.hybrid.surplusfeedtogrid/.classpath b/io.openems.edge.controller.ess.hybrid.surplusfeedtogrid/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.controller.ess.hybrid.surplusfeedtogrid/.classpath +++ b/io.openems.edge.controller.ess.hybrid.surplusfeedtogrid/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.controller.ess.hybrid.surplusfeedtogrid/test/io/openems/edge/controller/ess/hybrid/surplusfeedtogrid/ControllerEssHybridSurplusFeedToGridImplTest.java b/io.openems.edge.controller.ess.hybrid.surplusfeedtogrid/test/io/openems/edge/controller/ess/hybrid/surplusfeedtogrid/ControllerEssHybridSurplusFeedToGridImplTest.java index de89c02eda6..edb1435b1e8 100644 --- a/io.openems.edge.controller.ess.hybrid.surplusfeedtogrid/test/io/openems/edge/controller/ess/hybrid/surplusfeedtogrid/ControllerEssHybridSurplusFeedToGridImplTest.java +++ b/io.openems.edge.controller.ess.hybrid.surplusfeedtogrid/test/io/openems/edge/controller/ess/hybrid/surplusfeedtogrid/ControllerEssHybridSurplusFeedToGridImplTest.java @@ -1,8 +1,10 @@ package io.openems.edge.controller.ess.hybrid.surplusfeedtogrid; +import static io.openems.edge.controller.ess.hybrid.surplusfeedtogrid.ControllerEssHybridSurplusFeedToGrid.ChannelId.SURPLUS_FEED_TO_GRID_IS_LIMITED; +import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_GREATER_OR_EQUALS; + import org.junit.Test; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyConfigurationAdmin; import io.openems.edge.controller.test.ControllerTest; @@ -10,41 +12,33 @@ public class ControllerEssHybridSurplusFeedToGridImplTest { - private static final String CTRL_ID = "ctrl0"; - - private static final ChannelAddress CTRL_SURPLUS_FEED_TO_GRID_IS_LIMITED = new ChannelAddress(CTRL_ID, - "SurplusFeedToGridIsLimited"); - - private static final String ESS_ID = "ess0"; - - private static final ChannelAddress ESS_SET_ACTIVE_POWER_GREATER_OR_EQUALS = new ChannelAddress(ESS_ID, - "SetActivePowerGreaterOrEquals"); - @Test public void test() throws Exception { - final var ess = new DummyHybridEss(ESS_ID); + final var ess = new DummyHybridEss("ess0"); final var test = new ControllerTest(new ControllerEssHybridSurplusFeedToGridImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("ess", ess) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .build()); ess.withSurplusPower(null); test.next(new TestCase() // - .output(ESS_SET_ACTIVE_POWER_GREATER_OR_EQUALS, null)); + .output("ess0", SET_ACTIVE_POWER_GREATER_OR_EQUALS, null)); ess.withSurplusPower(5000); ess.withMaxApparentPower(10000); test.next(new TestCase() // - .output(CTRL_SURPLUS_FEED_TO_GRID_IS_LIMITED, false) // - .output(ESS_SET_ACTIVE_POWER_GREATER_OR_EQUALS, 5000)); + .output(SURPLUS_FEED_TO_GRID_IS_LIMITED, false) // + .output("ess0", SET_ACTIVE_POWER_GREATER_OR_EQUALS, 5000)); ess.withSurplusPower(5000); ess.withMaxApparentPower(2000); test.next(new TestCase() // - .output(CTRL_SURPLUS_FEED_TO_GRID_IS_LIMITED, true) // - .output(ESS_SET_ACTIVE_POWER_GREATER_OR_EQUALS, 2000)); + .output(SURPLUS_FEED_TO_GRID_IS_LIMITED, true) // + .output("ess0", SET_ACTIVE_POWER_GREATER_OR_EQUALS, 2000)) // + + .deactivate(); } } \ No newline at end of file diff --git a/io.openems.edge.controller.ess.limiter14a/.classpath b/io.openems.edge.controller.ess.limiter14a/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.controller.ess.limiter14a/.classpath +++ b/io.openems.edge.controller.ess.limiter14a/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.controller.ess.limiter14a/src/io/openems/edge/controller/ess/limiter14a/Config.java b/io.openems.edge.controller.ess.limiter14a/src/io/openems/edge/controller/ess/limiter14a/Config.java index 69f598db15b..c2768e430d8 100644 --- a/io.openems.edge.controller.ess.limiter14a/src/io/openems/edge/controller/ess/limiter14a/Config.java +++ b/io.openems.edge.controller.ess.limiter14a/src/io/openems/edge/controller/ess/limiter14a/Config.java @@ -16,7 +16,7 @@ @AttributeDefinition(name = "Is enabled?", description = "Is this Component enabled?") boolean enabled() default true; - + @AttributeDefinition(name = "Ess-ID", description = "ID of Ess.") String ess_id() default "ess0"; diff --git a/io.openems.edge.controller.ess.limiter14a/src/io/openems/edge/controller/ess/limiter14a/ControllerEssLimiter14a.java b/io.openems.edge.controller.ess.limiter14a/src/io/openems/edge/controller/ess/limiter14a/ControllerEssLimiter14a.java index 1a517ebf33c..9a5ce279253 100644 --- a/io.openems.edge.controller.ess.limiter14a/src/io/openems/edge/controller/ess/limiter14a/ControllerEssLimiter14a.java +++ b/io.openems.edge.controller.ess.limiter14a/src/io/openems/edge/controller/ess/limiter14a/ControllerEssLimiter14a.java @@ -1,8 +1,10 @@ package io.openems.edge.controller.ess.limiter14a; -import io.openems.common.channel.PersistencePriority; +import static io.openems.common.channel.PersistencePriority.HIGH; +import static io.openems.common.types.OpenemsType.BOOLEAN; +import static io.openems.common.types.OpenemsType.LONG; + import io.openems.common.channel.Unit; -import io.openems.common.types.OpenemsType; import io.openems.edge.common.channel.Channel; import io.openems.edge.common.channel.Doc; import io.openems.edge.common.channel.value.Value; @@ -11,15 +13,20 @@ public interface ControllerEssLimiter14a extends Controller, OpenemsComponent { + /** + * If RESTRICTION_MODE is true, ESS charge power is limited to 4.2 kW. + */ + public static final int ESS_LIMIT_14A_ENWG = -4200; + public enum ChannelId implements io.openems.edge.common.channel.ChannelId { - RESTRICTION_MODE(Doc.of(RestrictionMode.values()) // - .persistencePriority(PersistencePriority.HIGH)), // - - CUMULATED_RESTRICTION_TIME(Doc.of(OpenemsType.LONG) // + RESTRICTION_MODE(Doc.of(BOOLEAN) // + .persistencePriority(HIGH)), // + + CUMULATED_RESTRICTION_TIME(Doc.of(LONG) // .unit(Unit.CUMULATED_SECONDS) // - .persistencePriority(PersistencePriority.HIGH)); // - + .persistencePriority(HIGH)); // + private final Doc doc; private ChannelId(Doc doc) { @@ -50,15 +57,4 @@ public default Channel getRestrictionModeChannel() { public default Boolean getRestrictionMode() { return this.getRestrictionModeChannel().value().get(); } - - /** - * Internal method to set the 'nextValue' on {@link ChannelId#RESTRICTION_MODE} - * Channel. - * - * @param value the next value - */ - public default void _setRestrictionMode(boolean value) { - this.getRestrictionModeChannel().setNextValue(value); - } - } diff --git a/io.openems.edge.controller.ess.limiter14a/src/io/openems/edge/controller/ess/limiter14a/ControllerEssLimiter14aImpl.java b/io.openems.edge.controller.ess.limiter14a/src/io/openems/edge/controller/ess/limiter14a/ControllerEssLimiter14aImpl.java index a9c1082637f..138acdf6d82 100644 --- a/io.openems.edge.controller.ess.limiter14a/src/io/openems/edge/controller/ess/limiter14a/ControllerEssLimiter14aImpl.java +++ b/io.openems.edge.controller.ess.limiter14a/src/io/openems/edge/controller/ess/limiter14a/ControllerEssLimiter14aImpl.java @@ -11,6 +11,7 @@ import org.osgi.service.component.annotations.ReferencePolicy; import org.osgi.service.component.annotations.ReferencePolicyOption; import org.osgi.service.metatype.annotations.Designate; + import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.types.ChannelAddress; import io.openems.edge.common.channel.BooleanReadChannel; @@ -32,11 +33,11 @@ configurationPolicy = ConfigurationPolicy.REQUIRE // ) public class ControllerEssLimiter14aImpl extends AbstractOpenemsComponent implements // - ControllerEssLimiter14a, Controller, OpenemsComponent, TimedataProvider { - + ControllerEssLimiter14a, Controller, OpenemsComponent, TimedataProvider { + @Reference private Sum sum; - + @Reference(policy = ReferencePolicy.DYNAMIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.OPTIONAL) private volatile Timedata timedata = null; @@ -50,7 +51,7 @@ public class ControllerEssLimiter14aImpl extends AbstractOpenemsComponent implem private ManagedSymmetricEss ess; private ChannelAddress inputChannelAddress; - + private final CalculateActiveTime cumulatedRestrictionTime = new CalculateActiveTime(this, ControllerEssLimiter14a.ChannelId.CUMULATED_RESTRICTION_TIME); @@ -66,7 +67,7 @@ public ControllerEssLimiter14aImpl() { private void activate(ComponentContext context, Config config) throws OpenemsNamedException { super.activate(context, config.id(), config.alias(), config.enabled()); this.inputChannelAddress = ChannelAddress.fromString(config.inputChannelAddress()); - + if (OpenemsComponent.updateReferenceFilter(this.cm, this.servicePid(), "ess", config.ess_id())) { return; } @@ -84,9 +85,9 @@ public void run() throws OpenemsNamedException { // 0/1 is reversed on relays board var isActive = onGrid && !inputChannel.value().orElse(true); if (isActive) { - this.ess.setActivePowerGreaterOrEquals(-4200); + this.ess.setActivePowerGreaterOrEquals(ESS_LIMIT_14A_ENWG); } - + this.channel(ControllerEssLimiter14a.ChannelId.RESTRICTION_MODE).setNextValue(isActive); this.cumulatedRestrictionTime.update(isActive); } diff --git a/io.openems.edge.controller.ess.limiter14a/src/io/openems/edge/controller/ess/limiter14a/RestrictionMode.java b/io.openems.edge.controller.ess.limiter14a/src/io/openems/edge/controller/ess/limiter14a/RestrictionMode.java deleted file mode 100644 index 99858973ea1..00000000000 --- a/io.openems.edge.controller.ess.limiter14a/src/io/openems/edge/controller/ess/limiter14a/RestrictionMode.java +++ /dev/null @@ -1,32 +0,0 @@ -package io.openems.edge.controller.ess.limiter14a; - -import io.openems.common.types.OptionsEnum; - -public enum RestrictionMode implements OptionsEnum { - UNDEFINED(-1, "Undefined"), // - ON(1, "On"), // - OFF(0, "Off"); - - private int value; - private String name; - - private RestrictionMode(int value, String name) { - this.value = value; - this.name = name; - } - - @Override - public int getValue() { - return this.value; - } - - @Override - public String getName() { - return this.name; - } - - @Override - public OptionsEnum getUndefined() { - return UNDEFINED; - } -} \ No newline at end of file diff --git a/io.openems.edge.controller.ess.limiter14a/src/io/openems/edge/controller/ess/limiter14a/package-info.java b/io.openems.edge.controller.ess.limiter14a/src/io/openems/edge/controller/ess/limiter14a/package-info.java new file mode 100644 index 00000000000..5193664e933 --- /dev/null +++ b/io.openems.edge.controller.ess.limiter14a/src/io/openems/edge/controller/ess/limiter14a/package-info.java @@ -0,0 +1,3 @@ +@org.osgi.annotation.versioning.Version("1.0.0") +@org.osgi.annotation.bundle.Export +package io.openems.edge.controller.ess.limiter14a; diff --git a/io.openems.edge.controller.ess.limiter14a/test/io/openems/edge/controller/ess/limiter14a/ControllerEssLimiter14aTest.java b/io.openems.edge.controller.ess.limiter14a/test/io/openems/edge/controller/ess/limiter14a/ControllerEssLimiter14aTest.java index 22405060dc7..eff3dea6dd6 100644 --- a/io.openems.edge.controller.ess.limiter14a/test/io/openems/edge/controller/ess/limiter14a/ControllerEssLimiter14aTest.java +++ b/io.openems.edge.controller.ess.limiter14a/test/io/openems/edge/controller/ess/limiter14a/ControllerEssLimiter14aTest.java @@ -1,61 +1,61 @@ package io.openems.edge.controller.ess.limiter14a; +import static io.openems.edge.common.sum.Sum.ChannelId.GRID_MODE; +import static io.openems.edge.controller.ess.limiter14a.ControllerEssLimiter14a.ChannelId.RESTRICTION_MODE; +import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_GREATER_OR_EQUALS; +import static io.openems.edge.io.test.DummyInputOutput.ChannelId.INPUT_OUTPUT0; +import static org.junit.Assert.assertEquals; + import org.junit.Test; import io.openems.common.exceptions.OpenemsException; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.sum.DummySum; import io.openems.edge.common.sum.GridMode; +import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyComponentManager; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.controller.test.ControllerTest; import io.openems.edge.ess.test.DummyManagedSymmetricEss; -import io.openems.edge.timedata.test.DummyTimedata; import io.openems.edge.io.test.DummyInputOutput; +import io.openems.edge.timedata.test.DummyTimedata; public class ControllerEssLimiter14aTest { - private static final String ESS_ID = "ess0"; - private static final String CTRL_ID = "ctrlEssLimiter14a0"; - - private static final ChannelAddress RESTRICTION_MODE = new ChannelAddress(CTRL_ID, "RestrictionMode"); - private static final ChannelAddress GPIO = new ChannelAddress("io0", "InputOutput0"); - private static final ChannelAddress LIMITATION = new ChannelAddress(ESS_ID, "SetActivePowerGreaterOrEquals"); - private static final ChannelAddress GRID_MODE = new ChannelAddress("_sum", "GridMode"); - @Test public void testController() throws OpenemsException, Exception { - new ControllerTest(new ControllerEssLimiter14aImpl()) // + var sut = new ControllerEssLimiter14aImpl(); + new ControllerTest(sut) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // .addReference("timedata", new DummyTimedata("timedata0")) // - .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) // + .addReference("ess", new DummyManagedSymmetricEss("ess0")) // .addReference("sum", new DummySum()) // .addComponent(new DummyInputOutput("io0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID)// + .setId("ctrl0") // + .setEssId("ess0")// .setInputChannelAddress("io0/InputOutput0")// .build()) .next(new TestCase() // // Since logic is reversed - .input(GPIO, false) // - .input(GRID_MODE, GridMode.ON_GRID) - .output(LIMITATION, -4200) - .output(RESTRICTION_MODE, RestrictionMode.ON)) // + .input("io0", INPUT_OUTPUT0, false) // + .input(GRID_MODE, GridMode.ON_GRID) // + .output("ess0", SET_ACTIVE_POWER_GREATER_OR_EQUALS, -4200) // + .output(RESTRICTION_MODE, true)) // .next(new TestCase() // - .input(GPIO, null) // - .output(LIMITATION, null)) // + .input("io0", INPUT_OUTPUT0, null) // + .output("ess0", SET_ACTIVE_POWER_GREATER_OR_EQUALS, null)) // .next(new TestCase() // - .input(GPIO, 1) // + .input("io0", INPUT_OUTPUT0, 1) // .input(GRID_MODE, GridMode.OFF_GRID) // - .output(RESTRICTION_MODE, RestrictionMode.OFF)) // + .output(RESTRICTION_MODE, false)) // .next(new TestCase() // - .input(GPIO, false) // + .input("io0", INPUT_OUTPUT0, false) // .input(GRID_MODE, GridMode.OFF_GRID) // - .output(LIMITATION, null)) // - ; + .output("ess0", SET_ACTIVE_POWER_GREATER_OR_EQUALS, null)) // + .deactivate(); + + assertEquals(false, sut.getRestrictionMode()); } } diff --git a/io.openems.edge.controller.ess.limiter14a/test/io/openems/edge/controller/ess/limiter14a/MyConfig.java b/io.openems.edge.controller.ess.limiter14a/test/io/openems/edge/controller/ess/limiter14a/MyConfig.java index c0c2a3ddab8..028564e3ea1 100644 --- a/io.openems.edge.controller.ess.limiter14a/test/io/openems/edge/controller/ess/limiter14a/MyConfig.java +++ b/io.openems.edge.controller.ess.limiter14a/test/io/openems/edge/controller/ess/limiter14a/MyConfig.java @@ -12,7 +12,7 @@ protected static class Builder { private Builder() { } - + public Builder setInputChannelAddress(String inputChannelAddress) { this.inputChannelAddress = inputChannelAddress; return this; @@ -22,7 +22,7 @@ public Builder setId(String id) { this.id = id; return this; } - + public Builder setEssId(String id) { this.essId = id; return this; diff --git a/io.openems.edge.controller.ess.limittotaldischarge/.classpath b/io.openems.edge.controller.ess.limittotaldischarge/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.controller.ess.limittotaldischarge/.classpath +++ b/io.openems.edge.controller.ess.limittotaldischarge/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.controller.ess.limittotaldischarge/bnd.bnd b/io.openems.edge.controller.ess.limittotaldischarge/bnd.bnd index 0f4d0e0a95c..db75c019243 100644 --- a/io.openems.edge.controller.ess.limittotaldischarge/bnd.bnd +++ b/io.openems.edge.controller.ess.limittotaldischarge/bnd.bnd @@ -8,7 +8,8 @@ Bundle-Version: 1.0.0.${tstamp} io.openems.common,\ io.openems.edge.common,\ io.openems.edge.controller.api,\ - io.openems.edge.ess.api + io.openems.edge.energy.api,\ + io.openems.edge.ess.api,\ -testpath: \ ${testpath} diff --git a/io.openems.edge.controller.ess.limittotaldischarge/src/io/openems/edge/controller/ess/limittotaldischarge/ControllerEssLimitTotalDischargeImpl.java b/io.openems.edge.controller.ess.limittotaldischarge/src/io/openems/edge/controller/ess/limittotaldischarge/ControllerEssLimitTotalDischargeImpl.java index a75ecf85f1b..371543c895a 100644 --- a/io.openems.edge.controller.ess.limittotaldischarge/src/io/openems/edge/controller/ess/limittotaldischarge/ControllerEssLimitTotalDischargeImpl.java +++ b/io.openems.edge.controller.ess.limittotaldischarge/src/io/openems/edge/controller/ess/limittotaldischarge/ControllerEssLimitTotalDischargeImpl.java @@ -1,8 +1,12 @@ package io.openems.edge.controller.ess.limittotaldischarge; +import static io.openems.edge.energy.api.EnergyUtils.socToEnergy; +import static java.lang.Math.max; + import java.time.Duration; import java.time.Instant; import java.util.Optional; +import java.util.function.IntSupplier; import org.osgi.service.component.ComponentContext; import org.osgi.service.component.annotations.Activate; @@ -19,6 +23,8 @@ import io.openems.edge.common.component.ComponentManager; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.controller.api.Controller; +import io.openems.edge.energy.api.EnergySchedulable; +import io.openems.edge.energy.api.EnergyScheduleHandler; import io.openems.edge.ess.api.ManagedSymmetricEss; import io.openems.edge.ess.power.api.Phase; import io.openems.edge.ess.power.api.Pwr; @@ -30,9 +36,10 @@ configurationPolicy = ConfigurationPolicy.REQUIRE // ) public class ControllerEssLimitTotalDischargeImpl extends AbstractOpenemsComponent - implements ControllerEssLimitTotalDischarge, Controller, OpenemsComponent { + implements ControllerEssLimitTotalDischarge, EnergySchedulable, Controller, OpenemsComponent { private final Logger log = LoggerFactory.getLogger(ControllerEssLimitTotalDischargeImpl.class); + private final EnergyScheduleHandler energyScheduleHandler; @Reference private ComponentManager componentManager; @@ -55,6 +62,8 @@ public ControllerEssLimitTotalDischargeImpl() { Controller.ChannelId.values(), // ControllerEssLimitTotalDischarge.ChannelId.values() // ); + this.energyScheduleHandler = buildEnergyScheduleHandler(// + () -> this.minSoc); } @Activate @@ -211,4 +220,28 @@ private boolean changeState(State nextState) { return false; } } + + /** + * Builds the {@link EnergyScheduleHandler}. + * + *

+ * This is public so that it can be used by the EnergyScheduler integration + * test. + * + * @param minSoc a supplier for the configured minSoc + * @return a {@link EnergyScheduleHandler} + */ + public static EnergyScheduleHandler buildEnergyScheduleHandler(IntSupplier minSoc) { + return EnergyScheduleHandler.WithOnlyOneState.create() // + .setContextFunction(simContext -> socToEnergy(simContext.ess().totalEnergy(), minSoc.getAsInt())) // + .setSimulator((simContext, period, energyFlow, minEnergy) -> { + energyFlow.setEssMaxDischarge(max(0, simContext.ess.getInitialEnergy() - minEnergy)); + }) // + .build(); + } + + @Override + public EnergyScheduleHandler getEnergyScheduleHandler() { + return this.energyScheduleHandler; + } } diff --git a/io.openems.edge.controller.ess.limittotaldischarge/test/io/openems/edge/controller/ess/limittotaldischarge/ControllerEssLimitTotalDischargeImplTest.java b/io.openems.edge.controller.ess.limittotaldischarge/test/io/openems/edge/controller/ess/limittotaldischarge/ControllerEssLimitTotalDischargeImplTest.java index d86836ed672..acc57399ef6 100644 --- a/io.openems.edge.controller.ess.limittotaldischarge/test/io/openems/edge/controller/ess/limittotaldischarge/ControllerEssLimitTotalDischargeImplTest.java +++ b/io.openems.edge.controller.ess.limittotaldischarge/test/io/openems/edge/controller/ess/limittotaldischarge/ControllerEssLimitTotalDischargeImplTest.java @@ -1,11 +1,14 @@ package io.openems.edge.controller.ess.limittotaldischarge; +import static io.openems.common.test.TestUtils.createDummyClock; +import static io.openems.edge.controller.ess.limittotaldischarge.ControllerEssLimitTotalDischarge.ChannelId.AWAITING_HYSTERESIS; +import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_LESS_OR_EQUALS; +import static io.openems.edge.ess.api.SymmetricEss.ChannelId.SOC; + import java.time.temporal.ChronoUnit; import org.junit.Test; -import io.openems.common.test.TimeLeapClock; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyComponentManager; import io.openems.edge.controller.test.ControllerTest; @@ -13,47 +16,39 @@ public class ControllerEssLimitTotalDischargeImplTest { - private static final String CTRL_ID = "ctrl0"; - private static final ChannelAddress CTRL_AWAITING_HYSTERESIS = new ChannelAddress(CTRL_ID, "AwaitingHysteresis"); - - private static final String ESS_ID = "ess0"; - private static final ChannelAddress ESS_SOC = new ChannelAddress(ESS_ID, "Soc"); - private static final ChannelAddress ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS = new ChannelAddress(ESS_ID, - "SetActivePowerLessOrEquals"); - @Test public void test() throws Exception { - // Initialize mocked Clock - final var clock = new TimeLeapClock(); + final var clock = createDummyClock(); new ControllerTest(new ControllerEssLimitTotalDischargeImpl()) // .addReference("componentManager", new DummyComponentManager(clock)) // - .addComponent(new DummyManagedSymmetricEss(ESS_ID) // + .addComponent(new DummyManagedSymmetricEss("ess0") // .withSoc(20) // .withCapacity(9000)) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setMinSoc(15) // .setForceChargeSoc(10) // .setForceChargePower(1000) // .build()) .next(new TestCase() // - .input(ESS_SOC, 20) // - .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, null)// - .output(CTRL_AWAITING_HYSTERESIS, false)) // + .input("ess0", SOC, 20) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, null)// + .output(AWAITING_HYSTERESIS, false)) // .next(new TestCase() // - .input(ESS_SOC, 15) // - .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, 0) // - .output(CTRL_AWAITING_HYSTERESIS, false)) // + .input("ess0", SOC, 15) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 0) // + .output(AWAITING_HYSTERESIS, false)) // .next(new TestCase() // - .input(ESS_SOC, 16) // - .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, 0) // - .output(CTRL_AWAITING_HYSTERESIS, true)) // + .input("ess0", SOC, 16) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 0) // + .output(AWAITING_HYSTERESIS, true)) // .next(new TestCase() // .timeleap(clock, 6, ChronoUnit.MINUTES) // - .input(ESS_SOC, 16) // - .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, null) // - .output(CTRL_AWAITING_HYSTERESIS, false)); + .input("ess0", SOC, 16) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, null) // + .output(AWAITING_HYSTERESIS, false)) // + .deactivate(); } } diff --git a/io.openems.edge.controller.ess.linearpowerband/.classpath b/io.openems.edge.controller.ess.linearpowerband/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.controller.ess.linearpowerband/.classpath +++ b/io.openems.edge.controller.ess.linearpowerband/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.controller.ess.linearpowerband/test/io/openems/edge/controller/ess/linearpowerband/ControllerEssLinearPowerBandImplTest.java b/io.openems.edge.controller.ess.linearpowerband/test/io/openems/edge/controller/ess/linearpowerband/ControllerEssLinearPowerBandImplTest.java index 676304b3b6e..6e85c146ed7 100644 --- a/io.openems.edge.controller.ess.linearpowerband/test/io/openems/edge/controller/ess/linearpowerband/ControllerEssLinearPowerBandImplTest.java +++ b/io.openems.edge.controller.ess.linearpowerband/test/io/openems/edge/controller/ess/linearpowerband/ControllerEssLinearPowerBandImplTest.java @@ -1,8 +1,10 @@ package io.openems.edge.controller.ess.linearpowerband; +import static io.openems.edge.controller.ess.linearpowerband.ControllerEssLinearPowerBand.ChannelId.STATE_MACHINE; +import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_EQUALS; + import org.junit.Test; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyConfigurationAdmin; import io.openems.edge.controller.test.ControllerTest; @@ -10,21 +12,14 @@ public class ControllerEssLinearPowerBandImplTest { - private static final String CTRL_ID = "ctrl0"; - private static final String ESS_ID = "ess0"; - - private static final ChannelAddress STATE_MACHINE = new ChannelAddress(CTRL_ID, "StateMachine"); - private static final ChannelAddress ESS_SET_ACTIVE_POWER_EQUALS = new ChannelAddress(ESS_ID, - "SetActivePowerEquals"); - @Test public void test() throws Exception { new ControllerTest(new ControllerEssLinearPowerBandImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) // + .addReference("ess", new DummyManagedSymmetricEss("ess0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setMinPower(-1000) // .setMaxPower(1000) // .setAdjustPower(300) // @@ -32,49 +27,49 @@ public void test() throws Exception { .build()) // .next(new TestCase() // .output(STATE_MACHINE, State.DOWNWARDS) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -300)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -300)) // .next(new TestCase() // .output(STATE_MACHINE, State.DOWNWARDS) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -600)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -600)) // .next(new TestCase() // .output(STATE_MACHINE, State.DOWNWARDS) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -900)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -900)) // .next(new TestCase() // .output(STATE_MACHINE, State.DOWNWARDS) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -1000)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -1000)) // .next(new TestCase() // .output(STATE_MACHINE, State.UPWARDS) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -700)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -700)) // .next(new TestCase() // .output(STATE_MACHINE, State.UPWARDS) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -400)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -400)) // .next(new TestCase() // .output(STATE_MACHINE, State.UPWARDS) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -100)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -100)) // .next(new TestCase() // .output(STATE_MACHINE, State.UPWARDS) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 200)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 200)) // .next(new TestCase() // .output(STATE_MACHINE, State.UPWARDS) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 500)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 500)) // .next(new TestCase() // .output(STATE_MACHINE, State.UPWARDS) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 800)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 800)) // .next(new TestCase() // .output(STATE_MACHINE, State.UPWARDS) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 1000)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 1000)) // .next(new TestCase() // .output(STATE_MACHINE, State.DOWNWARDS) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 700)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 700)) // .next(new TestCase() // .output(STATE_MACHINE, State.DOWNWARDS) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 400)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 400)) // .next(new TestCase() // .output(STATE_MACHINE, State.DOWNWARDS) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 100)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 100)) // .next(new TestCase() // .output(STATE_MACHINE, State.DOWNWARDS) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -200)) // - ; + .output("ess0", SET_ACTIVE_POWER_EQUALS, -200)) // + .deactivate(); } } diff --git a/io.openems.edge.controller.ess.mindischargeperiod/.classpath b/io.openems.edge.controller.ess.mindischargeperiod/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.controller.ess.mindischargeperiod/.classpath +++ b/io.openems.edge.controller.ess.mindischargeperiod/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.controller.ess.mindischargeperiod/test/io/openems/edge/controller/ess/mindischargeperiod/ControllerEssMinimumDischargePowerImplTest.java b/io.openems.edge.controller.ess.mindischargeperiod/test/io/openems/edge/controller/ess/mindischargeperiod/ControllerEssMinimumDischargePowerImplTest.java index e2a90e1983b..53afa7614c7 100644 --- a/io.openems.edge.controller.ess.mindischargeperiod/test/io/openems/edge/controller/ess/mindischargeperiod/ControllerEssMinimumDischargePowerImplTest.java +++ b/io.openems.edge.controller.ess.mindischargeperiod/test/io/openems/edge/controller/ess/mindischargeperiod/ControllerEssMinimumDischargePowerImplTest.java @@ -7,21 +7,18 @@ public class ControllerEssMinimumDischargePowerImplTest { - private static final String CTRL_ID = "ctrl0"; - private static final String ESS_ID = "ess0"; - @Test public void test() throws Exception { new ControllerTest(new ControllerEssMinimumDischargePowerImpl()) // .addReference("componentManager", new DummyComponentManager()) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setActivateDischargePower(0) // .setDischargeTime(0) // .setMinDischargePower(0) // - .build()); // - ; + .build()) // + .deactivate(); } } diff --git a/io.openems.edge.controller.ess.reactivepowervoltagecharacteristic/.classpath b/io.openems.edge.controller.ess.reactivepowervoltagecharacteristic/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.controller.ess.reactivepowervoltagecharacteristic/.classpath +++ b/io.openems.edge.controller.ess.reactivepowervoltagecharacteristic/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.controller.ess.reactivepowervoltagecharacteristic/test/io/openems/edge/controller/ess/reactivepowervoltagecharacteristic/ControllerEssReactivePowerVoltageCharacteristicImplTest.java b/io.openems.edge.controller.ess.reactivepowervoltagecharacteristic/test/io/openems/edge/controller/ess/reactivepowervoltagecharacteristic/ControllerEssReactivePowerVoltageCharacteristicImplTest.java index 1b4492ede55..0278054c009 100644 --- a/io.openems.edge.controller.ess.reactivepowervoltagecharacteristic/test/io/openems/edge/controller/ess/reactivepowervoltagecharacteristic/ControllerEssReactivePowerVoltageCharacteristicImplTest.java +++ b/io.openems.edge.controller.ess.reactivepowervoltagecharacteristic/test/io/openems/edge/controller/ess/reactivepowervoltagecharacteristic/ControllerEssReactivePowerVoltageCharacteristicImplTest.java @@ -1,14 +1,15 @@ package io.openems.edge.controller.ess.reactivepowervoltagecharacteristic; -import java.time.Instant; -import java.time.ZoneOffset; -import java.time.temporal.ChronoUnit; +import static io.openems.common.test.TestUtils.createDummyClock; +import static io.openems.common.utils.JsonUtils.buildJsonArray; +import static io.openems.common.utils.JsonUtils.buildJsonObject; +import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_REACTIVE_POWER_EQUALS; +import static io.openems.edge.ess.api.SymmetricEss.ChannelId.MAX_APPARENT_POWER; +import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.VOLTAGE; +import static java.time.temporal.ChronoUnit.SECONDS; import org.junit.Test; -import io.openems.common.test.TimeLeapClock; -import io.openems.common.types.ChannelAddress; -import io.openems.common.utils.JsonUtils; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyComponentManager; import io.openems.edge.common.test.DummyConfigurationAdmin; @@ -18,86 +19,79 @@ public class ControllerEssReactivePowerVoltageCharacteristicImplTest { - private static final String CTRL_ID = "ctrlReactivePowerVoltageCharacteristic0"; - private static final String ESS_ID = "ess0"; - private static final String METER_ID = "meter0"; - private static final ChannelAddress ESS_REACTIVE_POWER = new ChannelAddress(ESS_ID, "SetReactivePowerEquals"); - private static final ChannelAddress METER_VOLTAGE = new ChannelAddress(METER_ID, "Voltage"); - private static final ChannelAddress MAX_APPARENT_POWER = new ChannelAddress(ESS_ID, "MaxApparentPower"); - @Test public void test() throws Exception { - final var clock = new TimeLeapClock(Instant.parse("2020-10-05T14:00:00.00Z"), ZoneOffset.UTC); + final var clock = createDummyClock(); new ControllerTest(new ControllerEssReactivePowerVoltageCharacteristicImpl())// .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager(clock)) // - .addReference("meter", new DummyElectricityMeter(METER_ID)) // - .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) // + .addReference("meter", new DummyElectricityMeter("meter0")) // + .addReference("ess", new DummyManagedSymmetricEss("ess0")) // .activate(MyConfig.create()// - .setId(CTRL_ID)// - .setEssId(ESS_ID)// - .setMeterId(METER_ID)// + .setId("ctrl0")// + .setEssId("ess0")// + .setMeterId("meter0")// .setNominalVoltage(240)// .setWaitForHysteresis(5)// - .setPowerVoltConfig(JsonUtils.buildJsonArray()// - .add(JsonUtils.buildJsonObject()// + .setPowerVoltConfig(buildJsonArray()// + .add(buildJsonObject()// .addProperty("voltageRatio", 0.9) // .addProperty("percent", 60) // .build()) // - .add(JsonUtils.buildJsonObject()// + .add(buildJsonObject()// .addProperty("voltageRatio", 0.93) // .addProperty("percent", 0) // .build()) // - .add(JsonUtils.buildJsonObject()// + .add(buildJsonObject()// .addProperty("voltageRatio", 1.07) // .addProperty("percent", 0) // .build()) // - .add(JsonUtils.buildJsonObject()// + .add(buildJsonObject()// .addProperty("voltageRatio", 1.1) // .addProperty("percent", -60) // .build() // ).build().toString() // ).build()) // .next(new TestCase("First Input") // - .input(METER_VOLTAGE, 240_000) // [mV] - .input(MAX_APPARENT_POWER, 10_000)) // [VA] + .input("meter0", VOLTAGE, 240_000) // [mV] + .input("ess0", MAX_APPARENT_POWER, 10_000)) // [VA] .next(new TestCase() // - .output(ESS_REACTIVE_POWER, 0))// + .output("ess0", SET_REACTIVE_POWER_EQUALS, 0))// .next(new TestCase("Second Input") // - .timeleap(clock, 5, ChronoUnit.SECONDS)// - .input(METER_VOLTAGE, 216_000) // [mV] - .input(MAX_APPARENT_POWER, 10_000) // [VA] - .output(ESS_REACTIVE_POWER, 6000))// + .timeleap(clock, 5, SECONDS)// + .input("meter0", VOLTAGE, 216_000) // [mV] + .input("ess0", MAX_APPARENT_POWER, 10_000) // [VA] + .output("ess0", SET_REACTIVE_POWER_EQUALS, 6000))// .next(new TestCase("Third Input")// - .timeleap(clock, 5, ChronoUnit.SECONDS)// - .input(METER_VOLTAGE, 220_000) // [mV] - .input(MAX_APPARENT_POWER, 10_000) // [VA] - .output(ESS_REACTIVE_POWER, 2600))// + .timeleap(clock, 5, SECONDS)// + .input("meter0", VOLTAGE, 220_000) // [mV] + .input("ess0", MAX_APPARENT_POWER, 10_000) // [VA] + .output("ess0", SET_REACTIVE_POWER_EQUALS, 2600))// .next(new TestCase()// - .timeleap(clock, 5, ChronoUnit.SECONDS)// - .input(METER_VOLTAGE, 223_000) // [mV] - .input(MAX_APPARENT_POWER, 10_000) // [VA] - .output(ESS_REACTIVE_POWER, 100))// + .timeleap(clock, 5, SECONDS)// + .input("meter0", VOLTAGE, 223_000) // [mV] + .input("ess0", MAX_APPARENT_POWER, 10_000) // [VA] + .output("ess0", SET_REACTIVE_POWER_EQUALS, 100))// .next(new TestCase()// - .timeleap(clock, 5, ChronoUnit.SECONDS)// - .input(METER_VOLTAGE, 223_200) // [mV] - .input(MAX_APPARENT_POWER, 10_000) // [VA] - .output(ESS_REACTIVE_POWER, 0))// + .timeleap(clock, 5, SECONDS)// + .input("meter0", VOLTAGE, 223_200) // [mV] + .input("ess0", MAX_APPARENT_POWER, 10_000) // [VA] + .output("ess0", SET_REACTIVE_POWER_EQUALS, 0))// .next(new TestCase()// - .timeleap(clock, 5, ChronoUnit.SECONDS)// - .input(METER_VOLTAGE, 256_800) // [mV] - .input(MAX_APPARENT_POWER, 10_000) // [VA] - .output(ESS_REACTIVE_POWER, 0))// + .timeleap(clock, 5, SECONDS)// + .input("meter0", VOLTAGE, 256_800) // [mV] + .input("ess0", MAX_APPARENT_POWER, 10_000) // [VA] + .output("ess0", SET_REACTIVE_POWER_EQUALS, 0))// .next(new TestCase()// - .timeleap(clock, 5, ChronoUnit.SECONDS)// - .input(METER_VOLTAGE, 260_000) // [mV] - .input(MAX_APPARENT_POWER, 10_000) // [VA] - .output(ESS_REACTIVE_POWER, -2600))// + .timeleap(clock, 5, SECONDS)// + .input("meter0", VOLTAGE, 260_000) // [mV] + .input("ess0", MAX_APPARENT_POWER, 10_000) // [VA] + .output("ess0", SET_REACTIVE_POWER_EQUALS, -2600))// .next(new TestCase()// - .timeleap(clock, 5, ChronoUnit.SECONDS)// - .input(METER_VOLTAGE, 264_000) // [mV] - .input(MAX_APPARENT_POWER, 10_000) // [VA] - .output(ESS_REACTIVE_POWER, -6000))// - ; + .timeleap(clock, 5, SECONDS)// + .input("meter0", VOLTAGE, 264_000) // [mV] + .input("ess0", MAX_APPARENT_POWER, 10_000) // [VA] + .output("ess0", SET_REACTIVE_POWER_EQUALS, -6000)) // + .deactivate(); } } diff --git a/io.openems.edge.controller.ess.selltogridlimit/.classpath b/io.openems.edge.controller.ess.selltogridlimit/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.controller.ess.selltogridlimit/.classpath +++ b/io.openems.edge.controller.ess.selltogridlimit/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.controller.ess.selltogridlimit/test/io/openems/edge/controller/symmetric/selltogridlimit/ControllerEssSellToGridLimitImplTest.java b/io.openems.edge.controller.ess.selltogridlimit/test/io/openems/edge/controller/symmetric/selltogridlimit/ControllerEssSellToGridLimitImplTest.java index 5c191d0c4f5..0870799ee2a 100644 --- a/io.openems.edge.controller.ess.selltogridlimit/test/io/openems/edge/controller/symmetric/selltogridlimit/ControllerEssSellToGridLimitImplTest.java +++ b/io.openems.edge.controller.ess.selltogridlimit/test/io/openems/edge/controller/symmetric/selltogridlimit/ControllerEssSellToGridLimitImplTest.java @@ -1,47 +1,39 @@ package io.openems.edge.controller.symmetric.selltogridlimit; +import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_LESS_OR_EQUALS; + import org.junit.Test; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyConfigurationAdmin; import io.openems.edge.controller.test.ControllerTest; +import io.openems.edge.ess.api.SymmetricEss; import io.openems.edge.ess.test.DummyManagedSymmetricEss; +import io.openems.edge.meter.api.ElectricityMeter; import io.openems.edge.meter.test.DummyElectricityMeter; public class ControllerEssSellToGridLimitImplTest { - private static final String CTRL_ID = "ctrl0"; - - private static final String ESS_ID = "ess0"; - - private static final ChannelAddress ESS_ACTIVE_POWER = new ChannelAddress(ESS_ID, "ActivePower"); - private static final ChannelAddress ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS = new ChannelAddress(ESS_ID, - "SetActivePowerLessOrEquals"); - - private static final String METER_ID = "meter00"; - - private static final ChannelAddress METER_ACTIVE_POWER = new ChannelAddress(METER_ID, "ActivePower"); - @Test public void test() throws Exception { new ControllerTest(new ControllerEssSellToGridLimitImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) // - .addReference("meter", new DummyElectricityMeter(METER_ID)) // + .addReference("ess", new DummyManagedSymmetricEss("ess0")) // + .addReference("meter", new DummyElectricityMeter("meter0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // - .setMeterId(METER_ID) // + .setId("ctrl0") // + .setEssId("ess0") // + .setMeterId("meter0") // .setMaximumSellToGridPower(5000) // .build()) // .next(new TestCase() // - .input(METER_ACTIVE_POWER, -5000) // - .input(ESS_ACTIVE_POWER, 3000) // - .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, null)) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -5000) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 3000) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, null)) // .next(new TestCase() // - .input(METER_ACTIVE_POWER, -6000) // - .input(ESS_ACTIVE_POWER, 3000) // - .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, 2000)); + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -6000) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 3000) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 2000)) // + .deactivate(); } } \ No newline at end of file diff --git a/io.openems.edge.controller.ess.standby/.classpath b/io.openems.edge.controller.ess.standby/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.controller.ess.standby/.classpath +++ b/io.openems.edge.controller.ess.standby/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.controller.ess.standby/test/io/openems/edge/controller/ess/standby/ControllerEssStandbyImplTest.java b/io.openems.edge.controller.ess.standby/test/io/openems/edge/controller/ess/standby/ControllerEssStandbyImplTest.java index 6f3053a0b16..ac45a6cbb7e 100644 --- a/io.openems.edge.controller.ess.standby/test/io/openems/edge/controller/ess/standby/ControllerEssStandbyImplTest.java +++ b/io.openems.edge.controller.ess.standby/test/io/openems/edge/controller/ess/standby/ControllerEssStandbyImplTest.java @@ -1,5 +1,15 @@ package io.openems.edge.controller.ess.standby; +import static io.openems.edge.common.sum.Sum.ChannelId.CONSUMPTION_ACTIVE_POWER; +import static io.openems.edge.common.sum.Sum.ChannelId.GRID_ACTIVE_POWER; +import static io.openems.edge.common.sum.Sum.ChannelId.PRODUCTION_ACTIVE_POWER; +import static io.openems.edge.controller.ess.standby.ControllerEssStandby.ChannelId.STATE_MACHINE; +import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.ALLOWED_CHARGE_POWER; +import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_EQUALS; +import static io.openems.edge.ess.api.SymmetricEss.ChannelId.ACTIVE_POWER; +import static java.time.temporal.ChronoUnit.HOURS; +import static java.time.temporal.ChronoUnit.MINUTES; + import java.time.DayOfWeek; import java.time.ZoneId; import java.time.ZonedDateTime; @@ -9,10 +19,8 @@ import org.junit.Test; import io.openems.common.test.TimeLeapClock; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.sum.DummySum; import io.openems.edge.common.sum.GridMode; -import io.openems.edge.common.sum.Sum; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyComponentManager; import io.openems.edge.controller.ess.standby.statemachine.StateMachine.State; @@ -23,23 +31,6 @@ public class ControllerEssStandbyImplTest { private static final int MAX_APPARENT_POWER = 50_000; // [W] - private static final String CTRL_ID = "ctrlEssStandby0"; - private static final ChannelAddress STATE_MACHINE = new ChannelAddress(CTRL_ID, "StateMachine"); - - private static final String SUM_ID = Sum.SINGLETON_COMPONENT_ID; - private static final ChannelAddress SUM_GRID_ACTIVE_POWER = new ChannelAddress(SUM_ID, "GridActivePower"); - private static final ChannelAddress SUM_PRODUCTION_ACTIVE_POWER = new ChannelAddress(SUM_ID, - "ProductionActivePower"); - private static final ChannelAddress SUM_CONSUMPTION_ACTIVE_POWER = new ChannelAddress(SUM_ID, - "ConsumptionActivePower"); - - private static final String ESS_ID = "ess0"; - private static final ChannelAddress ESS_ACTIVE_POWER = new ChannelAddress(ESS_ID, "ActivePower"); - private static final ChannelAddress ESS_SET_ACTIVE_POWER_EQUALS = new ChannelAddress(ESS_ID, - "SetActivePowerEquals"); - private static final ChannelAddress ESS_ALLOWED_CHARGE_POWER = new ChannelAddress(ESS_ID, "AllowedChargePower"); - - // Initialize mocked Clock private static TimeLeapClock clock; @Before @@ -49,7 +40,7 @@ public void initialize() { private static ControllerTest tillDischarge() throws Exception { // Initialize ESS - final var ess = new DummyManagedSymmetricEss(ESS_ID) // + final var ess = new DummyManagedSymmetricEss("ess0") // .withGridMode(GridMode.ON_GRID) // .withMaxApparentPower(MAX_APPARENT_POWER) // .withSoc(70); @@ -59,8 +50,8 @@ private static ControllerTest tillDischarge() throws Exception { .addReference("sum", new DummySum()) // .addComponent(ess) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setStartDate("01.02.2020") // .setEndDate("01.03.2020") // .setDayOfWeek(DayOfWeek.SUNDAY) // @@ -68,7 +59,7 @@ private static ControllerTest tillDischarge() throws Exception { .next(new TestCase() // .output(STATE_MACHINE, State.UNDEFINED)) // .next(new TestCase("1 second before midnight (friday) before 01.02.2020") // - .timeleap(clock, 10, ChronoUnit.MINUTES) // + .timeleap(clock, 10, MINUTES) // .output(STATE_MACHINE, State.UNDEFINED)) // .next(new TestCase("midnight (saturday)") // .timeleap(clock, 1, ChronoUnit.SECONDS) // @@ -80,113 +71,117 @@ private static ControllerTest tillDischarge() throws Exception { * DISCHARGE */ .next(new TestCase("sunday -> switch to DISCHARGE") // - .input(SUM_GRID_ACTIVE_POWER, 10_000 /* buy from grid */) // - .input(ESS_ACTIVE_POWER, 100 /* discharge */) // + .input(GRID_ACTIVE_POWER, 10_000 /* buy from grid */) // + .input("ess0", ACTIVE_POWER, 100 /* discharge */) // .output(STATE_MACHINE, State.DISCHARGE) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 10_100)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 10_100)) // .next(new TestCase("discharge > 70 % of maxApparentPower") // - .timeleap(clock, 30, ChronoUnit.MINUTES) // - .input(SUM_GRID_ACTIVE_POWER, 29_900 /* buy from grid */) // - .input(ESS_ACTIVE_POWER, 10_100 /* discharge */) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 40_000)) // + .timeleap(clock, 30, MINUTES) // + .input(GRID_ACTIVE_POWER, 29_900 /* buy from grid */) // + .input("ess0", ACTIVE_POWER, 10_100 /* discharge */) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 40_000)) // .next(new TestCase("discharge > 70 % of maxApparentPower - 9 minutes") // - .timeleap(clock, 9, ChronoUnit.MINUTES) // - .input(SUM_GRID_ACTIVE_POWER, 0 /* buy from grid */) // - .input(ESS_ACTIVE_POWER, 40_000 /* discharge */) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 40_000)) // + .timeleap(clock, 9, MINUTES) // + .input(GRID_ACTIVE_POWER, 0 /* buy from grid */) // + .input("ess0", ACTIVE_POWER, 40_000 /* discharge */) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 40_000)) // .next(new TestCase("discharge > 70 % of maxApparentPower - 10 minutes: reduce to 50 %") // - .timeleap(clock, 1, ChronoUnit.MINUTES) // - .input(SUM_GRID_ACTIVE_POWER, 0 /* buy from grid */) // - .input(ESS_ACTIVE_POWER, 40_000 /* discharge */) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, (int) (MAX_APPARENT_POWER * 0.5))) + .timeleap(clock, 1, MINUTES) // + .input(GRID_ACTIVE_POWER, 0 /* buy from grid */) // + .input("ess0", ACTIVE_POWER, 40_000 /* discharge */) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, (int) (MAX_APPARENT_POWER * 0.5))) .next(new TestCase("do not charge") // - .timeleap(clock, 1, ChronoUnit.MINUTES) // - .input(SUM_GRID_ACTIVE_POWER, -100 /* buy from grid */) // - .input(ESS_ACTIVE_POWER, 0 /* discharge */) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 0)); + .timeleap(clock, 1, MINUTES) // + .input(GRID_ACTIVE_POWER, -100 /* buy from grid */) // + .input("ess0", ACTIVE_POWER, 0 /* discharge */) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 0)) // + .deactivate(); } private static ControllerTest tillSlowCharge1_1() throws Exception { return tillDischarge() // .next(new TestCase("production > consumption") // - .input(SUM_GRID_ACTIVE_POWER, 0 /* buy from grid */) // - .input(SUM_PRODUCTION_ACTIVE_POWER, 1000) // - .input(SUM_CONSUMPTION_ACTIVE_POWER, 999) // - .input(ESS_ACTIVE_POWER, 10_000 /* discharge */) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 10_000)) // + .input(GRID_ACTIVE_POWER, 0 /* buy from grid */) // + .input(PRODUCTION_ACTIVE_POWER, 1000) // + .input(CONSUMPTION_ACTIVE_POWER, 999) // + .input("ess0", ACTIVE_POWER, 10_000 /* discharge */) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 10_000)) // .next(new TestCase("production > consumption: more than 1 minute -> SLOW_CHARGE") // - .timeleap(clock, 1, ChronoUnit.MINUTES) // - .input(SUM_GRID_ACTIVE_POWER, 0 /* buy from grid */) // - .input(SUM_PRODUCTION_ACTIVE_POWER, 1000) // - .input(SUM_CONSUMPTION_ACTIVE_POWER, 999)) // + .timeleap(clock, 1, MINUTES) // + .input(GRID_ACTIVE_POWER, 0 /* buy from grid */) // + .input(PRODUCTION_ACTIVE_POWER, 1000) // + .input(CONSUMPTION_ACTIVE_POWER, 999)) // /* * SLOW_CHARGE */ .next(new TestCase("SLOW_CHARGE") // - .output(STATE_MACHINE, State.SLOW_CHARGE_1)); // + .output(STATE_MACHINE, State.SLOW_CHARGE_1)) // + .deactivate(); } private static ControllerTest tillSlowCharge1_2() throws Exception { return tillDischarge() // .next(new TestCase("latest at 12 -> SLOW_CHARGE") // - .timeleap(clock, 12, ChronoUnit.HOURS)) // + .timeleap(clock, 12, HOURS)) // /* * SLOW_CHARGE */ .next(new TestCase("SLOW_CHARGE") // - .output(STATE_MACHINE, State.SLOW_CHARGE_1)); // + .output(STATE_MACHINE, State.SLOW_CHARGE_1)) // + .deactivate(); } private static ControllerTest tillSlowCharge2_1() throws Exception { return tillSlowCharge1_2() // .next(new TestCase("") // - .input(ESS_ACTIVE_POWER, 0) // - .input(SUM_GRID_ACTIVE_POWER, -20_000 /* sell to grid */) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -20_000)) // + .input("ess0", ACTIVE_POWER, 0) // + .input(GRID_ACTIVE_POWER, -20_000 /* sell to grid */) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -20_000)) // .next(new TestCase("Charge with minimum 20 %") // - .input(ESS_ACTIVE_POWER, 0) // - .input(SUM_GRID_ACTIVE_POWER, (int) (MAX_APPARENT_POWER * -0.19) /* sell to grid */) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, (int) (MAX_APPARENT_POWER * -0.20))) // + .input("ess0", ACTIVE_POWER, 0) // + .input(GRID_ACTIVE_POWER, (int) (MAX_APPARENT_POWER * -0.19)) // sell to grid + .output("ess0", SET_ACTIVE_POWER_EQUALS, (int) (MAX_APPARENT_POWER * -0.20))) // .next(new TestCase("Charge with maximum 50 %") // - .input(ESS_ACTIVE_POWER, 0) // - .input(SUM_GRID_ACTIVE_POWER, (int) (MAX_APPARENT_POWER * -0.51) /* sell to grid */) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, (int) (MAX_APPARENT_POWER * -0.50))) // + .input("ess0", ACTIVE_POWER, 0) // + .input(GRID_ACTIVE_POWER, (int) (MAX_APPARENT_POWER * -0.51)) // sell to grid + .output("ess0", SET_ACTIVE_POWER_EQUALS, (int) (MAX_APPARENT_POWER * -0.50))) // .next(new TestCase("after 30 minutes -> FAST_CHARGE") // - .timeleap(clock, 30, ChronoUnit.MINUTES)) // + .timeleap(clock, 30, MINUTES)) // /* * FAST_CHARGE */ .next(new TestCase("FAST_CHARGE with max power") // .output(STATE_MACHINE, State.FAST_CHARGE) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, MAX_APPARENT_POWER * -1)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, MAX_APPARENT_POWER * -1)) // .next(new TestCase("after 10 minutes -> SLOW_CHARGE_2") // - .timeleap(clock, 10, ChronoUnit.MINUTES)) // + .timeleap(clock, 10, MINUTES)) // /* * SLOW_CHARGE_2 */ .next(new TestCase("SLOW_CHARGE_2") // - .input(ESS_ACTIVE_POWER, 0) // - .input(ESS_ALLOWED_CHARGE_POWER, -60_000) // - .input(SUM_GRID_ACTIVE_POWER, -20_000 /* sell to grid */) // + .input("ess0", ACTIVE_POWER, 0) // + .input("ess0", ALLOWED_CHARGE_POWER, -60_000) // + .input(GRID_ACTIVE_POWER, -20_000 /* sell to grid */) // .output(STATE_MACHINE, State.SLOW_CHARGE_2) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -20_000)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -20_000)) // /* * FINISHED */ .next(new TestCase("no more charging allowed -> FINISHED") // - .input(ESS_ALLOWED_CHARGE_POWER, 0)) // + .input("ess0", ALLOWED_CHARGE_POWER, 0)) // .next(new TestCase("FINISHED") // .output(STATE_MACHINE, State.FINISHED)) // /* * UNDEFINED */ .next(new TestCase("on day change -> UNDEFINED") // - .timeleap(clock, 11, ChronoUnit.HOURS)) // + .timeleap(clock, 11, HOURS)) // .next(new TestCase("UNDEFINED") // .output(STATE_MACHINE, State.UNDEFINED)) // .next(new TestCase("After test period") // .timeleap(clock, 30, ChronoUnit.DAYS) // - .output(STATE_MACHINE, State.UNDEFINED)); // + .output(STATE_MACHINE, State.UNDEFINED)) // + .deactivate(); } @Test diff --git a/io.openems.edge.controller.ess.timeofusetariff/.classpath b/io.openems.edge.controller.ess.timeofusetariff/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.controller.ess.timeofusetariff/.classpath +++ b/io.openems.edge.controller.ess.timeofusetariff/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.controller.ess.timeofusetariff/bnd.bnd b/io.openems.edge.controller.ess.timeofusetariff/bnd.bnd index d45bd8553ce..a69fb5d597a 100644 --- a/io.openems.edge.controller.ess.timeofusetariff/bnd.bnd +++ b/io.openems.edge.controller.ess.timeofusetariff/bnd.bnd @@ -3,17 +3,21 @@ Bundle-Vendor: FENECON GmbH Bundle-License: https://opensource.org/licenses/EPL-2.0 Bundle-Version: 1.0.0.${tstamp} +# TODO remove emergencycapacityreserve and limittotaldischarge after v1 + -buildpath: \ ${buildpath},\ io.openems.common,\ io.openems.edge.common,\ io.openems.edge.controller.api,\ io.openems.edge.controller.ess.emergencycapacityreserve,\ + io.openems.edge.controller.ess.limiter14a,\ io.openems.edge.controller.ess.limittotaldischarge,\ io.openems.edge.energy.api,\ io.openems.edge.ess.api,\ io.openems.edge.timedata.api,\ io.openems.edge.timeofusetariff.api,\ + org.apache.commons.math3,\ -testpath: \ ${testpath},\ diff --git a/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/Config.java b/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/Config.java index 99793e89191..7a3900cfc79 100644 --- a/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/Config.java +++ b/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/Config.java @@ -26,23 +26,14 @@ @AttributeDefinition(name = "Control-Mode", description = "Set the control-mode.") ControlMode controlMode() default ControlMode.DELAY_DISCHARGE; - @AttributeDefinition(name = "Risk level of the customer", description = """ - Low Risk: Less dependence on predictions; charge/discharge of the battery should always be according to the expected behavior. \ - High Risk: High dependence on predictions; Battery is scheduled to charge/discharge completely based on predictions.""") - RiskLevel riskLevel() default RiskLevel.MEDIUM; - // TODO This will eventually be moved globally/to a 'PowerOptimizer" Controller; // should be per Phase (fuse) @AttributeDefinition(name = "Max Charge Power from the grid [W]", description = "Maximum charge power from the grid") int maxChargePowerFromGrid() default 20_000; - // TODO This will eventually be moved globally - @AttributeDefinition(name = "Limit Charge Power for §14a EnWG", description = "Always apply §14a EnWG limitation of 4.2 kW") - boolean limitChargePowerFor14aEnWG() default false; - @AttributeDefinition(name = "Ess target filter", description = "This is auto-generated by 'Ess-ID'.") String ess_target() default "(enabled=true)"; String webconsole_configurationFactory_nameHint() default "Controller Ess Time-Of-Use Tariff [{id}]"; -} \ No newline at end of file +} diff --git a/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/TimeOfUseTariffController.java b/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/TimeOfUseTariffController.java index 9876f79201e..b241b53977a 100644 --- a/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/TimeOfUseTariffController.java +++ b/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/TimeOfUseTariffController.java @@ -8,10 +8,11 @@ import io.openems.edge.common.channel.value.Value; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.controller.api.Controller; +import io.openems.edge.controller.ess.timeofusetariff.v1.EnergyScheduleHandlerV1; +import io.openems.edge.energy.api.EnergySchedulable; -public interface TimeOfUseTariffController extends Controller, OpenemsComponent { - - public static final int PERIODS_PER_HOUR = 4; +@SuppressWarnings("deprecation") +public interface TimeOfUseTariffController extends Controller, EnergySchedulable, OpenemsComponent { public enum ChannelId implements io.openems.edge.common.channel.ChannelId { /** @@ -52,6 +53,14 @@ public Doc doc() { } } + /** + * Get the {@link EnergyScheduleHandlerV1}. + * + * @return {@link EnergyScheduleHandlerV1} + */ + @Deprecated + public EnergyScheduleHandlerV1 getEnergyScheduleHandlerV1(); + /** * Gets the Channel for {@link ChannelId#QUARTERLY_PRICES}. * diff --git a/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/TimeOfUseTariffControllerImpl.java b/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/TimeOfUseTariffControllerImpl.java index 5ae75684899..aaa0fd7f5fb 100644 --- a/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/TimeOfUseTariffControllerImpl.java +++ b/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/TimeOfUseTariffControllerImpl.java @@ -1,12 +1,31 @@ package io.openems.edge.controller.ess.timeofusetariff; -import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.BALANCING; import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.CHARGE_GRID; import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.DELAY_DISCHARGE; +import static io.openems.edge.controller.ess.timeofusetariff.Utils.ESS_MAX_SOC; import static io.openems.edge.controller.ess.timeofusetariff.Utils.calculateAutomaticMode; +import static io.openems.edge.controller.ess.timeofusetariff.Utils.calculateChargeEnergyInChargeGrid; +import static io.openems.edge.energy.api.simulation.Coefficient.CONS; +import static io.openems.edge.energy.api.simulation.Coefficient.ESS; +import static io.openems.edge.energy.api.simulation.Coefficient.GRID_TO_CONS; +import static io.openems.edge.energy.api.simulation.Coefficient.GRID_TO_ESS; +import static io.openems.edge.energy.api.simulation.Coefficient.PROD_TO_ESS; +import static io.openems.edge.energy.api.simulation.Coefficient.PROD_TO_GRID; +import static java.lang.Math.min; +import static java.lang.Math.round; +import static org.apache.commons.math3.optim.linear.Relationship.EQ; +import static org.apache.commons.math3.optim.nonlinear.scalar.GoalType.MAXIMIZE; +import static org.apache.commons.math3.optim.nonlinear.scalar.GoalType.MINIMIZE; +import static org.osgi.service.component.annotations.ReferenceCardinality.MANDATORY; +import static org.osgi.service.component.annotations.ReferenceCardinality.MULTIPLE; +import static org.osgi.service.component.annotations.ReferenceCardinality.OPTIONAL; +import static org.osgi.service.component.annotations.ReferencePolicy.STATIC; +import static org.osgi.service.component.annotations.ReferencePolicyOption.GREEDY; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.IntSupplier; +import java.util.function.Supplier; import org.osgi.service.cm.ConfigurationAdmin; import org.osgi.service.component.ComponentContext; @@ -16,9 +35,6 @@ import org.osgi.service.component.annotations.Deactivate; import org.osgi.service.component.annotations.Modified; import org.osgi.service.component.annotations.Reference; -import org.osgi.service.component.annotations.ReferenceCardinality; -import org.osgi.service.component.annotations.ReferencePolicy; -import org.osgi.service.component.annotations.ReferencePolicyOption; import org.osgi.service.metatype.annotations.Designate; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; @@ -30,12 +46,18 @@ import io.openems.edge.common.sum.Sum; import io.openems.edge.controller.api.Controller; import io.openems.edge.controller.ess.emergencycapacityreserve.ControllerEssEmergencyCapacityReserve; +import io.openems.edge.controller.ess.limiter14a.ControllerEssLimiter14a; import io.openems.edge.controller.ess.limittotaldischarge.ControllerEssLimitTotalDischarge; -import io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffControllerImpl.Context; import io.openems.edge.controller.ess.timeofusetariff.Utils.ApplyState; +import io.openems.edge.controller.ess.timeofusetariff.jsonrpc.GetScheduleRequest; +import io.openems.edge.controller.ess.timeofusetariff.jsonrpc.GetScheduleResponse; +import io.openems.edge.controller.ess.timeofusetariff.v1.EnergyScheduleHandlerV1; +import io.openems.edge.controller.ess.timeofusetariff.v1.EnergyScheduleHandlerV1.ContextV1; +import io.openems.edge.controller.ess.timeofusetariff.v1.UtilsV1; import io.openems.edge.energy.api.EnergySchedulable; import io.openems.edge.energy.api.EnergyScheduleHandler; import io.openems.edge.energy.api.EnergyScheduler; +import io.openems.edge.energy.api.simulation.EnergyFlow; import io.openems.edge.ess.api.ManagedSymmetricEss; import io.openems.edge.ess.power.api.Phase; import io.openems.edge.ess.power.api.Pwr; @@ -50,15 +72,13 @@ immediate = true, // configurationPolicy = ConfigurationPolicy.REQUIRE // ) +@SuppressWarnings("deprecation") public class TimeOfUseTariffControllerImpl extends AbstractOpenemsComponent implements TimeOfUseTariffController, - EnergySchedulable, Controller, OpenemsComponent, TimedataProvider, ComponentJsonApi { + EnergySchedulable, Controller, OpenemsComponent, TimedataProvider, ComponentJsonApi { - public static record Context(List ctrlEmergencyCapacityReserves, - List ctrlLimitTotalDischarges, ManagedSymmetricEss ess, - ControlMode controlMode, int maxChargePowerFromGrid, boolean limitChargePowerFor14aEnWG) { - } - - private final EnergyScheduleHandler energyScheduleHandler; + @Deprecated + private final EnergyScheduleHandlerV1 energyScheduleHandlerV1; + private final EnergyScheduleHandler.WithDifferentStates energyScheduleHandler; private final CalculateActiveTime calculateDelayedTime = new CalculateActiveTime(this, TimeOfUseTariffController.ChannelId.DELAYED_TIME); private final CalculateActiveTime calculateChargedTime = new CalculateActiveTime(this, @@ -77,23 +97,26 @@ public static record Context(List ctrlEme @Reference private TimeOfUseTariff timeOfUseTariff; - @Reference(policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.OPTIONAL) + @Reference(policyOption = GREEDY, cardinality = OPTIONAL) private volatile Timedata timedata; - @Reference(policyOption = ReferencePolicyOption.GREEDY, // - cardinality = ReferenceCardinality.MULTIPLE, // - target = "(&(enabled=true)(isReserveSocEnabled=true))") - private List ctrlEmergencyCapacityReserves = new CopyOnWriteArrayList<>(); + @Deprecated + @Reference(policyOption = GREEDY, cardinality = MULTIPLE, target = "(&(enabled=true)(isReserveSocEnabled=true))") + private volatile List ctrlEmergencyCapacityReserves = new CopyOnWriteArrayList<>(); - @Reference(policyOption = ReferencePolicyOption.GREEDY, // - cardinality = ReferenceCardinality.MULTIPLE, // - target = "(enabled=true)") - private List ctrlLimitTotalDischarges = new CopyOnWriteArrayList<>(); + @Deprecated + @Reference(policyOption = GREEDY, cardinality = MULTIPLE, target = "(enabled=true)") + private volatile List ctrlLimitTotalDischarges = new CopyOnWriteArrayList<>(); - @Reference(policy = ReferencePolicy.STATIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.MANDATORY) + @Deprecated + @Reference(policyOption = GREEDY, cardinality = MULTIPLE, target = "(enabled=true)") + private volatile List ctrlLimiter14as = new CopyOnWriteArrayList<>(); + + @Reference(policy = STATIC, policyOption = GREEDY, cardinality = MANDATORY) private ManagedSymmetricEss ess; @Reference + @Deprecated private EnergyScheduler energyScheduler; private Config config = null; @@ -104,11 +127,17 @@ public TimeOfUseTariffControllerImpl() { Controller.ChannelId.values(), // TimeOfUseTariffController.ChannelId.values() // ); - this.energyScheduleHandler = new EnergyScheduleHandler<>(// + + this.energyScheduleHandlerV1 = new EnergyScheduleHandlerV1(// () -> this.config.controlMode().states, // - () -> new Context(this.ctrlEmergencyCapacityReserves, this.ctrlLimitTotalDischarges, this.ess, - this.config.controlMode(), this.config.maxChargePowerFromGrid(), - this.config.limitChargePowerFor14aEnWG())); + () -> new ContextV1(this.ctrlEmergencyCapacityReserves, this.ctrlLimitTotalDischarges, + this.ctrlLimiter14as, this.ess, this.config.controlMode(), + this.config.maxChargePowerFromGrid())); + + this.energyScheduleHandler = buildEnergyScheduleHandler(// + () -> this.ess, // + () -> this.config.controlMode(), // + () -> this.config.maxChargePowerFromGrid()); } @Activate @@ -121,6 +150,7 @@ private void activate(ComponentContext context, Config config) { private void modified(ComponentContext context, Config config) { super.modified(context, config.id(), config.alias(), config.enabled()); this.applyConfig(config); + this.energyScheduleHandler.triggerReschedule("TimeOfUseTariffControllerImpl::modified()"); } private synchronized void applyConfig(Config config) { @@ -138,12 +168,31 @@ protected void deactivate() { @Override public void run() throws OpenemsNamedException { - // Mode given from the configuration. - var as = switch (this.config.mode()) { - case AUTOMATIC -> calculateAutomaticMode(this.sum, this.ess, - this.energyScheduleHandler.getCurrentEssChargeInChargeGrid(), this.config.maxChargePowerFromGrid(), - this.config.limitChargePowerFor14aEnWG(), this.getCurrentPeriodState()); - case OFF -> new ApplyState(StateMachine.BALANCING, null); + var version = this.energyScheduler.getImplementationVersion(); + if (version == null) { + return; + } + + // Version and Mode given from the configuration. + final var as = switch (this.energyScheduler.getImplementationVersion()) { + + case V1_ESS_ONLY // + -> switch (this.config.mode()) { + case AUTOMATIC // + -> UtilsV1.calculateAutomaticMode(this.energyScheduleHandlerV1, this.sum, this.ess, + this.ctrlLimiter14as, this.config.maxChargePowerFromGrid()); + case OFF // + -> new ApplyState(StateMachine.BALANCING, null); + }; + + case V2_ENERGY_SCHEDULABLE // + -> switch (this.config.mode()) { + case AUTOMATIC // + -> calculateAutomaticMode(this.sum, this.ess, this.config.maxChargePowerFromGrid(), + this.energyScheduleHandler.getCurrentPeriod()); + case OFF // + -> new ApplyState(StateMachine.BALANCING, null); + }; }; // Update Channels @@ -159,14 +208,6 @@ public void run() throws OpenemsNamedException { } } - private StateMachine getCurrentPeriodState() { - var state = this.energyScheduleHandler.getCurrentState(); - if (state != null) { - return state; - } - return BALANCING; // Default Fallback - } - @Override public Timedata getTimedata() { return this.timedata; @@ -174,21 +215,145 @@ public Timedata getTimedata() { @Override public void buildJsonApiRoutes(JsonApiBuilder builder) { - this.energyScheduler.buildJsonApiRoutes(builder); + var version = this.energyScheduler.getImplementationVersion(); + if (version == null) { + return; + } + + builder.handleRequest(GetScheduleRequest.METHOD, call -> // + switch (version) { + case V1_ESS_ONLY // + -> this.energyScheduler.handleGetScheduleRequestV1(call, this.id()); + + case V2_ENERGY_SCHEDULABLE // + -> GetScheduleResponse.from(call.getRequest().getId(), // + this.id(), this.componentManager.getClock(), this.ess, this.timedata, this.energyScheduleHandler); + }); } @Override public String debugLog() { var b = new StringBuilder() // .append(this.getStateMachine()); // - if (this.getCurrentPeriodState() == null) { - b.append("|No Schedule available"); + + var version = this.energyScheduler.getImplementationVersion(); + if (version != null) { + switch (version) { + case V1_ESS_ONLY -> { + if (this.energyScheduleHandlerV1 == null || this.energyScheduleHandlerV1.getCurrentState() == null) { + b.append("|No Schedule available"); + } + } + case V2_ENERGY_SCHEDULABLE -> { + if (this.energyScheduleHandler.getCurrentPeriod() == null) { + b.append("|No Schedule available"); + } + } + } } return b.toString(); } + /** + * Builds the {@link EnergyScheduleHandler}. + * + *

+ * This is public so that it can be used by the EnergyScheduler integration + * test. + * + * @param ess a supplier for the {@link ManagedSymmetricEss} + * @param controlMode a supplier for the configured + * {@link ControlMode} + * @param maxChargePowerFromGrid a supplier for the configured + * maxChargePowerFromGrid + * @return a typed {@link EnergyScheduleHandler} + */ + public static EnergyScheduleHandler.WithDifferentStates buildEnergyScheduleHandler( + Supplier ess, Supplier controlMode, IntSupplier maxChargePowerFromGrid) { + return EnergyScheduleHandler.WithDifferentStates.create() // + .setDefaultState(StateMachine.BALANCING) // + .setAvailableStates(() -> controlMode.get().states) // + .setContextFunction(simContext -> { + // Maximium-SoC in CHARGE_GRID is 90 % + var maxSocEnergyInChargeGrid = round(simContext.ess().totalEnergy() * (ESS_MAX_SOC / 100)); + var essChargeInChargeGrid = calculateChargeEnergyInChargeGrid(simContext); + return new EshContext(ess.get(), controlMode.get(), maxChargePowerFromGrid.getAsInt(), + maxSocEnergyInChargeGrid, essChargeInChargeGrid); + }) // + .setSimulator((simContext, period, energyFlow, ctrlContext, state) -> { + switch (state) { + case BALANCING -> applyBalancing(energyFlow); // TODO Move to CtrlBalancing + case DELAY_DISCHARGE -> applyDelayDischarge(energyFlow); + case CHARGE_GRID -> { + energyFlow.setEssMaxCharge( + ctrlContext.maxSocEnergyInChargeGrid - simContext.ess.getInitialEnergy()); + applyChargeGrid(energyFlow, ctrlContext.essChargeInChargeGrid); + } + } + return 0.; + }) // + .setPostProcessor(Utils::postprocessSimulatorState) // + .build(); + } + + /** + * Simulate {@link EnergyFlow} in {@link StateMachine#BALANCING}. + * + * @param model the {@link EnergyFlow.Model} + */ + public static void applyBalancing(EnergyFlow.Model model) { + var consumption = model.setExtremeCoefficientValue(CONS, MINIMIZE); + var target = consumption - model.production; + model.setFittingCoefficientValue(ESS, EQ, target); + } + + /** + * Simulate {@link EnergyFlow} in DELAY_DISCHARGE. + * + * @param model the {@link EnergyFlow.Model} + */ + public static void applyDelayDischarge(EnergyFlow.Model model) { + var consumption = model.setExtremeCoefficientValue(CONS, MINIMIZE); + var target = min(0 /* Charge -> apply Balancing */, consumption - model.production); + model.setFittingCoefficientValue(ESS, EQ, target); + } + + /** + * Simulate {@link EnergyFlow} in {@link StateMachine#CHARGE_GRID}. + * + * @param model the {@link EnergyFlow.Model} + * @param chargeEnergy the target charge-from-grid energy + */ + public static void applyChargeGrid(EnergyFlow.Model model, int chargeEnergy) { + model.setExtremeCoefficientValue(CONS, MINIMIZE); + model.setExtremeCoefficientValue(PROD_TO_ESS, MAXIMIZE); + model.setExtremeCoefficientValue(GRID_TO_CONS, MAXIMIZE); + model.setFittingCoefficientValue(GRID_TO_ESS, EQ, chargeEnergy); + } + + /** + * Simulate {@link EnergyFlow} in a future DISCHARGE_GRID state. + * + * @param model the {@link EnergyFlow.Model} + * @param dischargeEnergy the target discharge-to-grid energy + */ + public static void applyDischargeGrid(EnergyFlow.Model model, int dischargeEnergy) { + model.setExtremeCoefficientValue(CONS, MINIMIZE); + model.setExtremeCoefficientValue(PROD_TO_GRID, MAXIMIZE); + model.setFittingCoefficientValue(GRID_TO_ESS, EQ, -dischargeEnergy); + } + + public static record EshContext(ManagedSymmetricEss ess, ControlMode controlMode, int maxChargePowerFromGrid, + int maxSocEnergyInChargeGrid, int essChargeInChargeGrid) { + } + @Override - public EnergyScheduleHandler getEnergyScheduleHandler() { + public EnergyScheduleHandler.WithDifferentStates getEnergyScheduleHandler() { return this.energyScheduleHandler; } + + @Override + public EnergyScheduleHandlerV1 getEnergyScheduleHandlerV1() { + return this.energyScheduleHandlerV1; + } } diff --git a/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/Utils.java b/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/Utils.java index 6543f9b24c1..8c945077d45 100644 --- a/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/Utils.java +++ b/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/Utils.java @@ -1,23 +1,28 @@ package io.openems.edge.controller.ess.timeofusetariff; -import static io.openems.edge.common.type.TypeUtils.multiply; +import static com.google.common.math.Quantiles.percentiles; import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.BALANCING; import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.CHARGE_GRID; import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.DELAY_DISCHARGE; -import static io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffController.PERIODS_PER_HOUR; +import static io.openems.edge.energy.api.EnergyConstants.PERIODS_PER_HOUR; +import static io.openems.edge.energy.api.EnergyUtils.findFirstPeakIndex; +import static io.openems.edge.energy.api.EnergyUtils.findFirstValleyIndex; +import static io.openems.edge.energy.api.EnergyUtils.toPower; import static java.lang.Math.max; import static java.lang.Math.min; import static java.lang.Math.round; -import static java.util.stream.IntStream.concat; +import static java.util.Arrays.stream; -import java.util.List; -import java.util.Objects; +import com.google.common.primitives.ImmutableIntArray; import io.openems.common.types.ChannelAddress; import io.openems.edge.common.sum.Sum; import io.openems.edge.controller.api.Controller; -import io.openems.edge.controller.ess.emergencycapacityreserve.ControllerEssEmergencyCapacityReserve; -import io.openems.edge.controller.ess.limittotaldischarge.ControllerEssLimitTotalDischarge; +import io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffControllerImpl.EshContext; +import io.openems.edge.energy.api.EnergyScheduleHandler.WithDifferentStates.Period; +import io.openems.edge.energy.api.simulation.EnergyFlow; +import io.openems.edge.energy.api.simulation.GlobalSimulationsContext; +import io.openems.edge.energy.api.simulation.OneSimulationContext; import io.openems.edge.ess.api.HybridEss; import io.openems.edge.ess.api.ManagedSymmetricEss; @@ -33,10 +38,7 @@ private Utils() { } /** Keep some buffer to avoid scheduling errors because of bad predictions. */ - public static final float ESS_MAX_SOC = 90F; - - /** Limit Charge Power for §14a EnWG. */ - public static final int ESS_LIMIT_14A_ENWG = -4200; + public static final float ESS_MAX_SOC = 94F; /** * C-Rate (capacity divided by time) during {@link StateMachine#CHARGE_GRID}. @@ -52,51 +54,26 @@ private Utils() { public static final ChannelAddress SUM_ESS_DISCHARGE_POWER = new ChannelAddress("_sum", "EssDischargePower"); public static final ChannelAddress SUM_ESS_SOC = new ChannelAddress("_sum", "EssSoc"); - /** - * Returns the configured minimum SoC, or zero. - * - * @param ctrlLimitTotalDischarges the list of - * {@link ControllerEssLimitTotalDischarge} - * @param ctrlEmergencyCapacityReserves the list of - * {@link ControllerEssEmergencyCapacityReserve} - * @return the value in [%] - */ - public static int getEssMinSocPercentage(List ctrlLimitTotalDischarges, - List ctrlEmergencyCapacityReserves) { - return concat(// - ctrlLimitTotalDischarges.stream() // - .map(ctrl -> ctrl.getMinSoc().get()) // - .filter(Objects::nonNull) // - .mapToInt(v -> max(0, v)), // only positives - ctrlEmergencyCapacityReserves.stream() // - .map(ctrl -> ctrl.getActualReserveSoc().get()) // - .filter(Objects::nonNull) // - .mapToInt(v -> max(0, v))) // only positives - .max().orElse(0); - } - public static record ApplyState(StateMachine actualState, Integer setPoint) { } /** * Calculate Automatic Mode. * - * @param sum the {@link Sum} - * @param ess the {@link ManagedSymmetricEss} - * @param essChargeInChargeGrid ESS Charge Energy in CHARGE_GRID State [Wh] - * @param maxChargePowerFromGrid the configured max charge from grid power - * @param limitChargePowerFor14aEnWG Limit Charge Power for §14a EnWG - * @param targetState the scheduled target {@link StateMachine} + * @param sum the {@link Sum} + * @param ess the {@link ManagedSymmetricEss} + * @param maxChargePowerFromGrid the configured max charge from grid power + * @param period the scheduled {@link Period} * @return {@link ApplyState} */ - public static ApplyState calculateAutomaticMode(Sum sum, ManagedSymmetricEss ess, Integer essChargeInChargeGrid, - int maxChargePowerFromGrid, boolean limitChargePowerFor14aEnWG, StateMachine targetState) { + public static ApplyState calculateAutomaticMode(Sum sum, ManagedSymmetricEss ess, int maxChargePowerFromGrid, + Period period) { final StateMachine actualState; final Integer setPoint; var gridActivePower = sum.getGridActivePower().get(); // current buy-from/sell-to grid var essActivePower = ess.getActivePower().get(); // current charge/discharge ESS - if (gridActivePower == null || essActivePower == null) { + if (period == null || gridActivePower == null || essActivePower == null) { // undefined state return new ApplyState(BALANCING, null); } @@ -104,9 +81,9 @@ public static ApplyState calculateAutomaticMode(Sum sum, ManagedSymmetricEss ess // Post-process and get actual state final var pwrBalancing = gridActivePower + essActivePower; final var pwrDelayDischarge = calculateDelayDischargePower(ess); - final var pwrChargeGrid = calculateChargeGridPower(essChargeInChargeGrid, ess, essActivePower, gridActivePower, - maxChargePowerFromGrid, limitChargePowerFor14aEnWG); - actualState = postprocessRunState(targetState, pwrBalancing, pwrDelayDischarge, pwrChargeGrid); + final var pwrChargeGrid = calculateChargeGridPower(period.context().essChargeInChargeGrid(), ess, + essActivePower, gridActivePower, maxChargePowerFromGrid); + actualState = postprocessRunState(ess, period.state(), pwrBalancing, pwrDelayDischarge, pwrChargeGrid); // Get and apply ActivePower Less-or-Equals Set-Point setPoint = switch (actualState) { @@ -126,6 +103,7 @@ public static ApplyState calculateAutomaticMode(Sum sum, ManagedSymmetricEss ess * NOTE: this can be useful, if live operation deviates from predicted * operation, e.g. because predictions were wrong. * + * @param ess the {@link ManagedSymmetricEss} * @param state the initial state * @param pwrBalancing the power set-point as it would be in * {@link StateMachine#BALANCING} @@ -135,14 +113,18 @@ public static ApplyState calculateAutomaticMode(Sum sum, ManagedSymmetricEss ess * {@link StateMachine#CHARGE_GRID} * @return the new state */ - public static StateMachine postprocessRunState(StateMachine state, int pwrBalancing, int pwrDelayDischarge, - int pwrChargeGrid) { + public static StateMachine postprocessRunState(ManagedSymmetricEss ess, StateMachine state, int pwrBalancing, + int pwrDelayDischarge, int pwrChargeGrid) { if (state == CHARGE_GRID) { // CHARGE_GRID,... if (pwrChargeGrid >= pwrDelayDischarge) { // but battery charge/discharge is the same as DELAY_DISCHARGE state = DELAY_DISCHARGE; } + var soc = ess.getSoc(); + if (soc.isDefined() && soc.get() >= ESS_MAX_SOC) { + state = DELAY_DISCHARGE; + } } if (state == DELAY_DISCHARGE) { @@ -156,8 +138,42 @@ public static StateMachine postprocessRunState(StateMachine state, int pwrBalanc return state; } - protected static int calculateEssChargeInChargeGridPowerFromParams(Integer essChargeInChargeGrid, - ManagedSymmetricEss ess) { + /** + * Post-Process a state of a Period during Simulation, i.e. replace with + * 'better' state with the same behaviour. + * + *

+ * NOTE: heavy computation is ok here, because this method is called only at the + * end with the best Schedule. + * + * @param osc the {@link OneSimulationContext} + * @param ef the {@link EnergyFlow} for the state + * @param context the {@link EshContext} + * @param state the initial state + * @return the new state + */ + public static StateMachine postprocessSimulatorState(OneSimulationContext osc, EnergyFlow ef, EshContext context, + StateMachine state) { + if (state == CHARGE_GRID) { + // CHARGE_GRID,... + if (ef.getGridToEss() <= 0) { + // but battery is not charged from grid + state = DELAY_DISCHARGE; + } + } + + if (state == DELAY_DISCHARGE) { + // DELAY_DISCHARGE,... + if (osc.ess.getInitialEnergy() == 0 || ef.getEss() < 0) { + // but battery is empty or gets charged + state = BALANCING; + } + } + + return state; + } + + protected static int calculateEssChargeInChargeGridPower(Integer essChargeInChargeGrid, ManagedSymmetricEss ess) { if (essChargeInChargeGrid != null) { return toPower(essChargeInChargeGrid); } @@ -176,31 +192,23 @@ protected static int calculateEssChargeInChargeGridPowerFromParams(Integer essCh * Calculates the Max-ActivePower constraint for * {@link StateMachine#CHARGE_GRID}. * - * @param essChargeInChargeGrid ESS Charge Energy in CHARGE_GRID State [Wh] - * @param ess the {@link ManagedSymmetricEss} - * @param essActivePower the ESS ActivePower - * @param gridActivePower the Grid ActivePower - * @param maxChargePowerFromGrid the configured max charge from grid power - * @param limitChargePowerFor14aEnWG Limit Charge Power for §14a EnWG - * @return the set-point or null + * @param essChargeInChargeGrid ESS Charge Energy in CHARGE_GRID State [Wh] + * @param ess the {@link ManagedSymmetricEss} + * @param essActivePower the ESS ActivePower + * @param gridActivePower the Grid ActivePower + * @param maxChargePowerFromGrid the configured max charge from grid power + * @return the negative set-point or null */ public static int calculateChargeGridPower(Integer essChargeInChargeGrid, ManagedSymmetricEss ess, - int essActivePower, int gridActivePower, int maxChargePowerFromGrid, boolean limitChargePowerFor14aEnWG) { + int essActivePower, int gridActivePower, int maxChargePowerFromGrid) { var realGridPower = gridActivePower + essActivePower; // 'real', without current ESS charge/discharge - var targetChargePower = calculateEssChargeInChargeGridPowerFromParams(essChargeInChargeGrid, ess) // + var targetChargePower = calculateEssChargeInChargeGridPower(essChargeInChargeGrid, ess) // + min(0, realGridPower) * -1; // add excess production var effectiveGridBuyPower = max(0, realGridPower) + targetChargePower; var chargePower = max(0, targetChargePower - max(0, effectiveGridBuyPower - maxChargePowerFromGrid)); // Invert to negative for CHARGE - chargePower *= -1; - - // Apply §14a EnWG limit - if (limitChargePowerFor14aEnWG) { - chargePower = max(ESS_LIMIT_14A_ENWG, chargePower); - } - - return chargePower; + return chargePower * -1; } /** @@ -236,12 +244,63 @@ public static int calculateDelayDischargePower(ManagedSymmetricEss ess) { } /** - * Converts energy [Wh/15 min] to power [W]. + * Calculates the default ESS charge energy per period in + * {@link StateMachine#CHARGE_GRID}. + * + *

+ * Applies {@link #ESS_CHARGE_C_RATE} with the minimum of usable ESS energy or + * predicted consumption energy that cannot be supplied from production. * - * @param energy the energy value - * @return the power value + * @param gsc the {@link GlobalSimulationsContext} + * @return the value in [Wh] */ - private static Integer toPower(Integer energy) { - return multiply(energy, PERIODS_PER_HOUR); + public static int calculateChargeEnergyInChargeGrid(GlobalSimulationsContext gsc) { + var refs = ImmutableIntArray.builder(); + + // Uses the total available energy as reference (= fallback) + var fallback = max(0, round(ESS_MAX_SOC / 100F * gsc.ess().totalEnergy())); + add(refs, fallback); + + // Uses the total excess consumption as reference + add(refs, gsc.periods().stream() // + .mapToInt(p -> p.consumption() - p.production()) // calculates excess Consumption Energy per Period + .sum()); + + add(refs, gsc.periods().stream() // + .takeWhile(p -> p.consumption() >= p.production()) // take only first Periods + .mapToInt(p -> p.consumption() - p.production()) // calculates excess Consumption Energy per Period + .sum()); + + // Uses the excess consumption during high price periods as reference + { + var prices = gsc.periods().stream() // + .mapToDouble(GlobalSimulationsContext.Period::price) // + .toArray(); + var peakIndex = findFirstPeakIndex(findFirstValleyIndex(0, prices), prices); + var firstPrices = stream(prices) // + .limit(peakIndex) // + .toArray(); + if (firstPrices.length > 0) { + var percentilePrice = percentiles().index(95).compute(firstPrices); + add(refs, gsc.periods().stream() // + .limit(peakIndex) // + .filter(p -> p.price() >= percentilePrice) // takes only prices > percentile + .mapToInt(p -> p.consumption() - p.production()) // excess Consumption Energy per Period + .sum()); + } + } + + return (int) round(// + refs.build().stream() // + .average() // + .orElse(fallback) // + * ESS_CHARGE_C_RATE / PERIODS_PER_HOUR); } + + private static void add(ImmutableIntArray.Builder builder, int value) { + if (value > 0) { + builder.add(value); + } + } + } diff --git a/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/jsonrpc/GetScheduleRequest.java b/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/jsonrpc/GetScheduleRequest.java new file mode 100644 index 00000000000..ff357bfd827 --- /dev/null +++ b/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/jsonrpc/GetScheduleRequest.java @@ -0,0 +1,49 @@ +package io.openems.edge.controller.ess.timeofusetariff.jsonrpc; + +import com.google.gson.JsonObject; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.exceptions.OpenemsException; +import io.openems.common.jsonrpc.base.JsonrpcRequest; + +/** + * Represents a JSON-RPC Request for 'getSchedule'. + * + *

+ * {
+ *   "jsonrpc": "2.0",
+ *   "id": "UUID",
+ *   "method": "getSchedule",
+ *   "params": {}
+ * }
+ * 
+ */ +public class GetScheduleRequest extends JsonrpcRequest { + + public static final String METHOD = "getSchedule"; + + /** + * Create {@link GetScheduleRequest} from a template {@link JsonrpcRequest}. + * + * @param r the template {@link JsonrpcRequest} + * @return the {@link GetScheduleRequest} + * @throws OpenemsNamedException on parse error + */ + public static GetScheduleRequest from(JsonrpcRequest r) throws OpenemsException { + return new GetScheduleRequest(r); + } + + public GetScheduleRequest() { + super(METHOD); + } + + private GetScheduleRequest(JsonrpcRequest request) { + super(request, METHOD); + } + + @Override + public JsonObject getParams() { + return new JsonObject(); + } + +} diff --git a/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/jsonrpc/GetScheduleResponse.java b/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/jsonrpc/GetScheduleResponse.java new file mode 100644 index 00000000000..d60e3eae6e6 --- /dev/null +++ b/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/jsonrpc/GetScheduleResponse.java @@ -0,0 +1,230 @@ +package io.openems.edge.controller.ess.timeofusetariff.jsonrpc; + +import static io.openems.common.utils.JsonUtils.buildJsonObject; +import static io.openems.common.utils.JsonUtils.getAsOptionalDouble; +import static io.openems.common.utils.JsonUtils.getAsOptionalInt; +import static io.openems.common.utils.JsonUtils.toJsonArray; +import static io.openems.edge.common.type.TypeUtils.fitWithin; +import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.BALANCING; +import static io.openems.edge.controller.ess.timeofusetariff.Utils.SUM_CONSUMPTION; +import static io.openems.edge.controller.ess.timeofusetariff.Utils.SUM_ESS_DISCHARGE_POWER; +import static io.openems.edge.controller.ess.timeofusetariff.Utils.SUM_ESS_SOC; +import static io.openems.edge.controller.ess.timeofusetariff.Utils.SUM_GRID; +import static io.openems.edge.controller.ess.timeofusetariff.Utils.SUM_PRODUCTION; +import static io.openems.edge.energy.api.EnergyUtils.toPower; +import static java.lang.Math.round; +import static java.util.Optional.ofNullable; + +import java.time.Clock; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; +import java.util.Set; +import java.util.SortedMap; +import java.util.UUID; +import java.util.function.Function; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.ImmutableSortedMap; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.jsonrpc.base.JsonrpcResponseSuccess; +import io.openems.common.timedata.Resolution; +import io.openems.common.types.ChannelAddress; +import io.openems.edge.controller.ess.timeofusetariff.StateMachine; +import io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffController; +import io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffControllerImpl; +import io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffControllerImpl.EshContext; +import io.openems.edge.controller.ess.timeofusetariff.Utils; +import io.openems.edge.energy.api.EnergyScheduleHandler; +import io.openems.edge.energy.api.EnergyScheduleHandler.WithDifferentStates.Period; +import io.openems.edge.ess.api.SymmetricEss; +import io.openems.edge.timedata.api.Timedata; + +/** + * Represents a JSON-RPC Response for 'getSchedule'. + * + *
+ * {
+ *   "jsonrpc": "2.0",
+ *   "id": "UUID",
+ *   "result": {
+ *     'schedule': [{
+ *      'timestamp':...,
+ *      'price':...,
+ *      'state':...,
+ *      'grid':...,
+ *      'production':...,
+ *      'consumption':...,
+ *      'ess':...,
+ *      'soc':...,
+ *     }]
+ *   }
+ * }
+ * 
+ */ +public class GetScheduleResponse extends JsonrpcResponseSuccess { + + private static final Logger LOG = LoggerFactory.getLogger(GetScheduleResponse.class); + + private final JsonObject result; + + public GetScheduleResponse(UUID id, JsonObject result) { + super(id); + this.result = result; + } + + @Override + public JsonObject getResult() { + return this.result; + } + + /** + * Builds a {@link GetScheduleResponse} with last three hours data and current + * Schedule. + * + * @param requestId the JSON-RPC request-id + * @param componentId the Component-ID of the parent + * {@link TimeOfUseTariffController} + * @param clock a {@link Clock} + * @param ess the {@link SymmetricEss} + * @param timedata the {@link Timedata} + * @param energyScheduleHandler the {@link EnergyScheduleHandler} + * @return the {@link GetScheduleResponse} + * @throws OpenemsNamedException on error + */ + public static GetScheduleResponse from(UUID requestId, String componentId, Clock clock, SymmetricEss ess, + Timedata timedata, + EnergyScheduleHandler.WithDifferentStates energyScheduleHandler) { + final var schedule = energyScheduleHandler.getSchedule(); + final JsonArray result; + if (schedule.isEmpty()) { + result = new JsonArray(); + } else { + final var historic = fromHistoricData(componentId, schedule.firstKey(), timedata); + final var future = fromSchedule(ess, schedule); + result = Stream.concat(historic, future) // + .collect(toJsonArray()); + } + + return new GetScheduleResponse(requestId, // + buildJsonObject() // + .add("schedule", result) // + .build()); + } + + /** + * Queries the last three hours' data and converts it to a {@link Stream} of + * {@link JsonObject}s suitable for a {@link GetScheduleResponse}. + * + * @param componentId Component-ID of {@link TimeOfUseTariffControllerImpl} + * @param firstSchedule {@link ZonedDateTime} of the first entry in the Schedule + * (rounded down to 15 minutes) + * @param timedata the {@link Timedata} + * @return {@link Stream} of {@link JsonObject}s + */ + // TODO protected is sufficient after v1 + public static Stream fromHistoricData(String componentId, ZonedDateTime firstSchedule, + Timedata timedata) { + // Process last three hours of historic data + final var fromTime = firstSchedule.minusHours(3); + final var toTime = firstSchedule.minusSeconds(1); + final var channelQuarterlyPrices = new ChannelAddress(componentId, "QuarterlyPrices"); + final var channelStateMachine = new ChannelAddress(componentId, "StateMachine"); + SortedMap> data = null; + try { + data = timedata.queryHistoricData(null, fromTime, toTime, // + Set.of(channelQuarterlyPrices, channelStateMachine, // + Utils.SUM_GRID, SUM_PRODUCTION, SUM_CONSUMPTION, SUM_ESS_DISCHARGE_POWER, SUM_ESS_SOC), + new Resolution(15, ChronoUnit.MINUTES)); + } catch (Exception e) { + LOG.warn("Unable to read historic data: " + e.getMessage()); + } + if (data == null) { + return Stream.of(); + } + + return data.entrySet().stream() // + .map(e -> { + var d = e.getValue(); + Function getter = (c) -> ofNullable(d.get(c)) + .orElse(JsonNull.INSTANCE); + + return buildJsonObject() // + .addProperty("timestamp", e.getKey()) // + .addProperty("price", + getAsOptionalDouble(getter.apply(channelQuarterlyPrices)).orElse(null)) // + .addProperty("state", + getAsOptionalInt(getter.apply(channelStateMachine)).orElse(BALANCING.getValue())) // + .addProperty("grid", getAsOptionalInt(getter.apply(SUM_GRID)).orElse(null)) // + .addProperty("production", getAsOptionalInt(getter.apply(SUM_PRODUCTION)).orElse(null)) // + .addProperty("consumption", getAsOptionalInt(getter.apply(SUM_CONSUMPTION)).orElse(null)) // + .addProperty("ess", getAsOptionalInt(getter.apply(SUM_ESS_DISCHARGE_POWER)).orElse(null)) // + .addProperty("soc", getAsOptionalInt(getter.apply(SUM_ESS_SOC)).orElse(null)) // + .build(); + }); + } + + /** + * Converts the Schedule to a {@link Stream} of {@link JsonObject}s suitable for + * a {@link GetScheduleResponse}. + * + * @param ess the {@link SymmetricEss} + * @param schedule the {@link EnergyScheduleHandler} schedule + * @return {@link Stream} of {@link JsonObject}s + */ + protected static Stream fromSchedule(SymmetricEss ess, + ImmutableSortedMap> schedule) { + final var essTotalEnergy = ess.getCapacity().orElse(0); + return schedule.entrySet().stream() // + .map(e -> { + var p = e.getValue(); + + return buildJsonObject() // + .addProperty("timestamp", e.getKey()) // + .addProperty("price", p.price()) // + .addProperty("state", p.state().getValue()) // + .addProperty("grid", toPower(p.energyFlow().getGrid())) // + .addProperty("production", toPower(p.energyFlow().getProd())) // + .addProperty("consumption", toPower(p.energyFlow().getCons())) // + .addProperty("ess", toPower(p.energyFlow().getEss())) // + .addProperty("soc", round(fitWithin(0F, 100F, // + p.essInitialEnergy() * 100F / essTotalEnergy))) // + .build(); + }); + } + + /** + * Creates an empty default Schedule in case no Schedule is available. + * + * @param clock the {@link Clock} + * @param defaultState the default {@link StateMachine} + * @return {@link Stream} of {@link JsonObject}s + */ + protected static Stream empty(Clock clock, StateMachine defaultState) { + final var now = ZonedDateTime.now(clock); + final var numberOfPeriods = 96; + + return IntStream.range(0, numberOfPeriods) // + .mapToObj(i -> { + return buildJsonObject() // + .addProperty("timestamp", now.plusMinutes(i * 15)) // + .add("price", JsonNull.INSTANCE) // + .addProperty("state", defaultState.getValue()) // + .add("grid", JsonNull.INSTANCE) // + .add("production", JsonNull.INSTANCE) // + .add("consumption", JsonNull.INSTANCE) // + .add("ess", JsonNull.INSTANCE) // + .add("soc", JsonNull.INSTANCE) // + .build(); + }); + } + +} diff --git a/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/jsonrpc/package-info.java b/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/jsonrpc/package-info.java new file mode 100644 index 00000000000..c3c8d61c926 --- /dev/null +++ b/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/jsonrpc/package-info.java @@ -0,0 +1,3 @@ +@org.osgi.annotation.versioning.Version("1.0.0") +@org.osgi.annotation.bundle.Export +package io.openems.edge.controller.ess.timeofusetariff.jsonrpc; diff --git a/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/v1/EnergyScheduleHandlerV1.java b/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/v1/EnergyScheduleHandlerV1.java new file mode 100644 index 00000000000..667738a293c --- /dev/null +++ b/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/v1/EnergyScheduleHandlerV1.java @@ -0,0 +1,90 @@ +package io.openems.edge.controller.ess.timeofusetariff.v1; + +import static io.openems.common.utils.DateUtils.roundDownToQuarter; + +import java.time.ZonedDateTime; +import java.util.List; +import java.util.Optional; +import java.util.function.Supplier; + +import com.google.common.collect.ImmutableSortedMap; + +import io.openems.edge.controller.ess.emergencycapacityreserve.ControllerEssEmergencyCapacityReserve; +import io.openems.edge.controller.ess.limiter14a.ControllerEssLimiter14a; +import io.openems.edge.controller.ess.limittotaldischarge.ControllerEssLimitTotalDischarge; +import io.openems.edge.controller.ess.timeofusetariff.ControlMode; +import io.openems.edge.controller.ess.timeofusetariff.StateMachine; +import io.openems.edge.ess.api.ManagedSymmetricEss; + +@Deprecated +public class EnergyScheduleHandlerV1 { + + public static record ContextV1(List ctrlEmergencyCapacityReserves, + List ctrlLimitTotalDischarges, + List ctrlLimiter14as, ManagedSymmetricEss ess, ControlMode controlMode, + int maxChargePowerFromGrid) { + } + + private final Supplier availableStates; + private final Supplier context; + + private ImmutableSortedMap> schedule = ImmutableSortedMap.of(); + + public EnergyScheduleHandlerV1(Supplier availableStates, Supplier context) { + this.availableStates = availableStates; + this.context = context; + } + + /** + * Gets the available States. + * + * @return an Array of States + */ + public StateMachine[] getAvailableStates() { + return this.availableStates.get(); + } + + /** + * Gets the Context. + * + * @return the Context + */ + public ContextV1 getContext() { + return this.context.get(); + } + + public static record Period(STATE state, Integer essChargeInChargeGrid) { + } + + /** + * Sets the Schedule. Called by Optimizer. + * + * @param schedule the Schedule + */ + public synchronized void setSchedule(ImmutableSortedMap> schedule) { + this.schedule = schedule; + } + + /** + * Gets the current State or null. + * + * @return the State or null + */ + public synchronized StateMachine getCurrentState() { + return Optional.ofNullable(this.schedule.get(roundDownToQuarter(ZonedDateTime.now()))) // + .map(Period::state) // + .orElse(null); + } + + /** + * Gets the current essChargeInChargeGrid or null. + * + * @return the essChargeInChargeGrid or null + */ + public synchronized Integer getCurrentEssChargeInChargeGrid() { + return Optional.ofNullable(this.schedule.get(roundDownToQuarter(ZonedDateTime.now()))) // + .map(Period::essChargeInChargeGrid) // + .orElse(null); + } + +} diff --git a/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/v1/UtilsV1.java b/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/v1/UtilsV1.java new file mode 100644 index 00000000000..7570929552c --- /dev/null +++ b/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/v1/UtilsV1.java @@ -0,0 +1,151 @@ +package io.openems.edge.controller.ess.timeofusetariff.v1; + +import static io.openems.edge.controller.ess.limiter14a.ControllerEssLimiter14a.ESS_LIMIT_14A_ENWG; +import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.BALANCING; +import static io.openems.edge.controller.ess.timeofusetariff.Utils.calculateChargeGridPower; +import static io.openems.edge.controller.ess.timeofusetariff.Utils.calculateDelayDischargePower; +import static io.openems.edge.controller.ess.timeofusetariff.Utils.postprocessRunState; +import static java.lang.Math.max; +import static java.util.stream.IntStream.concat; + +import java.util.List; +import java.util.Objects; + +import io.openems.edge.common.sum.Sum; +import io.openems.edge.controller.ess.emergencycapacityreserve.ControllerEssEmergencyCapacityReserve; +import io.openems.edge.controller.ess.limiter14a.ControllerEssLimiter14a; +import io.openems.edge.controller.ess.limittotaldischarge.ControllerEssLimitTotalDischarge; +import io.openems.edge.controller.ess.timeofusetariff.StateMachine; +import io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffController; +import io.openems.edge.controller.ess.timeofusetariff.Utils.ApplyState; +import io.openems.edge.ess.api.ManagedSymmetricEss; + +/** + * Utils for {@link TimeOfUseTariffController}. + * + *

+ * All energy values are in [Wh] and positive, unless stated differently. + */ +@Deprecated +public final class UtilsV1 { + + private UtilsV1() { + } + + public static final int PERIODS_PER_HOUR = 4; + + /** + * Returns the configured minimum SoC, or zero. + * + * @param ctrlLimitTotalDischarges the list of + * {@link ControllerEssLimitTotalDischarge} + * @param ctrlEmergencyCapacityReserves the list of + * {@link ControllerEssEmergencyCapacityReserve} + * @return the value in [%] + */ + public static int getEssMinSocPercentage(List ctrlLimitTotalDischarges, + List ctrlEmergencyCapacityReserves) { + return concat(// + ctrlLimitTotalDischarges.stream() // + .map(ctrl -> ctrl.getMinSoc().get()) // + .filter(Objects::nonNull) // + .mapToInt(v -> max(0, v)), // only positives + ctrlEmergencyCapacityReserves.stream() // + .map(ctrl -> ctrl.getActualReserveSoc().get()) // + .filter(Objects::nonNull) // + .mapToInt(v -> max(0, v))) // only positives + .max().orElse(0); + } + + /** + * Calculate Automatic Mode. + * + * @param esh the {@link EnergyScheduleHandlerV1} + * @param sum the {@link Sum} + * @param ess the {@link ManagedSymmetricEss} + * @param ctrlLimiter14as the list of {@link ControllerEssLimiter14a}s + * @param maxChargePowerFromGrid the configured max charge from grid power + * @return {@link ApplyState} + */ + public static ApplyState calculateAutomaticMode(EnergyScheduleHandlerV1 esh, Sum sum, ManagedSymmetricEss ess, + List ctrlLimiter14as, int maxChargePowerFromGrid) { + final var targetState = getCurrentPeriodState(esh); + final var essChargeInChargeGrid = esh.getCurrentEssChargeInChargeGrid(); + final var limitChargePowerFor14aEnWG = calculateLimitChargePowerFor14aEnWG(ctrlLimiter14as); + return calculateAutomaticMode(sum, ess, essChargeInChargeGrid, maxChargePowerFromGrid, + limitChargePowerFor14aEnWG, targetState); + } + + /** + * Calculate Automatic Mode. + * + * @param sum the {@link Sum} + * @param ess the {@link ManagedSymmetricEss} + * @param essChargeInChargeGrid ESS Charge Energy in CHARGE_GRID State [Wh] + * @param maxChargePowerFromGrid the configured max charge from grid power + * @param limitChargePowerFor14aEnWG Limit Charge Power for §14a EnWG + * @param targetState the scheduled target {@link StateMachine} + * @return {@link ApplyState} + */ + protected static ApplyState calculateAutomaticMode(Sum sum, ManagedSymmetricEss ess, Integer essChargeInChargeGrid, + int maxChargePowerFromGrid, int limitChargePowerFor14aEnWG, StateMachine targetState) { + final StateMachine actualState; + final Integer setPoint; + + var gridActivePower = sum.getGridActivePower().get(); // current buy-from/sell-to grid + var essActivePower = ess.getActivePower().get(); // current charge/discharge ESS + if (gridActivePower == null || essActivePower == null) { + // undefined state + return new ApplyState(BALANCING, null); + } + + // Post-process and get actual state + final var pwrBalancing = gridActivePower + essActivePower; + final var pwrDelayDischarge = calculateDelayDischargePower(ess); + final var pwrChargeGrid = max(limitChargePowerFor14aEnWG, calculateChargeGridPower(// + essChargeInChargeGrid, ess, essActivePower, gridActivePower, maxChargePowerFromGrid)); + actualState = postprocessRunState(ess, targetState, pwrBalancing, pwrDelayDischarge, pwrChargeGrid); + + // Get and apply ActivePower Less-or-Equals Set-Point + setPoint = switch (actualState) { + case BALANCING -> null; // delegate to next priority Controller + case DELAY_DISCHARGE -> pwrDelayDischarge; + case CHARGE_GRID -> pwrChargeGrid; + }; + + return new ApplyState(actualState, setPoint); + } + + /** + * Gets the current period state of the {@link EnergyScheduleHandlerV1} or + * {@link StateMachine#BALANCING}. + * + * @param esh the {@link EnergyScheduleHandlerV1} + * @return the {@link StateMachine} + */ + public static StateMachine getCurrentPeriodState(EnergyScheduleHandlerV1 esh) { + if (esh != null) { + var state = esh.getCurrentState(); + if (state != null) { + return state; + } + } + return BALANCING; // Default Fallback + } + + /** + * Calculates the limit for §14a EnWG. + * + * @param ctrlLimiter14as the list of {@link ControllerEssLimiter14a}s + * @return the (negative) charge value or {@link Integer#MIN_VALUE} for no limit + */ + public static int calculateLimitChargePowerFor14aEnWG(List ctrlLimiter14as) { + var isLimited = ctrlLimiter14as.stream() // + .map(c -> c.getRestrictionMode()) // + .anyMatch(r -> r == Boolean.TRUE); + if (!isLimited) { + return Integer.MIN_VALUE; + } + return ESS_LIMIT_14A_ENWG; // 4.2 kW + } +} diff --git a/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/v1/package-info.java b/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/v1/package-info.java new file mode 100644 index 00000000000..7c842e31832 --- /dev/null +++ b/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/v1/package-info.java @@ -0,0 +1,3 @@ +@org.osgi.annotation.versioning.Version("1.0.0") +@org.osgi.annotation.bundle.Export +package io.openems.edge.controller.ess.timeofusetariff.v1; diff --git a/io.openems.edge.controller.ess.timeofusetariff/test/io/openems/edge/controller/ess/timeofusetariff/MyConfig.java b/io.openems.edge.controller.ess.timeofusetariff/test/io/openems/edge/controller/ess/timeofusetariff/MyConfig.java index bc0c137b4a2..d71ec3bc20f 100644 --- a/io.openems.edge.controller.ess.timeofusetariff/test/io/openems/edge/controller/ess/timeofusetariff/MyConfig.java +++ b/io.openems.edge.controller.ess.timeofusetariff/test/io/openems/edge/controller/ess/timeofusetariff/MyConfig.java @@ -14,8 +14,6 @@ protected static class Builder { private ControlMode controlMode; private int essMaxChargePower; private int maxChargePowerFromGrid; - private boolean limitChargePowerFor14aEnWG; - private RiskLevel riskLevel; private Builder() { } @@ -55,16 +53,6 @@ public Builder setMaxChargePowerFromGrid(int maxChargePowerFromGrid) { return this; } - public Builder setRiskLevel(RiskLevel riskLevel) { - this.riskLevel = riskLevel; - return this; - } - - public Builder setLimitChargePowerFor14aEnWG(boolean limitChargePowerFor14aEnWG) { - this.limitChargePowerFor14aEnWG = limitChargePowerFor14aEnWG; - return this; - } - public MyConfig build() { return new MyConfig(this); } @@ -111,16 +99,6 @@ public int maxChargePowerFromGrid() { return this.builder.maxChargePowerFromGrid; } - @Override - public boolean limitChargePowerFor14aEnWG() { - return this.builder.limitChargePowerFor14aEnWG; - } - - @Override - public RiskLevel riskLevel() { - return this.builder.riskLevel; - } - @Override public String ess_target() { return ConfigUtils.generateReferenceTargetFilter(this.id(), this.ess_id()); diff --git a/io.openems.edge.controller.ess.timeofusetariff/test/io/openems/edge/controller/ess/timeofusetariff/TimeOfUseTariffControllerImplTest.java b/io.openems.edge.controller.ess.timeofusetariff/test/io/openems/edge/controller/ess/timeofusetariff/TimeOfUseTariffControllerImplTest.java index d3f32289678..469536f9d5a 100644 --- a/io.openems.edge.controller.ess.timeofusetariff/test/io/openems/edge/controller/ess/timeofusetariff/TimeOfUseTariffControllerImplTest.java +++ b/io.openems.edge.controller.ess.timeofusetariff/test/io/openems/edge/controller/ess/timeofusetariff/TimeOfUseTariffControllerImplTest.java @@ -1,70 +1,124 @@ package io.openems.edge.controller.ess.timeofusetariff; +import static io.openems.common.test.TestUtils.createDummyClock; import static io.openems.edge.controller.ess.timeofusetariff.ControlMode.CHARGE_CONSUMPTION; import static io.openems.edge.controller.ess.timeofusetariff.Mode.AUTOMATIC; -import static io.openems.edge.controller.ess.timeofusetariff.RiskLevel.MEDIUM; import java.time.Clock; -import java.time.Instant; -import java.time.ZoneOffset; import org.junit.Test; -import io.openems.common.test.TimeLeapClock; +import io.openems.common.jsonrpc.base.JsonrpcRequest; +import io.openems.common.jsonrpc.base.JsonrpcResponse; +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.common.jsonapi.Call; import io.openems.edge.common.sum.DummySum; import io.openems.edge.common.test.AbstractComponentTest.TestCase; +import io.openems.edge.common.test.AbstractDummyOpenemsComponent; import io.openems.edge.common.test.DummyComponentManager; import io.openems.edge.common.test.DummyConfigurationAdmin; +import io.openems.edge.controller.api.Controller; +import io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffControllerImpl.EshContext; import io.openems.edge.controller.test.ControllerTest; +import io.openems.edge.energy.api.EnergyScheduleHandler; +import io.openems.edge.energy.api.EnergyScheduler; +import io.openems.edge.energy.api.Version; +import io.openems.edge.ess.api.SymmetricEss; import io.openems.edge.ess.test.DummyManagedSymmetricEss; +import io.openems.edge.timedata.api.Timedata; import io.openems.edge.timedata.test.DummyTimedata; import io.openems.edge.timeofusetariff.test.DummyTimeOfUseTariffProvider; public class TimeOfUseTariffControllerImplTest { - public static final Clock CLOCK = new TimeLeapClock(Instant.parse("2020-03-04T14:19:00.00Z"), ZoneOffset.UTC); + private static class DummyEnergyScheduler extends AbstractDummyOpenemsComponent + implements EnergyScheduler { - private static final String CTRL_ID = "ctrl0"; + private final Version version; + + public DummyEnergyScheduler(Version version) { + super("_energy", "_energy", // + OpenemsComponent.ChannelId.values(), // + Controller.ChannelId.values()); + this.version = version; + } + + @Override + public JsonrpcResponse handleGetScheduleRequestV1(Call call, String id) { + return null; + } + + @Override + public Version getImplementationVersion() { + return this.version; + } + + @Override + protected DummyEnergyScheduler self() { + return this; + } + + } @Test public void test() throws Exception { - create(CLOCK); + final var clock = createDummyClock(); + create(clock, Version.V2_ENERGY_SCHEDULABLE, // + new DummyManagedSymmetricEss("ess0") // + .withSoc(60) // + .withCapacity(10000), // + new DummyTimedata("timedata0")) // + .deactivate(); } /** * Creates a {@link TimeOfUseTariffControllerImpl} instance. * - * @param clock a {@link Clock} + * @param clock a {@link Clock} + * @param version the {@link EnergyScheduler} implementation {@link Version} + * @param ess the {@link SymmetricEss} + * @param timedata the {@link Timedata} * @return the object * @throws Exception on error */ - public static TimeOfUseTariffControllerImpl create(Clock clock) throws Exception { + public static TimeOfUseTariffControllerImpl create(Clock clock, Version version, SymmetricEss ess, + Timedata timedata) throws Exception { var componentManager = new DummyComponentManager(clock); var sum = new DummySum(); var timeOfUseTariff = DummyTimeOfUseTariffProvider.empty(clock); + var energyScheduler = new DummyEnergyScheduler(version); var sut = new TimeOfUseTariffControllerImpl(); new ControllerTest(sut) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", componentManager) // - .addReference("timedata", new DummyTimedata("timedata0")) // + .addReference("energyScheduler", energyScheduler) // + .addReference("timedata", timedata) // .addReference("timeOfUseTariff", timeOfUseTariff) // .addReference("sum", sum) // - .addReference("ess", new DummyManagedSymmetricEss("ess0") // - .withSoc(60) // - .withCapacity(10000)) // + .addReference("ess", ess) // .activate(MyConfig.create() // - .setId(CTRL_ID) // + .setId("ctrl0") // .setEnabled(false) // .setEssId("ess0") // .setMode(AUTOMATIC) // .setControlMode(CHARGE_CONSUMPTION) // .setEssMaxChargePower(5000) // .setMaxChargePowerFromGrid(10000) // - .setLimitChargePowerFor14aEnWG(false) // - .setRiskLevel(MEDIUM) // .build()) // .next(new TestCase()); return sut; } + + /** + * Gets the {@link EnergyScheduleHandler}. + * + * @param ctrl the {@link TimeOfUseTariffControllerImpl} + * @return the object + * @throws Exception on error + */ + public static EnergyScheduleHandler.WithDifferentStates getEnergyScheduleHandler( + TimeOfUseTariffControllerImpl ctrl) throws Exception { + return ctrl.getEnergyScheduleHandler(); + } } diff --git a/io.openems.edge.controller.ess.timeofusetariff/test/io/openems/edge/controller/ess/timeofusetariff/UtilsTest.java b/io.openems.edge.controller.ess.timeofusetariff/test/io/openems/edge/controller/ess/timeofusetariff/UtilsTest.java index c7a104c3419..e24333dac4b 100644 --- a/io.openems.edge.controller.ess.timeofusetariff/test/io/openems/edge/controller/ess/timeofusetariff/UtilsTest.java +++ b/io.openems.edge.controller.ess.timeofusetariff/test/io/openems/edge/controller/ess/timeofusetariff/UtilsTest.java @@ -4,23 +4,44 @@ import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.BALANCING; import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.CHARGE_GRID; import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.DELAY_DISCHARGE; +import static io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffControllerImpl.applyDelayDischarge; import static io.openems.edge.controller.ess.timeofusetariff.Utils.calculateAutomaticMode; +import static io.openems.edge.controller.ess.timeofusetariff.Utils.calculateChargeEnergyInChargeGrid; import static io.openems.edge.controller.ess.timeofusetariff.Utils.calculateChargeGridPower; import static io.openems.edge.controller.ess.timeofusetariff.Utils.calculateDelayDischargePower; -import static io.openems.edge.controller.ess.timeofusetariff.Utils.calculateEssChargeInChargeGridPowerFromParams; +import static io.openems.edge.controller.ess.timeofusetariff.Utils.calculateEssChargeInChargeGridPower; import static io.openems.edge.controller.ess.timeofusetariff.Utils.calculateMaxChargeProductionPower; +import static io.openems.edge.controller.ess.timeofusetariff.Utils.postprocessSimulatorState; import static org.junit.Assert.assertEquals; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; + import org.junit.Test; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +import io.openems.common.test.TimeLeapClock; import io.openems.edge.common.sum.DummySum; +import io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffControllerImpl.EshContext; import io.openems.edge.controller.ess.timeofusetariff.Utils.ApplyState; +import io.openems.edge.energy.api.EnergyScheduleHandler; +import io.openems.edge.energy.api.RiskLevel; +import io.openems.edge.energy.api.simulation.EnergyFlow; +import io.openems.edge.energy.api.simulation.GlobalSimulationsContext; +import io.openems.edge.energy.api.simulation.GlobalSimulationsContext.Ess; +import io.openems.edge.energy.api.simulation.OneSimulationContext; import io.openems.edge.ess.api.SymmetricEss; import io.openems.edge.ess.test.DummyHybridEss; import io.openems.edge.ess.test.DummyManagedSymmetricEss; public class UtilsTest { + public static final TimeLeapClock CLOCK = new TimeLeapClock(Instant.ofEpochSecond(946684800), ZoneId.of("UTC")); + public static final ZonedDateTime TIME = ZonedDateTime.now(CLOCK); + @Test public void testCalculateChargeGridPower() { assertEquals(-10000, calculateChargeGridPower(null, // @@ -28,31 +49,20 @@ public void testCalculateChargeGridPower() { .withCapacity(20000), // /* essActivePower */ -6000, // /* gridActivePower */ 10000, // - /* maxChargePowerFromGrid */ 20000, // - /* limitChargePowerFor14aEnWG */ false)); - - assertEquals(-4200, calculateChargeGridPower(null, // - new DummyManagedSymmetricEss("ess0") // - .withCapacity(20000), // - /* essActivePower */ -6000, // - /* gridActivePower */ 10000, // - /* maxChargePowerFromGrid */ 20000, // - /* limitChargePowerFor14aEnWG */ true)); + /* maxChargePowerFromGrid */ 20000)); assertEquals(-11000, calculateChargeGridPower(null, // new DummyManagedSymmetricEss("ess0") // .withCapacity(20000), // /* essActivePower */ -6000, // /* gridActivePower */ 5000, // - /* maxChargePowerFromGrid */ 20000, // - /* limitChargePowerFor14aEnWG */ false)); + /* maxChargePowerFromGrid */ 20000)); assertEquals(-5860, calculateChargeGridPower(1340, // new DummyManagedSymmetricEss("ess0"), // /* essActivePower */ -1000, // /* gridActivePower */ 500, // - /* maxChargePowerFromGrid */ 24000, // - /* limitChargePowerFor14aEnWG */ false)); + /* maxChargePowerFromGrid */ 24000)); // Would be -3584, but limited to 5000 which is already surpassed // TODO if this should actually serve as blackout-protection, a positive value @@ -61,16 +71,14 @@ public void testCalculateChargeGridPower() { new DummyManagedSymmetricEss("ess0"), // /* essActivePower */ 1000, // /* gridActivePower */ 9000, // - /* maxChargePowerFromGrid */ 5000, // - /* limitChargePowerFor14aEnWG */ false)); + /* maxChargePowerFromGrid */ 5000)); assertEquals(-8360, calculateChargeGridPower(1340, // new DummyHybridEss("ess0") // .withDcDischargePower(-1500), // /* essActivePower */ -1000, // /* gridActivePower */ -2000, // - /* maxChargePowerFromGrid */ 24000, // - /* limitChargePowerFor14aEnWG */ false)); + /* maxChargePowerFromGrid */ 24000)); } @Test @@ -111,22 +119,28 @@ public void testCalculateDelayDischarge() { } @Test - public void testCalculateMaxChargeGridPowerFromParams() { + public void testCalculateMaxChargeGridPower() { final var ess = new DummyManagedSymmetricEss("ess0"); // No params, initial ESS - assertEquals(0, calculateEssChargeInChargeGridPowerFromParams(null, ess)); + assertEquals(0, calculateEssChargeInChargeGridPower(null, ess)); // No params, ESS with MaxApparentPower withValue(ess, SymmetricEss.ChannelId.MAX_APPARENT_POWER, 1000); - assertEquals(250, calculateEssChargeInChargeGridPowerFromParams(null, ess)); + assertEquals(250, calculateEssChargeInChargeGridPower(null, ess)); // No params, ESS with Capacity withValue(ess, SymmetricEss.ChannelId.CAPACITY, 15000); - assertEquals(7500, calculateEssChargeInChargeGridPowerFromParams(null, ess)); + assertEquals(7500, calculateEssChargeInChargeGridPower(null, ess)); // With params (22 kWh; but few Consumption) - assertEquals(5360, calculateEssChargeInChargeGridPowerFromParams(1340, ess)); + assertEquals(5360, calculateEssChargeInChargeGridPower(1340, ess)); + } + + private static EnergyScheduleHandler.WithDifferentStates.Period mockPeriod( + StateMachine state, int essChargeInChargeGrid) { + return new EnergyScheduleHandler.WithDifferentStates.Period(state, 0, + new EshContext(null, null, 0, 0, essChargeInChargeGrid), null, 0); } @Test @@ -135,19 +149,15 @@ public void testCalculateAutomaticMode() { calculateAutomaticMode(// new DummySum(), // new DummyManagedSymmetricEss("ess0"), // - /* essChargeInChargeGrid */ 1000, // /* maxChargePowerFromGrid */ 2000, // - /* limitChargePowerFor14aEnWG */ true, // - BALANCING)); + mockPeriod(BALANCING, /* essChargeInChargeGrid */ 1000))); assertEquals("Null-Check", new ApplyState(BALANCING, null), // calculateAutomaticMode(// new DummySum() // .withGridActivePower(100), // new DummyManagedSymmetricEss("ess0"), // - /* essChargeInChargeGrid */ 1000, // /* maxChargePowerFromGrid */ 2000, // - /* limitChargePowerFor14aEnWG */ true, // - BALANCING)); + mockPeriod(BALANCING, /* essChargeInChargeGrid */ 1000))); assertEquals("BALANCING", new ApplyState(BALANCING, null), // calculateAutomaticMode(// @@ -155,10 +165,8 @@ public void testCalculateAutomaticMode() { .withGridActivePower(100), // new DummyManagedSymmetricEss("ess0") // .withActivePower(500), // - /* essChargeInChargeGrid */ 1000, // /* maxChargePowerFromGrid */ 2000, // - /* limitChargePowerFor14aEnWG */ true, // - BALANCING)); + mockPeriod(BALANCING, /* essChargeInChargeGrid */ 1000))); assertEquals("DELAY_DISCHARGE stays DELAY_DISCHARGE", new ApplyState(DELAY_DISCHARGE, 0), // calculateAutomaticMode(// @@ -166,20 +174,17 @@ public void testCalculateAutomaticMode() { .withGridActivePower(100), // new DummyManagedSymmetricEss("ess0") // .withActivePower(500), // - /* essChargeInChargeGrid */ 1000, // /* maxChargePowerFromGrid */ 2000, // - /* limitChargePowerFor14aEnWG */ true, // - DELAY_DISCHARGE)); + mockPeriod(DELAY_DISCHARGE, /* essChargeInChargeGrid */ 1000))); + assertEquals("DELAY_DISCHARGE to BALANCING", new ApplyState(BALANCING, null), // calculateAutomaticMode(// new DummySum() // .withGridActivePower(-500), // new DummyManagedSymmetricEss("ess0") // .withActivePower(500), // - /* essChargeInChargeGrid */ 1000, // /* maxChargePowerFromGrid */ 2000, // - /* limitChargePowerFor14aEnWG */ true, // - DELAY_DISCHARGE)); + mockPeriod(DELAY_DISCHARGE, /* essChargeInChargeGrid */ 1000))); assertEquals("CHARGE_GRID stays CHARGE_GRID", new ApplyState(CHARGE_GRID, -1400), // calculateAutomaticMode(// @@ -187,29 +192,96 @@ public void testCalculateAutomaticMode() { .withGridActivePower(100), // new DummyManagedSymmetricEss("ess0") // .withActivePower(500), // - /* essChargeInChargeGrid */ 1000, // /* maxChargePowerFromGrid */ 2000, // - /* limitChargePowerFor14aEnWG */ true, // - CHARGE_GRID)); + mockPeriod(CHARGE_GRID, /* essChargeInChargeGrid */ 1000))); + assertEquals("CHARGE_GRID to DELAY_DISCHARGE", new ApplyState(DELAY_DISCHARGE, 0), // calculateAutomaticMode(// new DummySum() // .withGridActivePower(100), // new DummyManagedSymmetricEss("ess0") // .withActivePower(500), // - /* essChargeInChargeGrid */ 1000, // /* maxChargePowerFromGrid */ 400, // - /* limitChargePowerFor14aEnWG */ true, // - CHARGE_GRID)); + mockPeriod(CHARGE_GRID, /* essChargeInChargeGrid */ 1000))); + assertEquals("CHARGE_GRID to BALANCING", new ApplyState(BALANCING, null), // calculateAutomaticMode(// new DummySum() // .withGridActivePower(-500), // new DummyManagedSymmetricEss("ess0") // .withActivePower(500), // - /* essChargeInChargeGrid */ 1000, // /* maxChargePowerFromGrid */ 0, // - /* limitChargePowerFor14aEnWG */ true, // - CHARGE_GRID)); + mockPeriod(CHARGE_GRID, /* essChargeInChargeGrid */ 1000))); + } + + @Test + public void testCalculateChargeEnergyInChargeGrid() { + assertEquals(1436, calculateChargeEnergyInChargeGrid(// + new GlobalSimulationsContext(CLOCK, RiskLevel.MEDIUM, TIME, ImmutableList.of(), ImmutableList.of(), // + new GlobalSimulationsContext.Grid(0, 20000), // + new GlobalSimulationsContext.Ess(0, 12223, 5000, 5000), // + ImmutableMap.of(), // + ImmutableList.of()))); + + assertEquals(540, calculateChargeEnergyInChargeGrid(// + new GlobalSimulationsContext(CLOCK, RiskLevel.MEDIUM, TIME, ImmutableList.of(), ImmutableList.of(), // + new GlobalSimulationsContext.Grid(0, 20000), // + new GlobalSimulationsContext.Ess(0, 12223, 5000, 5000), // + ImmutableMap.of(), // + ImmutableList.of(// + new GlobalSimulationsContext.Period.Quarter(TIME, 0, 1000, 0), // + new GlobalSimulationsContext.Period.Quarter(TIME, 100, 1100, 0), // + new GlobalSimulationsContext.Period.Quarter(TIME, 200, 0, 0) // + )))); + + assertEquals(558, calculateChargeEnergyInChargeGrid(// + new GlobalSimulationsContext(CLOCK, RiskLevel.MEDIUM, TIME, ImmutableList.of(), ImmutableList.of(), // + new GlobalSimulationsContext.Grid(0, 20000), // + new GlobalSimulationsContext.Ess(0, 12223, 5000, 5000), // + ImmutableMap.of(), // + ImmutableList.of(// + new GlobalSimulationsContext.Period.Quarter(TIME, 0, 700, 123), // + new GlobalSimulationsContext.Period.Quarter(TIME, 100, 600, 123), // + new GlobalSimulationsContext.Period.Quarter(TIME, 200, 500, 125), // + new GlobalSimulationsContext.Period.Quarter(TIME, 300, 400, 126), // + new GlobalSimulationsContext.Period.Quarter(TIME, 400, 300, 123), // + new GlobalSimulationsContext.Period.Quarter(TIME, 500, 200, 122), // + new GlobalSimulationsContext.Period.Quarter(TIME, 600, 100, 121), // + new GlobalSimulationsContext.Period.Quarter(TIME, 700, 0, 121) // + )))); + + assertEquals(515, calculateChargeEnergyInChargeGrid(// + new GlobalSimulationsContext(CLOCK, RiskLevel.MEDIUM, TIME, ImmutableList.of(), ImmutableList.of(), // + new GlobalSimulationsContext.Grid(0, 20000), // + new GlobalSimulationsContext.Ess(0, 12223, 5000, 5000), // + ImmutableMap.of(), // + ImmutableList.of(// + new GlobalSimulationsContext.Period.Quarter(TIME, 0, 700, 120), // + new GlobalSimulationsContext.Period.Quarter(TIME, 100, 600, 121), // + new GlobalSimulationsContext.Period.Quarter(TIME, 200, 500, 122), // + new GlobalSimulationsContext.Period.Quarter(TIME, 300, 1140, 126), // + new GlobalSimulationsContext.Period.Quarter(TIME, 400, 1150, 125), // + new GlobalSimulationsContext.Period.Quarter(TIME, 500, 200, 122), // + new GlobalSimulationsContext.Period.Quarter(TIME, 600, 100, 121), // + new GlobalSimulationsContext.Period.Quarter(TIME, 700, 0, 121) // + )))); + } + + @Test + public void testPostprocessSimulatorState() { + var m = new EnergyFlow.Model(// + /* production */ 200, // + /* consumption */ 500, // + /* essMaxCharge */ 5000, // + /* essMaxDischarge */ 0, // + /* gridMaxBuy */ 4000, // + /* gridMaxSell */ 10000); + applyDelayDischarge(m); + var ef = m.solve(); + m.logMinMaxValues(); + var gsc = new GlobalSimulationsContext(CLOCK, RiskLevel.MEDIUM, TIME, null, null, null, new Ess(0, 0, 0, 0), + ImmutableMap.of(), null); + var osc = OneSimulationContext.from(gsc); + assertEquals(BALANCING, postprocessSimulatorState(osc, ef, null, DELAY_DISCHARGE)); } } diff --git a/io.openems.edge.controller.ess.timeofusetariff/test/io/openems/edge/controller/ess/timeofusetariff/jsonrpc/GetScheduleResponseTest.java b/io.openems.edge.controller.ess.timeofusetariff/test/io/openems/edge/controller/ess/timeofusetariff/jsonrpc/GetScheduleResponseTest.java new file mode 100644 index 00000000000..6a2ac539352 --- /dev/null +++ b/io.openems.edge.controller.ess.timeofusetariff/test/io/openems/edge/controller/ess/timeofusetariff/jsonrpc/GetScheduleResponseTest.java @@ -0,0 +1,246 @@ +package io.openems.edge.controller.ess.timeofusetariff.jsonrpc; + +import static io.openems.common.utils.DateUtils.roundDownToQuarter; +import static io.openems.common.utils.JsonUtils.getAsJsonArray; +import static io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffControllerImpl.applyBalancing; +import static io.openems.edge.controller.ess.timeofusetariff.UtilsTest.CLOCK; +import static io.openems.edge.controller.ess.timeofusetariff.jsonrpc.TestData.CONSUMPTION_PREDICTION_QUARTERLY; +import static io.openems.edge.controller.ess.timeofusetariff.jsonrpc.TestData.PAST_HOURLY_PRICES; +import static io.openems.edge.controller.ess.timeofusetariff.jsonrpc.TestData.PAST_SOC; +import static io.openems.edge.controller.ess.timeofusetariff.jsonrpc.TestData.PAST_STATES; +import static io.openems.edge.controller.ess.timeofusetariff.jsonrpc.TestData.PRODUCTION_888_20231106; +import static io.openems.edge.controller.ess.timeofusetariff.jsonrpc.TestData.PRODUCTION_PREDICTION_QUARTERLY; +import static org.junit.Assert.assertEquals; + +import java.time.ZonedDateTime; + +import org.junit.Test; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSortedMap; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.types.ChannelAddress; +import io.openems.common.utils.JsonUtils; +import io.openems.common.utils.UuidUtils; +import io.openems.edge.controller.ess.timeofusetariff.StateMachine; +import io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffControllerImplTest; +import io.openems.edge.controller.ess.timeofusetariff.Utils; +import io.openems.edge.energy.api.EnergyScheduleHandler.AbstractEnergyScheduleHandler; +import io.openems.edge.energy.api.EnergyScheduleHandler.WithDifferentStates.Period; +import io.openems.edge.energy.api.RiskLevel; +import io.openems.edge.energy.api.Version; +import io.openems.edge.energy.api.simulation.EnergyFlow; +import io.openems.edge.energy.api.simulation.GlobalSimulationsContext; +import io.openems.edge.ess.test.DummyManagedSymmetricEss; +import io.openems.edge.timedata.test.DummyTimedata; + +public class GetScheduleResponseTest { + + @Test + public void test() throws Exception { + final var now = roundDownToQuarter(ZonedDateTime.now(CLOCK)); + final var ess = new DummyManagedSymmetricEss("ess0") // + .withCapacity(10000); + final var model = new EnergyFlow.Model(// + /* production */ 2500, // + /* consumption */ 500, // + /* essMaxCharge */ 5000, // + /* essMaxDischarge */ 0, // + /* gridMaxBuy */ 4000, // + /* gridMaxSell */ 10000); + applyBalancing(model); + final var energyFlow = model.solve(); + + // Simulate historic data + final var timedata = new DummyTimedata("timedata0"); + final var fromDate = now.minusHours(3); + for (var i = 0; i < 12; i++) { + var quarter = fromDate.plusMinutes(i * 15); + timedata.add(quarter, new ChannelAddress("ctrl0", "QuarterlyPrices"), PAST_HOURLY_PRICES[i]); + timedata.add(quarter, new ChannelAddress("ctrl0", "StateMachine"), PAST_STATES[i]); + timedata.add(quarter, Utils.SUM_PRODUCTION, PRODUCTION_PREDICTION_QUARTERLY[i]); + timedata.add(quarter, Utils.SUM_CONSUMPTION, CONSUMPTION_PREDICTION_QUARTERLY[i]); + timedata.add(quarter, Utils.SUM_ESS_SOC, PAST_SOC[i]); + timedata.add(quarter, Utils.SUM_ESS_DISCHARGE_POWER, PRODUCTION_888_20231106[i]); + timedata.add(quarter, Utils.SUM_GRID, PRODUCTION_888_20231106[i]); + } + + // Simulate future Schedule + var ctrl = TimeOfUseTariffControllerImplTest.create(CLOCK, Version.V2_ENERGY_SCHEDULABLE, ess, timedata); + var esh = TimeOfUseTariffControllerImplTest.getEnergyScheduleHandler(ctrl); + ((AbstractEnergyScheduleHandler /* this is safe */) esh) + .initialize(new GlobalSimulationsContext(CLOCK, RiskLevel.MEDIUM, null, null, null, null, // + new GlobalSimulationsContext.Ess(0, 0, 0, 0), ImmutableMap.of(), ImmutableList.of())); + esh.applySchedule(ImmutableSortedMap.naturalOrder() // + .put(now.plusMinutes(0), new Period.Transition(1, 0.1, energyFlow, 5000)) // + .put(now.plusMinutes(15), new Period.Transition(0, 0.2, energyFlow, 6000)) // + .put(now.plusMinutes(30), new Period.Transition(0, 0.3, energyFlow, 7000)) // + .build()); + + final var gsr = GetScheduleResponse.from(UuidUtils.getNilUuid(), "ctrl0", CLOCK, ess, timedata, esh); + + var schedule = getAsJsonArray(gsr.getResult(), "schedule"); + + assertEquals(""" + [ + { + "timestamp": "1999-12-31T21:00:00Z", + "price": 158.0, + "state": 1, + "grid": 0, + "production": 0, + "consumption": 1021, + "ess": 0, + "soc": 60 + }, + { + "timestamp": "1999-12-31T21:15:00Z", + "price": 160.0, + "state": 1, + "grid": 0, + "production": 0, + "consumption": 1208, + "ess": 0, + "soc": 62 + }, + { + "timestamp": "1999-12-31T21:30:00Z", + "price": 171.0, + "state": 1, + "grid": 0, + "production": 0, + "consumption": 713, + "ess": 0, + "soc": 64 + }, + { + "timestamp": "1999-12-31T21:45:00Z", + "price": 174.0, + "state": 1, + "grid": 0, + "production": 0, + "consumption": 931, + "ess": 0, + "soc": 66 + }, + { + "timestamp": "1999-12-31T22:00:00Z", + "price": 161.0, + "state": 1, + "grid": 0, + "production": 0, + "consumption": 2847, + "ess": 0, + "soc": 65 + }, + { + "timestamp": "1999-12-31T22:15:00Z", + "price": 152.0, + "state": 3, + "grid": 0, + "production": 0, + "consumption": 2551, + "ess": 0, + "soc": 67 + }, + { + "timestamp": "1999-12-31T22:30:00Z", + "price": 120.0, + "state": 3, + "grid": 0, + "production": 0, + "consumption": 1558, + "ess": 0, + "soc": 70 + }, + { + "timestamp": "1999-12-31T22:45:00Z", + "price": 111.0, + "state": 1, + "grid": 0, + "production": 0, + "consumption": 1234, + "ess": 0, + "soc": 73 + }, + { + "timestamp": "1999-12-31T23:00:00Z", + "price": 105.0, + "state": 2, + "grid": 0, + "production": 0, + "consumption": 433, + "ess": 0, + "soc": 76 + }, + { + "timestamp": "1999-12-31T23:15:00Z", + "price": 105.0, + "state": 1, + "grid": 0, + "production": 0, + "consumption": 633, + "ess": 0, + "soc": 79 + }, + { + "timestamp": "1999-12-31T23:30:00Z", + "price": 74.0, + "state": 2, + "grid": 0, + "production": 0, + "consumption": 1355, + "ess": 0, + "soc": 83 + }, + { + "timestamp": "1999-12-31T23:45:00Z", + "price": 73.0, + "state": 2, + "grid": 0, + "production": 0, + "consumption": 606, + "ess": 0, + "soc": 87 + }, + { + "timestamp": "2000-01-01T00:00:00Z", + "price": 0.1, + "state": 0, + "grid": 0, + "production": 10000, + "consumption": 2000, + "ess": -8000, + "soc": 50 + }, + { + "timestamp": "2000-01-01T00:15:00Z", + "price": 0.2, + "state": 1, + "grid": 0, + "production": 10000, + "consumption": 2000, + "ess": -8000, + "soc": 60 + }, + { + "timestamp": "2000-01-01T00:30:00Z", + "price": 0.3, + "state": 1, + "grid": 0, + "production": 10000, + "consumption": 2000, + "ess": -8000, + "soc": 70 + } + ]""", JsonUtils.prettyToString(schedule)); + } + + @Test + public void testEmpty() throws OpenemsNamedException { + var response = GetScheduleResponse.empty(CLOCK, StateMachine.BALANCING).toList().get(0); + assertEquals(StateMachine.BALANCING.getValue(), JsonUtils.getAsInt(response, "state")); + } +} diff --git a/io.openems.edge.energy/test/io/openems/edge/energy/TestData.java b/io.openems.edge.controller.ess.timeofusetariff/test/io/openems/edge/controller/ess/timeofusetariff/jsonrpc/TestData.java similarity index 61% rename from io.openems.edge.energy/test/io/openems/edge/energy/TestData.java rename to io.openems.edge.controller.ess.timeofusetariff/test/io/openems/edge/controller/ess/timeofusetariff/jsonrpc/TestData.java index c89873819e7..a56e5a5097d 100644 --- a/io.openems.edge.energy/test/io/openems/edge/energy/TestData.java +++ b/io.openems.edge.controller.ess.timeofusetariff/test/io/openems/edge/controller/ess/timeofusetariff/jsonrpc/TestData.java @@ -1,31 +1,26 @@ -package io.openems.edge.energy; - -import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.BALANCING; -import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.DELAY_DISCHARGE; - -import io.openems.edge.controller.ess.timeofusetariff.StateMachine; +package io.openems.edge.controller.ess.timeofusetariff.jsonrpc; public class TestData { // Edge 888; 06.11.2023 - public static final Integer[] PRODUCTION_888_20231106 = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 12, 19, 24, 92, 301, 441, 653, 741, 1921, 1923, 1649, 2045, 2638, 3399, 4071, 4359, + protected static final Integer[] PRODUCTION_888_20231106 = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 12, 19, 24, 92, 301, 441, 653, 741, 1921, 1923, 1649, 2045, 2638, 3399, 4071, 4359, 4516, 5541, 6993, 6292, 3902, 7700, 9098, 9555, 8119, 6868, 6560, 6380, 6193, 5389, 4349, 3743, 5367, 5319, 4383, 2243, 1122, 1315, 1107, 268, 48, 2, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - public static final Integer[] CONSUMPTION_888_20231106 = { 308, 285, 384, 471, 480, 385, 464, 448, 288, 333, 346, + protected static final Integer[] CONSUMPTION_888_20231106 = { 308, 285, 384, 471, 480, 385, 464, 448, 288, 333, 346, 313, 1786, 332, 300, 259, 373, 358, 279, 308, 309, 415, 392, 299, 2913, 3105, 4416, 4442, 497, 5910, 4106, 2171, 3898, 922, 1601, 1088, 303, 2384, 430, 2428, 2899, 371, 613, 1663, 366, 2072, 456, 1589, 2004, 488, 199, 1628, 613, 198, 1796, 202, 1180, 4975, 4493, 5511, 7757, 2926, 2640, 4335, 2630, 2799, 5111, 2979, 3062, 4842, 4194, 4474, 4750, 4876, 1238, 1395, 1425, 1123, 3366, 4088, 418, 436, 3234, 1504, 1092, 1853, 365, 628, 2095, 552, 1113, 1808, 3223, 1629, 1329, 264 }; - public static final Double[] PRICES_888_20231106 = { 155., 152., 152., 152., 157., 172., 238., 266., 266., 241., + protected static final Double[] PRICES_888_20231106 = { 155., 152., 152., 152., 157., 172., 238., 266., 266., 241., 224., 219., 221., 232., 248., 271., 286., 316., 332., 318., 284., 278., 270., 257. }; - public static final Integer[] PRODUCTION_PREDICTION_QUARTERLY = { + protected static final Integer[] PRODUCTION_PREDICTION_QUARTERLY = { /* 00:00-03:45 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // /* 04:00-07:45 */ @@ -52,8 +47,7 @@ public class TestData { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // }; - public static final Integer[] CONSUMPTION_PREDICTION_QUARTERLY = { - + protected static final Integer[] CONSUMPTION_PREDICTION_QUARTERLY = { /* 00:00-03:450 */ 1021, 1208, 713, 931, 2847, 2551, 1558, 1234, 433, 633, 1355, 606, 430, 1432, 1121, 502, // /* 04:00-07:45 */ @@ -80,7 +74,7 @@ public class TestData { 3226, 2358, 1778, 1002, 455, 654, 534, 1587, 1638, 459, 330, 258, 368, 728, 1096, 878 // }; - public static final Double[] HOURLY_PRICES_SUMMER = { // + protected static final Double[] HOURLY_PRICES_SUMMER = { // 70.95, 71.98, 71.95, 74.96, // 78.93, 80., 84.01, 111.03, // 105.04, 105., 74.23, 73.28, // @@ -89,35 +83,19 @@ public class TestData { 149.99, 157.43, 130.9, 120.14 // }; - public static final StateMachine[] STATES = { // - BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, DELAY_DISCHARGE, - BALANCING, DELAY_DISCHARGE, DELAY_DISCHARGE, DELAY_DISCHARGE, DELAY_DISCHARGE, DELAY_DISCHARGE, - DELAY_DISCHARGE, DELAY_DISCHARGE, DELAY_DISCHARGE, DELAY_DISCHARGE, DELAY_DISCHARGE, DELAY_DISCHARGE, - DELAY_DISCHARGE, DELAY_DISCHARGE, DELAY_DISCHARGE, DELAY_DISCHARGE, DELAY_DISCHARGE, DELAY_DISCHARGE, - DELAY_DISCHARGE, DELAY_DISCHARGE, DELAY_DISCHARGE, BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, - BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, - BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, - BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, - BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, - BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, - BALANCING, BALANCING, BALANCING, DELAY_DISCHARGE, DELAY_DISCHARGE, DELAY_DISCHARGE, DELAY_DISCHARGE, - DELAY_DISCHARGE, DELAY_DISCHARGE, DELAY_DISCHARGE, DELAY_DISCHARGE, DELAY_DISCHARGE, DELAY_DISCHARGE, - DELAY_DISCHARGE, DELAY_DISCHARGE, DELAY_DISCHARGE // - }; - - public static final Integer[] PAST_STATES = { // + protected static final Integer[] PAST_STATES = { // 1, 1, 1, 1, // 1, 3, 3, 1, // 2, 1, 2, 2, // }; - public static final Integer[] PAST_SOC = { // + protected static final Integer[] PAST_SOC = { // 60, 62, 64, 66, // 65, 67, 70, 73, // 76, 79, 83, 87, // }; - public static final Integer[] PAST_HOURLY_PRICES = { // + protected static final Integer[] PAST_HOURLY_PRICES = { // 158, 160, 171, 174, // 161, 152, 120, 111, // 105, 105, 74, 73, // diff --git a/io.openems.edge.controller.ess.timeofusetariff/test/io/openems/edge/controller/ess/timeofusetariff/v1/UtilsV1Test.java b/io.openems.edge.controller.ess.timeofusetariff/test/io/openems/edge/controller/ess/timeofusetariff/v1/UtilsV1Test.java new file mode 100644 index 00000000000..68379b42d5a --- /dev/null +++ b/io.openems.edge.controller.ess.timeofusetariff/test/io/openems/edge/controller/ess/timeofusetariff/v1/UtilsV1Test.java @@ -0,0 +1,135 @@ +package io.openems.edge.controller.ess.timeofusetariff.v1; + +import static io.openems.edge.controller.ess.limiter14a.ControllerEssLimiter14a.ESS_LIMIT_14A_ENWG; +import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.BALANCING; +import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.CHARGE_GRID; +import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.DELAY_DISCHARGE; +import static io.openems.edge.controller.ess.timeofusetariff.v1.UtilsV1.calculateAutomaticMode; +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +import io.openems.edge.common.sum.DummySum; +import io.openems.edge.controller.ess.timeofusetariff.Utils.ApplyState; +import io.openems.edge.ess.test.DummyManagedSymmetricEss; + +@SuppressWarnings("deprecation") +public class UtilsV1Test { + + @Test + public void testCalculateAutomaticMode() { + assertEquals("Null-Check", new ApplyState(BALANCING, null), // + calculateAutomaticMode(// + new DummySum(), // + new DummyManagedSymmetricEss("ess0"), // + /* essChargeInChargeGrid */ 1000, // + /* maxChargePowerFromGrid */ 2000, // + /* limitChargePowerFor14aEnWG */ ESS_LIMIT_14A_ENWG, // + BALANCING)); + assertEquals("Null-Check", new ApplyState(BALANCING, null), // + calculateAutomaticMode(// + new DummySum() // + .withGridActivePower(100), // + new DummyManagedSymmetricEss("ess0"), // + /* essChargeInChargeGrid */ 1000, // + /* maxChargePowerFromGrid */ 2000, // + /* limitChargePowerFor14aEnWG */ ESS_LIMIT_14A_ENWG, // + BALANCING)); + + assertEquals("BALANCING", new ApplyState(BALANCING, null), // + calculateAutomaticMode(// + new DummySum() // + .withGridActivePower(100), // + new DummyManagedSymmetricEss("ess0") // + .withActivePower(500), // + /* essChargeInChargeGrid */ 1000, // + /* maxChargePowerFromGrid */ 2000, // + /* limitChargePowerFor14aEnWG */ ESS_LIMIT_14A_ENWG, // + BALANCING)); + + assertEquals("DELAY_DISCHARGE stays DELAY_DISCHARGE", new ApplyState(DELAY_DISCHARGE, 0), // + calculateAutomaticMode(// + new DummySum() // + .withGridActivePower(100), // + new DummyManagedSymmetricEss("ess0") // + .withActivePower(500), // + /* essChargeInChargeGrid */ 1000, // + /* maxChargePowerFromGrid */ 2000, // + /* limitChargePowerFor14aEnWG */ ESS_LIMIT_14A_ENWG, // + DELAY_DISCHARGE)); + assertEquals("DELAY_DISCHARGE to BALANCING", new ApplyState(BALANCING, null), // + calculateAutomaticMode(// + new DummySum() // + .withGridActivePower(-500), // + new DummyManagedSymmetricEss("ess0") // + .withActivePower(500), // + /* essChargeInChargeGrid */ 1000, // + /* maxChargePowerFromGrid */ 2000, // + /* limitChargePowerFor14aEnWG */ ESS_LIMIT_14A_ENWG, // + DELAY_DISCHARGE)); + + assertEquals("CHARGE_GRID stays CHARGE_GRID", new ApplyState(CHARGE_GRID, -1400), // + calculateAutomaticMode(// + new DummySum() // + .withGridActivePower(100), // + new DummyManagedSymmetricEss("ess0") // + .withActivePower(500), // + /* essChargeInChargeGrid */ 1000, // + /* maxChargePowerFromGrid */ 2000, // + /* limitChargePowerFor14aEnWG */ ESS_LIMIT_14A_ENWG, // + CHARGE_GRID)); + assertEquals("CHARGE_GRID to DELAY_DISCHARGE", new ApplyState(DELAY_DISCHARGE, 0), // + calculateAutomaticMode(// + new DummySum() // + .withGridActivePower(100), // + new DummyManagedSymmetricEss("ess0") // + .withSoc(93) // + .withActivePower(500), // + /* essChargeInChargeGrid */ 1000, // + /* maxChargePowerFromGrid */ 400, // + /* limitChargePowerFor14aEnWG */ ESS_LIMIT_14A_ENWG, // + CHARGE_GRID)); + assertEquals("CHARGE_GRID to DELAY_DISCHARGE", new ApplyState(DELAY_DISCHARGE, 0), // + calculateAutomaticMode(// + new DummySum() // + .withGridActivePower(100), // + new DummyManagedSymmetricEss("ess0") // + .withActivePower(500) // + .withSoc(94), // + /* essChargeInChargeGrid */ 1000, // + /* maxChargePowerFromGrid */ 1000, // + /* limitChargePowerFor14aEnWG */ ESS_LIMIT_14A_ENWG, // + CHARGE_GRID)); + assertEquals("CHARGE_GRID to BALANCING", new ApplyState(BALANCING, null), // + calculateAutomaticMode(// + new DummySum() // + .withGridActivePower(-500), // + new DummyManagedSymmetricEss("ess0") // + .withActivePower(500), // + /* essChargeInChargeGrid */ 1000, // + /* maxChargePowerFromGrid */ 0, // + /* limitChargePowerFor14aEnWG */ ESS_LIMIT_14A_ENWG, // + CHARGE_GRID)); + + assertEquals("CHARGE_GRID with §14a EnWG limit", new ApplyState(CHARGE_GRID, -4200), // + calculateAutomaticMode(// + new DummySum() // + .withGridActivePower(100), // + new DummyManagedSymmetricEss("ess0") // + .withActivePower(500), // + /* essChargeInChargeGrid */ 6000, // + /* maxChargePowerFromGrid */ 7000, // + /* limitChargePowerFor14aEnWG */ ESS_LIMIT_14A_ENWG, // + CHARGE_GRID)); + assertEquals("CHARGE_GRID without §14a EnWG limit", new ApplyState(CHARGE_GRID, -6400), // + calculateAutomaticMode(// + new DummySum() // + .withGridActivePower(100), // + new DummyManagedSymmetricEss("ess0") // + .withActivePower(500), // + /* essChargeInChargeGrid */ 6000, // + /* maxChargePowerFromGrid */ 7000, // + /* limitChargePowerFor14aEnWG */ Integer.MIN_VALUE, // + CHARGE_GRID)); + } +} diff --git a/io.openems.edge.controller.evcs.fixactivepower/.classpath b/io.openems.edge.controller.evcs.fixactivepower/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.controller.evcs.fixactivepower/.classpath +++ b/io.openems.edge.controller.evcs.fixactivepower/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.controller.evcs.fixactivepower/bnd.bnd b/io.openems.edge.controller.evcs.fixactivepower/bnd.bnd index 98e4180dcd7..ba83284def5 100644 --- a/io.openems.edge.controller.evcs.fixactivepower/bnd.bnd +++ b/io.openems.edge.controller.evcs.fixactivepower/bnd.bnd @@ -8,7 +8,8 @@ Bundle-Version: 1.0.0.${tstamp} io.openems.common,\ io.openems.edge.common,\ io.openems.edge.controller.api,\ - io.openems.edge.evcs.api + io.openems.edge.evcs.api,\ + io.openems.edge.meter.api,\ -testpath: \ ${testpath} diff --git a/io.openems.edge.controller.evcs.fixactivepower/test/io/openems/edge/controller/evcs/fixactivepower/ControllerEvcsFixActivePowerImplTest.java b/io.openems.edge.controller.evcs.fixactivepower/test/io/openems/edge/controller/evcs/fixactivepower/ControllerEvcsFixActivePowerImplTest.java index e3f8ed55368..85c46e49269 100644 --- a/io.openems.edge.controller.evcs.fixactivepower/test/io/openems/edge/controller/evcs/fixactivepower/ControllerEvcsFixActivePowerImplTest.java +++ b/io.openems.edge.controller.evcs.fixactivepower/test/io/openems/edge/controller/evcs/fixactivepower/ControllerEvcsFixActivePowerImplTest.java @@ -7,19 +7,16 @@ public class ControllerEvcsFixActivePowerImplTest { - private static final String CTRL_ID = "ctrl0"; - private static final String EVCS_ID = "evcs0"; - @Test public void test() throws Exception { new ControllerTest(new ControllerEvcsFixActivePowerImpl()) // .addReference("componentManager", new DummyComponentManager()) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEvcsId(EVCS_ID) // + .setId("ctrl0") // + .setEvcsId("evcs0") // .setPower(0) // .setUpdateFrequency(1) // - .build()); // - ; + .build()) // + .deactivate(); } } diff --git a/io.openems.edge.controller.evcs/.classpath b/io.openems.edge.controller.evcs/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.controller.evcs/.classpath +++ b/io.openems.edge.controller.evcs/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.controller.evcs/bnd.bnd b/io.openems.edge.controller.evcs/bnd.bnd index c3a69ba9f20..f4a1c2c3874 100644 --- a/io.openems.edge.controller.evcs/bnd.bnd +++ b/io.openems.edge.controller.evcs/bnd.bnd @@ -8,8 +8,11 @@ Bundle-Version: 1.0.0.${tstamp} io.openems.common,\ io.openems.edge.common,\ io.openems.edge.controller.api,\ + io.openems.edge.energy.api,\ io.openems.edge.ess.api,\ - io.openems.edge.evcs.api + io.openems.edge.evcs.api,\ + io.openems.edge.meter.api,\ -testpath: \ - ${testpath} + ${testpath},\ + org.apache.commons.math3,\ diff --git a/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/ChargingLowerThanTargetHandler.java b/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/ChargingLowerThanTargetHandler.java index 418c50c5e0d..f052d2615bd 100644 --- a/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/ChargingLowerThanTargetHandler.java +++ b/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/ChargingLowerThanTargetHandler.java @@ -64,7 +64,7 @@ protected boolean isLower(ManagedEvcs evcs) throws InvalidValueException { * @throws InvalidValueException invalidValueException */ protected boolean isChargingLowerThanTarget(ManagedEvcs evcs) throws InvalidValueException { - int chargePower = evcs.getChargePower().orElse(0); + int chargePower = evcs.getActivePower().orElse(0); int chargePowerTarget = evcs.getSetChargePowerLimit().orElse(evcs.getMaximumHardwarePower().getOrError()); if (chargePowerTarget - chargePower > chargePowerTarget * CHARGING_TARGET_MAX_DIFFERENCE_PERCENT) { diff --git a/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/Config.java b/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/Config.java index bfda174afa0..bcd03b9d7b4 100644 --- a/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/Config.java +++ b/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/Config.java @@ -19,6 +19,13 @@ @AttributeDefinition(name = "Is enabled?", description = "Is this Component enabled?") boolean enabled() default true; + // TODO this will change in future + @AttributeDefinition(name = "Enable EnergyScheduler SMART-Mode", description = "") + boolean smartMode() default false; + + @AttributeDefinition(name = "JSON Configuration for SMART mode", description = "") + String smartConfig() default ""; + @AttributeDefinition(name = "Debug Mode", description = "Activates the debug mode") boolean debugMode() default false; @@ -43,6 +50,12 @@ @AttributeDefinition(name = "Energy limit in this session in [Wh]", description = "Set the Energylimit in this Session in Wh. The charging station will only charge till this limit; '0' is no limit.") int energySessionLimit() default 0; + @AttributeDefinition(name = "Minimum charging time while charging with excess power", description = "Minimum time (Seconds) is applied to avoid continuous switching between charging and not charging") + int excessChargeHystersis() default 120; + + @AttributeDefinition(name = "Minimum pause time while charging with excess power", description = "Minimum time (Seconds) is applied to avoid continuous switching between charging and not charging") + int excessChargePauseHysteresis() default 30; + @AttributeDefinition(name = "Evcs target filter", description = "This is auto-generated by 'Evcs-ID'.") String evcs_target() default "(enabled=true)"; diff --git a/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/ControllerEvcs.java b/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/ControllerEvcs.java index 5a4a779dd4f..8c33df85291 100644 --- a/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/ControllerEvcs.java +++ b/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/ControllerEvcs.java @@ -1,14 +1,23 @@ package io.openems.edge.controller.evcs; -import io.openems.common.types.OpenemsType; +import static io.openems.common.channel.PersistencePriority.HIGH; +import static io.openems.common.types.OpenemsType.BOOLEAN; + +import io.openems.common.channel.Level; import io.openems.edge.common.channel.Doc; +import io.openems.edge.common.channel.StateChannel; +import io.openems.edge.common.component.OpenemsComponent; -public interface ControllerEvcs { +public interface ControllerEvcs extends OpenemsComponent { public enum ChannelId implements io.openems.edge.common.channel.ChannelId { - AWAITING_HYSTERESIS(Doc.of(OpenemsType.BOOLEAN)) // - ; // + AWAITING_HYSTERESIS(Doc.of(BOOLEAN) // + .persistencePriority(HIGH)), // + SMART_MODE(Doc.of(SmartMode.values()) // + .persistencePriority(HIGH)), // + EVCS_IS_READ_ONLY(Doc.of(Level.INFO) // + .translationKey(ControllerEvcs.class, "evcsIsReadOnly")); // private final Doc doc; @@ -22,4 +31,17 @@ public Doc doc() { } } -} \ No newline at end of file + public default void setEvcsIsReadOnlyChannel(boolean val) { + this.getEvcsIsReadOnlyChannel().setNextValue(val); + } + + /** + * Gets the Channel for {@link ChannelId#EVCS_IS_READ_ONLY}. + * + * @return the Channel + */ + public default StateChannel getEvcsIsReadOnlyChannel() { + return this.channel(ChannelId.EVCS_IS_READ_ONLY); + } + +} diff --git a/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/ControllerEvcsImpl.java b/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/ControllerEvcsImpl.java index a4aeb209438..5fea3d38e06 100644 --- a/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/ControllerEvcsImpl.java +++ b/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/ControllerEvcsImpl.java @@ -1,7 +1,17 @@ package io.openems.edge.controller.evcs; +import static io.openems.common.utils.FunctionUtils.doNothing; +import static io.openems.edge.controller.evcs.Utils.FORCE_CHARGE_POWER; +import static io.openems.edge.controller.evcs.Utils.MIN_CHARGE_POWER; +import static io.openems.edge.controller.evcs.Utils.buildEshManual; +import static io.openems.edge.controller.evcs.Utils.buildEshSmart; +import static java.lang.Math.max; + import java.io.IOException; import java.time.Clock; +import java.time.Duration; +import java.time.Instant; +import java.util.function.BiConsumer; import org.osgi.service.cm.Configuration; import org.osgi.service.cm.ConfigurationAdmin; @@ -19,13 +29,26 @@ import io.openems.common.channel.AccessMode; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.exceptions.OpenemsException; +import io.openems.edge.common.channel.value.Value; import io.openems.edge.common.component.AbstractOpenemsComponent; import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.common.jsonapi.ComponentJsonApi; +import io.openems.edge.common.jsonapi.JsonApiBuilder; import io.openems.edge.common.modbusslave.ModbusSlave; import io.openems.edge.common.modbusslave.ModbusSlaveTable; import io.openems.edge.common.sum.Sum; import io.openems.edge.controller.api.Controller; +import io.openems.edge.controller.evcs.Utils.EshContext.EshManualContext; +import io.openems.edge.controller.evcs.Utils.EshContext.EshSmartContext; +import io.openems.edge.controller.evcs.jsonrpc.GetScheduleRequest; +import io.openems.edge.controller.evcs.jsonrpc.GetScheduleResponse; +import io.openems.edge.energy.api.EnergySchedulable; +import io.openems.edge.energy.api.EnergyScheduleHandler; +import io.openems.edge.energy.api.EnergyScheduleHandler.WithDifferentStates.Period; +import io.openems.edge.evcs.api.ChargeMode; +import io.openems.edge.evcs.api.ChargeState; import io.openems.edge.evcs.api.ManagedEvcs; +import io.openems.edge.evcs.api.Status; @Designate(ocd = Config.class, factory = true) @Component(// @@ -33,13 +56,27 @@ immediate = true, // configurationPolicy = ConfigurationPolicy.REQUIRE // ) -public class ControllerEvcsImpl extends AbstractOpenemsComponent implements Controller, OpenemsComponent, ModbusSlave { +public class ControllerEvcsImpl extends AbstractOpenemsComponent + implements Controller, ControllerEvcs, EnergySchedulable, OpenemsComponent, ModbusSlave, ComponentJsonApi { private static final int CHARGE_POWER_BUFFER = 200; private static final double DEFAULT_UPPER_TARGET_DIFFERENCE_PERCENT = 0.10; // 10% private final Logger log = LoggerFactory.getLogger(ControllerEvcsImpl.class); private final ChargingLowerThanTargetHandler chargingLowerThanTargetHandler; + private final Clock clock; + + private EnergyScheduleHandler energyScheduleHandler; + private BiConsumer, Value> onEvcsStatusChange; + + // Time of last charge power change, used for the hysteresis + private Instant lastInitialCharge = Instant.MIN; + + // Time of last charge pause, used for the hysteresis + private Instant lastChargePause = Instant.MIN; + + // Last charge power, used for the hysteresis + private int lastChargePower = 0; @Reference private ConfigurationAdmin cm; @@ -62,6 +99,7 @@ protected ControllerEvcsImpl(Clock clock) { Controller.ChannelId.values(), // ControllerEvcs.ChannelId.values() // ); + this.clock = clock; this.chargingLowerThanTargetHandler = new ChargingLowerThanTargetHandler(clock); } @@ -79,17 +117,29 @@ private void activate(ComponentContext context, Config config) throws OpenemsNam } this.config = config; + + this.energyScheduleHandler = config.smartMode() // + ? buildEshSmart(() -> EshSmartContext.fromConfig(this.config)) // + : buildEshManual(() -> EshManualContext.fromConfig(this.config)); + this.onEvcsStatusChange = (oldStatus, newStatus) -> { + // Trigger Reschedule on Status change + this.energyScheduleHandler + .triggerReschedule("ControllerEvcsImpl::onEvcsStatusChange from " + oldStatus + " to " + newStatus); + }; + this.evcs._setChargeMode(config.chargeMode()); if (OpenemsComponent.updateReferenceFilter(this.cm, this.servicePid(), "evcs", config.evcs_id())) { return; } this.evcs._setMaximumPower(null); + this.evcs.getStatusChannel().onChange(this.onEvcsStatusChange); } @Override @Deactivate protected void deactivate() { + this.evcs.getStatusChannel().removeOnChangeCallback(this.onEvcsStatusChange); super.deactivate(); } @@ -99,6 +149,11 @@ protected void deactivate() { */ @Override public void run() throws OpenemsNamedException { + if (this.evcs.isReadOnly()) { + this.setEvcsIsReadOnlyChannel(true); + return; + } + this.setEvcsIsReadOnlyChannel(false); final var isClustered = this.evcs.getIsClustered().orElse(false); @@ -130,21 +185,44 @@ public void run() throws OpenemsNamedException { this.resetMinMaxChannels(); return; } - case CHARGING_REJECTED, READY_FOR_CHARGING, CHARGING_FINISHED -> { - this.evcs._setMaximumPower(null); - } - case CHARGING -> { - } + case CHARGING_REJECTED, READY_FOR_CHARGING // + -> this.evcs._setMaximumPower(null); + case CHARGING // + -> doNothing(); } } + // Read parameters from Config or scheduled Period + final SmartMode smartMode; + final ChargeMode chargeMode; + final Priority priority; + final int forceChargePower; + final int defaultChargeMinPower; + var p = getCurrentPeriod(this.energyScheduleHandler); + if (p != null) { + this.logInfo(this.log, "ESH: " + p); + smartMode = p.state(); + chargeMode = p.state().chargeMode; + priority = p.state().priority; + forceChargePower = FORCE_CHARGE_POWER; + defaultChargeMinPower = MIN_CHARGE_POWER; + } else { + smartMode = null; + chargeMode = this.config.chargeMode(); + priority = this.config.priority(); + forceChargePower = this.config.forceChargeMinPower() * this.evcs.getPhasesAsInt(); + defaultChargeMinPower = this.config.defaultChargeMinPower(); + } + this.channel(ControllerEvcs.ChannelId.SMART_MODE).setNextValue(smartMode); + /* * Calculates the next charging power depending on the charge mode and priority */ - var nextChargePower = // - switch (this.config.chargeMode()) { + var nextChargePower = chargeMode == null // + ? 0 // + : switch (chargeMode) { case EXCESS_POWER -> // - switch (this.config.priority()) { + switch (priority) { case CAR -> calculateChargePowerFromExcessPower(this.sum, this.evcs); case STORAGE -> { // SoC > 97 % or always, when there is no ESS is available @@ -155,17 +233,18 @@ public void run() throws OpenemsNamedException { } } }; - case FORCE_CHARGE -> this.config.forceChargeMinPower() * this.evcs.getPhasesAsInt(); + case FORCE_CHARGE -> forceChargePower; }; - var nextMinPower = // - switch (this.config.chargeMode()) { - case EXCESS_POWER -> this.config.defaultChargeMinPower(); + var nextMinPower = chargeMode == null // + ? 0 // + : switch (chargeMode) { + case EXCESS_POWER -> defaultChargeMinPower; case FORCE_CHARGE -> 0; }; this.evcs._setMinimumPower(nextMinPower); - nextChargePower = Math.max(nextChargePower, nextMinPower); + nextChargePower = max(nextChargePower, nextMinPower); // Charging under minimum hardware power isn't possible var minimumHardwarePower = this.evcs.getMinimumHardwarePower().orElse(0); @@ -178,7 +257,7 @@ public void run() throws OpenemsNamedException { */ if (nextChargePower != 0) { - int chargePower = this.evcs.getChargePower().orElse(0); + int activePower = this.evcs.getActivePower().orElse(0); /** * Check the difference of the current charge power and the previous charging @@ -196,15 +275,20 @@ public void run() throws OpenemsNamedException { int currMax = this.evcs.getMaximumPower().orElse(0); /** - * If the charge power would increases again above the current maximum power, it - * resets the maximum Power. + * If the power would increases again above the current maximum power, it resets + * the maximum Power. */ - if (chargePower > currMax * (1 + DEFAULT_UPPER_TARGET_DIFFERENCE_PERCENT)) { + if (activePower > currMax * (1 + DEFAULT_UPPER_TARGET_DIFFERENCE_PERCENT)) { this.evcs._setMaximumPower(null); } } } + if (chargeMode == ChargeMode.EXCESS_POWER) { + // Apply hysteresis + nextChargePower = this.applyHysteresis(nextChargePower); + } + if (isClustered) { this.evcs.setChargePowerRequest(nextChargePower); } else { @@ -251,7 +335,7 @@ private void adaptConfigToHardwareLimits() { private static int calculateChargePowerFromExcessPower(Sum sum, ManagedEvcs evcs) throws OpenemsNamedException { int buyFromGrid = sum.getGridActivePower().orElse(0); int essDischarge = sum.getEssDischargePower().orElse(0); - int evcsCharge = evcs.getChargePower().orElse(0); + int evcsCharge = evcs.getActivePower().orElse(0); return evcsCharge - buyFromGrid - essDischarge; } @@ -265,7 +349,7 @@ private static int calculateChargePowerFromExcessPower(Sum sum, ManagedEvcs evcs */ private static int calculateExcessPowerAfterEss(Sum sum, ManagedEvcs evcs) { int buyFromGrid = sum.getGridActivePower().orElse(0); - int evcsCharge = evcs.getChargePower().orElse(0); + int evcsCharge = evcs.getActivePower().orElse(0); var result = evcsCharge - buyFromGrid; @@ -275,6 +359,82 @@ private static int calculateExcessPowerAfterEss(Sum sum, ManagedEvcs evcs) { return result > 0 ? result : 0; } + /** + * Applies the hysteresis to avoid too quick changes between a charge process + * and a pause. + * + * @param nextChargePower the next charge power limit + * @return next charge power or the last power if hysteresis is active + */ + private int applyHysteresis(int nextChargePower) { + int targetChargePower = nextChargePower; + boolean showWarning = false; + var now = Instant.now(this.clock); + + // Wait at least the EVCS-specific response time, required to increase and + // decrease the charging power + if (awaitLastChanges(this.evcs.getChargeState().asEnum())) { + // Still waiting for increasing, decreasing the power or undefined + return this.lastChargePower; + } + // TODO: Show info, test and check if bellow logic still needed or need to be + // different (Change only when we would change for xSeconds) + + // New charge power limit + if (this.lastChargePower <= 0 && nextChargePower > 0) { + var hysteresis = Duration.ofSeconds(this.config.excessChargePauseHysteresis()); + if (this.lastChargePause.plus(hysteresis).isBefore(now)) { + + // Start charing + this.lastInitialCharge = now; + } else { + // Wait for hysteresis + showWarning = true; + targetChargePower = this.lastChargePower; + } + } + + // Pause charging by limiting to zero + if (this.lastChargePower > 0 && nextChargePower <= 0) { + var hysteresis = Duration.ofSeconds(this.config.excessChargeHystersis()); + if (this.lastInitialCharge.plus(hysteresis).isBefore(now)) { + + // Pause charing + targetChargePower = 0; + this.lastChargePause = now; + } else { + // Wait for hysteresis + showWarning = true; + targetChargePower = this.lastChargePower; + } + } + + // Apply results + this.lastChargePower = targetChargePower; + this.channel(ControllerEvcs.ChannelId.AWAITING_HYSTERESIS).setNextValue(showWarning); + + return targetChargePower; + } + + /** + * Check if the evcs should wait for last changes. + * + *

+ * Since the charging stations and each car have their own response time until + * they charge at the set power, the controller waits until everything runs + * normally. + * + * @param chargeState current evcs charge state + * @return The cvcs should await or not + */ + private static boolean awaitLastChanges(ChargeState chargeState) { + if (chargeState.equals(ChargeState.INCREASING) || chargeState.equals(ChargeState.INCREASING)) { + // Still waiting for increasing, decreasing the power + return true; + } + return false; + } + @Override public ModbusSlaveTable getModbusSlaveTable(AccessMode accessMode) { return new ModbusSlaveTable(// @@ -326,4 +486,33 @@ protected void logDebug(Logger log, String message) { this.logInfo(this.log, message); } } + + @Override + public void buildJsonApiRoutes(JsonApiBuilder builder) { + builder.handleRequest(GetScheduleRequest.METHOD, call -> GetScheduleResponse.from(call.getRequest().getId(), // + this.energyScheduleHandler)); + } + + @Override + public String debugLog() { + var p = getCurrentPeriod(this.energyScheduleHandler); + if (p == null) { + return null; + } + return p.state().name(); + } + + @Override + public EnergyScheduleHandler getEnergyScheduleHandler() { + return this.energyScheduleHandler; + } + + private static Period getCurrentPeriod(EnergyScheduleHandler esh) { + if (esh == null || esh instanceof EnergyScheduleHandler.WithOnlyOneState) { + return null; + } + @SuppressWarnings("unchecked") + var e = (EnergyScheduleHandler.WithDifferentStates) esh; + return e.getCurrentPeriod(); + } } diff --git a/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/SmartMode.java b/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/SmartMode.java new file mode 100644 index 00000000000..c8f82efa313 --- /dev/null +++ b/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/SmartMode.java @@ -0,0 +1,42 @@ +package io.openems.edge.controller.evcs; + +import io.openems.common.types.OptionsEnum; +import io.openems.edge.evcs.api.ChargeMode; + +public enum SmartMode implements OptionsEnum { + ZERO(0, "Zero", null, null), // + // TODO SURPLUS_PV without min-charge power and only if there is predicted + // surplus power + // SURPLUS_PV(1, "Surplus PV with Min charge power", ChargeMode.EXCESS_POWER, + // Priority.CAR), // + FORCE(3, "Force charge", ChargeMode.FORCE_CHARGE, null) // + ; + + private final int value; + private final String name; + + public final ChargeMode chargeMode; + public final Priority priority; + + private SmartMode(int value, String name, ChargeMode chargeMode, Priority priority) { + this.value = value; + this.name = name; + this.chargeMode = chargeMode; + this.priority = priority; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return ZERO; + } +} \ No newline at end of file diff --git a/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/Utils.java b/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/Utils.java new file mode 100644 index 00000000000..6b185129d8b --- /dev/null +++ b/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/Utils.java @@ -0,0 +1,272 @@ +package io.openems.edge.controller.evcs; + +import static io.openems.common.utils.FunctionUtils.doNothing; +import static io.openems.common.utils.JsonUtils.getAsInt; +import static io.openems.common.utils.JsonUtils.parseToJsonArray; +import static io.openems.edge.energy.api.EnergyUtils.toEnergy; +import static java.lang.Math.max; +import static java.lang.Math.min; + +import java.time.Clock; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.function.Supplier; + +import com.google.common.collect.ImmutableList; +import com.google.common.math.Quantiles; +import com.google.gson.JsonObject; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.jscalendar.JSCalendar; +import io.openems.common.jscalendar.JSCalendar.Task; +import io.openems.edge.controller.evcs.Utils.EshContext.EshManualContext; +import io.openems.edge.controller.evcs.Utils.EshContext.EshSmartContext; +import io.openems.edge.energy.api.EnergyScheduleHandler; +import io.openems.edge.energy.api.EnergyScheduleHandler.WithDifferentStates.InitialPopulation; +import io.openems.edge.energy.api.simulation.EnergyFlow; +import io.openems.edge.energy.api.simulation.OneSimulationContext; +import io.openems.edge.energy.api.simulation.OneSimulationContext.Evcs; +import io.openems.edge.evcs.api.ChargeMode; + +public final class Utils { + + protected static final int FORCE_CHARGE_POWER = 11000; // [W] + protected static final int MIN_CHARGE_POWER = 4600; // [W] + + private static int TARGET_HOUR = 7; + + private Utils() { + } + + /** + * Builds an {@link EnergyScheduleHandler} for SMART mode. + * + * @param context a supplier for {@link EshSmartContext} + * @return the {@link EnergyScheduleHandler.WithDifferentStates} + */ + public static EnergyScheduleHandler.WithDifferentStates buildEshSmart( + Supplier context) { + return EnergyScheduleHandler.WithDifferentStates.create() // + .setDefaultState(SmartMode.ZERO) // + // TODO if there is no surplus power, SmartMode.SURPLUS_PV should not be an + // option + .setAvailableStates(() -> SmartMode.values()) // + .setContextFunction(simContext -> context.get()) // + .setInitialPopulationsFunction(gsc -> { + // Sets initial population to FORCE charge during cheapest periods + var targetDateTime = getTargetDateTime(gsc.startTime(), TARGET_HOUR); + var threshold = Quantiles.percentiles() // + .index(5) // + .compute(gsc.periods().stream() // + .takeWhile(p -> !p.time().isAfter(targetDateTime)) // + .mapToDouble(p -> p.price()) // + .toArray()); + var times = gsc.periods().stream() // + .filter(p -> p.price() < threshold) // + .map(p -> p.time()) // + .toList(); + return List.of(// + InitialPopulation.of(times, SmartMode.FORCE)); + }) // + .setSimulator((simContext, period, energyFlow, ctrlContext, mode) -> { + final var evcsOne = simContext.evcss.get(ctrlContext.evcsId); + switch (mode) { + case FORCE -> applyChargeEnergy(energyFlow, ctrlContext, evcsOne, mode.chargeMode, mode.priority, + MIN_CHARGE_POWER, FORCE_CHARGE_POWER); + case ZERO -> doNothing(); + } + + if (period.time().isAfter(getTargetDateTime(simContext.global.startTime(), TARGET_HOUR)) + && evcsOne.getInitialEnergySession() < ctrlContext.energySessionLimit) { + // TODO apply JSCalendar SmartConfig + // Charged less than EnergySessionLimit till next 7am. + return 1_000_000; // add high cost + } + return 0.; + }) // + .setPostProcessor(Utils::postprocessSimulatorState) // + .build(); + } + + /** + * Builds an {@link EnergyScheduleHandler} for MANUL mode. + * + * @param context a supplier for {@link EshManualContext} + * @return the {@link EnergyScheduleHandler.WithOnlyOneState} + */ + public static EnergyScheduleHandler.WithOnlyOneState buildEshManual( + Supplier context) { + return EnergyScheduleHandler.WithOnlyOneState.create() // + .setContextFunction(simContext -> context.get()) // + .setSimulator((simContext, period, energyFlow, ctrlContext) -> { + final var evcsGlobal = simContext.global.evcss().get(ctrlContext.evcsId); + if (!ctrlContext.enabledCharging || evcsGlobal == null) { + return; + } + switch (evcsGlobal.status()) { + case CHARGING: + case READY_FOR_CHARGING: + break; + case CHARGING_REJECTED: + case ENERGY_LIMIT_REACHED: + case ERROR: + case NOT_READY_FOR_CHARGING: + case STARTING: + case UNDEFINED: + return; + } + + final var evcsOne = simContext.evcss.get(ctrlContext.evcsId); + applyChargeEnergy(energyFlow, ctrlContext, evcsOne, ctrlContext.chargeMode, ctrlContext.priority, + ctrlContext.defaultChargeMinPower, ctrlContext.forceChargeMinPower); + }) // + .build(); + } + + public static sealed interface EshContext { + + /** + * Gets the configured Energy-Session-Limit. + * + * @return the value + */ + public int energySessionLimit(); + + public static record EshManualContext(boolean enabledCharging, ChargeMode chargeMode, int forceChargeMinPower, + int defaultChargeMinPower, Priority priority, String evcsId, int energySessionLimit) + implements EshContext { + + /** + * Factory for {@link EshManualContext} from {@link Config}. + * + * @param config the {@link Config} + * @return the {@link EshManualContext} + */ + public static EshManualContext fromConfig(Config config) { + return new EshManualContext(config.enabledCharging(), config.chargeMode(), config.forceChargeMinPower(), + config.defaultChargeMinPower(), config.priority(), config.evcs_id(), + config.energySessionLimit()); + } + } + + public static record EshSmartContext(String evcsId, int energySessionLimit /* TODO required */, + ImmutableList> tasks) implements EshContext { + + /** + * Factory for {@link EshSmartContext} from {@link Config}. + * + * @param config the {@link Config} + * @return the {@link EshSmartContext} + */ + public static EshSmartContext fromConfig(Config config) { + return new EshSmartContext(config.evcs_id(), config.energySessionLimit(), + Payload.fromJson(config.smartConfig())); + } + + public static record Payload(int energySessionLimit) { + /** + * Parses the String configuration to {@link Payload} objects. + * + * @param smartConfig the configuration + * @return the result + */ + public static ImmutableList> fromJson(String smartConfig) { + if (smartConfig == null || smartConfig.isBlank()) { + return ImmutableList.of(); + } + + try { + return JSCalendar.Task.fromJson(parseToJsonArray(smartConfig), Payload::fromJson); + } catch (OpenemsNamedException e) { + e.printStackTrace(); + return ImmutableList.of(); + } + } + + /** + * Parses one {@link JsonObject} to a {@link Payload} object. + * + * @param j the {@link JsonObject} + * @return the {@link Payload} object + * @throws OpenemsNamedException on error + */ + public static Payload fromJson(JsonObject j) throws OpenemsNamedException { + var sessionEnergy = getAsInt(j, "sessionEnergy"); + return new Payload(sessionEnergy); + } + } + } + } + + protected static ZonedDateTime getTargetDateTime(ZonedDateTime startTime, int hour) { + var localTime = startTime.withZoneSameInstant(Clock.systemDefaultZone().getZone()); + var targetDate = localTime.getHour() > hour // + ? startTime.plusDays(1) // + : startTime; + return targetDate.truncatedTo(ChronoUnit.DAYS).withHour(hour); + } + + private static void applyChargeEnergy(EnergyFlow.Model energyFlow, EshContext ctrlContext, Evcs evcsOne, + ChargeMode chargeMode, Priority priority, int chargeMinPower, int forceChargePower) { + if (evcsOne == null) { + return; + } + + // Evaluate Charge-Energy per mode + final var chargeEnergy = switch (chargeMode) { + case EXCESS_POWER // + -> switch (priority) { + case CAR // + -> toEnergy(// + max(chargeMinPower, energyFlow.production - energyFlow.unmanagedConsumption)); + case STORAGE -> 0; // TODO not implemented + }; + case FORCE_CHARGE // + -> toEnergy(forceChargePower); + }; + + if (chargeEnergy <= 0) { + return; // stop early + } + + // Apply Session Limit + final int limitedChargeEnergy; + if (ctrlContext.energySessionLimit() > 0) { + limitedChargeEnergy = min(chargeEnergy, + max(0, ctrlContext.energySessionLimit() - evcsOne.getInitialEnergySession())); + } else { + limitedChargeEnergy = chargeEnergy; + } + + if (limitedChargeEnergy > 0) { + energyFlow.addConsumption(limitedChargeEnergy); + evcsOne.calculateInitialEnergySession(limitedChargeEnergy); + } + } + + /** + * Post-Process a state of a Period during Simulation, i.e. replace with + * 'better' state with the same behaviour. + * + *

+ * NOTE: heavy computation is ok here, because this method is called only at the + * end with the best Schedule. + * + * @param osc the {@link OneSimulationContext} + * @param ef the {@link EnergyFlow} for the state + * @param context the {@link EshContext} + * @param mode the initial {@link SmartMode} + * @return the new state + */ + protected static SmartMode postprocessSimulatorState(OneSimulationContext osc, EnergyFlow ef, EshContext context, + SmartMode mode) { + if (mode == SmartMode.ZERO) { + return mode; + } + if (ef.getManagedCons() == 0) { // TODO this works only reliably with one ControllerEvcs + return SmartMode.ZERO; + } + return mode; + } +} diff --git a/io.openems.edge.energy/src/io/openems/edge/energy/jsonrpc/GetScheduleRequest.java b/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/jsonrpc/GetScheduleRequest.java similarity index 95% rename from io.openems.edge.energy/src/io/openems/edge/energy/jsonrpc/GetScheduleRequest.java rename to io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/jsonrpc/GetScheduleRequest.java index 1dbdb0dc7d8..6113d81b21e 100644 --- a/io.openems.edge.energy/src/io/openems/edge/energy/jsonrpc/GetScheduleRequest.java +++ b/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/jsonrpc/GetScheduleRequest.java @@ -1,4 +1,4 @@ -package io.openems.edge.energy.jsonrpc; +package io.openems.edge.controller.evcs.jsonrpc; import com.google.gson.JsonObject; diff --git a/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/jsonrpc/GetScheduleResponse.java b/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/jsonrpc/GetScheduleResponse.java new file mode 100644 index 00000000000..3aebf1e7e0a --- /dev/null +++ b/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/jsonrpc/GetScheduleResponse.java @@ -0,0 +1,136 @@ +package io.openems.edge.controller.evcs.jsonrpc; + +import static io.openems.common.utils.JsonUtils.buildJsonObject; +import static io.openems.common.utils.JsonUtils.toJsonArray; +import static io.openems.common.utils.StringUtils.toShortString; + +import java.time.Clock; +import java.time.ZonedDateTime; +import java.util.UUID; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.ImmutableSortedMap; +import com.google.gson.JsonArray; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; + +import io.openems.common.jsonrpc.base.JsonrpcResponseSuccess; +import io.openems.edge.controller.evcs.SmartMode; +import io.openems.edge.controller.evcs.Utils.EshContext.EshSmartContext; +import io.openems.edge.energy.api.EnergyScheduleHandler; +import io.openems.edge.energy.api.EnergyScheduleHandler.WithDifferentStates.Period; + +/** + * Represents a JSON-RPC Response for 'getSchedule'. + * + *

+ * {
+ *   "jsonrpc": "2.0",
+ *   "id": "UUID",
+ *   "result": {
+ *     'schedule': [{
+ *      'timestamp':...,
+ *      'price':...,
+ *      'state':...
+ *     }]
+ *   }
+ * }
+ * 
+ */ +public class GetScheduleResponse extends JsonrpcResponseSuccess { + + private static final Logger LOG = LoggerFactory.getLogger(GetScheduleResponse.class); + + private final JsonObject result; + + public GetScheduleResponse(UUID id, JsonObject result) { + super(id); + this.result = result; + } + + @Override + public JsonObject getResult() { + return this.result; + } + + /** + * Builds a {@link GetScheduleResponse} with last three hours data and current + * Schedule. + * + * @param requestId the JSON-RPC request-id + * @param energyScheduleHandler the {@link EnergyScheduleHandler} + * @return the {@link GetScheduleResponse} + */ + public static GetScheduleResponse from(UUID requestId, EnergyScheduleHandler energyScheduleHandler) { + LOG.info("OPTIMIZER JSONRPC start"); + if (energyScheduleHandler == null + || energyScheduleHandler instanceof EnergyScheduleHandler.WithOnlyOneState) { + return null; + } + @SuppressWarnings("unchecked") + var esh = (EnergyScheduleHandler.WithDifferentStates) energyScheduleHandler; + final var schedule = esh.getSchedule(); + final JsonArray result; + if (schedule.isEmpty()) { + result = new JsonArray(); + } else { + final var historic = Stream.of(); // TODO + final var future = fromSchedule(schedule); + result = Stream.concat(historic, future) // + .collect(toJsonArray()); + } + LOG.info("OPTIMIZER JSONRPC finished. " + toShortString(result, 100)); + + return new GetScheduleResponse(requestId, // + buildJsonObject() // + .add("schedule", result) // + .build()); + } + + /** + * Converts the Schedule to a {@link Stream} of {@link JsonObject}s suitable for + * a {@link GetScheduleResponse}. + * + * @param schedule the {@link EnergyScheduleHandler} schedule + * @return {@link Stream} of {@link JsonObject}s + */ + protected static Stream fromSchedule( + ImmutableSortedMap> schedule) { + return schedule.entrySet().stream() // + .map(e -> { + var p = e.getValue(); + + return buildJsonObject() // + .addProperty("timestamp", e.getKey()) // + .addProperty("price", p.price()) // + .addProperty("state", p.state().getValue()) // + .build(); + }); + } + + /** + * Creates an empty default Schedule in case no Schedule is available. + * + * @param clock the {@link Clock} + * @param defaultMode the default {@link SmartMode} + * @return {@link Stream} of {@link JsonObject}s + */ + protected static Stream empty(Clock clock, SmartMode defaultMode) { + final var now = ZonedDateTime.now(clock); + final var numberOfPeriods = 96; + + return IntStream.range(0, numberOfPeriods) // + .mapToObj(i -> { + return buildJsonObject() // + .addProperty("timestamp", now.plusMinutes(i * 15)) // + .add("price", JsonNull.INSTANCE) // + .addProperty("state", defaultMode.getValue()) // + .build(); + }); + } + +} diff --git a/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/jsonrpc/package-info.java b/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/jsonrpc/package-info.java new file mode 100644 index 00000000000..5e86ab9a93c --- /dev/null +++ b/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/jsonrpc/package-info.java @@ -0,0 +1,3 @@ +@org.osgi.annotation.versioning.Version("1.0.0") +@org.osgi.annotation.bundle.Export +package io.openems.edge.controller.evcs.jsonrpc; diff --git a/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/package-info.java b/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/package-info.java new file mode 100644 index 00000000000..310c93b9fb0 --- /dev/null +++ b/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/package-info.java @@ -0,0 +1,3 @@ +@org.osgi.annotation.versioning.Version("1.0.0") +@org.osgi.annotation.bundle.Export +package io.openems.edge.controller.evcs; diff --git a/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/translation_de.properties b/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/translation_de.properties new file mode 100644 index 00000000000..36b880ce587 --- /dev/null +++ b/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/translation_de.properties @@ -0,0 +1 @@ +evcsIsReadOnly = Ladestation ist lesend eingebunden \ No newline at end of file diff --git a/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/translation_en.properties b/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/translation_en.properties new file mode 100644 index 00000000000..e407d9be6f1 --- /dev/null +++ b/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/translation_en.properties @@ -0,0 +1 @@ +evcsIsReadOnly = Read only charging station \ No newline at end of file diff --git a/io.openems.edge.controller.evcs/test/io/openems/edge/controller/evcs/ControllerEvcsImplTest.java b/io.openems.edge.controller.evcs/test/io/openems/edge/controller/evcs/ControllerEvcsImplTest.java index 1c4f620d146..3a9bb67ad68 100644 --- a/io.openems.edge.controller.evcs/test/io/openems/edge/controller/evcs/ControllerEvcsImplTest.java +++ b/io.openems.edge.controller.evcs/test/io/openems/edge/controller/evcs/ControllerEvcsImplTest.java @@ -1,168 +1,159 @@ package io.openems.edge.controller.evcs; +import static io.openems.common.test.TestUtils.createDummyClock; +import static io.openems.edge.common.sum.Sum.ChannelId.ESS_DISCHARGE_POWER; +import static io.openems.edge.common.sum.Sum.ChannelId.ESS_SOC; +import static io.openems.edge.common.sum.Sum.ChannelId.GRID_ACTIVE_POWER; +import static io.openems.edge.controller.evcs.ControllerEvcs.ChannelId.AWAITING_HYSTERESIS; +import static io.openems.edge.controller.evcs.Priority.CAR; +import static io.openems.edge.evcs.api.ChargeMode.EXCESS_POWER; +import static io.openems.edge.evcs.api.ChargeMode.FORCE_CHARGE; +import static io.openems.edge.evcs.api.Evcs.ChannelId.MAXIMUM_HARDWARE_POWER; +import static io.openems.edge.evcs.api.Evcs.ChannelId.MAXIMUM_POWER; +import static io.openems.edge.evcs.api.Evcs.ChannelId.STATUS; +import static io.openems.edge.evcs.api.ManagedEvcs.ChannelId.IS_CLUSTERED; +import static io.openems.edge.evcs.api.ManagedEvcs.ChannelId.SET_CHARGE_POWER_LIMIT; +import static io.openems.edge.evcs.api.ManagedEvcs.ChannelId.SET_CHARGE_POWER_REQUEST; +import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.ACTIVE_POWER; +import static java.time.temporal.ChronoUnit.MINUTES; import static org.junit.Assert.assertEquals; -import java.time.Instant; -import java.time.ZoneOffset; - import org.junit.Test; -import io.openems.common.test.TimeLeapClock; -import io.openems.common.types.ChannelAddress; -import io.openems.edge.common.filter.DisabledRampFilter; import io.openems.edge.common.sum.DummySum; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyConfigurationAdmin; +import io.openems.edge.controller.evcs.Utils.EshContext.EshSmartContext; import io.openems.edge.controller.test.ControllerTest; -import io.openems.edge.evcs.api.ChargeMode; +import io.openems.edge.energy.api.EnergyScheduleHandler; +import io.openems.edge.energy.api.simulation.GlobalSimulationsContext; +import io.openems.edge.evcs.api.Evcs; import io.openems.edge.evcs.api.Status; -import io.openems.edge.evcs.test.DummyEvcsPower; import io.openems.edge.evcs.test.DummyManagedEvcs; public class ControllerEvcsImplTest { - private static final DummyEvcsPower EVCS_POWER = new DummyEvcsPower(new DisabledRampFilter()); - private static final DummyManagedEvcs EVCS = new DummyManagedEvcs("evcs0", EVCS_POWER); - - private static final String EVCS_ID = EVCS.id(); - private static final boolean DEFAULT_ENABLE_CHARGING = true; - private static final ChargeMode DEFAULT_CHARGE_MODE = ChargeMode.EXCESS_POWER; private static final int DEFAULT_FORCE_CHARGE_MIN_POWER = 7360; private static final int DEFAULT_CHARGE_MIN_POWER = 0; - private static final Priority DEFAULT_PRIORITY = Priority.CAR; - private static final int DEFAULT_ENERGY_SESSION_LIMIT = 0; - - private static ChannelAddress sumGridActivePower = new ChannelAddress("_sum", "GridActivePower"); - private static ChannelAddress sumEssDischargePower = new ChannelAddress("_sum", "EssDischargePower"); - private static ChannelAddress sumEssSoc = new ChannelAddress("_sum", "EssSoc"); - private static ChannelAddress evcs0ChargePower = new ChannelAddress("evcs0", "ChargePower"); - private static ChannelAddress evcs0SetChargePowerLimit = new ChannelAddress("evcs0", "SetChargePowerLimit"); - private static ChannelAddress evcs0MaximumPower = new ChannelAddress("evcs0", "MaximumPower"); - private static ChannelAddress evcs0IsClustered = new ChannelAddress("evcs0", "IsClustered"); - private static ChannelAddress evcs0SetPowerRequest = new ChannelAddress("evcs0", "SetChargePowerRequest"); - private static ChannelAddress evcs0Status = new ChannelAddress("evcs0", "Status"); - private static ChannelAddress evcs0MaximumHardwarePower = new ChannelAddress("evcs0", "MaximumHardwarePower"); @Test public void excessChargeTest1() throws Exception { - new ControllerTest(new ControllerEvcsImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("sum", new DummySum()) // - .addReference("evcs", EVCS) // + .addReference("evcs", DummyManagedEvcs.ofDisabled("evcs0")) // .activate(MyConfig.create() // .setId("ctrlEvcs0") // - .setEvcsId(EVCS_ID) // - .setEnableCharging(DEFAULT_ENABLE_CHARGING) // - .setChargeMode(DEFAULT_CHARGE_MODE) // + .setEvcsId("evcs0") // + .setEnableCharging(true) // + .setChargeMode(EXCESS_POWER) // .setForceChargeMinPower(DEFAULT_FORCE_CHARGE_MIN_POWER) // .setDefaultChargeMinPower(DEFAULT_CHARGE_MIN_POWER) // - .setPriority(DEFAULT_PRIORITY) // - .setEnergySessionLimit(DEFAULT_ENERGY_SESSION_LIMIT) // + .setPriority(CAR) // + .setEnergySessionLimit(0) // .build()) // .next(new TestCase() // - .input(sumEssDischargePower, -6000) // - .input(evcs0IsClustered, false) // - .input(sumGridActivePower, 0) // - .input(evcs0ChargePower, 0) // - .output(evcs0SetChargePowerLimit, 6000)) // - ; + .input(ESS_DISCHARGE_POWER, -6000) // + .input("evcs0", IS_CLUSTERED, false) // + .input(GRID_ACTIVE_POWER, 0) // + .input("evcs0", ACTIVE_POWER, 0) // + .output("evcs0", SET_CHARGE_POWER_LIMIT, 6000)) // + .deactivate(); } @Test public void excessChargeTest2() throws Exception { - - final var test = new ControllerTest(new ControllerEvcsImpl()) // + new ControllerTest(new ControllerEvcsImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("sum", new DummySum()) // - .addReference("evcs", EVCS) // + .addReference("evcs", DummyManagedEvcs.ofDisabled("evcs0")) // .activate(MyConfig.create() // .setId("ctrlEvcs0") // - .setEvcsId(EVCS_ID) // - .setEnableCharging(DEFAULT_ENABLE_CHARGING) // - .setChargeMode(DEFAULT_CHARGE_MODE) // + .setEvcsId("evcs0") // + .setEnableCharging(true) // + .setChargeMode(EXCESS_POWER) // .setForceChargeMinPower(DEFAULT_FORCE_CHARGE_MIN_POWER) // .setDefaultChargeMinPower(DEFAULT_CHARGE_MIN_POWER) // .setPriority(Priority.STORAGE) // - .setEnergySessionLimit(DEFAULT_ENERGY_SESSION_LIMIT) // - .build()); // - - test.next(new TestCase() // - .input(sumEssSoc, 50) // - .input(sumEssDischargePower, -5000) // - .input(evcs0IsClustered, false) // - .input(sumGridActivePower, -40000) // - .input(evcs0ChargePower, 5000) // - .input(evcs0MaximumHardwarePower, 22080) // - .output(evcs0SetChargePowerLimit, 44800)); + .setEnergySessionLimit(0) // + .build()) // + .next(new TestCase() // + .input(ESS_SOC, 50) // + .input(ESS_DISCHARGE_POWER, -5000) // + .input("evcs0", IS_CLUSTERED, false) // + .input(GRID_ACTIVE_POWER, -40000) // + .input("evcs0", ACTIVE_POWER, 5000) // + .input("evcs0", MAXIMUM_HARDWARE_POWER, 22080) // + .output("evcs0", SET_CHARGE_POWER_LIMIT, 44800)) // + .deactivate(); } @Test public void forceChargeTest() throws Exception { - new ControllerTest(new ControllerEvcsImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("sum", new DummySum()) // - .addReference("evcs", EVCS) // + .addReference("evcs", DummyManagedEvcs.ofDisabled("evcs0")) // .activate(MyConfig.create() // .setId("ctrlEvcs0") // - .setEvcsId(EVCS_ID) // - .setEnableCharging(DEFAULT_ENABLE_CHARGING) // - .setChargeMode(ChargeMode.FORCE_CHARGE) // + .setEvcsId("evcs0") // + .setEnableCharging(true) // + .setChargeMode(FORCE_CHARGE) // .setForceChargeMinPower(DEFAULT_FORCE_CHARGE_MIN_POWER) // .setDefaultChargeMinPower(DEFAULT_CHARGE_MIN_POWER) // - .setPriority(DEFAULT_PRIORITY) // - .setEnergySessionLimit(DEFAULT_ENERGY_SESSION_LIMIT) // + .setPriority(CAR) // s + .setEnergySessionLimit(0) // .build()) // .next(new TestCase() // - .input(sumEssDischargePower, -5000) // - .input(evcs0IsClustered, false) // - .input(sumGridActivePower, -40000) // - .input(evcs0ChargePower, 5000) // - .output(evcs0SetChargePowerLimit, 22080)); + .input(ESS_DISCHARGE_POWER, -5000) // + .input("evcs0", IS_CLUSTERED, false) // + .input(GRID_ACTIVE_POWER, -40000) // + .input("evcs0", ACTIVE_POWER, 5000) // + .output("evcs0", SET_CHARGE_POWER_LIMIT, 22080)) // + .deactivate(); } @Test public void chargingDisabledTest() throws Exception { - new ControllerTest(new ControllerEvcsImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("sum", new DummySum()) // - .addReference("evcs", EVCS) // + .addReference("evcs", DummyManagedEvcs.ofDisabled("evcs0")) // .activate(MyConfig.create() // .setId("ctrlEvcs0") // - .setEvcsId(EVCS_ID) // + .setEvcsId("evcs0") // .setEnableCharging(false) // - .setChargeMode(DEFAULT_CHARGE_MODE) // + .setChargeMode(EXCESS_POWER) // .setForceChargeMinPower(DEFAULT_FORCE_CHARGE_MIN_POWER) // .setDefaultChargeMinPower(DEFAULT_CHARGE_MIN_POWER) // - .setPriority(DEFAULT_PRIORITY) // - .setEnergySessionLimit(DEFAULT_ENERGY_SESSION_LIMIT) // + .setPriority(CAR) // + .setEnergySessionLimit(0) // .build()) // .next(new TestCase() // - .output(evcs0SetChargePowerLimit, 0)); + .output("evcs0", SET_CHARGE_POWER_LIMIT, 0)) // + .deactivate(); } @Test public void wrongConfigParametersTest() throws Exception { - var cm = new DummyConfigurationAdmin(); new ControllerTest(new ControllerEvcsImpl()) // .addReference("cm", cm) // .addReference("sum", new DummySum()) // - .addReference("evcs", EVCS) // + .addReference("evcs", DummyManagedEvcs.ofDisabled("evcs0")) // .activate(MyConfig.create() // .setId("ctrlEvcs0") // - .setEvcsId(EVCS_ID) // - .setEnableCharging(DEFAULT_ENABLE_CHARGING) // - .setChargeMode(DEFAULT_CHARGE_MODE) // + .setEvcsId("evcs0") // + .setEnableCharging(true) // + .setChargeMode(EXCESS_POWER) // .setForceChargeMinPower(30_000) // .setDefaultChargeMinPower(30_000) // - .setPriority(DEFAULT_PRIORITY) // - .setEnergySessionLimit(DEFAULT_ENERGY_SESSION_LIMIT) // + .setPriority(CAR) // + .setEnergySessionLimit(0) // .build()) // .next(new TestCase() // - .input(evcs0MaximumHardwarePower, 12000)); + .input("evcs0", MAXIMUM_HARDWARE_POWER, 12000)) // + .deactivate(); assertEquals(12000, (int) (Integer) cm.getConfiguration("ctrlEvcs0").getProperties().get("defaultChargeMinPower")); @@ -170,101 +161,214 @@ public void wrongConfigParametersTest() throws Exception { @Test public void clusterTest() throws Exception { - final var clock = new TimeLeapClock(Instant.ofEpochSecond(1577836800) /* starts at 1. January 2020 00:00:00 */, - ZoneOffset.UTC); - + final var clock = createDummyClock(); new ControllerTest(new ControllerEvcsImpl(clock)) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("sum", new DummySum()) // - .addReference("evcs", EVCS) // + .addReference("evcs", DummyManagedEvcs.ofDisabled("evcs0")) // .activate(MyConfig.create() // .setId("ctrlEvcs0") // - .setEvcsId(EVCS_ID) // - .setEnableCharging(DEFAULT_ENABLE_CHARGING) // - .setChargeMode(DEFAULT_CHARGE_MODE) // + .setEvcsId("evcs0") // + .setEnableCharging(true) // + .setChargeMode(EXCESS_POWER) // .setForceChargeMinPower(3_333) // .setDefaultChargeMinPower(DEFAULT_CHARGE_MIN_POWER) // - .setPriority(DEFAULT_PRIORITY) // - .setEnergySessionLimit(DEFAULT_ENERGY_SESSION_LIMIT) // + .setPriority(CAR) // + .setEnergySessionLimit(0) // .build()) // .next(new TestCase() // - .input(sumEssDischargePower, -10000) // - .input(evcs0IsClustered, true) // - .input(sumGridActivePower, 0) // - .input(evcs0ChargePower, 0) // - .input(evcs0Status, Status.CHARGING) // - .output(evcs0SetPowerRequest, 10000)) + .input(ESS_DISCHARGE_POWER, -10000) // + .input("evcs0", IS_CLUSTERED, true) // + .input(GRID_ACTIVE_POWER, 0) // + .input("evcs0", ACTIVE_POWER, 0) // + .input("evcs0", STATUS, Status.CHARGING) // + .output("evcs0", SET_CHARGE_POWER_REQUEST, 10000)) .next(new TestCase() // - .input(sumEssDischargePower, -6000) // - .input(evcs0IsClustered, true) // - .input(sumGridActivePower, 0) // - .input(evcs0ChargePower, 0) // - .input(evcs0Status, Status.NOT_READY_FOR_CHARGING) // - .output(evcs0SetPowerRequest, 0)) // + .input(ESS_DISCHARGE_POWER, -6000) // + .input("evcs0", IS_CLUSTERED, true) // + .input(GRID_ACTIVE_POWER, 0) // + .input("evcs0", ACTIVE_POWER, 0) // + .input("evcs0", STATUS, Status.NOT_READY_FOR_CHARGING) // + .output("evcs0", SET_CHARGE_POWER_REQUEST, 0)) // f .next(new TestCase() // - .input(sumEssDischargePower, -6000) // - .input(evcs0IsClustered, true) // - .input(sumGridActivePower, 0) // - .input(evcs0ChargePower, 0) // - .input(evcs0Status, null) // - .output(evcs0SetPowerRequest, 0)) // + .input(ESS_DISCHARGE_POWER, -6000) // + .input("evcs0", IS_CLUSTERED, true) // + .input(GRID_ACTIVE_POWER, 0) // + .input("evcs0", ACTIVE_POWER, 0) // + .input("evcs0", STATUS, null) // + .output("evcs0", SET_CHARGE_POWER_REQUEST, 0)) // .next(new TestCase() // - .input(sumEssDischargePower, -6000) // - .input(evcs0IsClustered, true) // - .input(sumGridActivePower, 0) // - .input(evcs0ChargePower, 0) // - .input(evcs0Status, Status.CHARGING_REJECTED) // - .output(evcs0SetPowerRequest, 6000) // - .output(evcs0MaximumPower, null)) // - ; + .input(ESS_DISCHARGE_POWER, -6000) // + .input("evcs0", IS_CLUSTERED, true) // + .input(GRID_ACTIVE_POWER, 0) // + .input("evcs0", ACTIVE_POWER, 0) // + .input("evcs0", STATUS, Status.CHARGING_REJECTED) // + .output("evcs0", SET_CHARGE_POWER_REQUEST, 6000) // + .output("evcs0", MAXIMUM_POWER, null)) // + .deactivate(); } @Test public void clusterTestDisabledCharging() throws Exception { - new ControllerTest(new ControllerEvcsImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("sum", new DummySum()) // - .addReference("evcs", EVCS) // + .addReference("evcs", DummyManagedEvcs.ofDisabled("evcs0")) // .activate(MyConfig.create() // .setId("ctrlEvcs0") // - .setEvcsId(EVCS_ID) // + .setEvcsId("evcs0") // .setEnableCharging(false) // - .setChargeMode(DEFAULT_CHARGE_MODE) // + .setChargeMode(EXCESS_POWER) // .setForceChargeMinPower(3_333) // .setDefaultChargeMinPower(DEFAULT_CHARGE_MIN_POWER) // - .setPriority(DEFAULT_PRIORITY) // - .setEnergySessionLimit(DEFAULT_ENERGY_SESSION_LIMIT) // + .setPriority(CAR) // + .setEnergySessionLimit(0) // .build()) // .next(new TestCase() // - .input(sumEssDischargePower, -10000) // - .input(evcs0IsClustered, true) // - .input(sumGridActivePower, 0) // - .input(evcs0ChargePower, 0) // - .input(evcs0Status, Status.CHARGING) // - .output(evcs0SetChargePowerLimit, 0)) + .input(ESS_DISCHARGE_POWER, -10000) // + .input("evcs0", IS_CLUSTERED, true) // + .input(GRID_ACTIVE_POWER, 0) // + .input("evcs0", ACTIVE_POWER, 0) // + .input("evcs0", STATUS, Status.CHARGING) // + .output("evcs0", SET_CHARGE_POWER_LIMIT, 0)) .next(new TestCase() // - .input(sumEssDischargePower, -6000) // - .input(evcs0IsClustered, true) // - .input(sumGridActivePower, 0) // - .input(evcs0ChargePower, 0) // - .input(evcs0Status, Status.NOT_READY_FOR_CHARGING) // - .output(evcs0SetChargePowerLimit, 0)) // + .input(ESS_DISCHARGE_POWER, -6000) // + .input("evcs0", IS_CLUSTERED, true) // + .input(GRID_ACTIVE_POWER, 0) // + .input("evcs0", ACTIVE_POWER, 0) // + .input("evcs0", STATUS, Status.NOT_READY_FOR_CHARGING) // + .output("evcs0", SET_CHARGE_POWER_LIMIT, 0)) // .next(new TestCase() // - .input(sumEssDischargePower, -6000) // - .input(evcs0IsClustered, true) // - .input(sumGridActivePower, 0) // - .input(evcs0ChargePower, 0) // - .input(evcs0Status, null) // - .output(evcs0SetChargePowerLimit, 0)) // + .input(ESS_DISCHARGE_POWER, -6000) // + .input("evcs0", IS_CLUSTERED, true) // + .input(GRID_ACTIVE_POWER, 0) // + .input("evcs0", ACTIVE_POWER, 0) // + .input("evcs0", STATUS, null) // + .output("evcs0", SET_CHARGE_POWER_LIMIT, 0)) // .next(new TestCase() // - .input(sumEssDischargePower, -6000) // - .input(evcs0IsClustered, true) // - .input(sumGridActivePower, 0) // - .input(evcs0ChargePower, 0) // - .input(evcs0Status, Status.CHARGING_REJECTED) // - .output(evcs0SetChargePowerLimit, 0) // - .output(evcs0MaximumPower, null)) // - ; + .input(ESS_DISCHARGE_POWER, -6000) // + .input("evcs0", IS_CLUSTERED, true) // + .input(GRID_ACTIVE_POWER, 0) // + .input("evcs0", ACTIVE_POWER, 0) // + .input("evcs0", STATUS, Status.CHARGING_REJECTED) // + .output("evcs0", SET_CHARGE_POWER_LIMIT, 0) // + .output("evcs0", MAXIMUM_POWER, null)) // + .deactivate(); + } + + @Test + public void hysteresisTest() throws Exception { + final var clock = createDummyClock(); + new ControllerTest(new ControllerEvcsImpl(clock)) // + .addReference("cm", new DummyConfigurationAdmin()) // + .addReference("sum", new DummySum()) // + .addReference("evcs", DummyManagedEvcs.ofDisabled("evcs0")) // + .activate(MyConfig.create() // + .setId("ctrlEvcs0") // + .setEvcsId("evcs0") // + .setEnableCharging(true) // + .setChargeMode(EXCESS_POWER) // + .setForceChargeMinPower(DEFAULT_FORCE_CHARGE_MIN_POWER) // + .setDefaultChargeMinPower(DEFAULT_CHARGE_MIN_POWER) // + .setPriority(CAR) // + .setEnergySessionLimit(0) // + .setExcessChargeHystersis(120) // + .setExcessChargePauseHysteresis(30) // + .build()) // + .next(new TestCase() // + .input(ESS_DISCHARGE_POWER, 0) // + .input("evcs0", IS_CLUSTERED, false) // + .input(GRID_ACTIVE_POWER, -6_000) // + .input("evcs0", ACTIVE_POWER, 0) // + .output("evcs0", SET_CHARGE_POWER_LIMIT, 6_000)) // + .next(new TestCase() // + .input(ESS_DISCHARGE_POWER, 0) // + .input(GRID_ACTIVE_POWER, -200) // + .input("evcs0", ACTIVE_POWER, 5800) // + .output("evcs0", SET_CHARGE_POWER_LIMIT, 6_000)) // + .next(new TestCase() // + .input(ESS_DISCHARGE_POWER, 0) // + .input(GRID_ACTIVE_POWER, 500) // + .input("evcs0", ACTIVE_POWER, 5800) // + .input("evcs0", Evcs.ChannelId.MINIMUM_HARDWARE_POWER, Evcs.DEFAULT_MINIMUM_HARDWARE_POWER) + .output("evcs0", SET_CHARGE_POWER_LIMIT, 5300)) + + // Active hysteresis + .next(new TestCase() // + .input(ESS_DISCHARGE_POWER, 0) // + .input(GRID_ACTIVE_POWER, 1000) // + .input("evcs0", ACTIVE_POWER, 5000) // + .output("evcs0", SET_CHARGE_POWER_LIMIT, 5_300) // + .output(AWAITING_HYSTERESIS, true)) // + .next(new TestCase() // + .timeleap(clock, 6, MINUTES)) // + + // Passed hysteresis + .next(new TestCase() // + .input(ESS_DISCHARGE_POWER, 0) // + .input(GRID_ACTIVE_POWER, 1000) // + .input("evcs0", ACTIVE_POWER, 5000) // + .output("evcs0", SET_CHARGE_POWER_LIMIT, 0) // + .output(AWAITING_HYSTERESIS, false)) // + + // Active hysteresis + .next(new TestCase() // + .input(ESS_DISCHARGE_POWER, 0) // + .input(GRID_ACTIVE_POWER, -5000) // + .input("evcs0", ACTIVE_POWER, 0) // + .output("evcs0", SET_CHARGE_POWER_LIMIT, 0) // + .output(AWAITING_HYSTERESIS, true)) + + .next(new TestCase() // + .timeleap(clock, 1, MINUTES)) // + + // New charge process starting after another 30 seconds + .next(new TestCase() // + .input(ESS_DISCHARGE_POWER, 0) // + .input(GRID_ACTIVE_POWER, -5000) // + .input("evcs0", ACTIVE_POWER, 0) // + .output("evcs0", SET_CHARGE_POWER_LIMIT, 5000) // + .output(AWAITING_HYSTERESIS, false)) // + .deactivate(); + } + + @Test + public void smartTest() throws Exception { + final var clock = createDummyClock(); + final var sut = new ControllerEvcsImpl(clock); + new ControllerTest(sut) // + .addReference("cm", new DummyConfigurationAdmin()) // + .addReference("sum", new DummySum()) // + .addReference("evcs", DummyManagedEvcs.ofDisabled("evcs0")) // + .activate(MyConfig.create() // + .setId("ctrlEvcs0") // + .setEvcsId("evcs0") // + .setSmartMode(true) // + .setSmartConfig(""" + [{ + "@type": "Task", + "uid": "00000000-0000-0000-0000-000000000000", + "updated": "2020-01-01T00:00:00Z", + "start": "2024-06-17T00:00:00", + "recurrenceRules": [ + { + "frequency": "weekly", + "byDay": [ + "sa", + "su" + ] + } + ], + "payload": { + "sessionEnergy": 10001 + } + }]""") // + .build()) // + .next(new TestCase()) // + .deactivate(); + @SuppressWarnings("unchecked") + var esh = (EnergyScheduleHandler.WithDifferentStates) sut + .getEnergyScheduleHandler(); + esh.initialize(new GlobalSimulationsContext(clock, null, null, null, null, null, null, null, null)); } } diff --git a/io.openems.edge.controller.evcs/test/io/openems/edge/controller/evcs/MyConfig.java b/io.openems.edge.controller.evcs/test/io/openems/edge/controller/evcs/MyConfig.java index 5e8533f1b41..38d0462834a 100644 --- a/io.openems.edge.controller.evcs/test/io/openems/edge/controller/evcs/MyConfig.java +++ b/io.openems.edge.controller.evcs/test/io/openems/edge/controller/evcs/MyConfig.java @@ -12,11 +12,15 @@ protected static class Builder { private boolean debugMode = false; private String evcsId = "evcs0"; private boolean enabledCharging = true; + private boolean smartMode = false; + private String smartConfig = ""; private ChargeMode chargeMode = ChargeMode.FORCE_CHARGE; private int forceChargeMinPower = 7560; private int defaultChargeMinPower = 0; private Priority priority = Priority.CAR; private int energySessionLimit = 0; + private int excessChargeHystersis = 120; + private int excessChargePauseHysteresis = 30; private Builder() { } @@ -36,6 +40,16 @@ public Builder setDebugMode(boolean debugMode) { return this; } + public Builder setSmartMode(boolean smartMode) { + this.smartMode = smartMode; + return this; + } + + public Builder setSmartConfig(String smartConfig) { + this.smartConfig = smartConfig; + return this; + } + public Builder setEvcsId(String evcsId) { this.evcsId = evcsId; return this; @@ -71,6 +85,16 @@ public Builder setEnergySessionLimit(int energySessionLimit) { return this; } + public Builder setExcessChargeHystersis(int excessChargeHystersis) { + this.excessChargeHystersis = excessChargeHystersis; + return this; + } + + public Builder setExcessChargePauseHysteresis(int excessChargePauseHysteresis) { + this.excessChargePauseHysteresis = excessChargePauseHysteresis; + return this; + } + public MyConfig build() { return new MyConfig(this); } @@ -107,6 +131,16 @@ public boolean enabledCharging() { return this.builder.enabledCharging; } + @Override + public boolean smartMode() { + return this.builder.smartMode; + } + + @Override + public String smartConfig() { + return this.builder.smartConfig; + } + @Override public ChargeMode chargeMode() { return this.builder.chargeMode; @@ -137,6 +171,16 @@ public boolean debugMode() { return this.builder.debugMode; } + @Override + public int excessChargeHystersis() { + return this.builder.excessChargeHystersis; + } + + @Override + public int excessChargePauseHysteresis() { + return this.builder.excessChargePauseHysteresis; + } + @Override public String evcs_target() { return "(&(enabled=true)(!(service.pid=ctrlEvcs0))(|(id=" + this.evcs_id() + ")))"; diff --git a/io.openems.edge.controller.evcs/test/io/openems/edge/controller/evcs/UtilsTest.java b/io.openems.edge.controller.evcs/test/io/openems/edge/controller/evcs/UtilsTest.java new file mode 100644 index 00000000000..3d9cbd8e36e --- /dev/null +++ b/io.openems.edge.controller.evcs/test/io/openems/edge/controller/evcs/UtilsTest.java @@ -0,0 +1,91 @@ +package io.openems.edge.controller.evcs; + +import static io.openems.edge.controller.evcs.Utils.buildEshManual; +import static io.openems.edge.controller.evcs.Utils.buildEshSmart; +import static org.junit.Assert.assertEquals; + +import java.util.function.Supplier; + +import org.apache.commons.math3.optim.nonlinear.scalar.GoalType; +import org.junit.Test; + +import com.google.common.collect.ImmutableList; + +import io.openems.edge.controller.evcs.Utils.EshContext.EshManualContext; +import io.openems.edge.controller.evcs.Utils.EshContext.EshSmartContext; +import io.openems.edge.energy.api.EnergyScheduleHandler; +import io.openems.edge.energy.api.EnergyScheduleHandler.AbstractEnergyScheduleHandler; +import io.openems.edge.energy.api.simulation.Coefficient; +import io.openems.edge.energy.api.simulation.EnergyFlow; +import io.openems.edge.energy.api.simulation.GlobalSimulationsContext; +import io.openems.edge.energy.api.simulation.OneSimulationContext; +import io.openems.edge.energy.api.test.DummyGlobalSimulationsContext; +import io.openems.edge.evcs.api.ChargeMode; + +public class UtilsTest { + + private static Supplier generateEshSmartContext() { + return () -> new EshSmartContext("evcs0", 2000, ImmutableList.of()); + } + + private static Supplier generateEshManualContext(ChargeMode chargeMode) { + return () -> new EshManualContext(true, chargeMode, 6000, 2300, Priority.CAR, "evcs0", 2000); + } + + private static int cons(GlobalSimulationsContext gsc, int period) { + return gsc.periods().get(period).consumption(); + } + + @Test + public void testManualExcessCar() { + var esh = buildEshManual(generateEshManualContext(ChargeMode.EXCESS_POWER)); + var gsc = DummyGlobalSimulationsContext.fromHandlers(esh); + ((AbstractEnergyScheduleHandler) esh /* this is safe */).initialize(gsc); + var osc = OneSimulationContext.from(gsc); + + assertEquals(cons(gsc, 0) + 575, getConsumption(osc, esh, 0)); + assertEquals(cons(gsc, 1) + 575, getConsumption(osc, esh, 1)); + assertEquals(cons(gsc, 2) + 575, getConsumption(osc, esh, 2)); + assertEquals(cons(gsc, 3) + 275, getConsumption(osc, esh, 3)); + assertEquals(cons(gsc, 4), getConsumption(osc, esh, 4)); + } + + @Test + public void testManualForce() { + var esh = buildEshManual(generateEshManualContext(ChargeMode.FORCE_CHARGE)); + var gsc = DummyGlobalSimulationsContext.fromHandlers(esh); + ((AbstractEnergyScheduleHandler) esh /* this is safe */).initialize(gsc); + var osc = OneSimulationContext.from(gsc); + + assertEquals(cons(gsc, 0) + 1500, getConsumption(osc, esh, 0)); + assertEquals(cons(gsc, 1) + 500, getConsumption(osc, esh, 1)); + assertEquals(cons(gsc, 2), getConsumption(osc, esh, 2)); + assertEquals(cons(gsc, 3), getConsumption(osc, esh, 3)); + assertEquals(cons(gsc, 4), getConsumption(osc, esh, 4)); + } + + @Test + public void testInitialPopulation() { + var esh = buildEshSmart(generateEshSmartContext()); + var gsc = DummyGlobalSimulationsContext.fromHandlers(esh); + esh.initialize(gsc); + var ips = esh.getInitialPopulations(gsc); + assertEquals(1, ips.size()); + var ip = ips.get(0); + assertEquals(2, ip.periods().size()); + assertEquals("2020-01-01T11:00Z", ip.periods().get(0).toString()); + assertEquals("2020-01-01T12:00Z", ip.periods().get(1).toString()); + + var ps = gsc.periods().stream().filter(p -> ip.periods().contains(p.time())).toList(); + assertEquals(2, ps.size()); + assertEquals(282.9, ps.get(0).price(), 0.001); + assertEquals(260.7, ps.get(1).price(), 0.001); + } + + private static int getConsumption(OneSimulationContext osc, EnergyScheduleHandler esh, int periodIndex) { + var period = osc.global.periods().get(periodIndex); + var ef = EnergyFlow.Model.from(osc, period); + ((EnergyScheduleHandler.WithOnlyOneState) esh).simulatePeriod(osc, period, ef); + return ((int) ef.getExtremeCoefficientValue(Coefficient.CONS, GoalType.MINIMIZE)); + } +} diff --git a/io.openems.edge.controller.generic.jsonlogic/.classpath b/io.openems.edge.controller.generic.jsonlogic/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.controller.generic.jsonlogic/.classpath +++ b/io.openems.edge.controller.generic.jsonlogic/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.controller.generic.jsonlogic/test/io/openems/edge/controller/generic/jsonlogic/ControllerGenericJsonLogicImplTest.java b/io.openems.edge.controller.generic.jsonlogic/test/io/openems/edge/controller/generic/jsonlogic/ControllerGenericJsonLogicImplTest.java index 943591e76dd..f0153a6af4e 100644 --- a/io.openems.edge.controller.generic.jsonlogic/test/io/openems/edge/controller/generic/jsonlogic/ControllerGenericJsonLogicImplTest.java +++ b/io.openems.edge.controller.generic.jsonlogic/test/io/openems/edge/controller/generic/jsonlogic/ControllerGenericJsonLogicImplTest.java @@ -1,10 +1,11 @@ package io.openems.edge.controller.generic.jsonlogic; +import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_EQUALS; +import static io.openems.edge.ess.api.SymmetricEss.ChannelId.SOC; + import org.junit.Test; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.sum.DummySum; -import io.openems.edge.common.sum.Sum; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyComponentManager; import io.openems.edge.controller.test.ControllerTest; @@ -12,40 +13,32 @@ public class ControllerGenericJsonLogicImplTest { - private static final ChannelAddress ESS_SOC = new ChannelAddress(Sum.SINGLETON_COMPONENT_ID, - Sum.ChannelId.ESS_SOC.id()); - - private static final String ESS_ID = "ess0"; - - private static final ChannelAddress ESS_SET_ACTIVE_POWER_EQUALS = new ChannelAddress(ESS_ID, - "SetActivePowerEquals"); - @Test public void test() throws Exception { new ControllerTest(new ControllerGenericJsonLogicImpl()) // .addReference("componentManager", new DummyComponentManager()) // .addComponent(new DummySum()) // - .addComponent(new DummyManagedSymmetricEss(ESS_ID)) // + .addComponent(new DummyManagedSymmetricEss("ess0")) // .activate(MyConfig.create() // .setRule("{" // + " \"if\":["// + " {"// + " \"<\": ["// + " {"// - + " \"var\": \"" + ESS_SOC + "\""// + + " \"var\": \"ess0/Soc\""// + " },"// + " 50"// + " ]"// + " },"// + " ["// + " ["// - + " \"" + ESS_SET_ACTIVE_POWER_EQUALS + "\","// + + " \"ess0/SetActivePowerEquals\","// + " 5000"// + " ]"// + " ],"// + " ["// + " ["// - + " \"" + ESS_SET_ACTIVE_POWER_EQUALS + "\","// + + " \"ess0/SetActivePowerEquals\","// + " -2000"// + " ]"// + " ]"// @@ -53,12 +46,12 @@ public void test() throws Exception { + "}") // .build()) .next(new TestCase() // - .input(ESS_SOC, 40) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 5000)) // + .input("ess0", SOC, 40) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 5000)) // .next(new TestCase() // - .input(ESS_SOC, 60) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -2000) // - ); + .input("ess0", SOC, 60) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -2000)) // + .deactivate(); } } diff --git a/io.openems.edge.controller.generic.jsonlogic/test/io/openems/edge/controller/generic/jsonlogic/ControllerGenericJsonLogicImplTest2.java b/io.openems.edge.controller.generic.jsonlogic/test/io/openems/edge/controller/generic/jsonlogic/ControllerGenericJsonLogicImplTest2.java index d12b3dc936c..61d21f86d56 100644 --- a/io.openems.edge.controller.generic.jsonlogic/test/io/openems/edge/controller/generic/jsonlogic/ControllerGenericJsonLogicImplTest2.java +++ b/io.openems.edge.controller.generic.jsonlogic/test/io/openems/edge/controller/generic/jsonlogic/ControllerGenericJsonLogicImplTest2.java @@ -1,10 +1,14 @@ package io.openems.edge.controller.generic.jsonlogic; +import static io.openems.edge.common.sum.Sum.ChannelId.ESS_SOC; +import static io.openems.edge.common.sum.Sum.ChannelId.PRODUCTION_ACTIVE_POWER; +import static io.openems.edge.io.test.DummyInputOutput.ChannelId.INPUT_OUTPUT0; +import static io.openems.edge.io.test.DummyInputOutput.ChannelId.INPUT_OUTPUT1; +import static io.openems.edge.io.test.DummyInputOutput.ChannelId.INPUT_OUTPUT2; + import org.junit.Test; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.sum.DummySum; -import io.openems.edge.common.sum.Sum; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyComponentManager; import io.openems.edge.controller.test.ControllerTest; @@ -12,29 +16,19 @@ public class ControllerGenericJsonLogicImplTest2 { - private static final ChannelAddress SUM_PRODUCTION_POWER = new ChannelAddress(Sum.SINGLETON_COMPONENT_ID, - Sum.ChannelId.PRODUCTION_ACTIVE_POWER.id()); - private static final ChannelAddress SUM_SOC = new ChannelAddress(Sum.SINGLETON_COMPONENT_ID, - Sum.ChannelId.ESS_SOC.id()); - - private static final String IO_ID = "io0"; - private static final ChannelAddress INPUT0 = new ChannelAddress(IO_ID, "InputOutput0"); - private static final ChannelAddress OUTPUT0 = new ChannelAddress(IO_ID, "InputOutput1"); - private static final ChannelAddress OUTPUT1 = new ChannelAddress(IO_ID, "InputOutput2"); - @Test public void test() throws Exception { new ControllerTest(new ControllerGenericJsonLogicImpl()) // .addReference("componentManager", new DummyComponentManager()) // .addComponent(new DummySum()) // - .addComponent(new DummyInputOutput(IO_ID)) // + .addComponent(new DummyInputOutput("io0")) // .activate(MyConfig.create() // .setRule("{"// + " \"if\": ["// + " {"// + " \">\": ["// + " {"// - + " \"var\": \"" + SUM_PRODUCTION_POWER + "\""// + + " \"var\": \"_sum/ProductionActivePower\""// + " },"// + " 2000"// + " ]"// @@ -44,7 +38,7 @@ public void test() throws Exception { + " {"// + " \">\": ["// + " {"// - + " \"var\": \"" + SUM_SOC + "\""// + + " \"var\": \"_sum/EssSoc\""// + " },"// + " 70"// + " ]"// @@ -52,24 +46,24 @@ public void test() throws Exception { + " {"// + " \"if\": ["// + " {"// - + " \"var\": \"" + INPUT0 + "\""// + + " \"var\": \"io0/InputOutput0\""// + " },"// + " ["// + " ],"// + " {"// + " \"if\": ["// + " {"// - + " \"var\": \"" + OUTPUT0 + "\""// + + " \"var\": \"io0/InputOutput1\""// + " },"// + " ["// + " ["// - + " \"" + OUTPUT0 + "\","// + + " \"io0/InputOutput1\","// + " false"// + " ]"// + " ],"// + " ["// + " ["// - + " \"" + OUTPUT1 + "\","// + + " \"io0/InputOutput2\","// + " true"// + " ]"// + " ]"// @@ -86,7 +80,7 @@ public void test() throws Exception { + " {"// + " \"<\": ["// + " {"// - + " \"var\": \"" + SUM_SOC + "\""// + + " \"var\": \"_sum/EssSoc\""// + " },"// + " 40"// + " ]"// @@ -94,24 +88,24 @@ public void test() throws Exception { + " {"// + " \"if\": ["// + " {"// - + " \"var\": \"" + INPUT0 + "\""// + + " \"var\": \"io0/InputOutput0\""// + " },"// + " ["// + " ],"// + " {"// + " \"if\": ["// + " {"// - + " \"var\": \"" + OUTPUT1 + "\""// + + " \"var\": \"io0/InputOutput2\""// + " },"// + " ["// + " ["// - + " \"" + OUTPUT1 + "\","// + + " \"io0/InputOutput2\","// + " false"// + " ]"// + " ],"// + " ["// + " ["// - + " \"" + OUTPUT0 + "\","// + + " \"io0/InputOutput1\","// + " true"// + " ]"// + " ]"// @@ -127,30 +121,30 @@ public void test() throws Exception { + "}") // .build()) .next(new TestCase() // - .input(SUM_PRODUCTION_POWER, 2001) // - .input(SUM_SOC, 71) // - .input(INPUT0, false) // - .input(OUTPUT0, true) // - .output(OUTPUT0, false)) // + .input(PRODUCTION_ACTIVE_POWER, 2001) // + .input(ESS_SOC, 71) // + .input("io0", INPUT_OUTPUT0, false) // + .input("io0", INPUT_OUTPUT1, true) // + .output("io0", INPUT_OUTPUT1, false)) // .next(new TestCase() // - .input(SUM_PRODUCTION_POWER, 2001) // - .input(SUM_SOC, 71) // - .input(INPUT0, false) // - .input(OUTPUT0, false) // - .output(OUTPUT1, true)) // + .input(PRODUCTION_ACTIVE_POWER, 2001) // + .input(ESS_SOC, 71) // + .input("io0", INPUT_OUTPUT0, false) // + .input("io0", INPUT_OUTPUT1, false) // + .output("io0", INPUT_OUTPUT2, true)) // .next(new TestCase() // - .input(SUM_PRODUCTION_POWER, 1999) // - .input(SUM_SOC, 39) // - .input(INPUT0, false) // - .input(OUTPUT1, true) // - .output(OUTPUT1, false)) // + .input(PRODUCTION_ACTIVE_POWER, 1999) // + .input(ESS_SOC, 39) // + .input("io0", INPUT_OUTPUT0, false) // + .input("io0", INPUT_OUTPUT2, true) // + .output("io0", INPUT_OUTPUT2, false)) // .next(new TestCase() // - .input(SUM_PRODUCTION_POWER, 1999) // - .input(SUM_SOC, 39) // - .input(INPUT0, false) // - .input(OUTPUT1, false) // - .output(OUTPUT0, true)) // - ; + .input(PRODUCTION_ACTIVE_POWER, 1999) // + .input(ESS_SOC, 39) // + .input("io0", INPUT_OUTPUT0, false) // + .input("io0", INPUT_OUTPUT2, false) // + .output("io0", INPUT_OUTPUT1, true)) // + .deactivate(); } } diff --git a/io.openems.edge.controller.highloadtimeslot/.classpath b/io.openems.edge.controller.highloadtimeslot/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.controller.highloadtimeslot/.classpath +++ b/io.openems.edge.controller.highloadtimeslot/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.controller.highloadtimeslot/test/io/openems/edge/controller/highloadtimeslot/ControllerHighLoadTimeslotImplTest.java b/io.openems.edge.controller.highloadtimeslot/test/io/openems/edge/controller/highloadtimeslot/ControllerHighLoadTimeslotImplTest.java index 042f700767e..ad6992ddea2 100644 --- a/io.openems.edge.controller.highloadtimeslot/test/io/openems/edge/controller/highloadtimeslot/ControllerHighLoadTimeslotImplTest.java +++ b/io.openems.edge.controller.highloadtimeslot/test/io/openems/edge/controller/highloadtimeslot/ControllerHighLoadTimeslotImplTest.java @@ -1,5 +1,7 @@ package io.openems.edge.controller.highloadtimeslot; +import static io.openems.edge.controller.highloadtimeslot.WeekdayFilter.EVERDAY; + import org.junit.Test; import io.openems.edge.common.test.DummyComponentManager; @@ -7,24 +9,22 @@ public class ControllerHighLoadTimeslotImplTest { - private static final String CTRL_ID = "ctrl0"; - private static final String ESS_ID = "ess0"; - @Test public void test() throws Exception { new ControllerTest(new ControllerHighLoadTimeslotImpl()) // .addReference("componentManager", new DummyComponentManager()) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEss(ESS_ID).setHysteresisSoc(90) // + .setId("ctrl0") // + .setEss("ess0") // + .setHysteresisSoc(90) // .setChargePower(10000) // .setDischargePower(20000) // .setStartDate("01.01.2019") // .setEndDate("01.01.2020") // .setStartTime("08:00") // .setEndTime("13:00") // - .setWeekdayFilter(WeekdayFilter.EVERDAY) // - .build()); // - ; + .setWeekdayFilter(EVERDAY) // + .build()) // + .deactivate(); } } diff --git a/io.openems.edge.controller.io.alarm/.classpath b/io.openems.edge.controller.io.alarm/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.controller.io.alarm/.classpath +++ b/io.openems.edge.controller.io.alarm/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.controller.io.alarm/test/io/openems/edge/controller/io/alarm/ControllerIoAlarmImplTest.java b/io.openems.edge.controller.io.alarm/test/io/openems/edge/controller/io/alarm/ControllerIoAlarmImplTest.java index d4adfec3d38..4eb47a82b4b 100644 --- a/io.openems.edge.controller.io.alarm/test/io/openems/edge/controller/io/alarm/ControllerIoAlarmImplTest.java +++ b/io.openems.edge.controller.io.alarm/test/io/openems/edge/controller/io/alarm/ControllerIoAlarmImplTest.java @@ -1,8 +1,11 @@ package io.openems.edge.controller.io.alarm; +import static io.openems.edge.controller.io.alarm.DummyComponent.ChannelId.STATE_0; +import static io.openems.edge.controller.io.alarm.DummyComponent.ChannelId.STATE_1; +import static io.openems.edge.io.test.DummyInputOutput.ChannelId.INPUT_OUTPUT0; + import org.junit.Test; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyComponentManager; import io.openems.edge.controller.test.ControllerTest; @@ -10,44 +13,34 @@ public class ControllerIoAlarmImplTest { - private static final String CTRL_ID = "ctrl0"; - - private static final String DUMMY_ID = "dummy0"; - private static final ChannelAddress DUMMY_STATE0 = new ChannelAddress(DUMMY_ID, "State0"); - private static final ChannelAddress DUMMY_STATE1 = new ChannelAddress(DUMMY_ID, "State1"); - - private static final String IO_ID = "io0"; - private static final ChannelAddress IO_INPUT_OUTPUT0 = new ChannelAddress(IO_ID, "InputOutput0"); - @Test public void test() throws Exception { new ControllerTest(new ControllerIoAlarmImpl()) // .addReference("componentManager", new DummyComponentManager()) // - .addComponent(new DummyComponent(DUMMY_ID)) // - .addComponent(new DummyInputOutput(IO_ID)) // + .addComponent(new DummyComponent("dummy0")) // + .addComponent(new DummyInputOutput("io0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setInputChannelAddresses(// - DUMMY_STATE0.toString(), // - DUMMY_STATE1.toString()) - .setOutputChannelAddress(IO_INPUT_OUTPUT0.toString()) // + .setId("ctrl0") // + .setInputChannelAddresses("dummy0/State0", "dummy0/State1") + .setOutputChannelAddress("io0/InputOutput0") // .build()) .next(new TestCase() // - .input(DUMMY_STATE0, true) // - .input(DUMMY_STATE1, false) // - .output(IO_INPUT_OUTPUT0, true)) // + .input("dummy0", STATE_0, true) // + .input("dummy0", STATE_1, false) // + .output("io0", INPUT_OUTPUT0, true)) // .next(new TestCase() // - .input(DUMMY_STATE0, false) // - .input(DUMMY_STATE1, true) // - .output(IO_INPUT_OUTPUT0, true)) // + .input("dummy0", STATE_0, false) // + .input("dummy0", STATE_1, true) // + .output("io0", INPUT_OUTPUT0, true)) // .next(new TestCase() // - .input(DUMMY_STATE0, true) // - .input(DUMMY_STATE1, true) // - .output(IO_INPUT_OUTPUT0, true)) + .input("dummy0", STATE_0, true) // + .input("dummy0", STATE_1, true) // + .output("io0", INPUT_OUTPUT0, true)) .next(new TestCase() // - .input(DUMMY_STATE0, false) // - .input(DUMMY_STATE1, false) // - .output(IO_INPUT_OUTPUT0, false)); + .input("dummy0", STATE_0, false) // + .input("dummy0", STATE_1, false) // + .output("io0", INPUT_OUTPUT0, false)) // + .deactivate(); } } diff --git a/io.openems.edge.controller.io.analog/.classpath b/io.openems.edge.controller.io.analog/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.controller.io.analog/.classpath +++ b/io.openems.edge.controller.io.analog/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.controller.io.analog/test/io/openems/edge/controller/io/analog/MyControllerTest.java b/io.openems.edge.controller.io.analog/test/io/openems/edge/controller/io/analog/MyControllerTest.java index 6c31ef34601..3fc3bd26560 100644 --- a/io.openems.edge.controller.io.analog/test/io/openems/edge/controller/io/analog/MyControllerTest.java +++ b/io.openems.edge.controller.io.analog/test/io/openems/edge/controller/io/analog/MyControllerTest.java @@ -1,14 +1,14 @@ package io.openems.edge.controller.io.analog; -import java.time.Instant; -import java.time.ZoneOffset; +import static io.openems.common.test.TestUtils.createDummyClock; + import java.time.temporal.ChronoUnit; import org.junit.Test; -import io.openems.common.test.TimeLeapClock; import io.openems.common.types.ChannelAddress; import io.openems.edge.common.sum.DummySum; +import io.openems.edge.common.sum.Sum; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyConfigurationAdmin; import io.openems.edge.controller.test.ControllerTest; @@ -18,139 +18,129 @@ public class MyControllerTest { - private static final String CTRL_ID = "ctrl0"; - private static final String IO_ID = "analogIo0"; - - // Sum channels - private static final ChannelAddress SUM_ESS_DISCHARGE_POWER = new ChannelAddress("_sum", "EssDischargePower"); - private static final ChannelAddress SUM_GRID_ACTIVE_POWER = new ChannelAddress("_sum", "GridActivePower"); - - // AnalogIO channels - private static final ChannelAddress DEBUG_SET_OUTPUT_VOLTAGE = new ChannelAddress(IO_ID, "DebugSetOutputVoltage"); - private static final ChannelAddress DEBUG_SET_OUTPUT_PERCENT = new ChannelAddress(IO_ID, "DebugSetOutputPercent"); + private static final ChannelAddress DEBUG_SET_OUTPUT_VOLTAGE = new ChannelAddress("analogIo0", + "DebugSetOutputVoltage"); + private static final ChannelAddress DEBUG_SET_OUTPUT_PERCENT = new ChannelAddress("analogIo0", + "DebugSetOutputPercent"); @Test public void testOff() throws Exception { - - final var analogOutput = new DummyAnalogVoltageOutput(IO_ID); - final var clock = new TimeLeapClock(Instant.parse("2020-01-01T01:00:00.00Z"), ZoneOffset.UTC); + final var clock = createDummyClock(); new ControllerTest(new ControllerIoAnalogImpl(clock)) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("sum", new DummySum()) // .addReference("timedata", new DummyTimedata("timedata0")) // - .addReference("analogOutput", analogOutput) // + .addReference("analogOutput", new DummyAnalogVoltageOutput("analogIo0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setAnalogOutputId(IO_ID) // + .setId("ctrl0") // + .setAnalogOutputId("analogIo0") // .setManualTarget(6_000) // .setMaximumPower(10_000) // .setMode(Mode.OFF) // .setPowerBehaviour(PowerBehavior.LINEAR) // .build()) .next(new TestCase() // - .input(SUM_ESS_DISCHARGE_POWER, 2000) // - .input(SUM_GRID_ACTIVE_POWER, -5000)// + .input(Sum.ChannelId.ESS_DISCHARGE_POWER, 2000) // + .input(Sum.ChannelId.GRID_ACTIVE_POWER, -5000)// .output(DEBUG_SET_OUTPUT_PERCENT, 0f) // .output(DEBUG_SET_OUTPUT_VOLTAGE, 0)) // - ; + .deactivate(); } @Test public void testOn() throws Exception { - - final var analogOutput = new DummyAnalogVoltageOutput(IO_ID) // - .setRange(new Range(0, 100, 10000)); - final var clock = new TimeLeapClock(Instant.parse("2020-01-01T01:00:00.00Z"), ZoneOffset.UTC); + final var clock = createDummyClock(); new ControllerTest(new ControllerIoAnalogImpl(clock)) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("sum", new DummySum()) // .addReference("timedata", new DummyTimedata("timedata0")) // - .addReference("analogOutput", analogOutput) // + .addReference("analogOutput", new DummyAnalogVoltageOutput("analogIo0") // + .setRange(new Range(0, 100, 10000))) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setAnalogOutputId(IO_ID) // + .setId("ctrl0") // + .setAnalogOutputId("analogIo0") // .setManualTarget(6_000) // .setMaximumPower(10_000) // .setMode(Mode.ON) // .setPowerBehaviour(PowerBehavior.LINEAR) // .build()) .next(new TestCase() // - .input(SUM_ESS_DISCHARGE_POWER, 0) // - .input(SUM_GRID_ACTIVE_POWER, -5000)// + .input(Sum.ChannelId.ESS_DISCHARGE_POWER, 0) // + .input(Sum.ChannelId.GRID_ACTIVE_POWER, -5000)// .output(DEBUG_SET_OUTPUT_PERCENT, 60.000004f) // .output(DEBUG_SET_OUTPUT_VOLTAGE, 6000)) // - ; + .deactivate(); new ControllerTest(new ControllerIoAnalogImpl(clock)) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("sum", new DummySum()) // .addReference("timedata", new DummyTimedata("timedata0")) // - .addReference("analogOutput", analogOutput) // + .addReference("analogOutput", new DummyAnalogVoltageOutput("analogIo0") // + .setRange(new Range(0, 100, 10000))) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setAnalogOutputId(IO_ID) // + .setId("ctrl0") // + .setAnalogOutputId("analogIo0") // .setManualTarget(0) // .setMaximumPower(10_000) // .setMode(Mode.ON) // .setPowerBehaviour(PowerBehavior.LINEAR) // .build()) .next(new TestCase() // - .input(SUM_ESS_DISCHARGE_POWER, 0) // - .input(SUM_GRID_ACTIVE_POWER, -5000)// + .input(Sum.ChannelId.ESS_DISCHARGE_POWER, 0) // + .input(Sum.ChannelId.GRID_ACTIVE_POWER, -5000)// .output(DEBUG_SET_OUTPUT_PERCENT, 0f) // .output(DEBUG_SET_OUTPUT_VOLTAGE, 0)) // - ; + .deactivate(); } @Test public void testAutomatic() throws Exception { - - final var analogOutput = new DummyAnalogVoltageOutput(IO_ID); - final var clock = new TimeLeapClock(Instant.parse("2020-01-01T01:00:00.00Z"), ZoneOffset.UTC); + final var clock = createDummyClock(); new ControllerTest(new ControllerIoAnalogImpl(clock)) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("sum", new DummySum()) // .addReference("timedata", new DummyTimedata("timedata0")) // - .addReference("analogOutput", analogOutput) // + .addReference("analogOutput", new DummyAnalogVoltageOutput("analogIo0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setAnalogOutputId(IO_ID) // + .setId("ctrl0") // + .setAnalogOutputId("analogIo0") // .setManualTarget(6_000) // .setMaximumPower(10_000) // .setMode(Mode.AUTOMATIC) // .setPowerBehaviour(PowerBehavior.LINEAR) // .build()) .next(new TestCase() // - .input(SUM_ESS_DISCHARGE_POWER, 0) // - .input(SUM_GRID_ACTIVE_POWER, -5000)// + .input(Sum.ChannelId.ESS_DISCHARGE_POWER, 0) // + .input(Sum.ChannelId.GRID_ACTIVE_POWER, -5000)// .output(DEBUG_SET_OUTPUT_PERCENT, 50f) // .output(DEBUG_SET_OUTPUT_VOLTAGE, 5000)) // .next(new TestCase() // - .input(SUM_ESS_DISCHARGE_POWER, 0) // - .input(SUM_GRID_ACTIVE_POWER, -2444)// + .input(Sum.ChannelId.ESS_DISCHARGE_POWER, 0) // + .input(Sum.ChannelId.GRID_ACTIVE_POWER, -2444)// .output(DEBUG_SET_OUTPUT_PERCENT, 50f) // .output(DEBUG_SET_OUTPUT_VOLTAGE, 5000)) // .next(new TestCase() // .timeleap(clock, 4, ChronoUnit.SECONDS) // - .input(SUM_ESS_DISCHARGE_POWER, 0) // - .input(SUM_GRID_ACTIVE_POWER, -2444)// + .input(Sum.ChannelId.ESS_DISCHARGE_POWER, 0) // + .input(Sum.ChannelId.GRID_ACTIVE_POWER, -2444)// .output(DEBUG_SET_OUTPUT_PERCENT, 24.439999f) // .output(DEBUG_SET_OUTPUT_VOLTAGE, 2400)) // 100mV Steps .next(new TestCase() // .timeleap(clock, 4, ChronoUnit.SECONDS) // - .input(SUM_ESS_DISCHARGE_POWER, 0) // - .input(SUM_GRID_ACTIVE_POWER, -12444)// + .input(Sum.ChannelId.ESS_DISCHARGE_POWER, 0) // + .input(Sum.ChannelId.GRID_ACTIVE_POWER, -12444)// .output(DEBUG_SET_OUTPUT_PERCENT, 100f) // .output(DEBUG_SET_OUTPUT_VOLTAGE, 10000)) // .next(new TestCase() // .timeleap(clock, 4, ChronoUnit.SECONDS) // - .input(SUM_ESS_DISCHARGE_POWER, 0) // - .input(SUM_GRID_ACTIVE_POWER, 1000)// + .input(Sum.ChannelId.ESS_DISCHARGE_POWER, 0) // + .input(Sum.ChannelId.GRID_ACTIVE_POWER, 1000)// .output(DEBUG_SET_OUTPUT_PERCENT, 0f) // .output(DEBUG_SET_OUTPUT_VOLTAGE, 0)) // - ; + + .deactivate(); } } diff --git a/io.openems.edge.controller.io.channelsinglethreshold/.classpath b/io.openems.edge.controller.io.channelsinglethreshold/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.controller.io.channelsinglethreshold/.classpath +++ b/io.openems.edge.controller.io.channelsinglethreshold/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.controller.io.channelsinglethreshold/test/io/openems/edge/controller/io/channelsinglethreshold/ControllerIoChannelSingleThresholdImplTest.java b/io.openems.edge.controller.io.channelsinglethreshold/test/io/openems/edge/controller/io/channelsinglethreshold/ControllerIoChannelSingleThresholdImplTest.java index 7a780ffba36..19494c4a3f3 100644 --- a/io.openems.edge.controller.io.channelsinglethreshold/test/io/openems/edge/controller/io/channelsinglethreshold/ControllerIoChannelSingleThresholdImplTest.java +++ b/io.openems.edge.controller.io.channelsinglethreshold/test/io/openems/edge/controller/io/channelsinglethreshold/ControllerIoChannelSingleThresholdImplTest.java @@ -1,9 +1,11 @@ package io.openems.edge.controller.io.channelsinglethreshold; +import static io.openems.edge.controller.io.channelsinglethreshold.ControllerIoChannelSingleThreshold.ChannelId.AWAITING_HYSTERESIS; +import static io.openems.edge.ess.api.SymmetricEss.ChannelId.SOC; +import static io.openems.edge.io.test.DummyInputOutput.ChannelId.INPUT_OUTPUT0; + import org.junit.Test; -import io.openems.common.test.TimeLeapClock; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyComponentManager; import io.openems.edge.controller.test.ControllerTest; @@ -12,35 +14,26 @@ public class ControllerIoChannelSingleThresholdImplTest { - private static final String CTRL_ID = "ctrl0"; - private static final ChannelAddress CTRL_AWAITING_HYSTERESIS = new ChannelAddress(CTRL_ID, "AwaitingHysteresis"); - - private static final String ESS_ID = "ess0"; - private static final ChannelAddress ESS_SOC = new ChannelAddress(ESS_ID, "Soc"); - - private static final String IO_ID = "io0"; - private static final ChannelAddress IO_INPUT_OUTPUT0 = new ChannelAddress(IO_ID, "InputOutput0"); - @Test public void test() throws Exception { - final var clock = new TimeLeapClock(); new ControllerTest(new ControllerIoChannelSingleThresholdImpl()) // - .addReference("componentManager", new DummyComponentManager(clock)) // - .addComponent(new DummyManagedSymmetricEss(ESS_ID)) // - .addComponent(new DummyInputOutput(IO_ID)) // + .addReference("componentManager", new DummyComponentManager()) // + .addComponent(new DummyManagedSymmetricEss("ess0")) // + .addComponent(new DummyInputOutput("io0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // + .setId("ctrl0") // .setMode(Mode.AUTOMATIC) // - .setInputChannelAddress(ESS_SOC.toString()) // - .setOutputChannelAddress(IO_INPUT_OUTPUT0.toString()) // + .setInputChannelAddress("ess0/Soc") // + .setOutputChannelAddress("io0/InputOutput0") // .setThreshold(70) // .setSwitchedLoadPower(0) // .setMinimumSwitchingTime(60).setInvert(false) // .build()) .next(new TestCase() // - .input(ESS_SOC, 50) // - .output(IO_INPUT_OUTPUT0, false) // - .output(CTRL_AWAITING_HYSTERESIS, false)); // + .input("ess0", SOC, 50) // + .output("io0", INPUT_OUTPUT0, false) // + .output(AWAITING_HYSTERESIS, false)) // + .deactivate(); } } diff --git a/io.openems.edge.controller.io.fixdigitaloutput/.classpath b/io.openems.edge.controller.io.fixdigitaloutput/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.controller.io.fixdigitaloutput/.classpath +++ b/io.openems.edge.controller.io.fixdigitaloutput/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.controller.io.fixdigitaloutput/test/io/openems/edge/controller/io/fixdigitaloutput/ControllerIoFixDigitalOutputImplTest.java b/io.openems.edge.controller.io.fixdigitaloutput/test/io/openems/edge/controller/io/fixdigitaloutput/ControllerIoFixDigitalOutputImplTest.java index 9a80d917290..87dc6f60fb3 100644 --- a/io.openems.edge.controller.io.fixdigitaloutput/test/io/openems/edge/controller/io/fixdigitaloutput/ControllerIoFixDigitalOutputImplTest.java +++ b/io.openems.edge.controller.io.fixdigitaloutput/test/io/openems/edge/controller/io/fixdigitaloutput/ControllerIoFixDigitalOutputImplTest.java @@ -1,8 +1,9 @@ package io.openems.edge.controller.io.fixdigitaloutput; +import static io.openems.edge.io.test.DummyInputOutput.ChannelId.INPUT_OUTPUT0; + import org.junit.Test; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyComponentManager; import io.openems.edge.controller.test.ControllerTest; @@ -10,11 +11,6 @@ public class ControllerIoFixDigitalOutputImplTest { - private static final String CTRL_ID = "ctrl0"; - - private static final String IO_ID = "io0"; - private static final ChannelAddress IO_INPUT_OUTPUT0 = new ChannelAddress(IO_ID, "InputOutput0"); - @Test public void testOn() throws Exception { this.testSwitch(true); @@ -28,14 +24,14 @@ public void testOff() throws Exception { private void testSwitch(boolean on) throws Exception { new ControllerTest(new ControllerIoFixDigitalOutputImpl()) // .addReference("componentManager", new DummyComponentManager()) // - .addComponent(new DummyInputOutput(IO_ID)) // + .addComponent(new DummyInputOutput("io0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setOutputChannelAddress(IO_INPUT_OUTPUT0.toString()) // + .setId("ctrl0") // + .setOutputChannelAddress("io0/InputOutput0") // .setOn(on) // .build()) .next(new TestCase() // - .output(IO_INPUT_OUTPUT0, on)); + .output("io0", INPUT_OUTPUT0, on)) // + .deactivate(); } - } diff --git a/io.openems.edge.controller.io.heatingelement/.classpath b/io.openems.edge.controller.io.heatingelement/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.controller.io.heatingelement/.classpath +++ b/io.openems.edge.controller.io.heatingelement/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.controller.io.heatingelement/test/io/openems/edge/controller/io/heatingelement/ControllerHeatingElementImplTest4.java b/io.openems.edge.controller.io.heatingelement/test/io/openems/edge/controller/io/heatingelement/ControllerHeatingElementImplTest4.java index 854d4f90087..76db186ac35 100644 --- a/io.openems.edge.controller.io.heatingelement/test/io/openems/edge/controller/io/heatingelement/ControllerHeatingElementImplTest4.java +++ b/io.openems.edge.controller.io.heatingelement/test/io/openems/edge/controller/io/heatingelement/ControllerHeatingElementImplTest4.java @@ -1,16 +1,16 @@ package io.openems.edge.controller.io.heatingelement; -import java.time.Instant; -import java.time.ZoneOffset; -import java.time.temporal.ChronoUnit; +import static io.openems.common.test.TestUtils.createDummyClock; +import static io.openems.edge.common.sum.Sum.ChannelId.ESS_DISCHARGE_POWER; +import static io.openems.edge.common.sum.Sum.ChannelId.GRID_ACTIVE_POWER; +import static io.openems.edge.controller.io.heatingelement.ControllerIoHeatingElement.ChannelId.LEVEL; +import static java.time.temporal.ChronoUnit.SECONDS; import org.junit.Test; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.test.TimeLeapClock; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.sum.DummySum; -import io.openems.edge.common.sum.Sum; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyComponentManager; import io.openems.edge.controller.io.heatingelement.enums.Level; @@ -20,25 +20,19 @@ import io.openems.edge.io.test.DummyInputOutput; public class ControllerHeatingElementImplTest4 { - private static final String CTRL_ID = "ctrl0"; - private static final String IO_ID = "io0"; - private static final ChannelAddress IO_OUTPUT1 = new ChannelAddress(IO_ID, "InputOutput1"); - private static final ChannelAddress IO_OUTPUT2 = new ChannelAddress(IO_ID, "InputOutput2"); - private static final ChannelAddress IO_OUTPUT3 = new ChannelAddress(IO_ID, "InputOutput3"); - private static final TimeLeapClock clock = new TimeLeapClock( - Instant.ofEpochSecond(1577836800) /* starts at 1. January 2020 00:00:00 */, ZoneOffset.UTC); + private static final TimeLeapClock CLOCK = createDummyClock(); private static ControllerTest prepareTest(Mode mode, Level level) throws OpenemsNamedException, Exception { return new ControllerTest(new ControllerIoHeatingElementImpl()) // - .addReference("componentManager", new DummyComponentManager(clock)) // + .addReference("componentManager", new DummyComponentManager(CLOCK)) // .addReference("sum", new DummySum()) // .addComponent(new DummyInputOutput("io0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setOutputChannelPhaseL1(IO_OUTPUT1.toString()) // - .setOutputChannelPhaseL2(IO_OUTPUT2.toString()) // - .setOutputChannelPhaseL3(IO_OUTPUT3.toString()) // + .setId("ctrl0") // + .setOutputChannelPhaseL1("io0/InputOutput0") // + .setOutputChannelPhaseL2("io0/InputOutput1") // + .setOutputChannelPhaseL3("io0/InputOutput2") // .setEndTime("15:45:00") // .setPowerOfPhase(2000) // .setMode(mode) // @@ -49,13 +43,6 @@ private static ControllerTest prepareTest(Mode mode, Level level) throws Openems .build()); // } - private static final ChannelAddress ESSO_DISCHARGE_POWER = new ChannelAddress("_sum", - Sum.ChannelId.ESS_DISCHARGE_POWER.id()); - private static final ChannelAddress LEVEL = new ChannelAddress(CTRL_ID, - ControllerIoHeatingElement.ChannelId.LEVEL.id()); - private static final ChannelAddress GRID_ACTIVE_POWER = new ChannelAddress("_sum", - Sum.ChannelId.GRID_ACTIVE_POWER.id()); - @Test public void testDischargeTakeIntoAccount() throws OpenemsNamedException, Exception { prepareTest(Mode.AUTOMATIC, Level.LEVEL_3)// @@ -63,20 +50,20 @@ public void testDischargeTakeIntoAccount() throws OpenemsNamedException, Excepti .input(GRID_ACTIVE_POWER, -2500)// .output(LEVEL, Level.LEVEL_1)) // .next(new TestCase() // - .timeleap(clock, 181, ChronoUnit.SECONDS)// + .timeleap(CLOCK, 181, SECONDS)// .input(GRID_ACTIVE_POWER, -2500)// .output(LEVEL, Level.LEVEL_2))// // Grid power reducing because of 2kW heating power .next(new TestCase()// - .timeleap(clock, 181, ChronoUnit.SECONDS)// + .timeleap(CLOCK, 181, SECONDS)// .input(GRID_ACTIVE_POWER, -500) // - .output(LEVEL, Level.LEVEL_2)// - ).next(new TestCase() // - .timeleap(clock, 181, ChronoUnit.SECONDS)// + .output(LEVEL, Level.LEVEL_2)) // + .next(new TestCase() // + .timeleap(CLOCK, 181, SECONDS)// .input(GRID_ACTIVE_POWER, -500) // - .input(ESSO_DISCHARGE_POWER, 2300) // - .output(LEVEL, Level.LEVEL_1)// - ); // ; + .input(ESS_DISCHARGE_POWER, 2300) // + .output(LEVEL, Level.LEVEL_1)) // + .deactivate(); } @Test @@ -87,9 +74,10 @@ public void realDataTest() throws OpenemsNamedException, Exception { .input(GRID_ACTIVE_POWER, -6000)// .output(LEVEL, Level.LEVEL_3)) // .next(new TestCase()// - .timeleap(clock, 181, ChronoUnit.SECONDS)// + .timeleap(CLOCK, 181, SECONDS)// .input(GRID_ACTIVE_POWER, 0)// - .input(ESSO_DISCHARGE_POWER, 2280)// - .output(LEVEL, Level.LEVEL_1)); // ; + .input(ESS_DISCHARGE_POWER, 2280)// + .output(LEVEL, Level.LEVEL_1)) // + .deactivate(); } } diff --git a/io.openems.edge.controller.io.heatingelement/test/io/openems/edge/controller/io/heatingelement/ControllerIoHeatingElementImplTest.java b/io.openems.edge.controller.io.heatingelement/test/io/openems/edge/controller/io/heatingelement/ControllerIoHeatingElementImplTest.java index a2c130c4c3a..994a7aa400b 100644 --- a/io.openems.edge.controller.io.heatingelement/test/io/openems/edge/controller/io/heatingelement/ControllerIoHeatingElementImplTest.java +++ b/io.openems.edge.controller.io.heatingelement/test/io/openems/edge/controller/io/heatingelement/ControllerIoHeatingElementImplTest.java @@ -1,17 +1,22 @@ package io.openems.edge.controller.io.heatingelement; -import java.time.Instant; -import java.time.ZoneOffset; -import java.time.temporal.ChronoUnit; +import static io.openems.common.test.TestUtils.createDummyClock; +import static io.openems.edge.common.sum.Sum.ChannelId.GRID_ACTIVE_POWER; +import static io.openems.edge.controller.io.heatingelement.ControllerIoHeatingElement.ChannelId.PHASE1_TIME; +import static io.openems.edge.controller.io.heatingelement.ControllerIoHeatingElement.ChannelId.PHASE2_TIME; +import static io.openems.edge.controller.io.heatingelement.ControllerIoHeatingElement.ChannelId.PHASE3_TIME; +import static io.openems.edge.controller.io.heatingelement.enums.Level.LEVEL_3; +import static io.openems.edge.io.test.DummyInputOutput.ChannelId.INPUT_OUTPUT0; +import static io.openems.edge.io.test.DummyInputOutput.ChannelId.INPUT_OUTPUT1; +import static io.openems.edge.io.test.DummyInputOutput.ChannelId.INPUT_OUTPUT2; +import static java.time.temporal.ChronoUnit.HOURS; +import static java.time.temporal.ChronoUnit.MINUTES; import org.junit.Test; -import io.openems.common.test.TimeLeapClock; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.sum.DummySum; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyComponentManager; -import io.openems.edge.controller.io.heatingelement.enums.Level; import io.openems.edge.controller.io.heatingelement.enums.Mode; import io.openems.edge.controller.io.heatingelement.enums.WorkMode; import io.openems.edge.controller.test.ControllerTest; @@ -19,36 +24,22 @@ public class ControllerIoHeatingElementImplTest { - private static final String CTRL_ID = "ctrl0"; - private static final String IO_ID = "io0"; - - private static final ChannelAddress SUM_GRID_ACTIVE_POWER = new ChannelAddress("_sum", "GridActivePower"); - - private static final ChannelAddress IO_OUTPUT1 = new ChannelAddress(IO_ID, "InputOutput1"); - private static final ChannelAddress IO_OUTPUT2 = new ChannelAddress(IO_ID, "InputOutput2"); - private static final ChannelAddress IO_OUTPUT3 = new ChannelAddress(IO_ID, "InputOutput3"); - - private static final ChannelAddress CTRL_PHASE1TIME = new ChannelAddress(CTRL_ID, "Phase1Time"); - private static final ChannelAddress CTRL_PHASE2TIME = new ChannelAddress(CTRL_ID, "Phase2Time"); - private static final ChannelAddress CTRL_PHASE3TIME = new ChannelAddress(CTRL_ID, "Phase3Time"); - @Test public void test() throws Exception { - final var clock = new TimeLeapClock(Instant.ofEpochSecond(1577836800) /* starts at 1. January 2020 00:00:00 */, - ZoneOffset.UTC); + final var clock = createDummyClock(); new ControllerTest(new ControllerIoHeatingElementImpl()) // .addReference("componentManager", new DummyComponentManager(clock)) // .addReference("sum", new DummySum()) // - .addComponent(new DummyInputOutput(IO_ID)) // + .addComponent(new DummyInputOutput("io0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setOutputChannelPhaseL1(IO_OUTPUT1.toString()) // - .setOutputChannelPhaseL2(IO_OUTPUT2.toString()) // - .setOutputChannelPhaseL3(IO_OUTPUT3.toString()) // + .setId("ctrl0") // + .setOutputChannelPhaseL1("io0/InputOutput0") // + .setOutputChannelPhaseL2("io0/InputOutput1") // + .setOutputChannelPhaseL3("io0/InputOutput2") // .setEndTime("15:45:00") // .setPowerOfPhase(2000) // .setMode(Mode.AUTOMATIC) // - .setDefaultLevel(Level.LEVEL_3) // + .setDefaultLevel(LEVEL_3) // .setWorkMode(WorkMode.TIME) // .setMinTime(1) // .setMinimumSwitchingTime(60) // @@ -56,133 +47,134 @@ public void test() throws Exception { .next(new TestCase() // // Grid active power : 0, Excess power : 0, // from -> UNDEFINED --to--> LEVEL_0, no of relais = 0 - .input(SUM_GRID_ACTIVE_POWER, 0) // - .output(IO_OUTPUT1, false) // - .output(IO_OUTPUT2, false) // - .output(IO_OUTPUT3, false) // - .output(CTRL_PHASE1TIME, 0) // - .output(CTRL_PHASE1TIME, 0) // - .output(CTRL_PHASE2TIME, 0)) // + .input(GRID_ACTIVE_POWER, 0) // + .output("io0", INPUT_OUTPUT0, false) // + .output("io0", INPUT_OUTPUT1, false) // + .output("io0", INPUT_OUTPUT2, false) // + .output(PHASE1_TIME, 0) // + .output(PHASE1_TIME, 0) // + .output(PHASE2_TIME, 0)) // .next(new TestCase() // // Grid active power : 0, Excess power : 0, // from -> LEVEL_0 --to--> LEVEL_0, no of relais = 0 - .timeleap(clock, 15, ChronoUnit.MINUTES)// - .input(SUM_GRID_ACTIVE_POWER, 0) // - .output(IO_OUTPUT1, false) // - .output(IO_OUTPUT2, false) // - .output(IO_OUTPUT3, false) // - .output(CTRL_PHASE1TIME, 0) // - .output(CTRL_PHASE2TIME, 0) // - .output(CTRL_PHASE3TIME, 0)) // + .timeleap(clock, 15, MINUTES)// + .input(GRID_ACTIVE_POWER, 0) // + .output("io0", INPUT_OUTPUT0, false) // + .output("io0", INPUT_OUTPUT1, false) // + .output("io0", INPUT_OUTPUT2, false) // + .output(PHASE1_TIME, 0) // + .output(PHASE2_TIME, 0) // + .output(PHASE3_TIME, 0)) // .next(new TestCase() // // Grid active power : -2000, Excess power : 2000, // from -> LEVEL_0 --to--> LEVEL_1, no of relais = 1 - .timeleap(clock, 15, ChronoUnit.MINUTES)// - .input(SUM_GRID_ACTIVE_POWER, -2000) // - .output(IO_OUTPUT1, true) // - .output(IO_OUTPUT2, false) // - .output(IO_OUTPUT3, false) // - .output(CTRL_PHASE1TIME, 0) // - .output(CTRL_PHASE2TIME, 0) // - .output(CTRL_PHASE3TIME, 0)) // + .timeleap(clock, 15, MINUTES)// + .input(GRID_ACTIVE_POWER, -2000) // + .output("io0", INPUT_OUTPUT0, true) // + .output("io0", INPUT_OUTPUT1, false) // + .output("io0", INPUT_OUTPUT2, false) // + .output(PHASE1_TIME, 0) // + .output(PHASE2_TIME, 0) // + .output(PHASE3_TIME, 0)) // .next(new TestCase() // // Grid active power : -4000, Excess power : 6000, // from -> LEVEL_1 --to--> LEVEL_3, no of relais = 3 - .timeleap(clock, 15, ChronoUnit.MINUTES)// - .input(SUM_GRID_ACTIVE_POWER, -4000) // - .output(IO_OUTPUT1, true) // - .output(IO_OUTPUT2, true) // - .output(IO_OUTPUT3, true) // - .output(CTRL_PHASE1TIME, 15 * 60) // - .output(CTRL_PHASE2TIME, 0) // - .output(CTRL_PHASE3TIME, 0)) // + .timeleap(clock, 15, MINUTES)// + .input(GRID_ACTIVE_POWER, -4000) // + .output("io0", INPUT_OUTPUT0, true) // + .output("io0", INPUT_OUTPUT1, true) // + .output("io0", INPUT_OUTPUT2, true) // + .output(PHASE1_TIME, 15 * 60) // + .output(PHASE2_TIME, 0) // + .output(PHASE3_TIME, 0)) // .next(new TestCase() // // Grid active power : -6000, Excess power : 12000, // from -> LEVEL_3 --to--> LEVEL_3, no of relais = 3 - .timeleap(clock, 15, ChronoUnit.MINUTES)// - .input(SUM_GRID_ACTIVE_POWER, -6000) // - .output(IO_OUTPUT1, true) // - .output(IO_OUTPUT2, true) // - .output(IO_OUTPUT3, true) // - .output(CTRL_PHASE1TIME, 30 * 60) // - .output(CTRL_PHASE2TIME, 15 * 60) // - .output(CTRL_PHASE3TIME, 15 * 60)) // + .timeleap(clock, 15, MINUTES)// + .input(GRID_ACTIVE_POWER, -6000) // + .output("io0", INPUT_OUTPUT0, true) // + .output("io0", INPUT_OUTPUT1, true) // + .output("io0", INPUT_OUTPUT2, true) // + .output(PHASE1_TIME, 30 * 60) // + .output(PHASE2_TIME, 15 * 60) // + .output(PHASE3_TIME, 15 * 60)) // .next(new TestCase() // // Grid active power : -7000, Excess power : 13000, // from -> LEVEL_3 --to--> LEVEL_3, no of relais = 3 - .input(SUM_GRID_ACTIVE_POWER, -7000) // - .timeleap(clock, 15, ChronoUnit.MINUTES)// - .output(IO_OUTPUT1, true) // - .output(IO_OUTPUT2, true) // - .output(IO_OUTPUT3, true) // - .output(CTRL_PHASE1TIME, 45 * 60) // - .output(CTRL_PHASE2TIME, 30 * 60) // - .output(CTRL_PHASE3TIME, 30 * 60)) // + .input(GRID_ACTIVE_POWER, -7000) // + .timeleap(clock, 15, MINUTES)// + .output("io0", INPUT_OUTPUT0, true) // + .output("io0", INPUT_OUTPUT1, true) // + .output("io0", INPUT_OUTPUT2, true) // + .output(PHASE1_TIME, 45 * 60) // + .output(PHASE2_TIME, 30 * 60) // + .output(PHASE3_TIME, 30 * 60)) // .next(new TestCase() // // Grid active power : 0, Excess power : 6000, // from -> LEVEL_3 --to--> LEVEL_3, no of relais = 3 - .timeleap(clock, 15, ChronoUnit.MINUTES)// - .input(SUM_GRID_ACTIVE_POWER, 0) // - .output(IO_OUTPUT1, true) // - .output(IO_OUTPUT2, true) // - .output(IO_OUTPUT3, true) // - .output(CTRL_PHASE1TIME, 60 * 60) // - .output(CTRL_PHASE2TIME, 45 * 60) // - .output(CTRL_PHASE3TIME, 45 * 60)) // + .timeleap(clock, 15, MINUTES)// + .input(GRID_ACTIVE_POWER, 0) // + .output("io0", INPUT_OUTPUT0, true) // + .output("io0", INPUT_OUTPUT1, true) // + .output("io0", INPUT_OUTPUT2, true) // + .output(PHASE1_TIME, 60 * 60) // + .output(PHASE2_TIME, 45 * 60) // + .output(PHASE3_TIME, 45 * 60)) // .next(new TestCase() // // Grid active power : 1, Excess power : 0, // from -> LEVEL_3 --to--> LEVEL_0, no of relais = 0 - .timeleap(clock, 15, ChronoUnit.MINUTES)// - .input(SUM_GRID_ACTIVE_POWER, 1) // - .output(IO_OUTPUT1, false) // - .output(IO_OUTPUT2, false) // - .output(IO_OUTPUT3, false) // - .output(CTRL_PHASE1TIME, 75 * 60) // - .output(CTRL_PHASE2TIME, 60 * 60) // - .output(CTRL_PHASE3TIME, 60 * 60)) // + .timeleap(clock, 15, MINUTES)// + .input(GRID_ACTIVE_POWER, 1) // + .output("io0", INPUT_OUTPUT0, false) // + .output("io0", INPUT_OUTPUT1, false) // + .output("io0", INPUT_OUTPUT2, false) // + .output(PHASE1_TIME, 75 * 60) // + .output(PHASE2_TIME, 60 * 60) // + .output(PHASE3_TIME, 60 * 60)) // .next(new TestCase() // // Grid active power : 20000, Excess power : 0, // from -> LEVEL_0 --to--> LEVEL_0, no of relais = 0 - .timeleap(clock, 15, ChronoUnit.MINUTES)// - .input(SUM_GRID_ACTIVE_POWER, 20000) // - .output(IO_OUTPUT1, false) // - .output(IO_OUTPUT2, false) // - .output(IO_OUTPUT3, false) // - .output(CTRL_PHASE1TIME, 75 * 60) // - .output(CTRL_PHASE2TIME, 60 * 60) // - .output(CTRL_PHASE3TIME, 60 * 60)) // + .timeleap(clock, 15, MINUTES)// + .input(GRID_ACTIVE_POWER, 20000) // + .output("io0", INPUT_OUTPUT0, false) // + .output("io0", INPUT_OUTPUT1, false) // + .output("io0", INPUT_OUTPUT2, false) // + .output(PHASE1_TIME, 75 * 60) // + .output(PHASE2_TIME, 60 * 60) // + .output(PHASE3_TIME, 60 * 60)) // .next(new TestCase() // // Grid active power : -4000, Excess power : 10000, // from -> LEVEL_0 --to--> LEVEL_2, no of relais = 2 - .timeleap(clock, 15, ChronoUnit.MINUTES)// - .input(SUM_GRID_ACTIVE_POWER, -4000) // - .output(IO_OUTPUT1, true) // - .output(IO_OUTPUT2, true) // - .output(IO_OUTPUT3, false) // - .output(CTRL_PHASE1TIME, 75 * 60) // - .output(CTRL_PHASE2TIME, 60 * 60) // - .output(CTRL_PHASE3TIME, 60 * 60)) // + .timeleap(clock, 15, MINUTES)// + .input(GRID_ACTIVE_POWER, -4000) // + .output("io0", INPUT_OUTPUT0, true) // + .output("io0", INPUT_OUTPUT1, true) // + .output("io0", INPUT_OUTPUT2, false) // + .output(PHASE1_TIME, 75 * 60) // + .output(PHASE2_TIME, 60 * 60) // + .output(PHASE3_TIME, 60 * 60)) // .next(new TestCase() // // Grid active power : 0, Excess power : 4000, // from -> LEVEL_2 --to--> LEVEL_2, no of relais = 2 - .timeleap(clock, 15, ChronoUnit.MINUTES)// - .input(SUM_GRID_ACTIVE_POWER, 0) // - .output(IO_OUTPUT1, true) // - .output(IO_OUTPUT2, true) // - .output(IO_OUTPUT3, false) // - .output(CTRL_PHASE1TIME, 90 * 60) // - .output(CTRL_PHASE2TIME, 75 * 60) // - .output(CTRL_PHASE3TIME, 60 * 60)) // + .timeleap(clock, 15, MINUTES)// + .input(GRID_ACTIVE_POWER, 0) // + .output("io0", INPUT_OUTPUT0, true) // + .output("io0", INPUT_OUTPUT1, true) // + .output("io0", INPUT_OUTPUT2, false) // + .output(PHASE1_TIME, 90 * 60) // + .output(PHASE2_TIME, 75 * 60) // + .output(PHASE3_TIME, 60 * 60)) // .next(new TestCase() // // Switch to next day - .timeleap(clock, 22, ChronoUnit.HOURS)// - .input(SUM_GRID_ACTIVE_POWER, 0) // - .output(IO_OUTPUT1, true) // - .output(IO_OUTPUT2, true) // - .output(IO_OUTPUT3, false) // - .output(CTRL_PHASE1TIME, 30 * 60) // - .output(CTRL_PHASE2TIME, 30 * 60) // - .output(CTRL_PHASE3TIME, 0)); // + .timeleap(clock, 22, HOURS)// + .input(GRID_ACTIVE_POWER, 0) // + .output("io0", INPUT_OUTPUT0, true) // + .output("io0", INPUT_OUTPUT1, true) // + .output("io0", INPUT_OUTPUT2, false) // + .output(PHASE1_TIME, 30 * 60) // + .output(PHASE2_TIME, 30 * 60) // + .output(PHASE3_TIME, 0)) // + .deactivate(); } } diff --git a/io.openems.edge.controller.io.heatingelement/test/io/openems/edge/controller/io/heatingelement/ControllerIoHeatingElementImplTest2.java b/io.openems.edge.controller.io.heatingelement/test/io/openems/edge/controller/io/heatingelement/ControllerIoHeatingElementImplTest2.java index 31ec267cf14..788715d4715 100644 --- a/io.openems.edge.controller.io.heatingelement/test/io/openems/edge/controller/io/heatingelement/ControllerIoHeatingElementImplTest2.java +++ b/io.openems.edge.controller.io.heatingelement/test/io/openems/edge/controller/io/heatingelement/ControllerIoHeatingElementImplTest2.java @@ -1,13 +1,20 @@ package io.openems.edge.controller.io.heatingelement; +import static io.openems.edge.common.sum.Sum.ChannelId.GRID_ACTIVE_POWER; +import static io.openems.edge.controller.io.heatingelement.ControllerIoHeatingElement.ChannelId.FORCE_START_AT_SECONDS_OF_DAY; +import static io.openems.edge.controller.io.heatingelement.ControllerIoHeatingElement.ChannelId.STATUS; +import static io.openems.edge.controller.io.heatingelement.ControllerIoHeatingElement.ChannelId.TOTAL_PHASE_TIME; +import static io.openems.edge.io.test.DummyInputOutput.ChannelId.INPUT_OUTPUT0; +import static io.openems.edge.io.test.DummyInputOutput.ChannelId.INPUT_OUTPUT1; +import static io.openems.edge.io.test.DummyInputOutput.ChannelId.INPUT_OUTPUT2; +import static java.time.temporal.ChronoUnit.MINUTES; + import java.time.Instant; import java.time.ZoneOffset; -import java.time.temporal.ChronoUnit; import org.junit.Test; import io.openems.common.test.TimeLeapClock; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.sum.DummySum; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyComponentManager; @@ -20,20 +27,6 @@ public class ControllerIoHeatingElementImplTest2 { - private static final String CTRL_ID = "ctrl0"; - private static final String IO_ID = "io0"; - - private static final ChannelAddress SUM_GRID_ACTIVE_POWER = new ChannelAddress("_sum", "GridActivePower"); - - private static final ChannelAddress IO_OUTPUT1 = new ChannelAddress(IO_ID, "InputOutput1"); - private static final ChannelAddress IO_OUTPUT2 = new ChannelAddress(IO_ID, "InputOutput2"); - private static final ChannelAddress IO_OUTPUT3 = new ChannelAddress(IO_ID, "InputOutput3"); - - private static final ChannelAddress CTRL_FORCE_START_AT_SECONDS_OF_DAY = new ChannelAddress(CTRL_ID, - "ForceStartAtSecondsOfDay"); - private static final ChannelAddress CTRL_TOTAL_PHASE_TIME = new ChannelAddress(CTRL_ID, "TotalPhaseTime"); - private static final ChannelAddress CTRL_STATUS = new ChannelAddress(CTRL_ID, "Status"); - @Test public void minimumTime_lowerLevel_test() throws Exception { final var clock = new TimeLeapClock(Instant.ofEpochSecond(1577836800) /* starts at 1. January 2020 00:00:00 */, @@ -41,12 +34,12 @@ public void minimumTime_lowerLevel_test() throws Exception { new ControllerTest(new ControllerIoHeatingElementImpl()) // .addReference("componentManager", new DummyComponentManager(clock)) // .addReference("sum", new DummySum()) // - .addComponent(new DummyInputOutput(IO_ID)) // + .addComponent(new DummyInputOutput("io0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setOutputChannelPhaseL1(IO_OUTPUT1.toString()) // - .setOutputChannelPhaseL2(IO_OUTPUT2.toString()) // - .setOutputChannelPhaseL3(IO_OUTPUT3.toString()) // + .setId("ctrl0") // + .setOutputChannelPhaseL1("io0/InputOutput0") // + .setOutputChannelPhaseL2("io0/InputOutput1") // + .setOutputChannelPhaseL3("io0/InputOutput2") // .setEndTime("15:45:00") // .setPowerOfPhase(2000) // .setMode(Mode.AUTOMATIC) // @@ -56,20 +49,21 @@ public void minimumTime_lowerLevel_test() throws Exception { .setMinimumSwitchingTime(60) // .build()) // .next(new TestCase() // - .input(SUM_GRID_ACTIVE_POWER, 0) // - .output(IO_OUTPUT1, false))// + .input(GRID_ACTIVE_POWER, 0) // + .output("io0", INPUT_OUTPUT0, false))// .next(new TestCase() // - .timeleap(clock, 6, ChronoUnit.MINUTES)// - .input(SUM_GRID_ACTIVE_POWER, -2000) // - .output(IO_OUTPUT1, true) // - .output(CTRL_TOTAL_PHASE_TIME, 0) // - .output(CTRL_FORCE_START_AT_SECONDS_OF_DAY, 53_100 /* 14:45 */)) // + .timeleap(clock, 6, MINUTES)// + .input(GRID_ACTIVE_POWER, -2000) // + .output("io0", INPUT_OUTPUT0, true) // + .output(TOTAL_PHASE_TIME, 0) // + .output(FORCE_START_AT_SECONDS_OF_DAY, 53_100 /* 14:45 */)) // .next(new TestCase() // - .timeleap(clock, 6, ChronoUnit.MINUTES)// - .input(SUM_GRID_ACTIVE_POWER, 0) // - .output(IO_OUTPUT1, true) // - .output(CTRL_TOTAL_PHASE_TIME, 360 /* 6 minutes, one phase */) // - .output(CTRL_FORCE_START_AT_SECONDS_OF_DAY, 53_220 /* 14:47 - two minutes later */)); // + .timeleap(clock, 6, MINUTES)// + .input(GRID_ACTIVE_POWER, 0) // + .output("io0", INPUT_OUTPUT0, true) // + .output(TOTAL_PHASE_TIME, 360 /* 6 minutes, one phase */) // + .output(FORCE_START_AT_SECONDS_OF_DAY, 53_220 /* 14:47 - two minutes later */)) // + .deactivate(); } @Test @@ -79,12 +73,12 @@ public void minimumTime_sameLevel_test() throws Exception { new ControllerTest(new ControllerIoHeatingElementImpl()) // .addReference("componentManager", new DummyComponentManager(clock)) // .addReference("sum", new DummySum()) // - .addComponent(new DummyInputOutput(IO_ID)) // + .addComponent(new DummyInputOutput("io0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setOutputChannelPhaseL1(IO_OUTPUT1.toString()) // - .setOutputChannelPhaseL2(IO_OUTPUT2.toString()) // - .setOutputChannelPhaseL3(IO_OUTPUT3.toString()) // + .setId("ctrl0") // + .setOutputChannelPhaseL1("io0/InputOutput0") // + .setOutputChannelPhaseL2("io0/InputOutput1") // + .setOutputChannelPhaseL3("io0/InputOutput2") // .setEndTime("15:45:00") // .setPowerOfPhase(2000) // .setMode(Mode.AUTOMATIC) // @@ -94,24 +88,25 @@ public void minimumTime_sameLevel_test() throws Exception { .setMinimumSwitchingTime(60) // .build()) // .next(new TestCase() // - .input(SUM_GRID_ACTIVE_POWER, 0) // - .output(IO_OUTPUT1, false))// + .input(GRID_ACTIVE_POWER, 0) // + .output("io0", INPUT_OUTPUT0, false))// .next(new TestCase() // - .timeleap(clock, 6, ChronoUnit.MINUTES)// - .input(SUM_GRID_ACTIVE_POWER, -6000) // - .output(IO_OUTPUT1, true) // - .output(IO_OUTPUT2, true) // - .output(IO_OUTPUT3, true) // - .output(CTRL_TOTAL_PHASE_TIME, 0) // - .output(CTRL_FORCE_START_AT_SECONDS_OF_DAY, 53_100 /* 14:45 */)) // + .timeleap(clock, 6, MINUTES)// + .input(GRID_ACTIVE_POWER, -6000) // + .output("io0", INPUT_OUTPUT0, true) // + .output("io0", INPUT_OUTPUT1, true) // + .output("io0", INPUT_OUTPUT2, true) // + .output(TOTAL_PHASE_TIME, 0) // + .output(FORCE_START_AT_SECONDS_OF_DAY, 53_100 /* 14:45 */)) // .next(new TestCase() // - .timeleap(clock, 6, ChronoUnit.MINUTES)// - .input(SUM_GRID_ACTIVE_POWER, 0) // - .output(IO_OUTPUT1, true) // - .output(IO_OUTPUT2, true) // - .output(IO_OUTPUT3, true) // - .output(CTRL_TOTAL_PHASE_TIME, 1080 /* 6 minutes, all three phases */) // - .output(CTRL_FORCE_START_AT_SECONDS_OF_DAY, 53_460 /* 14:51 - six minutes later */)); // + .timeleap(clock, 6, MINUTES)// + .input(GRID_ACTIVE_POWER, 0) // + .output("io0", INPUT_OUTPUT0, true) // + .output("io0", INPUT_OUTPUT1, true) // + .output("io0", INPUT_OUTPUT2, true) // + .output(TOTAL_PHASE_TIME, 1080 /* 6 minutes, all three phases */) // + .output(FORCE_START_AT_SECONDS_OF_DAY, 53_460 /* 14:51 - six minutes later */)) // + .deactivate(); } @Test @@ -121,12 +116,12 @@ public void minimumTime_inForceMode_test() throws Exception { new ControllerTest(new ControllerIoHeatingElementImpl()) // .addReference("componentManager", new DummyComponentManager(clock)) // .addReference("sum", new DummySum()) // - .addComponent(new DummyInputOutput(IO_ID)) // + .addComponent(new DummyInputOutput("io0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setOutputChannelPhaseL1(IO_OUTPUT1.toString()) // - .setOutputChannelPhaseL2(IO_OUTPUT2.toString()) // - .setOutputChannelPhaseL3(IO_OUTPUT3.toString()) // + .setId("ctrl0") // + .setOutputChannelPhaseL1("io0/InputOutput0") // + .setOutputChannelPhaseL2("io0/InputOutput1") // + .setOutputChannelPhaseL3("io0/InputOutput2") // .setEndTime("16:00:00") // .setPowerOfPhase(2000) // .setMode(Mode.AUTOMATIC) // @@ -136,35 +131,36 @@ public void minimumTime_inForceMode_test() throws Exception { .setMinimumSwitchingTime(60) // .build()) // .next(new TestCase() // - .input(SUM_GRID_ACTIVE_POWER, 0) // - .output(IO_OUTPUT1, false))// + .input(GRID_ACTIVE_POWER, 0) // + .output("io0", INPUT_OUTPUT0, false))// .next(new TestCase() // - .timeleap(clock, 6, ChronoUnit.MINUTES)// /* 14:57 */ - .input(SUM_GRID_ACTIVE_POWER, 0) // - .output(IO_OUTPUT1, false) // - .output(IO_OUTPUT2, false) // - .output(IO_OUTPUT3, false) // - .output(CTRL_TOTAL_PHASE_TIME, 0) // - .output(CTRL_FORCE_START_AT_SECONDS_OF_DAY, 54_000 /* 15:00 */)) // + .timeleap(clock, 6, MINUTES)// /* 14:57 */ + .input(GRID_ACTIVE_POWER, 0) // + .output("io0", INPUT_OUTPUT0, false) // + .output("io0", INPUT_OUTPUT1, false) // + .output("io0", INPUT_OUTPUT2, false) // + .output(TOTAL_PHASE_TIME, 0) // + .output(FORCE_START_AT_SECONDS_OF_DAY, 54_000 /* 15:00 */)) // .next(new TestCase() // - .timeleap(clock, 4, ChronoUnit.MINUTES)// - .input(SUM_GRID_ACTIVE_POWER, 0) // - .output(IO_OUTPUT1, true) // - .output(IO_OUTPUT2, true) // - .output(IO_OUTPUT3, true) // - .output(CTRL_FORCE_START_AT_SECONDS_OF_DAY, 54_000 /* current time */)) + .timeleap(clock, 4, MINUTES)// + .input(GRID_ACTIVE_POWER, 0) // + .output("io0", INPUT_OUTPUT0, true) // + .output("io0", INPUT_OUTPUT1, true) // + .output("io0", INPUT_OUTPUT2, true) // + .output(FORCE_START_AT_SECONDS_OF_DAY, 54_000 /* current time */)) .next(new TestCase() // - .timeleap(clock, 3, ChronoUnit.MINUTES)// /* 15:03 */ - .input(SUM_GRID_ACTIVE_POWER, 0) // + .timeleap(clock, 3, MINUTES)// /* 15:03 */ + .input(GRID_ACTIVE_POWER, 0) // // Previous duration of each phase cannot be set as input if you want to count // already passed active time // .input(CTRL_PHASE1TIME, 180) // // .input(CTRL_PHASE2TIME, 180) // // .input(CTRL_PHASE3TIME, 180) // - .output(IO_OUTPUT1, true) // - .output(IO_OUTPUT2, true) // - .output(IO_OUTPUT3, true) // - .output(CTRL_STATUS, Status.ACTIVE_FORCED) // - .output(CTRL_FORCE_START_AT_SECONDS_OF_DAY, 54_180 /* current time */)); // + .output("io0", INPUT_OUTPUT0, true) // + .output("io0", INPUT_OUTPUT1, true) // + .output("io0", INPUT_OUTPUT2, true) // + .output(STATUS, Status.ACTIVE_FORCED) // + .output(FORCE_START_AT_SECONDS_OF_DAY, 54_180 /* current time */)) // + .deactivate(); } } diff --git a/io.openems.edge.controller.io.heatingelement/test/io/openems/edge/controller/io/heatingelement/ControllerIoHeatingElementImplTest3.java b/io.openems.edge.controller.io.heatingelement/test/io/openems/edge/controller/io/heatingelement/ControllerIoHeatingElementImplTest3.java index f5d21a8d14d..c8d2a90b648 100644 --- a/io.openems.edge.controller.io.heatingelement/test/io/openems/edge/controller/io/heatingelement/ControllerIoHeatingElementImplTest3.java +++ b/io.openems.edge.controller.io.heatingelement/test/io/openems/edge/controller/io/heatingelement/ControllerIoHeatingElementImplTest3.java @@ -1,5 +1,9 @@ package io.openems.edge.controller.io.heatingelement; +import static io.openems.edge.io.test.DummyInputOutput.ChannelId.INPUT_OUTPUT0; +import static io.openems.edge.io.test.DummyInputOutput.ChannelId.INPUT_OUTPUT1; +import static io.openems.edge.io.test.DummyInputOutput.ChannelId.INPUT_OUTPUT2; + import java.time.Instant; import java.time.ZoneOffset; @@ -7,7 +11,6 @@ import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.test.TimeLeapClock; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.sum.DummySum; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyComponentManager; @@ -19,13 +22,6 @@ public class ControllerIoHeatingElementImplTest3 { - private static final String CTRL_ID = "ctrl0"; - private static final String IO_ID = "io0"; - - private static final ChannelAddress IO_OUTPUT1 = new ChannelAddress(IO_ID, "InputOutput1"); - private static final ChannelAddress IO_OUTPUT2 = new ChannelAddress(IO_ID, "InputOutput2"); - private static final ChannelAddress IO_OUTPUT3 = new ChannelAddress(IO_ID, "InputOutput3"); - private static ControllerTest prepareTest(Mode mode, Level level) throws OpenemsNamedException, Exception { return new ControllerTest(new ControllerIoHeatingElementImpl()) // .addReference("componentManager", @@ -35,10 +31,10 @@ private static ControllerTest prepareTest(Mode mode, Level level) throws Openems .addReference("sum", new DummySum()) // .addComponent(new DummyInputOutput("io0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setOutputChannelPhaseL1(IO_OUTPUT1.toString()) // - .setOutputChannelPhaseL2(IO_OUTPUT2.toString()) // - .setOutputChannelPhaseL3(IO_OUTPUT3.toString()) // + .setId("ctrl0") // + .setOutputChannelPhaseL1("io0/InputOutput0") // + .setOutputChannelPhaseL2("io0/InputOutput1") // + .setOutputChannelPhaseL3("io0/InputOutput2") // .setEndTime("15:45:00") // .setPowerOfPhase(2000) // .setMode(mode) // @@ -53,50 +49,50 @@ private static ControllerTest prepareTest(Mode mode, Level level) throws Openems public void testOff() throws Exception { prepareTest(Mode.MANUAL_OFF, Level.LEVEL_3) // .next(new TestCase() // - .output(IO_OUTPUT1, false) // - .output(IO_OUTPUT2, false) // - .output(IO_OUTPUT3, false) // - ); + .output("io0", INPUT_OUTPUT0, false) // + .output("io0", INPUT_OUTPUT1, false) // + .output("io0", INPUT_OUTPUT2, false)) // + .deactivate(); } @Test public void testOnLevel0() throws Exception { prepareTest(Mode.MANUAL_ON, Level.LEVEL_0) // .next(new TestCase() // - .output(IO_OUTPUT1, false) // - .output(IO_OUTPUT2, false) // - .output(IO_OUTPUT3, false) // - ); + .output("io0", INPUT_OUTPUT0, false) // + .output("io0", INPUT_OUTPUT1, false) // + .output("io0", INPUT_OUTPUT2, false)) // + .deactivate(); } @Test public void testOnLevel1() throws Exception { prepareTest(Mode.MANUAL_ON, Level.LEVEL_1) // .next(new TestCase() // - .output(IO_OUTPUT1, true) // - .output(IO_OUTPUT2, false) // - .output(IO_OUTPUT3, false) // - ); + .output("io0", INPUT_OUTPUT0, true) // + .output("io0", INPUT_OUTPUT1, false) // + .output("io0", INPUT_OUTPUT2, false)) // + .deactivate(); } @Test public void testOnLevel2() throws Exception { prepareTest(Mode.MANUAL_ON, Level.LEVEL_2) // .next(new TestCase() // - .output(IO_OUTPUT1, true) // - .output(IO_OUTPUT2, true) // - .output(IO_OUTPUT3, false) // - ); + .output("io0", INPUT_OUTPUT0, true) // + .output("io0", INPUT_OUTPUT1, true) // + .output("io0", INPUT_OUTPUT2, false)) // + .deactivate(); } @Test public void testOnLevel3() throws Exception { prepareTest(Mode.MANUAL_ON, Level.LEVEL_3) // .next(new TestCase() // - .output(IO_OUTPUT1, true) // - .output(IO_OUTPUT2, true) // - .output(IO_OUTPUT3, true) // - ); + .output("io0", INPUT_OUTPUT0, true) // + .output("io0", INPUT_OUTPUT1, true) // + .output("io0", INPUT_OUTPUT2, true)) // + .deactivate(); } } diff --git a/io.openems.edge.controller.io.heatpump.sgready/.classpath b/io.openems.edge.controller.io.heatpump.sgready/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.controller.io.heatpump.sgready/.classpath +++ b/io.openems.edge.controller.io.heatpump.sgready/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.controller.io.heatpump.sgready/test/io/openems/edge/controller/io/heatpump/sgready/ControllerIoHeatPumpSgReadyImplTest.java b/io.openems.edge.controller.io.heatpump.sgready/test/io/openems/edge/controller/io/heatpump/sgready/ControllerIoHeatPumpSgReadyImplTest.java index 25c1389f41a..7a59a71d94b 100644 --- a/io.openems.edge.controller.io.heatpump.sgready/test/io/openems/edge/controller/io/heatpump/sgready/ControllerIoHeatPumpSgReadyImplTest.java +++ b/io.openems.edge.controller.io.heatpump.sgready/test/io/openems/edge/controller/io/heatpump/sgready/ControllerIoHeatPumpSgReadyImplTest.java @@ -1,13 +1,19 @@ package io.openems.edge.controller.io.heatpump.sgready; -import java.time.Instant; -import java.time.ZoneOffset; +import static io.openems.common.test.TestUtils.createDummyClock; +import static io.openems.edge.common.sum.Sum.ChannelId.ESS_DISCHARGE_POWER; +import static io.openems.edge.common.sum.Sum.ChannelId.ESS_SOC; +import static io.openems.edge.common.sum.Sum.ChannelId.GRID_ACTIVE_POWER; +import static io.openems.edge.controller.io.heatpump.sgready.ControllerIoHeatPumpSgReady.ChannelId.AWAITING_HYSTERESIS; +import static io.openems.edge.controller.io.heatpump.sgready.ControllerIoHeatPumpSgReady.ChannelId.STATUS; +import static io.openems.edge.io.test.DummyInputOutput.ChannelId.INPUT_OUTPUT0; +import static io.openems.edge.io.test.DummyInputOutput.ChannelId.INPUT_OUTPUT1; +import static java.time.temporal.ChronoUnit.SECONDS; + import java.time.temporal.ChronoUnit; import org.junit.Test; -import io.openems.common.test.TimeLeapClock; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.sum.DummySum; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyComponentManager; @@ -16,173 +22,136 @@ public class ControllerIoHeatPumpSgReadyImplTest { - private static final String CTRL_ID = "ctrHeatPump0"; - private static final String IO_ID = "io0"; - - private static final String outputChannel1 = "io0/InputOutput0"; - private static final String outputChannel2 = "io0/InputOutput1"; - - private static final ChannelAddress STATUS = new ChannelAddress(CTRL_ID, "Status"); - private static final ChannelAddress AWAITING_HYSTERESIS = new ChannelAddress(CTRL_ID, "AwaitingHysteresis"); - private static final ChannelAddress IO_OUTPUT_CHANNEL1 = new ChannelAddress(IO_ID, "InputOutput0"); - private static final ChannelAddress IO_OUTPUT_CHANNEL2 = new ChannelAddress(IO_ID, "InputOutput1"); - - private static final ChannelAddress SUM_GRID_ACTIVE_POWER = new ChannelAddress("_sum", "GridActivePower"); - private static final ChannelAddress SUM_ESS_SOC = new ChannelAddress("_sum", "EssSoc"); - private static final ChannelAddress SUM_ESS_DISCHARGE_POWER = new ChannelAddress("_sum", "EssDischargePower"); - @Test public void manual_undefined_test() throws Exception { - - final var clock = new TimeLeapClock(Instant.ofEpochSecond(1577836800) /* starts at 1. January 2020 00:00:00 */, - ZoneOffset.UTC); - new ControllerTest(new ControllerIoHeatPumpSgReadyImpl()) // - .addReference("componentManager", new DummyComponentManager(clock)) // + .addReference("componentManager", new DummyComponentManager()) // .addReference("sum", new DummySum()) // - .addComponent(new DummyInputOutput(IO_ID)) // + .addComponent(new DummyInputOutput("io0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // + .setId("ctrHeatPump0") // .setMode(Mode.MANUAL) // .setManualState(Status.UNDEFINED) // - .setOutputChannel1(outputChannel1) // - .setOutputChannel2(outputChannel2) // + .setOutputChannel1("io0/InputOutput0") // + .setOutputChannel2("io0/InputOutput1") // .build()) .next(new TestCase() // .output(STATUS, Status.REGULAR) // - .output(IO_OUTPUT_CHANNEL1, false) // - .output(IO_OUTPUT_CHANNEL2, false)); + .output("io0", INPUT_OUTPUT0, false) // + .output("io0", INPUT_OUTPUT1, false)) // + .deactivate(); } @Test public void manual_regular_test() throws Exception { - - final var clock = new TimeLeapClock(Instant.ofEpochSecond(1577836800) /* starts at 1. January 2020 00:00:00 */, - ZoneOffset.UTC); - new ControllerTest(new ControllerIoHeatPumpSgReadyImpl()) // - .addReference("componentManager", new DummyComponentManager(clock)) // + .addReference("componentManager", new DummyComponentManager()) // .addReference("sum", new DummySum()) // - .addComponent(new DummyInputOutput(IO_ID)) // + .addComponent(new DummyInputOutput("io0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // + .setId("ctrHeatPump0") // .setMode(Mode.MANUAL) // .setManualState(Status.REGULAR) // - .setOutputChannel1(outputChannel1) // - .setOutputChannel2(outputChannel2) // + .setOutputChannel1("io0/InputOutput0") // + .setOutputChannel2("io0/InputOutput1") // .build()) .next(new TestCase() // .output(STATUS, Status.REGULAR) // - .output(IO_OUTPUT_CHANNEL1, false) // - .output(IO_OUTPUT_CHANNEL2, false)); + .output("io0", INPUT_OUTPUT0, false) // + .output("io0", INPUT_OUTPUT1, false)) // + .deactivate(); } @Test public void manual_recommendation_test() throws Exception { - - final var clock = new TimeLeapClock(Instant.ofEpochSecond(1577836800) /* starts at 1. January 2020 00:00:00 */, - ZoneOffset.UTC); - new ControllerTest(new ControllerIoHeatPumpSgReadyImpl()) // - .addReference("componentManager", new DummyComponentManager(clock)) // + .addReference("componentManager", new DummyComponentManager()) // .addReference("sum", new DummySum()) // - .addComponent(new DummyInputOutput(IO_ID)) // + .addComponent(new DummyInputOutput("io0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // + .setId("ctrHeatPump0") // .setMode(Mode.MANUAL) // .setManualState(Status.RECOMMENDATION) // - .setOutputChannel1(outputChannel1) // - .setOutputChannel2(outputChannel2) // + .setOutputChannel1("io0/InputOutput0") // + .setOutputChannel2("io0/InputOutput1") // .build()) .next(new TestCase() // .output(STATUS, Status.RECOMMENDATION) // - .output(IO_OUTPUT_CHANNEL1, false) // - .output(IO_OUTPUT_CHANNEL2, true)); + .output("io0", INPUT_OUTPUT0, false) // + .output("io0", INPUT_OUTPUT1, true)) // + .deactivate(); } @Test public void manual_force_on_test() throws Exception { - - final var clock = new TimeLeapClock(Instant.ofEpochSecond(1577836800) /* starts at 1. January 2020 00:00:00 */, - ZoneOffset.UTC); - new ControllerTest(new ControllerIoHeatPumpSgReadyImpl()) // - .addReference("componentManager", new DummyComponentManager(clock)) // + .addReference("componentManager", new DummyComponentManager()) // .addReference("sum", new DummySum()) // - .addComponent(new DummyInputOutput(IO_ID)) // + .addComponent(new DummyInputOutput("io0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // + .setId("ctrHeatPump0") // .setMode(Mode.MANUAL) // .setManualState(Status.FORCE_ON) // - .setOutputChannel1(outputChannel1) // - .setOutputChannel2(outputChannel2) // + .setOutputChannel1("io0/InputOutput0") // + .setOutputChannel2("io0/InputOutput1") // .build()) .next(new TestCase() // .output(STATUS, Status.FORCE_ON) // - .output(IO_OUTPUT_CHANNEL1, true) // - .output(IO_OUTPUT_CHANNEL2, true)); + .output("io0", INPUT_OUTPUT0, true) // + .output("io0", INPUT_OUTPUT1, true)) // + .deactivate(); } @Test public void manual_lock_test() throws Exception { - - final var clock = new TimeLeapClock(Instant.ofEpochSecond(1577836800) /* starts at 1. January 2020 00:00:00 */, - ZoneOffset.UTC); - new ControllerTest(new ControllerIoHeatPumpSgReadyImpl()) // - .addReference("componentManager", new DummyComponentManager(clock)) // + .addReference("componentManager", new DummyComponentManager()) // .addReference("sum", new DummySum()) // - .addComponent(new DummyInputOutput(IO_ID)) // + .addComponent(new DummyInputOutput("io0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // + .setId("ctrHeatPump0") // .setMode(Mode.MANUAL) // .setManualState(Status.LOCK) // - .setOutputChannel1(outputChannel1) // - .setOutputChannel2(outputChannel2) // + .setOutputChannel1("io0/InputOutput0") // + .setOutputChannel2("io0/InputOutput1") // .build()) .next(new TestCase() // .output(STATUS, Status.LOCK) // - .output(IO_OUTPUT_CHANNEL1, true) // - .output(IO_OUTPUT_CHANNEL2, false)); + .output("io0", INPUT_OUTPUT0, true) // + .output("io0", INPUT_OUTPUT1, false)) // + .deactivate(); } @Test public void automatic_regular_test() throws Exception { - - final var clock = new TimeLeapClock(Instant.ofEpochSecond(1577836800) /* starts at 1. January 2020 00:00:00 */, - ZoneOffset.UTC); - new ControllerTest(new ControllerIoHeatPumpSgReadyImpl()) // - .addReference("componentManager", new DummyComponentManager(clock)) // + .addReference("componentManager", new DummyComponentManager()) // .addReference("sum", new DummySum()) // - .addComponent(new DummyInputOutput(IO_ID)) // + .addComponent(new DummyInputOutput("io0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // + .setId("ctrHeatPump0") // .setMode(Mode.AUTOMATIC) // .setAutomaticForceOnCtrlEnabled(false) // .setAutomaticRecommendationCtrlEnabled(false) // .setAutomaticLockCtrlEnabled(false) // - .setOutputChannel1(outputChannel1) // - .setOutputChannel2(outputChannel2) // + .setOutputChannel1("io0/InputOutput0") // + .setOutputChannel2("io0/InputOutput1") // .build()) .next(new TestCase() // .output(STATUS, Status.REGULAR) // - .output(IO_OUTPUT_CHANNEL1, false) // - .output(IO_OUTPUT_CHANNEL2, false)); + .output("io0", INPUT_OUTPUT0, false) // + .output("io0", INPUT_OUTPUT1, false)) // + .deactivate(); } @Test public void automatic_normal_config_test() throws Exception { - - final var clock = new TimeLeapClock(Instant.ofEpochSecond(1577836800) /* starts at 1. January 2020 00:00:00 */, - ZoneOffset.UTC); - new ControllerTest(new ControllerIoHeatPumpSgReadyImpl()) // - .addReference("componentManager", new DummyComponentManager(clock)) // + .addReference("componentManager", new DummyComponentManager()) // .addReference("sum", new DummySum()) // - .addComponent(new DummyInputOutput(IO_ID)) // + .addComponent(new DummyInputOutput("io0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // + .setId("ctrHeatPump0") // .setMode(Mode.AUTOMATIC) // .setAutomaticRecommendationCtrlEnabled(true) // .setAutomaticRecommendationSurplusPower(3000) // @@ -192,59 +161,57 @@ public void automatic_normal_config_test() throws Exception { .setAutomaticLockCtrlEnabled(true) // .setAutomaticLockGridBuyPower(5000) // .setAutomaticLockSoc(20) // - .setOutputChannel1(outputChannel1) // - .setOutputChannel2(outputChannel2) // + .setOutputChannel1("io0/InputOutput0") // + .setOutputChannel2("io0/InputOutput1") // .setMinimumSwitchingTime(0) // .build()) .next(new TestCase() // - .input(SUM_GRID_ACTIVE_POWER, -4000) // - .input(SUM_ESS_DISCHARGE_POWER, 0) // - .input(SUM_ESS_SOC, 95) // + .input(GRID_ACTIVE_POWER, -4000) // + .input(ESS_DISCHARGE_POWER, 0) // + .input(ESS_SOC, 95) // .output(STATUS, Status.RECOMMENDATION)) // .next(new TestCase() // - .input(SUM_GRID_ACTIVE_POWER, -3000) // - .input(SUM_ESS_DISCHARGE_POWER, 0) // - .input(SUM_ESS_SOC, 95) // + .input(GRID_ACTIVE_POWER, -3000) // + .input(ESS_DISCHARGE_POWER, 0) // + .input(ESS_SOC, 95) // .output(STATUS, Status.FORCE_ON)) // .next(new TestCase() // - .input(SUM_GRID_ACTIVE_POWER, 500) // - .input(SUM_ESS_DISCHARGE_POWER, 0) // - .input(SUM_ESS_SOC, 95) // + .input(GRID_ACTIVE_POWER, 500) // + .input(ESS_DISCHARGE_POWER, 0) // + .input(ESS_SOC, 95) // .output(STATUS, Status.RECOMMENDATION)) // .next(new TestCase() // - .input(SUM_GRID_ACTIVE_POWER, -2700) // - .input(SUM_ESS_DISCHARGE_POWER, 0) // - .input(SUM_ESS_SOC, 95) // + .input(GRID_ACTIVE_POWER, -2700) // + .input(ESS_DISCHARGE_POWER, 0) // + .input(ESS_SOC, 95) // .output(STATUS, Status.FORCE_ON)) // .next(new TestCase() // - .input(SUM_GRID_ACTIVE_POWER, -150) // - .input(SUM_ESS_DISCHARGE_POWER, 0) // - .input(SUM_ESS_SOC, 88) // + .input(GRID_ACTIVE_POWER, -150) // + .input(ESS_DISCHARGE_POWER, 0) // + .input(ESS_SOC, 88) // .output(STATUS, Status.RECOMMENDATION)) // .next(new TestCase() // - .input(SUM_GRID_ACTIVE_POWER, 500) // - .input(SUM_ESS_DISCHARGE_POWER, 0) // - .input(SUM_ESS_SOC, 88) // + .input(GRID_ACTIVE_POWER, 500) // + .input(ESS_DISCHARGE_POWER, 0) // + .input(ESS_SOC, 88) // .output(STATUS, Status.REGULAR)) .next(new TestCase() // - .input(SUM_GRID_ACTIVE_POWER, 5500) // - .input(SUM_ESS_DISCHARGE_POWER, 0) // - .input(SUM_ESS_SOC, 19) // - .output(STATUS, Status.LOCK)); + .input(GRID_ACTIVE_POWER, 5500) // + .input(ESS_DISCHARGE_POWER, 0) // + .input(ESS_SOC, 19) // + .output(STATUS, Status.LOCK)) // + .deactivate(); } @Test public void automatic_switching_time_test() throws Exception { - - final var clock = new TimeLeapClock(Instant.ofEpochSecond(1577836800) /* starts at 1. January 2020 00:00:00 */, - ZoneOffset.UTC); - + final var clock = createDummyClock(); new ControllerTest(new ControllerIoHeatPumpSgReadyImpl()) // .addReference("componentManager", new DummyComponentManager(clock)) // .addReference("sum", new DummySum()) // - .addComponent(new DummyInputOutput(IO_ID)) // + .addComponent(new DummyInputOutput("io0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // + .setId("ctrHeatPump0") // .setMode(Mode.AUTOMATIC) // .setAutomaticRecommendationCtrlEnabled(true) // .setAutomaticRecommendationSurplusPower(3000) // @@ -254,64 +221,62 @@ public void automatic_switching_time_test() throws Exception { .setAutomaticLockCtrlEnabled(true) // .setAutomaticLockGridBuyPower(5000) // .setAutomaticLockSoc(20) // - .setOutputChannel1(outputChannel1) // - .setOutputChannel2(outputChannel2) // + .setOutputChannel1("io0/InputOutput0") // + .setOutputChannel2("io0/InputOutput1") // .setMinimumSwitchingTime(60) // .build()) .next(new TestCase("Test 1") // - .input(SUM_GRID_ACTIVE_POWER, -4000) // - .input(SUM_ESS_DISCHARGE_POWER, 0) // - .input(SUM_ESS_SOC, 95) // + .input(GRID_ACTIVE_POWER, -4000) // + .input(ESS_DISCHARGE_POWER, 0) // + .input(ESS_SOC, 95) // .output(STATUS, Status.RECOMMENDATION)) // .next(new TestCase("Test 1") // - .timeleap(clock, 18, ChronoUnit.SECONDS) // + .timeleap(clock, 18, SECONDS) // .output(AWAITING_HYSTERESIS, true)) // .next(new TestCase("Test 2") // - .timeleap(clock, 18, ChronoUnit.SECONDS) // + .timeleap(clock, 18, SECONDS) // .output(AWAITING_HYSTERESIS, true)) // .next(new TestCase("Test 3") // - .timeleap(clock, 18, ChronoUnit.SECONDS) // + .timeleap(clock, 18, SECONDS) // .output(AWAITING_HYSTERESIS, true)) // .next(new TestCase("Test 4") // - .timeleap(clock, 18, ChronoUnit.SECONDS) // + .timeleap(clock, 18, SECONDS) // .output(AWAITING_HYSTERESIS, false)) .next(new TestCase("Test 5") // - .input(SUM_GRID_ACTIVE_POWER, -4000) // - .input(SUM_ESS_DISCHARGE_POWER, 0) // - .input(SUM_ESS_SOC, 95) // + .input(GRID_ACTIVE_POWER, -4000) // + .input(ESS_DISCHARGE_POWER, 0) // + .input(ESS_SOC, 95) // .output(STATUS, Status.FORCE_ON)) // .next(new TestCase("Test 6") // - .timeleap(clock, 30, ChronoUnit.SECONDS) // + .timeleap(clock, 30, SECONDS) // .output(STATUS, Status.FORCE_ON) // .output(AWAITING_HYSTERESIS, true)) // .next(new TestCase("Test 7") // - .timeleap(clock, 10, ChronoUnit.SECONDS) // - .input(SUM_GRID_ACTIVE_POWER, -500) // - .input(SUM_ESS_DISCHARGE_POWER, 0) // - .input(SUM_ESS_SOC, 95) // + .timeleap(clock, 10, SECONDS) // + .input(GRID_ACTIVE_POWER, -500) // + .input(ESS_DISCHARGE_POWER, 0) // + .input(ESS_SOC, 95) // .output(STATUS, Status.FORCE_ON) // .output(AWAITING_HYSTERESIS, true)) // .next(new TestCase("Test 8") // - .timeleap(clock, 30, ChronoUnit.SECONDS) // - .input(SUM_GRID_ACTIVE_POWER, 500) // - .input(SUM_ESS_DISCHARGE_POWER, 0) // - .input(SUM_ESS_SOC, 95) // + .timeleap(clock, 30, SECONDS) // + .input(GRID_ACTIVE_POWER, 500) // + .input(ESS_DISCHARGE_POWER, 0) // + .input(ESS_SOC, 95) // .output(AWAITING_HYSTERESIS, false) // - .output(STATUS, Status.RECOMMENDATION) // - ); + .output(STATUS, Status.RECOMMENDATION)) // + .deactivate(); } @Test public void automatic_switching2_time_test() throws Exception { - - final var clock = new TimeLeapClock(Instant.ofEpochSecond(1577836800), ZoneOffset.UTC); - - new ControllerTest(new ControllerIoHeatPumpSgReadyImpl()).addReference("componentManager", // - new DummyComponentManager(clock)) // + final var clock = createDummyClock(); + new ControllerTest(new ControllerIoHeatPumpSgReadyImpl())// + .addReference("componentManager", new DummyComponentManager(clock)) // .addReference("sum", new DummySum()) // - .addComponent(new DummyInputOutput(IO_ID)) // + .addComponent(new DummyInputOutput("io0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // + .setId("ctrHeatPump0") // .setMode(Mode.AUTOMATIC) // .setAutomaticRecommendationCtrlEnabled(true) // .setAutomaticRecommendationSurplusPower(3000) // @@ -321,63 +286,64 @@ public void automatic_switching2_time_test() throws Exception { .setAutomaticLockCtrlEnabled(true) // .setAutomaticLockGridBuyPower(5000) // .setAutomaticLockSoc(20) // - .setOutputChannel1(outputChannel1) // - .setOutputChannel2(outputChannel2) // + .setOutputChannel1("io0/InputOutput0") // + .setOutputChannel2("io0/InputOutput1") // .setMinimumSwitchingTime(60) // .build()) .next(new TestCase("Test 1") // - .input(SUM_GRID_ACTIVE_POWER, -4000) // - .input(SUM_ESS_DISCHARGE_POWER, 0) // - .input(SUM_ESS_SOC, 95) // + .input(GRID_ACTIVE_POWER, -4000) // + .input(ESS_DISCHARGE_POWER, 0) // + .input(ESS_SOC, 95) // .output(STATUS, Status.RECOMMENDATION)) // .next(new TestCase("Test 2") // - .timeleap(clock, 50, ChronoUnit.SECONDS)// - .input(SUM_GRID_ACTIVE_POWER, -3000) // - .input(SUM_ESS_DISCHARGE_POWER, 0) // - .input(SUM_ESS_SOC, 95) // + .timeleap(clock, 50, SECONDS)// + .input(GRID_ACTIVE_POWER, -3000) // + .input(ESS_DISCHARGE_POWER, 0) // + .input(ESS_SOC, 95) // .output(STATUS, Status.RECOMMENDATION) // .output(AWAITING_HYSTERESIS, true)) // .next(new TestCase("Test 3") // - .timeleap(clock, 15, ChronoUnit.SECONDS)// - .input(SUM_GRID_ACTIVE_POWER, -3000) // - .input(SUM_ESS_DISCHARGE_POWER, 0) // - .input(SUM_ESS_SOC, 95) // + .timeleap(clock, 15, SECONDS)// + .input(GRID_ACTIVE_POWER, -3000) // + .input(ESS_DISCHARGE_POWER, 0) // + .input(ESS_SOC, 95) // .output(AWAITING_HYSTERESIS, false)) // .next(new TestCase("Test 3 - Results") // .output(AWAITING_HYSTERESIS, true) // .output(STATUS, Status.FORCE_ON)) // .next(new TestCase("Test 4") // - .timeleap(clock, 65, ChronoUnit.SECONDS)// - .input(SUM_GRID_ACTIVE_POWER, -3000) // - .input(SUM_ESS_DISCHARGE_POWER, 0) // - .input(SUM_ESS_SOC, 95) // + .timeleap(clock, 65, SECONDS)// + .input(GRID_ACTIVE_POWER, -3000) // + .input(ESS_DISCHARGE_POWER, 0) // + .input(ESS_SOC, 95) // .output(AWAITING_HYSTERESIS, false) // .output(STATUS, Status.FORCE_ON)) // .next(new TestCase("Test 5") // - .timeleap(clock, 15, ChronoUnit.SECONDS)// - .input(SUM_GRID_ACTIVE_POWER, 500) // - .input(SUM_ESS_DISCHARGE_POWER, 0) // - .input(SUM_ESS_SOC, 95) // + .timeleap(clock, 15, SECONDS)// + .input(GRID_ACTIVE_POWER, 500) // + .input(ESS_DISCHARGE_POWER, 0) // + .input(ESS_SOC, 95) // .output(AWAITING_HYSTERESIS, false) // .output(STATUS, Status.RECOMMENDATION)) // .next(new TestCase("Test 6") // - .timeleap(clock, 65, ChronoUnit.SECONDS)// - .input(SUM_GRID_ACTIVE_POWER, 15000) // - .input(SUM_ESS_DISCHARGE_POWER, 0) // - .input(SUM_ESS_SOC, 15) // + .timeleap(clock, 65, SECONDS)// + .input(GRID_ACTIVE_POWER, 15000) // + .input(ESS_DISCHARGE_POWER, 0) // + .input(ESS_SOC, 15) // .output(AWAITING_HYSTERESIS, false) // .output(STATUS, Status.LOCK)) // .next(new TestCase("Test 7") // .timeleap(clock, 15, ChronoUnit.MINUTES) // - .input(SUM_GRID_ACTIVE_POWER, -2700) // - .input(SUM_ESS_DISCHARGE_POWER, 0) // - .input(SUM_ESS_SOC, 95) // + .input(GRID_ACTIVE_POWER, -2700) // + .input(ESS_DISCHARGE_POWER, 0) // + .input(ESS_SOC, 95) // .output(STATUS, Status.REGULAR)) // .next(new TestCase("Test 8") // .timeleap(clock, 15, ChronoUnit.MINUTES)// - .input(SUM_GRID_ACTIVE_POWER, -15000) // - .input(SUM_ESS_DISCHARGE_POWER, 0) // - .input(SUM_ESS_SOC, 88) // - .output(STATUS, Status.RECOMMENDATION)); // + .input(GRID_ACTIVE_POWER, -15000) // + .input(ESS_DISCHARGE_POWER, 0) // + .input(ESS_SOC, 88) // + .output(STATUS, Status.RECOMMENDATION)) // + .deactivate(); } } diff --git a/io.openems.edge.controller.pvinverter.fixpowerlimit/.classpath b/io.openems.edge.controller.pvinverter.fixpowerlimit/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.controller.pvinverter.fixpowerlimit/.classpath +++ b/io.openems.edge.controller.pvinverter.fixpowerlimit/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.controller.pvinverter.fixpowerlimit/test/io/openems/edge/controller/pvinverter/fixpowerlimit/ControllerPvInverterFixPowerLimitImplTest.java b/io.openems.edge.controller.pvinverter.fixpowerlimit/test/io/openems/edge/controller/pvinverter/fixpowerlimit/ControllerPvInverterFixPowerLimitImplTest.java index 03b22d8e4ce..f854cb3e17d 100644 --- a/io.openems.edge.controller.pvinverter.fixpowerlimit/test/io/openems/edge/controller/pvinverter/fixpowerlimit/ControllerPvInverterFixPowerLimitImplTest.java +++ b/io.openems.edge.controller.pvinverter.fixpowerlimit/test/io/openems/edge/controller/pvinverter/fixpowerlimit/ControllerPvInverterFixPowerLimitImplTest.java @@ -7,19 +7,16 @@ public class ControllerPvInverterFixPowerLimitImplTest { - private static final String CTRL_ID = "ctrl0"; - private static final String PV_INVERTER_ID = "pvInverter0"; - @Test public void test() throws Exception { new ControllerTest(new ControllerPvInverterFixPowerLimitImpl()) // .addReference("componentManager", new DummyComponentManager()) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setPvInverterId(PV_INVERTER_ID) // + .setId("ctrl0") // + .setPvInverterId("pvInverter0") // .setPowerLimit(10000) // - .build()); // - ; + .build()) // + .deactivate(); } } diff --git a/io.openems.edge.controller.pvinverter.selltogridlimit/.classpath b/io.openems.edge.controller.pvinverter.selltogridlimit/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.controller.pvinverter.selltogridlimit/.classpath +++ b/io.openems.edge.controller.pvinverter.selltogridlimit/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.controller.pvinverter.selltogridlimit/test/io/openems/edge/controller/pvinverter/selltogridlimit/ControllerPvInverterSellToGridLimitImplTest.java b/io.openems.edge.controller.pvinverter.selltogridlimit/test/io/openems/edge/controller/pvinverter/selltogridlimit/ControllerPvInverterSellToGridLimitImplTest.java index f1d89b53e25..ef3bc2313cb 100644 --- a/io.openems.edge.controller.pvinverter.selltogridlimit/test/io/openems/edge/controller/pvinverter/selltogridlimit/ControllerPvInverterSellToGridLimitImplTest.java +++ b/io.openems.edge.controller.pvinverter.selltogridlimit/test/io/openems/edge/controller/pvinverter/selltogridlimit/ControllerPvInverterSellToGridLimitImplTest.java @@ -1,153 +1,148 @@ package io.openems.edge.controller.pvinverter.selltogridlimit; +import static io.openems.common.types.OpenemsType.INTEGER; +import static io.openems.edge.common.type.TypeUtils.getAsType; +import static io.openems.edge.controller.pvinverter.selltogridlimit.ControllerPvInverterSellToGridLimitImpl.DEFAULT_MAX_ADJUSTMENT_RATE; +import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.ACTIVE_POWER; +import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.ACTIVE_POWER_L1; +import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.ACTIVE_POWER_L2; +import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.ACTIVE_POWER_L3; +import static io.openems.edge.pvinverter.api.ManagedSymmetricPvInverter.ChannelId.ACTIVE_POWER_LIMIT; + import org.junit.Test; -import io.openems.common.types.ChannelAddress; -import io.openems.common.types.OpenemsType; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyComponentManager; -import io.openems.edge.common.type.TypeUtils; import io.openems.edge.controller.test.ControllerTest; import io.openems.edge.meter.test.DummyElectricityMeter; import io.openems.edge.pvinverter.test.DummyManagedSymmetricPvInverter; public class ControllerPvInverterSellToGridLimitImplTest { - private static final String CTRL_ID = "ctrl0"; - - private static final String METER_ID = "meter0"; - private static final ChannelAddress GRID_ACTIVE_POWER = new ChannelAddress(METER_ID, "ActivePower"); - private static final ChannelAddress GRID_ACTIVE_POWER_L1 = new ChannelAddress(METER_ID, "ActivePowerL1"); - private static final ChannelAddress GRID_ACTIVE_POWER_L2 = new ChannelAddress(METER_ID, "ActivePowerL2"); - private static final ChannelAddress GRID_ACTIVE_POWER_L3 = new ChannelAddress(METER_ID, "ActivePowerL3"); - - private static final String PV_INVERTER = "pvInverter0"; - private static final ChannelAddress PV_INVERTER_ACTIVE_POWER = new ChannelAddress(PV_INVERTER, "ActivePower"); - private static final ChannelAddress PV_INVERTER_SET_ACTIVE_POWER_EQUALS = new ChannelAddress(PV_INVERTER, - "ActivePowerLimit"); - - private static final double ADJUST_RATE = ControllerPvInverterSellToGridLimitImpl.DEFAULT_MAX_ADJUSTMENT_RATE; - @Test public void symmetricMeterTest() throws Exception { new ControllerTest(new ControllerPvInverterSellToGridLimitImpl()) // .addReference("componentManager", new DummyComponentManager()) // - .addComponent(new DummyElectricityMeter(METER_ID)) // - .addComponent(new DummyManagedSymmetricPvInverter(PV_INVERTER)).activate(MyConfig.create() // - .setId(CTRL_ID) // - .setMeterId(METER_ID) // + .addComponent(new DummyElectricityMeter("meter0")) // + .addComponent(new DummyManagedSymmetricPvInverter("pvInverter0")) // + .activate(MyConfig.create() // + .setId("ctrl0") // + .setMeterId("meter0") // .setAsymmetricMode(false) // .setMaximumSellToGridPower(10_000) // - .setPvInverterId(PV_INVERTER) // + .setPvInverterId("pvInverter0") // .build()) .next(new TestCase() // - .input(GRID_ACTIVE_POWER, -15000) // - .input(PV_INVERTER_ACTIVE_POWER, 15000) // - .output(PV_INVERTER_SET_ACTIVE_POWER_EQUALS, 10000)) // + .input("meter0", ACTIVE_POWER, -15000) // + .input("pvInverter0", ACTIVE_POWER, 15000) // + .output("pvInverter0", ACTIVE_POWER_LIMIT, 10000)) // .next(new TestCase() // - .input(GRID_ACTIVE_POWER, -15000) // - .input(PV_INVERTER_ACTIVE_POWER, 10000) // - .output(PV_INVERTER_SET_ACTIVE_POWER_EQUALS, - TypeUtils.getAsType(OpenemsType.INTEGER, 10000 - 10000 * ADJUST_RATE))) // 5000 -> 8000 + .input("meter0", ACTIVE_POWER, -15000) // + .input("pvInverter0", ACTIVE_POWER, 10000) // + .output("pvInverter0", ACTIVE_POWER_LIMIT, + getAsType(INTEGER, 10000 - 10000 * DEFAULT_MAX_ADJUSTMENT_RATE))) // 5000 -> 8000 .next(new TestCase() // - .input(GRID_ACTIVE_POWER, -13000) // - .input(PV_INVERTER_ACTIVE_POWER, 8000) // - .output(PV_INVERTER_SET_ACTIVE_POWER_EQUALS, - TypeUtils.getAsType(OpenemsType.INTEGER, 8000 - 8000 * ADJUST_RATE))) // 5000 -> 6400 + .input("meter0", ACTIVE_POWER, -13000) // + .input("pvInverter0", ACTIVE_POWER, 8000) // + .output("pvInverter0", ACTIVE_POWER_LIMIT, + getAsType(INTEGER, 8000 - 8000 * DEFAULT_MAX_ADJUSTMENT_RATE))) // 5000 -> 6400 .next(new TestCase() // - .input(GRID_ACTIVE_POWER, -11400) // - .input(PV_INVERTER_ACTIVE_POWER, 6400) // - .output(PV_INVERTER_SET_ACTIVE_POWER_EQUALS, - TypeUtils.getAsType(OpenemsType.INTEGER, 6400 - 6400 * ADJUST_RATE))) // 5000 -> 5120 + .input("meter0", ACTIVE_POWER, -11400) // + .input("pvInverter0", ACTIVE_POWER, 6400) // + .output("pvInverter0", ACTIVE_POWER_LIMIT, + getAsType(INTEGER, 6400 - 6400 * DEFAULT_MAX_ADJUSTMENT_RATE))) // 5000 -> 5120 .next(new TestCase() // - .input(GRID_ACTIVE_POWER, -10120) // - .input(PV_INVERTER_ACTIVE_POWER, 5120) // - .output(PV_INVERTER_SET_ACTIVE_POWER_EQUALS, 5000)) // + .input("meter0", ACTIVE_POWER, -10120) // + .input("pvInverter0", ACTIVE_POWER, 5120) // + .output("pvInverter0", ACTIVE_POWER_LIMIT, 5000)) // .next(new TestCase() // - .input(GRID_ACTIVE_POWER, -9000) // - .input(PV_INVERTER_ACTIVE_POWER, 5000) // - .output(PV_INVERTER_SET_ACTIVE_POWER_EQUALS, 6000)) // + .input("meter0", ACTIVE_POWER, -9000) // + .input("pvInverter0", ACTIVE_POWER, 5000) // + .output("pvInverter0", ACTIVE_POWER_LIMIT, 6000)) // .next(new TestCase() // - .input(GRID_ACTIVE_POWER, 0) // - .input(PV_INVERTER_ACTIVE_POWER, 6000) // - .output(PV_INVERTER_SET_ACTIVE_POWER_EQUALS, - TypeUtils.getAsType(OpenemsType.INTEGER, 6000 + 6000 * ADJUST_RATE))); // 16000 -> 7200 + .input("meter0", ACTIVE_POWER, 0) // + .input("pvInverter0", ACTIVE_POWER, 6000) // + .output("pvInverter0", ACTIVE_POWER_LIMIT, + getAsType(INTEGER, 6000 + 6000 * DEFAULT_MAX_ADJUSTMENT_RATE))) // 16000 -> 7200 + .deactivate(); } @Test public void asymmetricMeterTest() throws Exception { new ControllerTest(new ControllerPvInverterSellToGridLimitImpl()) // .addReference("componentManager", new DummyComponentManager()) // - .addComponent(new DummyElectricityMeter(METER_ID)) // - .addComponent(new DummyManagedSymmetricPvInverter(PV_INVERTER)).activate(MyConfig.create() // - .setId(CTRL_ID) // - .setMeterId(METER_ID) // + .addComponent(new DummyElectricityMeter("meter0")) // + .addComponent(new DummyManagedSymmetricPvInverter("pvInverter0")) // + .activate(MyConfig.create() // + .setId("ctrl0") // + .setMeterId("meter0") // .setAsymmetricMode(true) // .setMaximumSellToGridPower(4_000) // 12_000 in total - .setPvInverterId(PV_INVERTER) // + .setPvInverterId("pvInverter0") // .build()) .next(new TestCase() // - .input(GRID_ACTIVE_POWER_L1, -2000) // - .input(GRID_ACTIVE_POWER_L2, -4000) // - .input(GRID_ACTIVE_POWER_L3, -3000) // - .input(PV_INVERTER_ACTIVE_POWER, 12000) // - .output(PV_INVERTER_SET_ACTIVE_POWER_EQUALS, 12000)) // + .input("meter0", ACTIVE_POWER_L1, -2000) // + .input("meter0", ACTIVE_POWER_L2, -4000) // + .input("meter0", ACTIVE_POWER_L3, -3000) // + .input("pvInverter0", ACTIVE_POWER, 12000) // + .output("pvInverter0", ACTIVE_POWER_LIMIT, 12000)) // .next(new TestCase() // - .input(GRID_ACTIVE_POWER_L1, -2000) // - .input(GRID_ACTIVE_POWER_L2, -5000) // - .input(GRID_ACTIVE_POWER_L3, -2000) // - .input(PV_INVERTER_ACTIVE_POWER, 12000) // - .output(PV_INVERTER_SET_ACTIVE_POWER_EQUALS, - TypeUtils.getAsType(OpenemsType.INTEGER, 12000 - 12000 * ADJUST_RATE))) // 9000 -> 9600 + .input("meter0", ACTIVE_POWER_L1, -2000) // + .input("meter0", ACTIVE_POWER_L2, -5000) // + .input("meter0", ACTIVE_POWER_L3, -2000) // + .input("pvInverter0", ACTIVE_POWER, 12000) // + .output("pvInverter0", ACTIVE_POWER_LIMIT, + getAsType(INTEGER, 12000 - 12000 * DEFAULT_MAX_ADJUSTMENT_RATE))) // 9000 -> 9600 .next(new TestCase() // - .input(GRID_ACTIVE_POWER_L1, -1200) // - .input(GRID_ACTIVE_POWER_L2, -4200) // - .input(GRID_ACTIVE_POWER_L3, -1200) // - .input(PV_INVERTER_ACTIVE_POWER, 9600) // - .output(PV_INVERTER_SET_ACTIVE_POWER_EQUALS, 9000)) // + .input("meter0", ACTIVE_POWER_L1, -1200) // + .input("meter0", ACTIVE_POWER_L2, -4200) // + .input("meter0", ACTIVE_POWER_L3, -1200) // + .input("pvInverter0", ACTIVE_POWER, 9600) // + .output("pvInverter0", ACTIVE_POWER_LIMIT, 9000)) // .next(new TestCase() // - .input(GRID_ACTIVE_POWER_L1, -1000) // - .input(GRID_ACTIVE_POWER_L2, -4000) // - .input(GRID_ACTIVE_POWER_L3, -1000) // - .input(PV_INVERTER_ACTIVE_POWER, 9000) // - .output(PV_INVERTER_SET_ACTIVE_POWER_EQUALS, 9000)) // + .input("meter0", ACTIVE_POWER_L1, -1000) // + .input("meter0", ACTIVE_POWER_L2, -4000) // + .input("meter0", ACTIVE_POWER_L3, -1000) // + .input("pvInverter0", ACTIVE_POWER, 9000) // + .output("pvInverter0", ACTIVE_POWER_LIMIT, 9000)) // .next(new TestCase() // - .input(GRID_ACTIVE_POWER_L1, -1000) // - .input(GRID_ACTIVE_POWER_L2, -3700) // - .input(GRID_ACTIVE_POWER_L3, -1000) // - .input(PV_INVERTER_ACTIVE_POWER, 9000) // - .output(PV_INVERTER_SET_ACTIVE_POWER_EQUALS, 9900)) // + .input("meter0", ACTIVE_POWER_L1, -1000) // + .input("meter0", ACTIVE_POWER_L2, -3700) // + .input("meter0", ACTIVE_POWER_L3, -1000) // + .input("pvInverter0", ACTIVE_POWER, 9000) // + .output("pvInverter0", ACTIVE_POWER_LIMIT, 9900)) // .next(new TestCase() // - .input(GRID_ACTIVE_POWER_L1, -2000) // - .input(GRID_ACTIVE_POWER_L2, -5000) // - .input(GRID_ACTIVE_POWER_L3, -2000) // - .input(PV_INVERTER_ACTIVE_POWER, 9900) // - .output(PV_INVERTER_SET_ACTIVE_POWER_EQUALS, - TypeUtils.getAsType(OpenemsType.INTEGER, 9900 - 9900 * ADJUST_RATE))) // 6900 -> 7920 + .input("meter0", ACTIVE_POWER_L1, -2000) // + .input("meter0", ACTIVE_POWER_L2, -5000) // + .input("meter0", ACTIVE_POWER_L3, -2000) // + .input("pvInverter0", ACTIVE_POWER, 9900) // + .output("pvInverter0", ACTIVE_POWER_LIMIT, + getAsType(INTEGER, 9900 - 9900 * DEFAULT_MAX_ADJUSTMENT_RATE))) // 6900 -> 7920 .next(new TestCase() // - .input(GRID_ACTIVE_POWER_L1, -2000) // - .input(GRID_ACTIVE_POWER_L2, -5000) // - .input(GRID_ACTIVE_POWER_L3, -2000) // - .input(PV_INVERTER_ACTIVE_POWER, 7920) // - .output(PV_INVERTER_SET_ACTIVE_POWER_EQUALS, - TypeUtils.getAsType(OpenemsType.INTEGER, 7920 - 7920 * ADJUST_RATE))); // 4920 -> 6336 + .input("meter0", ACTIVE_POWER_L1, -2000) // + .input("meter0", ACTIVE_POWER_L2, -5000) // + .input("meter0", ACTIVE_POWER_L3, -2000) // + .input("pvInverter0", ACTIVE_POWER, 7920) // + .output("pvInverter0", ACTIVE_POWER_LIMIT, + getAsType(INTEGER, 7920 - 7920 * DEFAULT_MAX_ADJUSTMENT_RATE))); // 4920 -> 6336 new ControllerTest(new ControllerPvInverterSellToGridLimitImpl()) // .addReference("componentManager", new DummyComponentManager()) // - .addComponent(new DummyElectricityMeter(METER_ID)) // - .addComponent(new DummyManagedSymmetricPvInverter(PV_INVERTER)).activate(MyConfig.create() // - .setId(CTRL_ID) // - .setMeterId(METER_ID) // + .addComponent(new DummyElectricityMeter("meter0")) // + .addComponent(new DummyManagedSymmetricPvInverter("pvInverter0")) // + .activate(MyConfig.create() // + .setId("ctrl0") // + .setMeterId("meter0") // .setAsymmetricMode(true) // .setMaximumSellToGridPower(4_000) // 12_000 in total - .setPvInverterId(PV_INVERTER) // + .setPvInverterId("pvInverter0") // .build()) .next(new TestCase() // - .input(GRID_ACTIVE_POWER_L1, 1000) // - .input(GRID_ACTIVE_POWER_L2, 2000) // - .input(GRID_ACTIVE_POWER_L3, 3000) // - .input(PV_INVERTER_ACTIVE_POWER, 1000) // - .output(PV_INVERTER_SET_ACTIVE_POWER_EQUALS, 16000)) // - ; + .input("meter0", ACTIVE_POWER_L1, 1000) // + .input("meter0", ACTIVE_POWER_L2, 2000) // + .input("meter0", ACTIVE_POWER_L3, 3000) // + .input("pvInverter0", ACTIVE_POWER, 1000) // + .output("pvInverter0", ACTIVE_POWER_LIMIT, 16000)) // + .deactivate(); } } diff --git a/io.openems.edge.controller.symmetric.balancingschedule/.classpath b/io.openems.edge.controller.symmetric.balancingschedule/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.controller.symmetric.balancingschedule/.classpath +++ b/io.openems.edge.controller.symmetric.balancingschedule/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.controller.symmetric.balancingschedule/test/io/openems/edge/controller/symmetric/balancingschedule/ControllerEssBalancingScheduleImplTest.java b/io.openems.edge.controller.symmetric.balancingschedule/test/io/openems/edge/controller/symmetric/balancingschedule/ControllerEssBalancingScheduleImplTest.java index 7501e1d9d60..ed88ef4c8be 100644 --- a/io.openems.edge.controller.symmetric.balancingschedule/test/io/openems/edge/controller/symmetric/balancingschedule/ControllerEssBalancingScheduleImplTest.java +++ b/io.openems.edge.controller.symmetric.balancingschedule/test/io/openems/edge/controller/symmetric/balancingschedule/ControllerEssBalancingScheduleImplTest.java @@ -1,39 +1,31 @@ package io.openems.edge.controller.symmetric.balancingschedule; +import static io.openems.common.utils.JsonUtils.buildJsonArray; +import static io.openems.common.utils.JsonUtils.buildJsonObject; +import static io.openems.edge.controller.symmetric.balancingschedule.ControllerEssBalancingSchedule.ChannelId.GRID_ACTIVE_POWER_SET_POINT; +import static io.openems.edge.controller.symmetric.balancingschedule.ControllerEssBalancingSchedule.ChannelId.NO_ACTIVE_SETPOINT; +import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_EQUALS_WITH_PID; +import static io.openems.edge.ess.api.SymmetricEss.ChannelId.GRID_MODE; +import static java.time.temporal.ChronoUnit.SECONDS; + import java.time.Instant; import java.time.ZoneOffset; -import java.time.temporal.ChronoUnit; import org.junit.Test; import io.openems.common.test.TimeLeapClock; -import io.openems.common.types.ChannelAddress; -import io.openems.common.utils.JsonUtils; import io.openems.edge.common.sum.GridMode; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyComponentManager; import io.openems.edge.common.test.DummyConfigurationAdmin; import io.openems.edge.controller.test.ControllerTest; +import io.openems.edge.ess.api.SymmetricEss; import io.openems.edge.ess.test.DummyManagedSymmetricEss; +import io.openems.edge.meter.api.ElectricityMeter; import io.openems.edge.meter.test.DummyElectricityMeter; public class ControllerEssBalancingScheduleImplTest { - private static final String CTRL_ID = "ctrl0"; - private static final String ESS_ID = "ess0"; - private static final String METER_ID = "meter0"; - - private static final ChannelAddress CTRL_NO_ACTIVE_SETPOINT = new ChannelAddress(CTRL_ID, "NoActiveSetpoint"); - private static final ChannelAddress CTRL_GRID_ACTIVE_POWER_SET_POINT = new ChannelAddress(CTRL_ID, - "GridActivePowerSetPoint"); - - private static final ChannelAddress ESS_GRID_MODE = new ChannelAddress(ESS_ID, "GridMode"); - private static final ChannelAddress ESS_ACTIVE_POWER = new ChannelAddress(ESS_ID, "ActivePower"); - private static final ChannelAddress SET_ACTIVE_POWER_EQUALS_WITH_PID = new ChannelAddress(ESS_ID, - "SetActivePowerEqualsWithPid"); - - private static final ChannelAddress GRID_ACTIVE_POWER = new ChannelAddress(METER_ID, "ActivePower"); - @Test public void test() throws Exception { final var start = 1577836800L; @@ -42,47 +34,47 @@ public void test() throws Exception { new ControllerTest(new ControllerEssBalancingScheduleImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager(clock)) // - .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) // - .addReference("meter", new DummyElectricityMeter(METER_ID)) // + .addReference("ess", new DummyManagedSymmetricEss("ess0")) // + .addReference("meter", new DummyElectricityMeter("meter0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // - .setMeterId(METER_ID) // - .setSchedule(JsonUtils.buildJsonArray()// - .add(JsonUtils.buildJsonObject()// + .setId("ctrl0") // + .setEssId("ess0") // + .setMeterId("meter0") // + .setSchedule(buildJsonArray()// + .add(buildJsonObject()// .addProperty("startTimestamp", start + 500) // .addProperty("duration", 900) // .addProperty("activePowerSetPoint", 0) // .build()) // - .add(JsonUtils.buildJsonObject()// + .add(buildJsonObject()// .addProperty("startTimestamp", start + 500 + 800) // .addProperty("duration", 900) // .addProperty("activePowerSetPoint", 3000) // - .build() // - ).build().toString() // - ).build()) // + .build()) // + .build().toString()) // + .build()) // .next(new TestCase("No active setpoint") // - .input(ESS_GRID_MODE, GridMode.ON_GRID) // - .input(GRID_ACTIVE_POWER, 4000) // - .input(ESS_ACTIVE_POWER, 1000) // - .output(CTRL_NO_ACTIVE_SETPOINT, true)) // + .input("ess0", GRID_MODE, GridMode.ON_GRID) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 4000) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 1000) // + .output(NO_ACTIVE_SETPOINT, true)) // .next(new TestCase("Balance to 0") // - .timeleap(clock, 500, ChronoUnit.SECONDS) // - .input(GRID_ACTIVE_POWER, 4000) // - .input(ESS_ACTIVE_POWER, 1000) // - .output(CTRL_NO_ACTIVE_SETPOINT, false) // - .output(SET_ACTIVE_POWER_EQUALS_WITH_PID, 5000)) // + .timeleap(clock, 500, SECONDS) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 4000) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 1000) // + .output(NO_ACTIVE_SETPOINT, false) // + .output("ess0", SET_ACTIVE_POWER_EQUALS_WITH_PID, 5000)) // .next(new TestCase("Balance to -2000 via Channel") // - .input(CTRL_GRID_ACTIVE_POWER_SET_POINT, -2000) // - .input(GRID_ACTIVE_POWER, 4000) // - .input(ESS_ACTIVE_POWER, 1000) // - .output(SET_ACTIVE_POWER_EQUALS_WITH_PID, 7000)) // + .input(GRID_ACTIVE_POWER_SET_POINT, -2000) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 4000) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 1000) // + .output("ess0", SET_ACTIVE_POWER_EQUALS_WITH_PID, 7000)) // .next(new TestCase("Balance to 3000") // - .timeleap(clock, 800, ChronoUnit.SECONDS) // - .input(GRID_ACTIVE_POWER, 4000) // - .input(ESS_ACTIVE_POWER, 1000) // - .output(CTRL_NO_ACTIVE_SETPOINT, false) // - .output(SET_ACTIVE_POWER_EQUALS_WITH_PID, 2000)) // - ; + .timeleap(clock, 800, SECONDS) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 4000) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 1000) // + .output(NO_ACTIVE_SETPOINT, false) // + .output("ess0", SET_ACTIVE_POWER_EQUALS_WITH_PID, 2000)) // + .deactivate(); } } diff --git a/io.openems.edge.controller.symmetric.fixreactivepower/.classpath b/io.openems.edge.controller.symmetric.fixreactivepower/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.controller.symmetric.fixreactivepower/.classpath +++ b/io.openems.edge.controller.symmetric.fixreactivepower/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.controller.symmetric.fixreactivepower/test/io/openems/edge/controller/symmetric/fixreactivepower/ControllerEssFixReactivePowerImplTest.java b/io.openems.edge.controller.symmetric.fixreactivepower/test/io/openems/edge/controller/symmetric/fixreactivepower/ControllerEssFixReactivePowerImplTest.java index 9ffd8bebe22..f576936930b 100644 --- a/io.openems.edge.controller.symmetric.fixreactivepower/test/io/openems/edge/controller/symmetric/fixreactivepower/ControllerEssFixReactivePowerImplTest.java +++ b/io.openems.edge.controller.symmetric.fixreactivepower/test/io/openems/edge/controller/symmetric/fixreactivepower/ControllerEssFixReactivePowerImplTest.java @@ -7,19 +7,16 @@ public class ControllerEssFixReactivePowerImplTest { - private static final String CTRL_ID = "ctrl0"; - private static final String ESS_ID = "ess0"; - @Test public void test() throws Exception { new ControllerTest(new ControllerEssFixReactivePowerImpl()) // .addReference("componentManager", new DummyComponentManager()) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setPower(1000) // - .build()); // - ; + .build()) // + .deactivate(); } } diff --git a/io.openems.edge.controller.symmetric.limitactivepower/.classpath b/io.openems.edge.controller.symmetric.limitactivepower/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.controller.symmetric.limitactivepower/.classpath +++ b/io.openems.edge.controller.symmetric.limitactivepower/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.controller.symmetric.limitactivepower/test/io/openems/edge/controller/symmetric/limitactivepower/ControllerEssLimitActivePowerImplTest.java b/io.openems.edge.controller.symmetric.limitactivepower/test/io/openems/edge/controller/symmetric/limitactivepower/ControllerEssLimitActivePowerImplTest.java index c9261c90ed3..ace177e6000 100644 --- a/io.openems.edge.controller.symmetric.limitactivepower/test/io/openems/edge/controller/symmetric/limitactivepower/ControllerEssLimitActivePowerImplTest.java +++ b/io.openems.edge.controller.symmetric.limitactivepower/test/io/openems/edge/controller/symmetric/limitactivepower/ControllerEssLimitActivePowerImplTest.java @@ -7,21 +7,18 @@ public class ControllerEssLimitActivePowerImplTest { - private static final String CTRL_ID = "ctrl0"; - private static final String ESS_ID = "ess0"; - @Test public void test() throws Exception { new ControllerTest(new ControllerEssLimitActivePowerImpl()) // .addReference("componentManager", new DummyComponentManager()) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setMaxChargePower(1000) // .setMaxDischargePower(1000) // .setValidatePowerConstraints(false) // - .build()); // - ; + .build()) // + .deactivate(); } } diff --git a/io.openems.edge.controller.symmetric.peakshaving/.classpath b/io.openems.edge.controller.symmetric.peakshaving/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.controller.symmetric.peakshaving/.classpath +++ b/io.openems.edge.controller.symmetric.peakshaving/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.controller.symmetric.peakshaving/test/io/openems/edge/controller/symmetric/peakshaving/ControllerEssPeakShavingImplTest.java b/io.openems.edge.controller.symmetric.peakshaving/test/io/openems/edge/controller/symmetric/peakshaving/ControllerEssPeakShavingImplTest.java index b1af67695a4..5249a7598aa 100644 --- a/io.openems.edge.controller.symmetric.peakshaving/test/io/openems/edge/controller/symmetric/peakshaving/ControllerEssPeakShavingImplTest.java +++ b/io.openems.edge.controller.symmetric.peakshaving/test/io/openems/edge/controller/symmetric/peakshaving/ControllerEssPeakShavingImplTest.java @@ -1,97 +1,91 @@ package io.openems.edge.controller.symmetric.peakshaving; +import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_EQUALS; + import org.junit.Test; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyComponentManager; import io.openems.edge.controller.test.ControllerTest; +import io.openems.edge.ess.api.SymmetricEss; import io.openems.edge.ess.test.DummyManagedSymmetricEss; import io.openems.edge.ess.test.DummyPower; +import io.openems.edge.meter.api.ElectricityMeter; import io.openems.edge.meter.test.DummyElectricityMeter; public class ControllerEssPeakShavingImplTest { - private static final String CTRL_ID = "ctrl0"; - - private static final String ESS_ID = "ess0"; - private static final ChannelAddress ESS_ACTIVE_POWER = new ChannelAddress(ESS_ID, "ActivePower"); - private static final ChannelAddress ESS_SET_ACTIVE_POWER_EQUALS = new ChannelAddress(ESS_ID, - "SetActivePowerEquals"); - - private static final String METER_ID = "meter0"; - private static final ChannelAddress METER_ACTIVE_POWER = new ChannelAddress(METER_ID, "ActivePower"); - @Test public void test() throws Exception { new ControllerTest(new ControllerEssPeakShavingImpl()) // .addReference("componentManager", new DummyComponentManager()) // - .addComponent(new DummyManagedSymmetricEss(ESS_ID) // + .addComponent(new DummyManagedSymmetricEss("ess0") // .setPower(new DummyPower(0.3, 0.3, 0.1))) // - .addComponent(new DummyElectricityMeter(METER_ID)) // + .addComponent(new DummyElectricityMeter("meter0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // - .setMeterId(METER_ID) // + .setId("ctrl0") // + .setEssId("ess0") // + .setMeterId("meter0") // .setPeakShavingPower(100_000) // .setRechargePower(50_000) // .build()) .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 0) // - .input(METER_ACTIVE_POWER, 120000) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 6000)) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 120000) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 6000)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 0) // - .input(METER_ACTIVE_POWER, 120000) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 12000)) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 120000) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 12000)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 3793) // - .input(METER_ACTIVE_POWER, 120000 - 3793) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 16483)) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 3793) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 120000 - 3793) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 16483)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 8981) // - .input(METER_ACTIVE_POWER, 120000 - 8981) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 19649)) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 8981) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 120000 - 8981) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 19649)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 13723) // - .input(METER_ACTIVE_POWER, 120000 - 13723) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 21577)) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 13723) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 120000 - 13723) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 21577)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 17469) // - .input(METER_ACTIVE_POWER, 120000 - 17469) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 22436)) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 17469) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 120000 - 17469) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 22436)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 20066) // - .input(METER_ACTIVE_POWER, 120000 - 20066) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 22531)) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 20066) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 120000 - 20066) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 22531)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 21564) // - .input(METER_ACTIVE_POWER, 120000 - 21564) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 22171)) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 21564) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 120000 - 21564) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 22171)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 22175) // - .input(METER_ACTIVE_POWER, 120000 - 22175) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 21608)) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 22175) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 120000 - 22175) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 21608)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 22173) // - .input(METER_ACTIVE_POWER, 120000 - 22173) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 21017)) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 22173) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 120000 - 22173) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 21017)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 21816) // - .input(METER_ACTIVE_POWER, 120000 - 21816) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 20508)) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 21816) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 120000 - 21816) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 20508)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 21311) // - .input(METER_ACTIVE_POWER, 120000 - 21311) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 20129)) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 21311) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 120000 - 21311) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 20129)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 20803) // - .input(METER_ACTIVE_POWER, 120000 - 20803) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 19889)) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 20803) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 120000 - 20803) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 19889)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 20377) // - .input(METER_ACTIVE_POWER, 120000 - 20377) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 19767)); // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 20377) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 120000 - 20377) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 19767)) // + .deactivate(); } } diff --git a/io.openems.edge.controller.symmetric.randompower/.classpath b/io.openems.edge.controller.symmetric.randompower/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.controller.symmetric.randompower/.classpath +++ b/io.openems.edge.controller.symmetric.randompower/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.controller.symmetric.randompower/test/io/openems/edge/controller/symmetric/randompower/ControllerEssRandomPowerImplTest.java b/io.openems.edge.controller.symmetric.randompower/test/io/openems/edge/controller/symmetric/randompower/ControllerEssRandomPowerImplTest.java index 94172daab79..ab9b1bfd8f3 100644 --- a/io.openems.edge.controller.symmetric.randompower/test/io/openems/edge/controller/symmetric/randompower/ControllerEssRandomPowerImplTest.java +++ b/io.openems.edge.controller.symmetric.randompower/test/io/openems/edge/controller/symmetric/randompower/ControllerEssRandomPowerImplTest.java @@ -7,19 +7,17 @@ public class ControllerEssRandomPowerImplTest { - private static final String CTRL_ID = "ctrl0"; - private static final String ESS_ID = "ess0"; - @Test public void test() throws Exception { new ControllerTest(new ControllerEssRandomPowerImpl()) // .addReference("componentManager", new DummyComponentManager()) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setMinPower(0) // .setMaxPower(1000) // - .build()); // + .build()) // + .deactivate(); } } diff --git a/io.openems.edge.controller.symmetric.timeslotpeakshaving/.classpath b/io.openems.edge.controller.symmetric.timeslotpeakshaving/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.controller.symmetric.timeslotpeakshaving/.classpath +++ b/io.openems.edge.controller.symmetric.timeslotpeakshaving/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.controller.symmetric.timeslotpeakshaving/test/io/openems/edge/controller/timeslotpeakshaving/ControllerEssTimeslotPeakshavingImplTest.java b/io.openems.edge.controller.symmetric.timeslotpeakshaving/test/io/openems/edge/controller/timeslotpeakshaving/ControllerEssTimeslotPeakshavingImplTest.java index f8f7066afcf..fb6cb7828ec 100644 --- a/io.openems.edge.controller.symmetric.timeslotpeakshaving/test/io/openems/edge/controller/timeslotpeakshaving/ControllerEssTimeslotPeakshavingImplTest.java +++ b/io.openems.edge.controller.symmetric.timeslotpeakshaving/test/io/openems/edge/controller/timeslotpeakshaving/ControllerEssTimeslotPeakshavingImplTest.java @@ -1,47 +1,40 @@ package io.openems.edge.controller.timeslotpeakshaving; +import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_EQUALS; +import static io.openems.edge.ess.api.SymmetricEss.ChannelId.SOC; +import static java.time.temporal.ChronoUnit.MINUTES; + import java.time.Instant; import java.time.ZoneOffset; -import java.time.temporal.ChronoUnit; import org.junit.Test; import io.openems.common.test.TimeLeapClock; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.sum.GridMode; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyComponentManager; import io.openems.edge.controller.test.ControllerTest; +import io.openems.edge.ess.api.SymmetricEss; import io.openems.edge.ess.test.DummyManagedSymmetricEss; import io.openems.edge.ess.test.DummyPower; +import io.openems.edge.meter.api.ElectricityMeter; import io.openems.edge.meter.test.DummyElectricityMeter; public class ControllerEssTimeslotPeakshavingImplTest { - private static final String CTRL_ID = "ctrl0"; - - private static final String ESS_ID = "ess0"; - private static final ChannelAddress ESS_SOC = new ChannelAddress(ESS_ID, "Soc"); - private static final ChannelAddress ESS_ACTIVE_POWER = new ChannelAddress(ESS_ID, "ActivePower"); - private static final ChannelAddress ESS_SET_ACTIVE_POWER_EQUALS = new ChannelAddress(ESS_ID, - "SetActivePowerEquals"); - - private static final String METER_ID = "meter0"; - private static final ChannelAddress METER_ACTIVE_POWER = new ChannelAddress(METER_ID, "ActivePower"); - @Test public void test() throws Exception { final var clock = new TimeLeapClock(Instant.parse("2020-02-03T08:30:00.00Z"), ZoneOffset.UTC); new ControllerTest(new ControllerEssTimeslotPeakshavingImpl()) // .addReference("componentManager", new DummyComponentManager(clock)) // - .addComponent(new DummyManagedSymmetricEss(ESS_ID) // + .addComponent(new DummyManagedSymmetricEss("ess0") // .setPower(new DummyPower(0.3, 0.3, 0.1)) // .withGridMode(GridMode.ON_GRID)) // - .addComponent(new DummyElectricityMeter(METER_ID)) // + .addComponent(new DummyElectricityMeter("meter0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // - .setMeterId(METER_ID) // + .setId("ctrl0") // + .setEssId("ess0") // + .setMeterId("meter0") // .setPeakShavingPower(100_000) // .setRechargePower(50_000) // .setSlowChargePower(50_000) // @@ -60,34 +53,37 @@ public void test() throws Exception { .setSunday(true) // .build()) .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 0) // - .input(ESS_SOC, 90) // - .input(METER_ACTIVE_POWER, 120000) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, null)) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) // + .input("ess0", SOC, 90) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 120000) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, null)) // .next(new TestCase() // - .timeleap(clock, 31, ChronoUnit.MINUTES)/* current time is 09:31, run in slow charge state */ - .input(ESS_SOC, 96) // - .input(ESS_ACTIVE_POWER, 5000) // - .input(METER_ACTIVE_POWER, 120000) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 13500)) // + .timeleap(clock, 31, MINUTES)/* current time is 09:31, run in slow charge state */ + .input("ess0", SOC, 96) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 5000) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 120000) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 13500)) // .next(new TestCase() // - .timeleap(clock, 31, ChronoUnit.MINUTES)/* current time is 09:31, run in hysterisis state */ - .input(ESS_SOC, 100) // - .input(ESS_ACTIVE_POWER, 5000) // - .input(METER_ACTIVE_POWER, 120000)) // nothing set on, Ess's setActivePower + .timeleap(clock, 31, MINUTES)/* current time is 09:31, run in hysterisis state */ + .input("ess0", SOC, 100) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 5000) // + // nothing set on, Ess's setActivePower + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 120000)) .next(new TestCase() // - .input(ESS_SOC, 94) // - .input(ESS_ACTIVE_POWER, 5000) // - .input(METER_ACTIVE_POWER, 120000) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 27000)) // + .input("ess0", SOC, 94) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 5000) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 120000) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 27000)) // .next(new TestCase() // - .timeleap(clock, 75, ChronoUnit.MINUTES)/* current time is 10:47, run in high threshold state */ - .input(ESS_ACTIVE_POWER, 5000) // - .input(METER_ACTIVE_POWER, 120000) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 33000)) // + .timeleap(clock, 75, MINUTES)/* current time is 10:47, run in high threshold state */ + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 5000) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 120000) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 33000)) // .next(new TestCase() // - .timeleap(clock, 75, ChronoUnit.MINUTES)/* current time is 12:02 run in normal state */ - .input(ESS_ACTIVE_POWER, 5000) // - .input(METER_ACTIVE_POWER, 120000)); // nothing set on, Ess's setActivePower + .timeleap(clock, 75, MINUTES)/* current time is 12:02 run in normal state */ + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 5000) // + // nothing set on, Ess's setActivePower + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 120000)) // + .deactivate(); } } diff --git a/io.openems.edge.core/.classpath b/io.openems.edge.core/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.core/.classpath +++ b/io.openems.edge.core/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.core/src/io/openems/edge/app/api/TimedataInfluxDb.java b/io.openems.edge.core/src/io/openems/edge/app/api/TimedataInfluxDb.java index 88bcaef205e..38ae41aa99b 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/api/TimedataInfluxDb.java +++ b/io.openems.edge.core/src/io/openems/edge/app/api/TimedataInfluxDb.java @@ -208,7 +208,7 @@ protected TimedataInfluxDb getApp() { protected OpenemsAppStatus getStatus() { return OpenemsAppStatus.BETA; } - + @Override public OpenemsAppPermissions getAppPermissions() { return OpenemsAppPermissions.create()// diff --git a/io.openems.edge.core/src/io/openems/edge/app/common/props/CommunicationProps.java b/io.openems.edge.core/src/io/openems/edge/app/common/props/CommunicationProps.java index 72697b13be4..79e1e8d8446 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/common/props/CommunicationProps.java +++ b/io.openems.edge.core/src/io/openems/edge/app/common/props/CommunicationProps.java @@ -1,8 +1,11 @@ package io.openems.edge.app.common.props; import static io.openems.edge.app.common.props.CommonProps.defaultDef; +import static io.openems.edge.core.appmanager.TranslationUtil.getTranslation; import static io.openems.edge.core.appmanager.formly.enums.InputType.NUMBER; import static io.openems.edge.core.appmanager.formly.enums.Validation.IP; +import static io.openems.edge.core.host.NetworkConfiguration.PATTERN_INET4ADDRESS; +import static java.util.stream.Collectors.joining; import java.util.ArrayList; import java.util.Arrays; @@ -10,15 +13,16 @@ import com.google.gson.JsonPrimitive; +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.utils.JsonUtils; import io.openems.edge.app.enums.ModbusType; import io.openems.edge.app.enums.OptionsFactory; import io.openems.edge.core.appmanager.AppDef; import io.openems.edge.core.appmanager.ComponentManagerSupplier; import io.openems.edge.core.appmanager.ComponentUtilSupplier; +import io.openems.edge.core.appmanager.HostSupplier; import io.openems.edge.core.appmanager.Nameable; import io.openems.edge.core.appmanager.OpenemsApp; -import io.openems.edge.core.appmanager.TranslationUtil; import io.openems.edge.core.appmanager.Type.Parameter.BundleProvider; import io.openems.edge.core.appmanager.formly.Case; import io.openems.edge.core.appmanager.formly.DefaultValueOptions; @@ -58,6 +62,34 @@ public static final AppDef ip() { f.setValidation(IP))); } + /** + * Creates a {@link AppDef} for a ip-address that excludes all IPs of system + * itself. + * + * @param the type of the app + * @param the type of the properties + * @param the type of the parameters + * @return the {@link AppDef} + */ + public static final // + AppDef excludingIp() { + return AppDef.copyOfGeneric(ip(), + def -> def.setField(JsonFormlyUtil::buildInputFromNameable, (app, prop, l, param, f) -> { + try { + var ips = app.getHost().getSystemIPs(); + final var exclusionPattern = ips.stream().map(ip -> ip.getHostAddress())// + .map(ip -> ip.replace(".", "\\.")) // + .collect(joining("|")); + + final var regex = "^(?!.*(?:" + exclusionPattern + ")$)" + PATTERN_INET4ADDRESS; + f.setValidation(regex, getTranslation(param.bundle(), "communication.excludingIp")); + } catch (OpenemsNamedException e) { + f.setValidation(IP); + } + })); + } + /** * Creates a {@link AppDef} for a port. * @@ -180,12 +212,12 @@ AppDef modbusGroup(// .notEqual(Exp.currentModelValue(modbusId)))); final var message = Exp.ifElse(filteredArray.length().equal(Exp.staticValue(1)), // - StringExpression.of(TranslationUtil.getTranslation(parameter.bundle(), + StringExpression.of(getTranslation(parameter.bundle(), "communication.modbusUnitId.alreadTaken.singular", filteredArray.join(", ").insideTranslation(), componentId)), // - StringExpression.of(TranslationUtil.getTranslation(parameter.bundle(), - "communication.modbusUnitId.alreadTaken.plural", - filteredArray.join(", ").insideTranslation(), componentId))); + StringExpression.of( + getTranslation(parameter.bundle(), "communication.modbusUnitId.alreadTaken.plural", + filteredArray.join(", ").insideTranslation(), componentId))); field.setCustomValidation(componentId, expression, message, modbusUnitId); diff --git a/io.openems.edge.core/src/io/openems/edge/app/common/props/ComponentProps.java b/io.openems.edge.core/src/io/openems/edge/app/common/props/ComponentProps.java index f524bc82f68..4415c1ef83a 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/common/props/ComponentProps.java +++ b/io.openems.edge.core/src/io/openems/edge/app/common/props/ComponentProps.java @@ -11,6 +11,7 @@ import com.google.gson.JsonNull; import com.google.gson.JsonPrimitive; +import io.openems.common.types.MeterType; import io.openems.common.utils.JsonUtils; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.core.appmanager.AppDef; @@ -31,7 +32,6 @@ import io.openems.edge.core.appmanager.formly.enums.DisplayType; import io.openems.edge.ess.api.ManagedSymmetricEss; import io.openems.edge.meter.api.ElectricityMeter; -import io.openems.edge.meter.api.MeterType; /** * Static method collection for {@link AppDef AppDefs} for selecting different diff --git a/io.openems.edge.core/src/io/openems/edge/app/common/props/PropsUtil.java b/io.openems.edge.core/src/io/openems/edge/app/common/props/PropsUtil.java index 547da4b3c0a..e613d84798b 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/common/props/PropsUtil.java +++ b/io.openems.edge.core/src/io/openems/edge/app/common/props/PropsUtil.java @@ -1,6 +1,6 @@ package io.openems.edge.app.common.props; -import io.openems.edge.app.integratedsystem.FeneconHome; +import io.openems.edge.app.integratedsystem.FeneconHome10; import io.openems.edge.app.integratedsystem.FeneconHome20; import io.openems.edge.app.integratedsystem.FeneconHome30; import io.openems.edge.core.appmanager.AppManagerUtil; @@ -11,25 +11,29 @@ private PropsUtil() { } /** - * Checks if a {@link FeneconHome}, {@link FeneconHome20} or - * {@link FeneconHome30} is installed. + * Checks if any type of home is installed. * * @param util the {@link AppManagerUtil} to get the installed instances - * @return true if a {@link FeneconHome} is installed otherwise false + * @return true if a {@link FeneconHome10} is installed otherwise false */ + // TODO has to be manually updated when a new home app gets added, maybe add + // subcategory for home public static boolean isHomeInstalled(AppManagerUtil util) { return !util.getInstantiatedAppsOf(// "App.FENECON.Home", // "App.FENECON.Home.20", // - "App.FENECON.Home.30" // + "App.FENECON.Home.30", // + "App.FENECON.Home6", // + "App.FENECON.Home10.Gen2", // + "App.FENECON.Home15" // ).isEmpty(); } /** - * Checks if a {@link FeneconHome} is installed. + * Checks if a {@link FeneconHome10} is installed. * * @param util the {@link AppManagerUtil} to get the installed instances - * @return true if a {@link FeneconHome} is installed otherwise false + * @return true if a {@link FeneconHome10} is installed otherwise false */ public static boolean isHome10Installed(AppManagerUtil util) { return !util.getInstantiatedAppsOf(// @@ -41,7 +45,8 @@ public static boolean isHome10Installed(AppManagerUtil util) { * Checks if a {@link FeneconHome20} or {@link FeneconHome30} is installed. * * @param util the {@link AppManagerUtil} to get the installed instances - * @return true if a {@link FeneconHome} is installed otherwise false + * @return true if a {@link FeneconHome20} or {@link FeneconHome30} is installed + * otherwise false */ public static boolean isHome20Or30Installed(AppManagerUtil util) { return !util.getInstantiatedAppsOf(// diff --git a/io.openems.edge.core/src/io/openems/edge/app/enums/MeterType.java b/io.openems.edge.core/src/io/openems/edge/app/enums/MeterType.java index 5f19565f139..f856cf3b2d7 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/enums/MeterType.java +++ b/io.openems.edge.core/src/io/openems/edge/app/enums/MeterType.java @@ -5,7 +5,7 @@ import io.openems.edge.core.appmanager.TranslationUtil; /** - * Copy of {@link io.openems.edge.meter.api.MeterType}. + * Copy of {@link io.openems.common.types.MeterType}. */ public enum MeterType implements TranslatableEnum { PRODUCTION("App.Meter.production"), // diff --git a/io.openems.edge.core/src/io/openems/edge/app/enums/SafetyCountry.java b/io.openems.edge.core/src/io/openems/edge/app/enums/SafetyCountry.java index b3fcbc77da0..186227a4caf 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/enums/SafetyCountry.java +++ b/io.openems.edge.core/src/io/openems/edge/app/enums/SafetyCountry.java @@ -11,6 +11,7 @@ public enum SafetyCountry implements TranslatableEnum { SWEDEN("sweden"), // CZECH("czech"), // HOLLAND("netherlands"), // + GREECE_MAINLAND("greece"), // ; private final String translationKey; diff --git a/io.openems.edge.core/src/io/openems/edge/app/evcs/AlpitronicEvcs.java b/io.openems.edge.core/src/io/openems/edge/app/evcs/AlpitronicEvcs.java index ad5cf6ed00d..94b0788f57a 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/evcs/AlpitronicEvcs.java +++ b/io.openems.edge.core/src/io/openems/edge/app/evcs/AlpitronicEvcs.java @@ -32,6 +32,7 @@ import io.openems.edge.app.evcs.AlpitronicEvcs.ParentProperty; import io.openems.edge.common.component.ComponentManager; import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.common.host.Host; import io.openems.edge.core.appmanager.AbstractOpenemsApp; import io.openems.edge.core.appmanager.AbstractOpenemsAppWithProps; import io.openems.edge.core.appmanager.AppConfiguration; @@ -39,6 +40,7 @@ import io.openems.edge.core.appmanager.AppDescriptor; import io.openems.edge.core.appmanager.ComponentUtil; import io.openems.edge.core.appmanager.ConfigurationTarget; +import io.openems.edge.core.appmanager.HostSupplier; import io.openems.edge.core.appmanager.InterfaceConfiguration; import io.openems.edge.core.appmanager.Nameable; import io.openems.edge.core.appmanager.OpenemsApp; @@ -81,8 +83,9 @@ *
*/ @Component(name = "App.Evcs.Alpitronic") -public class AlpitronicEvcs extends - AbstractOpenemsAppWithProps implements OpenemsApp { +public class AlpitronicEvcs + extends AbstractOpenemsAppWithProps + implements OpenemsApp, HostSupplier { public static interface ParentProperty extends Type { @@ -103,7 +106,7 @@ public enum Property implements ParentProperty { MODBUS_ID(AppDef.componentId("modbus0")), // // Properties NUMBER_OF_CONNECTORS(AppDef.copyOfGeneric(EvcsProps.numberOfChargePoints(4))), - IP(AppDef.copyOfGeneric(CommunicationProps.ip()) // + IP(AppDef.copyOfGeneric(CommunicationProps.excludingIp()) // .setDefaultValue("192.168.1.100")), // MAX_HARDWARE_POWER_ACCEPT_PROPERTY(AppDef.of() // .setAllowedToSave(false)), // @@ -206,15 +209,18 @@ public Function, BundleParameter> getParamter private static final IntFunction CTRL_EVCS_ID = value -> "CTRL_EVCS_ID_" + value; private final Map chargePointsDef = new TreeMap<>(); + private final Host host; @Activate public AlpitronicEvcs(// @Reference ComponentManager componentManager, // ComponentContext componentContext, // @Reference ConfigurationAdmin cm, // - @Reference ComponentUtil componentUtil // + @Reference ComponentUtil componentUtil, // + @Reference Host host // ) { super(componentManager, componentContext, cm, componentUtil); + this.host = host; for (int i = 0; i < MAX_NUMBER_OF_CHARGEPOINTS; i++) { final var name = EVCS_ALIAS.apply(i); this.chargePointsDef.put(name, @@ -325,4 +331,9 @@ protected ParentProperty[] propertyValues() { return builder.build().toArray(ParentProperty[]::new); } + @Override + public Host getHost() { + return this.host; + } + } diff --git a/io.openems.edge.core/src/io/openems/edge/app/evcs/DezonyEvcs.java b/io.openems.edge.core/src/io/openems/edge/app/evcs/DezonyEvcs.java index 88bcc11c66e..97924e1a33c 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/evcs/DezonyEvcs.java +++ b/io.openems.edge.core/src/io/openems/edge/app/evcs/DezonyEvcs.java @@ -25,6 +25,7 @@ import io.openems.edge.app.common.props.CommunicationProps; import io.openems.edge.app.evcs.DezonyEvcs.Property; import io.openems.edge.common.component.ComponentManager; +import io.openems.edge.common.host.Host; import io.openems.edge.core.appmanager.AbstractOpenemsApp; import io.openems.edge.core.appmanager.AbstractOpenemsAppWithProps; import io.openems.edge.core.appmanager.AppConfiguration; @@ -32,6 +33,7 @@ import io.openems.edge.core.appmanager.AppDescriptor; import io.openems.edge.core.appmanager.ComponentUtil; import io.openems.edge.core.appmanager.ConfigurationTarget; +import io.openems.edge.core.appmanager.HostSupplier; import io.openems.edge.core.appmanager.Nameable; import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; @@ -67,7 +69,7 @@ */ @Component(name = "App.Evcs.Dezony") public class DezonyEvcs extends AbstractOpenemsAppWithProps - implements OpenemsApp { + implements OpenemsApp, HostSupplier { public enum Property implements Type, Nameable { // Component-IDs @@ -75,7 +77,7 @@ public enum Property implements Type def.setDefaultValue("192.168.50.88") // .setRequired(true))), // PORT(AppDef.copyOfGeneric(CommunicationProps.port(), // @@ -110,10 +112,17 @@ public Function, BundleParameter> getParamter() { } + private final Host host; + @Activate - public DezonyEvcs(@Reference ComponentManager componentManager, ComponentContext componentContext, - @Reference ConfigurationAdmin cm, @Reference ComponentUtil componentUtil) { + public DezonyEvcs(@Reference ComponentManager componentManager, // + ComponentContext componentContext, // + @Reference ConfigurationAdmin cm, // + @Reference ComponentUtil componentUtil, // + @Reference Host host // + ) { super(componentManager, componentContext, cm, componentUtil); + this.host = host; } @Override @@ -188,4 +197,9 @@ protected Property[] propertyValues() { return Property.values(); } + @Override + public Host getHost() { + return this.host; + } + } diff --git a/io.openems.edge.core/src/io/openems/edge/app/evcs/EvcsProps.java b/io.openems.edge.core/src/io/openems/edge/app/evcs/EvcsProps.java index 258c66c960f..1904864db00 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/evcs/EvcsProps.java +++ b/io.openems.edge.core/src/io/openems/edge/app/evcs/EvcsProps.java @@ -1,7 +1,9 @@ package io.openems.edge.app.evcs; +import static io.openems.edge.app.common.props.CommonProps.defaultDef; import static io.openems.edge.core.appmanager.formly.enums.InputType.NUMBER; +import java.util.Arrays; import java.util.List; import java.util.stream.IntStream; @@ -11,7 +13,6 @@ import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.session.Language; import io.openems.common.utils.JsonUtils; -import io.openems.edge.app.common.props.CommonProps; import io.openems.edge.common.component.ComponentManager; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.core.appmanager.AppDef; @@ -28,6 +29,7 @@ import io.openems.edge.core.appmanager.formly.JsonFormlyUtil; import io.openems.edge.core.appmanager.formly.builder.FieldGroupBuilder; import io.openems.edge.core.appmanager.formly.enums.DisplayType; +import io.openems.edge.evcs.api.PhaseRotation; public final class EvcsProps { @@ -45,7 +47,7 @@ private EvcsProps() { public static AppDef numberOfChargePoints(// final int maxValue // ) { - return AppDef.copyOfGeneric(CommonProps.defaultDef(), def -> def // + return AppDef.copyOfGeneric(defaultDef(), def -> def // .setTranslatedLabel("App.Evcs.numberOfChargingStations.label") // .setDefaultValue(1) // .setField(JsonFormlyUtil::buildSelectFromNameable, (app, property, l, parameter, field) -> // @@ -103,7 +105,7 @@ private static void field(// */ public static AppDef clusterMaxHardwarePower( Nameable acceptProperty) { - return AppDef.copyOfGeneric(CommonProps.defaultDef(), def -> def // + return AppDef.copyOfGeneric(defaultDef(), def -> def // .setTranslatedLabel("App.Evcs.Cluster.maxChargeFromGrid.label") // .setAllowedToSave(false) // .setIsAllowedToSee((app, property, l, parameter, user) -> { @@ -182,4 +184,20 @@ private static final boolean isClusterInstalled(ComponentManager componentManage return false; } + /** + * Creates a {@link AppDef} for a {@link PhaseRotation}. + * + * @return the {@link AppDef} + */ + public static final AppDef phaseRotation() { + return AppDef.copyOfGeneric(defaultDef(), def -> def // + .setTranslatedLabel("App.Evcs.phaseRotation.label") // + .setTranslatedDescription("App.Evcs.phaseRotation.description") // + .setDefaultValue(PhaseRotation.L1_L2_L3) // + .setField(JsonFormlyUtil::buildSelectFromNameable, (app, property, l, parameter, field) -> { + field.setOptions(Arrays.stream(PhaseRotation.values()) // + .map(PhaseRotation::name) // + .toList()); + })); + } } diff --git a/io.openems.edge.core/src/io/openems/edge/app/evcs/HardyBarthEvcs.java b/io.openems.edge.core/src/io/openems/edge/app/evcs/HardyBarthEvcs.java index ed993465b63..5fc523eea85 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/evcs/HardyBarthEvcs.java +++ b/io.openems.edge.core/src/io/openems/edge/app/evcs/HardyBarthEvcs.java @@ -34,6 +34,7 @@ import io.openems.edge.app.evcs.HardyBarthEvcs.PropertyParent; import io.openems.edge.common.component.ComponentManager; import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.common.host.Host; import io.openems.edge.core.appmanager.AbstractOpenemsApp; import io.openems.edge.core.appmanager.AbstractOpenemsAppWithProps; import io.openems.edge.core.appmanager.AppConfiguration; @@ -41,6 +42,7 @@ import io.openems.edge.core.appmanager.AppDescriptor; import io.openems.edge.core.appmanager.ComponentUtil; import io.openems.edge.core.appmanager.ConfigurationTarget; +import io.openems.edge.core.appmanager.HostSupplier; import io.openems.edge.core.appmanager.InterfaceConfiguration; import io.openems.edge.core.appmanager.Nameable; import io.openems.edge.core.appmanager.OpenemsApp; @@ -73,6 +75,7 @@ "EVCS_ID": "evcs0", "CTRL_EVCS_ID": "ctrlEvcs0", "IP": "192.168.25.30", + "PHASE_ROTATION":"L1_L2_L3", "NUMBER_OF_CHARGING_STATIONS": 1, "EVCS_ID_CP_2": "evcs0", "CTRL_EVCS_ID_CP_2": "ctrlEvcs0", @@ -86,8 +89,9 @@ *
*/ @Component(name = "App.Evcs.HardyBarth") -public class HardyBarthEvcs extends - AbstractOpenemsAppWithProps implements OpenemsApp { +public class HardyBarthEvcs + extends AbstractOpenemsAppWithProps + implements OpenemsApp, HostSupplier { public interface PropertyParent extends Nameable, Type { @@ -144,6 +148,7 @@ public static enum Property implements PropertyParent { .collect(Exp.toArrayExpression()) // .every(i -> Exp.currentModelValue(EVCS_ID).notEqual(i)))); }))), // + PHASE_ROTATION(AppDef.copyOfGeneric(EvcsProps.phaseRotation())), // ; private final AppDef def; @@ -182,15 +187,16 @@ public enum SubPropertyFirstChargepoint implements PropertyParent { new Case(2, TranslationUtil.getTranslation(parameter.bundle(), // "App.Evcs.HardyBarth.alias.value", // TranslationUtil.getTranslation(parameter.bundle(), "right"))))))), // - IP(AppDef.copyOfGeneric(CommunicationProps.ip()) // + IP(AppDef.copyOfGeneric(CommunicationProps.excludingIp()) // .setDefaultValue("192.168.25.30") // .setAutoGenerateField(false) // .setRequired(true)), // ; - private final AppDef def; + private final AppDef def; - private SubPropertyFirstChargepoint(AppDef def) { + private SubPropertyFirstChargepoint( + AppDef def) { this.def = def; } @@ -199,7 +205,7 @@ private SubPropertyFirstChargepoint(AppDef def() { + public AppDef def() { return this.def; } @@ -237,16 +243,16 @@ public enum SubPropertySecondChargepoint implements PropertyParent { new JsonPrimitive(TranslationUtil.getTranslation(parameter.bundle(), "App.Evcs.HardyBarth.alias.value", // TranslationUtil.getTranslation(parameter.bundle(), "left")))) // .setRequired(true)), // - IP_CP_2(AppDef.copyOfGeneric(CommunicationProps.ip()) // + IP_CP_2(AppDef.copyOfGeneric(CommunicationProps.excludingIp()) // .setDefaultValue("192.168.25.31") // .setAutoGenerateField(false) // .setRequired(true)), // ; - private final AppDef def; + private final AppDef def; private SubPropertySecondChargepoint( - AppDef def) { + AppDef def) { this.def = def; } @@ -255,7 +261,7 @@ private SubPropertySecondChargepoint( * * @return the {@link AppDef} */ - public AppDef def() { + public AppDef def() { return this.def; } @@ -286,10 +292,13 @@ public Type self() { } + private final Host host; + @Activate public HardyBarthEvcs(@Reference ComponentManager componentManager, ComponentContext componentContext, - @Reference ConfigurationAdmin cm, @Reference ComponentUtil componentUtil) { + @Reference ConfigurationAdmin cm, @Reference ComponentUtil componentUtil, @Reference Host host) { super(componentManager, componentContext, cm, componentUtil); + this.host = host; } @Override @@ -317,6 +326,7 @@ OpenemsNamedException> appPropertyConfigurationFactory() { final var alias = this.getString(p, l, SubPropertyFirstChargepoint.ALIAS); final var ip = this.getString(p, l, SubPropertyFirstChargepoint.IP); final var evcsId = this.getId(t, p, Property.EVCS_ID); + final var phaseRotation = this.getString(p, l, Property.PHASE_ROTATION); final var ctrlEvcsId = this.getId(t, p, Property.CTRL_EVCS_ID); schedulerIds.add(new SchedulerComponent(ctrlEvcsId, "Controller.Evcs", this.getAppId())); @@ -324,6 +334,7 @@ OpenemsNamedException> appPropertyConfigurationFactory() { final var components = Lists.newArrayList(// new EdgeConfig.Component(evcsId, alias, factorieId, JsonUtils.buildJsonObject() // .addProperty("ip", ip) // + .addPropertyIfNotNull("phaseRotation", phaseRotation) // .build()), // new EdgeConfig.Component(ctrlEvcsId, controllerAlias, "Controller.Evcs", JsonUtils.buildJsonObject() // .addProperty("evcs.id", evcsId) // @@ -339,6 +350,7 @@ OpenemsNamedException> appPropertyConfigurationFactory() { components.add(new EdgeConfig.Component(evcsIdCp2, aliasCp2, factorieId, JsonUtils.buildJsonObject() // .addProperty("ip", ipCp2) // + .addPropertyIfNotNull("phaseRotation", phaseRotation) // .build())); components.add(new EdgeConfig.Component(ctrlEvcsIdCp2, controllerAlias, "Controller.Evcs", JsonUtils.buildJsonObject() // @@ -397,4 +409,9 @@ public OpenemsAppCategory[] getCategories() { return new OpenemsAppCategory[] { OpenemsAppCategory.EVCS }; } + @Override + public Host getHost() { + return this.host; + } + } diff --git a/io.openems.edge.core/src/io/openems/edge/app/evcs/KebaEvcs.java b/io.openems.edge.core/src/io/openems/edge/app/evcs/KebaEvcs.java index e9a680cb831..006875cd426 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/evcs/KebaEvcs.java +++ b/io.openems.edge.core/src/io/openems/edge/app/evcs/KebaEvcs.java @@ -24,6 +24,7 @@ import io.openems.edge.app.common.props.CommunicationProps; import io.openems.edge.app.evcs.KebaEvcs.Property; import io.openems.edge.common.component.ComponentManager; +import io.openems.edge.common.host.Host; import io.openems.edge.core.appmanager.AbstractOpenemsApp; import io.openems.edge.core.appmanager.AbstractOpenemsAppWithProps; import io.openems.edge.core.appmanager.AppConfiguration; @@ -31,6 +32,7 @@ import io.openems.edge.core.appmanager.AppDescriptor; import io.openems.edge.core.appmanager.ComponentUtil; import io.openems.edge.core.appmanager.ConfigurationTarget; +import io.openems.edge.core.appmanager.HostSupplier; import io.openems.edge.core.appmanager.InterfaceConfiguration; import io.openems.edge.core.appmanager.Nameable; import io.openems.edge.core.appmanager.OpenemsApp; @@ -55,7 +57,8 @@ "properties":{ "EVCS_ID": "evcs0", "CTRL_EVCS_ID": "ctrlEvcs0", - "IP":"192.168.25.11" + "IP":"192.168.25.11", + "PHASE_ROTATION":"L1_L2_L3" }, "appDescriptor": { "websiteUrl": {@link AppDescriptor#getWebsiteUrl()} @@ -65,7 +68,7 @@ */ @Component(name = "App.Evcs.Keba") public class KebaEvcs extends AbstractOpenemsAppWithProps - implements OpenemsApp { + implements OpenemsApp, HostSupplier { public enum Property implements Type, Nameable { // Component-IDs @@ -73,13 +76,14 @@ public enum Property implements Type def; @@ -105,10 +109,16 @@ public Function, BundleParameter> getParamter() { } + private final Host host; + @Activate - public KebaEvcs(@Reference ComponentManager componentManager, ComponentContext componentContext, - @Reference ConfigurationAdmin cm, @Reference ComponentUtil componentUtil) { + public KebaEvcs(@Reference ComponentManager componentManager, // + ComponentContext componentContext, // + @Reference ConfigurationAdmin cm, // + @Reference ComponentUtil componentUtil, // + @Reference Host host) { super(componentManager, componentContext, cm, componentUtil); + this.host = host; } @Override @@ -120,7 +130,7 @@ protected ThrowingTriFunction, L // values the user enters final var ip = this.getString(p, l, Property.IP); final var alias = this.getString(p, l, Property.ALIAS); - + final var phaseRotation = this.getString(p, l, Property.PHASE_ROTATION); // values which are being auto generated by the appmanager final var evcsId = this.getId(t, p, Property.EVCS_ID); final var ctrlEvcsId = this.getId(t, p, Property.CTRL_EVCS_ID); @@ -133,6 +143,7 @@ protected ThrowingTriFunction, L var components = Lists.newArrayList(// new EdgeConfig.Component(evcsId, alias, "Evcs.Keba.KeContact", JsonUtils.buildJsonObject() // .addPropertyIfNotNull("ip", ip) // + .addPropertyIfNotNull("phaseRotation", phaseRotation) // .build()), // new EdgeConfig.Component(ctrlEvcsId, controllerAlias, "Controller.Evcs", JsonUtils.buildJsonObject() // .addProperty("evcs.id", evcsId) // @@ -179,4 +190,9 @@ protected Property[] propertyValues() { return Property.values(); } + @Override + public Host getHost() { + return this.host; + } + } diff --git a/io.openems.edge.core/src/io/openems/edge/app/evcs/WebastoNextEvcs.java b/io.openems.edge.core/src/io/openems/edge/app/evcs/WebastoNextEvcs.java index 8530681aae6..0bc861a1f1c 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/evcs/WebastoNextEvcs.java +++ b/io.openems.edge.core/src/io/openems/edge/app/evcs/WebastoNextEvcs.java @@ -26,6 +26,7 @@ import io.openems.edge.app.common.props.CommunicationProps; import io.openems.edge.app.evcs.WebastoNextEvcs.Property; import io.openems.edge.common.component.ComponentManager; +import io.openems.edge.common.host.Host; import io.openems.edge.core.appmanager.AbstractOpenemsApp; import io.openems.edge.core.appmanager.AbstractOpenemsAppWithProps; import io.openems.edge.core.appmanager.AppConfiguration; @@ -33,6 +34,7 @@ import io.openems.edge.core.appmanager.AppDescriptor; import io.openems.edge.core.appmanager.ComponentUtil; import io.openems.edge.core.appmanager.ConfigurationTarget; +import io.openems.edge.core.appmanager.HostSupplier; import io.openems.edge.core.appmanager.Nameable; import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; @@ -69,7 +71,7 @@ */ @Component(name = "App.Evcs.Webasto.Next") public class WebastoNextEvcs extends AbstractOpenemsAppWithProps - implements OpenemsApp { + implements OpenemsApp, HostSupplier { public enum Property implements Type, Nameable { // Component-IDs @@ -78,7 +80,7 @@ public enum Property implements Type def // + IP(AppDef.copyOfGeneric(CommunicationProps.excludingIp(), def -> def // .setRequired(true))), // MODBUS_UNIT_ID(AppDef.copyOfGeneric(modbusUnitId(), def -> def // .setDefaultValue(1))), // @@ -111,14 +113,17 @@ public Function, BundleParameter> getParamte } + private final Host host; + @Activate public WebastoNextEvcs(// @Reference ComponentManager componentManager, // ComponentContext componentContext, // @Reference ConfigurationAdmin cm, // - @Reference ComponentUtil componentUtil // - ) { + @Reference ComponentUtil componentUtil, // + @Reference Host host) { super(componentManager, componentContext, cm, componentUtil); + this.host = host; } @Override @@ -201,4 +206,9 @@ protected Property[] propertyValues() { return Property.values(); } + @Override + public Host getHost() { + return this.host; + } + } diff --git a/io.openems.edge.core/src/io/openems/edge/app/evcs/WebastoUniteEvcs.java b/io.openems.edge.core/src/io/openems/edge/app/evcs/WebastoUniteEvcs.java index 9a3f9e9dff1..577e451ef2d 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/evcs/WebastoUniteEvcs.java +++ b/io.openems.edge.core/src/io/openems/edge/app/evcs/WebastoUniteEvcs.java @@ -26,6 +26,7 @@ import io.openems.edge.app.common.props.CommunicationProps; import io.openems.edge.app.evcs.WebastoUniteEvcs.Property; import io.openems.edge.common.component.ComponentManager; +import io.openems.edge.common.host.Host; import io.openems.edge.core.appmanager.AbstractOpenemsApp; import io.openems.edge.core.appmanager.AbstractOpenemsAppWithProps; import io.openems.edge.core.appmanager.AppConfiguration; @@ -33,6 +34,7 @@ import io.openems.edge.core.appmanager.AppDescriptor; import io.openems.edge.core.appmanager.ComponentUtil; import io.openems.edge.core.appmanager.ConfigurationTarget; +import io.openems.edge.core.appmanager.HostSupplier; import io.openems.edge.core.appmanager.Nameable; import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; @@ -69,7 +71,7 @@ */ @Component(name = "App.Evcs.Webasto.Unite") public class WebastoUniteEvcs extends AbstractOpenemsAppWithProps - implements OpenemsApp { + implements OpenemsApp, HostSupplier { public enum Property implements Type, Nameable { // Component-IDs @@ -78,7 +80,7 @@ public enum Property implements Type def // + IP(AppDef.copyOfGeneric(CommunicationProps.excludingIp(), def -> def // .setRequired(true))), // MODBUS_UNIT_ID(AppDef.copyOfGeneric(modbusUnitId(), def -> def // .setDefaultValue(255))), // @@ -111,14 +113,17 @@ public Function, BundleParameter> getParamt } + private final Host host; + @Activate public WebastoUniteEvcs(// @Reference ComponentManager componentManager, // ComponentContext componentContext, // @Reference ConfigurationAdmin cm, // - @Reference ComponentUtil componentUtil // - ) { + @Reference ComponentUtil componentUtil, // + @Reference Host host) { super(componentManager, componentContext, cm, componentUtil); + this.host = host; } @Override @@ -201,4 +206,9 @@ protected Property[] propertyValues() { return Property.values(); } + @Override + public Host getHost() { + return this.host; + } + } diff --git a/io.openems.edge.core/src/io/openems/edge/app/evcs/readonly/MennekesEvcsReadOnly.java b/io.openems.edge.core/src/io/openems/edge/app/evcs/readonly/MennekesEvcsReadOnly.java new file mode 100644 index 00000000000..181ef2565c7 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/app/evcs/readonly/MennekesEvcsReadOnly.java @@ -0,0 +1,177 @@ +package io.openems.edge.app.evcs.readonly; + +import static io.openems.edge.app.common.props.CommonProps.alias; +import static io.openems.edge.app.common.props.CommunicationProps.modbusUnitId; + +import java.util.Map; +import java.util.function.Function; + +import org.osgi.service.cm.ConfigurationAdmin; +import org.osgi.service.component.ComponentContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +import com.google.common.collect.Lists; +import com.google.gson.JsonElement; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.function.ThrowingTriFunction; +import io.openems.common.oem.OpenemsEdgeOem; +import io.openems.common.session.Language; +import io.openems.common.session.Role; +import io.openems.common.types.EdgeConfig; +import io.openems.common.utils.JsonUtils; +import io.openems.edge.app.common.props.CommunicationProps; +import io.openems.edge.app.evcs.readonly.MennekesEvcsReadOnly.Property; +import io.openems.edge.common.component.ComponentManager; +import io.openems.edge.core.appmanager.AbstractOpenemsApp; +import io.openems.edge.core.appmanager.AbstractOpenemsAppWithProps; +import io.openems.edge.core.appmanager.AppConfiguration; +import io.openems.edge.core.appmanager.AppDef; +import io.openems.edge.core.appmanager.AppDescriptor; +import io.openems.edge.core.appmanager.ComponentUtil; +import io.openems.edge.core.appmanager.ConfigurationTarget; +import io.openems.edge.core.appmanager.Nameable; +import io.openems.edge.core.appmanager.OpenemsApp; +import io.openems.edge.core.appmanager.OpenemsAppCardinality; +import io.openems.edge.core.appmanager.OpenemsAppCategory; +import io.openems.edge.core.appmanager.OpenemsAppPermissions; +import io.openems.edge.core.appmanager.Type; +import io.openems.edge.core.appmanager.Type.Parameter; +import io.openems.edge.core.appmanager.Type.Parameter.BundleParameter; +import io.openems.edge.core.appmanager.dependency.Tasks; + +/** + * Describes a read only App for Mennekes Charging Station. + * + *
+  {
+    "appId":"App.Evcs.Mennekes.ReadOnly",
+    "alias":"Mennekes Ladestation",
+    "instanceId": UUID,
+    "image": base64,
+    "properties":{
+      "EVCS_ID": "evcs0",
+      "MODBUS_ID": "modbus0",
+      "MODBUS_UNIT_ID": 1,
+    },
+    "appDescriptor": {
+    	"websiteUrl": {@link AppDescriptor#getWebsiteUrl()}
+    }
+  }
+ * 
+ */ +@Component(name = "App.Evcs.Mennekes.ReadOnly") +public class MennekesEvcsReadOnly extends + AbstractOpenemsAppWithProps implements OpenemsApp { + + public enum Property implements Type, Nameable { + // Component-IDs + EVCS_ID(AppDef.componentId("evcs0")), // + MODBUS_ID(AppDef.componentId("modbus0")), // + // Properties + ALIAS(alias()), // + IP(AppDef.copyOfGeneric(CommunicationProps.ip(), def -> def // + .setRequired(true))), // + MODBUS_UNIT_ID(AppDef.copyOfGeneric(modbusUnitId(), def -> def // + .setDefaultValue(1))), // + ; + + private final AppDef def; + + private Property(AppDef def) { + this.def = def; + } + + @Override + public Type self() { + return this; + } + + @Override + public AppDef def() { + return this.def; + } + + @Override + public Function, BundleParameter> getParamter() { + return Parameter.functionOf(AbstractOpenemsApp::getTranslationBundle); + } + + } + + @Activate + public MennekesEvcsReadOnly(@Reference ComponentManager componentManager, ComponentContext componentContext, + @Reference ConfigurationAdmin cm, @Reference ComponentUtil componentUtil) { + super(componentManager, componentContext, cm, componentUtil); + } + + @Override + protected ThrowingTriFunction, Language, AppConfiguration, OpenemsNamedException> appPropertyConfigurationFactory() { + return (t, p, l) -> { + + // values the user enters + final var alias = this.getString(p, l, Property.ALIAS); + final var ip = this.getString(p, l, Property.IP); + final var modbusUnitId = this.getInt(p, Property.MODBUS_UNIT_ID); + + // values which are being auto generated by the appmanager + final var evcsId = this.getId(t, p, Property.EVCS_ID); + final var modbusId = this.getId(t, p, Property.MODBUS_ID); + + var components = Lists.newArrayList(// + new EdgeConfig.Component(evcsId, alias, "Evcs.Mennekes", JsonUtils.buildJsonObject() // + .addProperty("modbus.id", modbusId) // + .addProperty("modbusUnitId", modbusUnitId) // + .addProperty("readOnly", true) // + .build()), // + new EdgeConfig.Component(modbusId, "Mennekes Modbus", "Bridge.Modbus.Tcp", + JsonUtils.buildJsonObject() // + .addProperty("ip", ip) // + .onlyIf(t == ConfigurationTarget.ADD, b -> b // + .addProperty("port", 502)) // + .build()) // + ); + + return AppConfiguration.create() // + .addTask(Tasks.component(components)) // + .build(); + }; + } + + @Override + public AppDescriptor getAppDescriptor(OpenemsEdgeOem oem) { + return AppDescriptor.create() // + .setWebsiteUrl(oem.getAppWebsiteUrl(this.getAppId())) // + .build(); + } + + @Override + public OpenemsAppCardinality getCardinality() { + return OpenemsAppCardinality.MULTIPLE; + } + + @Override + public OpenemsAppCategory[] getCategories() { + return new OpenemsAppCategory[] { OpenemsAppCategory.EVCS_READ_ONLY }; + } + + @Override + protected MennekesEvcsReadOnly getApp() { + return this; + } + + @Override + protected Property[] propertyValues() { + return Property.values(); + } + + @Override + public OpenemsAppPermissions getAppPermissions() { + return OpenemsAppPermissions.create()// + .setCanDelete(Role.ADMIN)// + .setCanSee(Role.ADMIN)// + .build(); + } +} diff --git a/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome.java b/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome10.java similarity index 95% rename from io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome.java rename to io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome10.java index 790bcc230fb..5a126ab6070 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome.java +++ b/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome10.java @@ -48,8 +48,8 @@ import io.openems.edge.app.enums.FeedInType; import io.openems.edge.app.enums.Parity; import io.openems.edge.app.enums.SafetyCountry; -import io.openems.edge.app.integratedsystem.FeneconHome.FeneconHomeParameter; -import io.openems.edge.app.integratedsystem.FeneconHome.Property; +import io.openems.edge.app.integratedsystem.FeneconHome10.FeneconHomeParameter; +import io.openems.edge.app.integratedsystem.FeneconHome10.Property; import io.openems.edge.common.component.ComponentManager; import io.openems.edge.core.appmanager.AbstractOpenemsApp; import io.openems.edge.core.appmanager.AbstractOpenemsAppWithProps; @@ -121,7 +121,7 @@ *
*/ @Component(name = "App.FENECON.Home") -public class FeneconHome extends AbstractOpenemsAppWithProps +public class FeneconHome10 extends AbstractOpenemsAppWithProps implements OpenemsApp, AppManagerUtilSupplier { public record FeneconHomeParameter(// @@ -131,7 +131,7 @@ public record FeneconHomeParameter(// } - public static enum Property implements Type { + public static enum Property implements Type { ALIAS(alias()), // // Battery Inverter SAFETY_COUNTRY(AppDef.copyOfGeneric(safetyCountry(), def -> def // @@ -166,6 +166,9 @@ public static enum Property implements Type { return new JsonPrimitive(parameter.defaultValues().feedInSetting()); }))), // + + NA_PROTECTION_ENABLED(IntegratedSystemProps.naProtectionEnabled()), // + HAS_ESS_LIMITER_14A(hasEssLimiter14a()), // // External AC PV HAS_AC_METER(AppDef.copyOfGeneric(hasAcMeter(), def -> def // @@ -227,24 +230,24 @@ public static enum Property implements Type def; + private final AppDef def; - private Property(AppDef def) { + private Property(AppDef def) { this.def = def; } @Override - public Type self() { + public Type self() { return this; } @Override - public AppDef def() { + public AppDef def() { return this.def; } @Override - public Function, FeneconHomeParameter> getParamter() { + public Function, FeneconHomeParameter> getParamter() { return t -> new FeneconHomeParameter(// AbstractOpenemsApp.getTranslationBundle(t.language), // createFeneconHomeDefaultValues(t.app.componentManager, t.app.componentUtil) // @@ -255,7 +258,7 @@ public Function, FeneconHomeParameter> getParamt private final AppManagerUtil appManagerUtil; @Activate - public FeneconHome(// + public FeneconHome10(// @Reference final ComponentManager componentManager, // final ComponentContext context, // @Reference final ConfigurationAdmin cm, // @@ -300,6 +303,7 @@ AppConfiguration, OpenemsNamedException> appPropertyConfigurationFactory() { final var safetyCountry = this.getEnum(p, SafetyCountry.class, Property.SAFETY_COUNTRY); final var feedInSetting = this.getString(p, Property.FEED_IN_SETTING); + final var naProtection = this.getBoolean(p, Property.NA_PROTECTION_ENABLED); var bundle = AbstractOpenemsApp.getTranslationBundle(l); var components = Lists.newArrayList(// @@ -355,7 +359,7 @@ AppConfiguration, OpenemsNamedException> appPropertyConfigurationFactory() { .addProperty("batteryStartUpRelay", "io0/Relay4") // .build()), batteryInverter(bundle, "batteryInverter0", hasEmergencyReserve, feedInType, maxFeedInPower, - modbusIdExternal, shadowManagmentDisabled, safetyCountry, feedInSetting), // + modbusIdExternal, shadowManagmentDisabled, safetyCountry, feedInSetting, naProtection), // new EdgeConfig.Component(essId, TranslationUtil.getTranslation(bundle, this.getAppId() + "." + essId + ".alias"), "Ess.Generic.ManagedSymmetric", JsonUtils.buildJsonObject() // @@ -476,7 +480,7 @@ public OpenemsAppPermissions getAppPermissions() { } @Override - protected FeneconHome getApp() { + protected FeneconHome10 getApp() { return this; } diff --git a/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome10Gen2.java b/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome10Gen2.java new file mode 100644 index 00000000000..59a1c2c5671 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome10Gen2.java @@ -0,0 +1,273 @@ +package io.openems.edge.app.integratedsystem; + +import static io.openems.edge.app.common.props.CommonProps.alias; +import static io.openems.edge.app.integratedsystem.FeneconHomeComponents.batteryInverter; +import static io.openems.edge.app.integratedsystem.FeneconHomeComponents.essLimiter14aToHardware; +import static io.openems.edge.app.integratedsystem.FeneconHomeComponents.gridOptimizedCharge; +import static io.openems.edge.app.integratedsystem.FeneconHomeComponents.modbusForExternalMeters; +import static io.openems.edge.app.integratedsystem.FeneconHomeComponents.predictor; +import static io.openems.edge.app.integratedsystem.FeneconHomeComponents.prepareBatteryExtension; +import static io.openems.edge.app.integratedsystem.FeneconHomeComponents.selfConsumptionOptimization; +import static io.openems.edge.app.integratedsystem.IntegratedSystemProps.hasEssLimiter14a; +import static io.openems.edge.app.integratedsystem.IntegratedSystemProps.maxFeedInPower; +import static io.openems.edge.app.integratedsystem.IntegratedSystemProps.safetyCountry; + +import java.util.ArrayList; +import java.util.Map; +import java.util.function.Function; + +import org.osgi.service.cm.ConfigurationAdmin; +import org.osgi.service.component.ComponentContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +import com.google.common.collect.Lists; +import com.google.gson.JsonElement; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.function.ThrowingTriFunction; +import io.openems.common.oem.OpenemsEdgeOem; +import io.openems.common.session.Language; +import io.openems.common.session.Role; +import io.openems.edge.app.enums.FeedInType; +import io.openems.edge.app.enums.SafetyCountry; +import io.openems.edge.app.integratedsystem.FeneconHome10Gen2.Property; +import io.openems.edge.common.component.ComponentManager; +import io.openems.edge.core.appmanager.AbstractOpenemsApp; +import io.openems.edge.core.appmanager.AbstractOpenemsAppWithProps; +import io.openems.edge.core.appmanager.AppConfiguration; +import io.openems.edge.core.appmanager.AppDef; +import io.openems.edge.core.appmanager.AppDescriptor; +import io.openems.edge.core.appmanager.AppManagerUtil; +import io.openems.edge.core.appmanager.AppManagerUtilSupplier; +import io.openems.edge.core.appmanager.ComponentUtil; +import io.openems.edge.core.appmanager.ConfigurationTarget; +import io.openems.edge.core.appmanager.OpenemsApp; +import io.openems.edge.core.appmanager.OpenemsAppCardinality; +import io.openems.edge.core.appmanager.OpenemsAppCategory; +import io.openems.edge.core.appmanager.OpenemsAppPermissions; +import io.openems.edge.core.appmanager.Type; +import io.openems.edge.core.appmanager.Type.Parameter.BundleParameter; +import io.openems.edge.core.appmanager.dependency.Tasks; +import io.openems.edge.core.appmanager.dependency.aggregatetask.SchedulerByCentralOrderConfiguration.SchedulerComponent; + +@Component(name = "App.FENECON.Home10.Gen2") +public class FeneconHome10Gen2 extends AbstractOpenemsAppWithProps + implements OpenemsApp, AppManagerUtilSupplier { + + public static enum Property implements Type { + ALIAS(alias()), // + // Battery Inverter + SAFETY_COUNTRY(AppDef.copyOfGeneric(safetyCountry(), def -> def // + .setRequired(true))), // + + FEED_IN_TYPE(IntegratedSystemProps.feedInType()), // + MAX_FEED_IN_POWER(maxFeedInPower(FEED_IN_TYPE)), // + FEED_IN_SETTING(IntegratedSystemProps.feedInSetting()), // + + NA_PROTECTION_ENABLED(IntegratedSystemProps.naProtectionEnabled()), // + + GRID_METER_CATEGORY(IntegratedSystemProps.gridMeterType()), // + CT_RATIO_FIRST(IntegratedSystemProps.ctRatioFirst(GRID_METER_CATEGORY)), // + + HAS_ESS_LIMITER_14A(hasEssLimiter14a()), // + + // DC PV Charger 1 + HAS_DC_PV1(IntegratedSystemProps.hasDcPv(1)), // + DC_PV1_ALIAS(IntegratedSystemProps.dcPvAlias(1, HAS_DC_PV1)), // + + // DC PV Charger 2 + HAS_DC_PV2(IntegratedSystemProps.hasDcPv(2)), // + DC_PV2_ALIAS(IntegratedSystemProps.dcPvAlias(2, HAS_DC_PV2)), // + + // DC PV Charger 3 + HAS_DC_PV3(IntegratedSystemProps.hasDcPv(3)), // + DC_PV3_ALIAS(IntegratedSystemProps.dcPvAlias(3, HAS_DC_PV3)), // + + // Emergency Reserve SoC + HAS_EMERGENCY_RESERVE(IntegratedSystemProps.hasEmergencyReserve()), // + EMERGENCY_RESERVE_ENABLED(IntegratedSystemProps.emergencyReserveEnabled(HAS_EMERGENCY_RESERVE)), // + EMERGENCY_RESERVE_SOC(IntegratedSystemProps.emergencyReserveSoc(EMERGENCY_RESERVE_ENABLED)), // + + // Shadow management + SHADOW_MANAGEMENT_DISABLED(IntegratedSystemProps.shadowManagementDisabled()), // + ; + + private final AppDef def; + + private Property(AppDef def) { + this.def = def; + } + + @Override + public Type self() { + return this; + } + + @Override + public AppDef def() { + return this.def; + } + + @Override + public Function, BundleParameter> getParamter() { + return t -> new BundleParameter(// + AbstractOpenemsApp.getTranslationBundle(t.language) // + ); + } + } + + private final AppManagerUtil appManagerUtil; + + @Activate + public FeneconHome10Gen2(// + @Reference ComponentManager componentManager, // + ComponentContext context, // + @Reference ConfigurationAdmin cm, // + @Reference ComponentUtil componentUtil, // + @Reference AppManagerUtil appManagerUtil // + ) { + super(componentManager, context, cm, componentUtil); + this.appManagerUtil = appManagerUtil; + } + + @Override + public AppDescriptor getAppDescriptor(OpenemsEdgeOem oem) { + return AppDescriptor.create() // + .setWebsiteUrl(oem.getAppWebsiteUrl(this.getAppId())) // + .build(); + } + + @Override + protected ThrowingTriFunction, Language, // + AppConfiguration, OpenemsNamedException> appPropertyConfigurationFactory() { + return (t, p, l) -> { + final var essId = "ess0"; + final var modbusIdInternal = "modbus0"; + final var modbusIdExternal = "modbus1"; + final var modbusIdExternalMeters = "modbus2"; + + final var hasEmergencyReserve = this.getBoolean(p, Property.HAS_EMERGENCY_RESERVE); + final var emergencyReserveEnabled = this.getBoolean(p, Property.EMERGENCY_RESERVE_ENABLED); + + final var feedInType = this.getEnum(p, FeedInType.class, Property.FEED_IN_TYPE); + final var maxFeedInPower = feedInType == FeedInType.DYNAMIC_LIMITATION + ? this.getInt(p, Property.MAX_FEED_IN_POWER) + : 0; + + final var shadowManagmentDisabled = this.getBoolean(p, Property.SHADOW_MANAGEMENT_DISABLED); + + final var gridMeterCategory = this.getEnum(p, GoodWeGridMeterCategory.class, Property.GRID_METER_CATEGORY); + + final Integer ctRatioFirst; + if (gridMeterCategory == GoodWeGridMeterCategory.COMMERCIAL_METER) { + ctRatioFirst = this.getInt(p, Property.CT_RATIO_FIRST); + } else { + ctRatioFirst = null; + } + final var hasEssLimiter14a = this.getBoolean(p, Property.HAS_ESS_LIMITER_14A); + + final var safetyCountry = this.getEnum(p, SafetyCountry.class, Property.SAFETY_COUNTRY); + final var feedInSetting = this.getString(p, Property.FEED_IN_SETTING); + final var naProtection = this.getBoolean(p, Property.NA_PROTECTION_ENABLED); + + final var deviceHardware = this.appManagerUtil + .getFirstInstantiatedAppByCategories(OpenemsAppCategory.OPENEMS_DEVICE_HARDWARE); + + var bundle = AbstractOpenemsApp.getTranslationBundle(l); + var components = Lists.newArrayList(// + // modbus + FeneconHomeComponents.modbusInternal(bundle, t, modbusIdInternal), + FeneconHomeComponents.modbusExternal(bundle, t, modbusIdExternal), + modbusForExternalMeters(bundle, t, modbusIdExternalMeters, deviceHardware), // + // ess + FeneconHomeComponents.ess(bundle, essId, "battery0", "batteryInverter0"), + FeneconHomeComponents.ctrlEssSurplusFeedToGrid(bundle, essId), predictor(bundle, t), // + // battery + FeneconHomeComponents.battery(bundle, "battery0", modbusIdInternal), + batteryInverter(bundle, "batteryInverter0", hasEmergencyReserve, feedInType, maxFeedInPower, + modbusIdExternal, shadowManagmentDisabled, safetyCountry, feedInSetting, naProtection), // + // meter + FeneconHomeComponents.gridMeter(bundle, "meter0", modbusIdExternal, gridMeterCategory, + ctRatioFirst), + // other + FeneconHomeComponents.power(), FeneconHomeComponents.io(bundle, modbusIdInternal)); + + for (int i = 0; i < 3; i++) { + final var oneBase = i + 1; + if (this.getBoolean(p, Property.valueOf("HAS_DC_PV" + oneBase))) { + components.add(FeneconHomeComponents.chargerPv("charger" + i, oneBase, + this.getString(p, l, Property.valueOf("DC_PV" + oneBase + "_ALIAS")), // + modbusIdExternal, "batteryInverter0")); + } + } + + if (hasEmergencyReserve) { + components.add(FeneconHomeComponents.emergencyMeter(bundle, modbusIdExternal)); + + // use 5(minimum value) as reserveSoc if emergencyReserveEnabled is not enabled + final var emergencyReserveSoc = this.getInt(p, Property.EMERGENCY_RESERVE_SOC); + components.add(FeneconHomeComponents.ctrlEmergencyCapacityReserve(bundle, t, essId, + emergencyReserveEnabled, emergencyReserveSoc)); + } + final var dependencies = Lists.newArrayList(// + gridOptimizedCharge(t, feedInType, maxFeedInPower), // + selfConsumptionOptimization(t, essId, "meter0"), // + prepareBatteryExtension() // + ); + + if (hasEssLimiter14a) { + dependencies.add(essLimiter14aToHardware(this.appManagerUtil, deviceHardware)); + } + + final var schedulerComponents = new ArrayList(); + if (hasEmergencyReserve) { + schedulerComponents.add(new SchedulerComponent("ctrlEmergencyCapacityReserve0", + "Controller.Ess.EmergencyCapacityReserve", this.getAppId())); + } + schedulerComponents.add(new SchedulerComponent("ctrlEssSurplusFeedToGrid0", + "Controller.Ess.Hybrid.Surplus-Feed-To-Grid", this.getAppId())); + + return AppConfiguration.create() // + .addTask(Tasks.component(components)) // + .addTask(Tasks.schedulerByCentralOrder(schedulerComponents)) // + .addDependencies(dependencies) // + .build(); + }; + } + + @Override + public OpenemsAppCategory[] getCategories() { + return new OpenemsAppCategory[] { OpenemsAppCategory.INTEGRATED_SYSTEM }; + } + + @Override + public OpenemsAppCardinality getCardinality() { + return OpenemsAppCardinality.SINGLE_IN_CATEGORY; + } + + @Override + public OpenemsAppPermissions getAppPermissions() { + return OpenemsAppPermissions.create() // + .setCanSee(Role.INSTALLER) // + .setCanDelete(Role.INSTALLER) // + .build(); + } + + @Override + protected FeneconHome10Gen2 getApp() { + return this; + } + + @Override + protected Property[] propertyValues() { + return Property.values(); + } + + @Override + public AppManagerUtil getAppManagerUtil() { + return this.appManagerUtil; + } + +} diff --git a/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome15.java b/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome15.java new file mode 100644 index 00000000000..0a4d292de77 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome15.java @@ -0,0 +1,273 @@ +package io.openems.edge.app.integratedsystem; + +import static io.openems.edge.app.common.props.CommonProps.alias; +import static io.openems.edge.app.integratedsystem.FeneconHomeComponents.batteryInverter; +import static io.openems.edge.app.integratedsystem.FeneconHomeComponents.essLimiter14aToHardware; +import static io.openems.edge.app.integratedsystem.FeneconHomeComponents.gridOptimizedCharge; +import static io.openems.edge.app.integratedsystem.FeneconHomeComponents.modbusForExternalMeters; +import static io.openems.edge.app.integratedsystem.FeneconHomeComponents.predictor; +import static io.openems.edge.app.integratedsystem.FeneconHomeComponents.prepareBatteryExtension; +import static io.openems.edge.app.integratedsystem.FeneconHomeComponents.selfConsumptionOptimization; +import static io.openems.edge.app.integratedsystem.IntegratedSystemProps.hasEssLimiter14a; +import static io.openems.edge.app.integratedsystem.IntegratedSystemProps.maxFeedInPower; +import static io.openems.edge.app.integratedsystem.IntegratedSystemProps.safetyCountry; + +import java.util.ArrayList; +import java.util.Map; +import java.util.function.Function; + +import org.osgi.service.cm.ConfigurationAdmin; +import org.osgi.service.component.ComponentContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +import com.google.common.collect.Lists; +import com.google.gson.JsonElement; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.function.ThrowingTriFunction; +import io.openems.common.oem.OpenemsEdgeOem; +import io.openems.common.session.Language; +import io.openems.common.session.Role; +import io.openems.edge.app.enums.FeedInType; +import io.openems.edge.app.enums.SafetyCountry; +import io.openems.edge.app.integratedsystem.FeneconHome15.Property; +import io.openems.edge.common.component.ComponentManager; +import io.openems.edge.core.appmanager.AbstractOpenemsApp; +import io.openems.edge.core.appmanager.AbstractOpenemsAppWithProps; +import io.openems.edge.core.appmanager.AppConfiguration; +import io.openems.edge.core.appmanager.AppDef; +import io.openems.edge.core.appmanager.AppDescriptor; +import io.openems.edge.core.appmanager.AppManagerUtil; +import io.openems.edge.core.appmanager.AppManagerUtilSupplier; +import io.openems.edge.core.appmanager.ComponentUtil; +import io.openems.edge.core.appmanager.ConfigurationTarget; +import io.openems.edge.core.appmanager.OpenemsApp; +import io.openems.edge.core.appmanager.OpenemsAppCardinality; +import io.openems.edge.core.appmanager.OpenemsAppCategory; +import io.openems.edge.core.appmanager.OpenemsAppPermissions; +import io.openems.edge.core.appmanager.Type; +import io.openems.edge.core.appmanager.Type.Parameter.BundleParameter; +import io.openems.edge.core.appmanager.dependency.Tasks; +import io.openems.edge.core.appmanager.dependency.aggregatetask.SchedulerByCentralOrderConfiguration.SchedulerComponent; + +@Component(name = "App.FENECON.Home15") +public class FeneconHome15 extends AbstractOpenemsAppWithProps + implements OpenemsApp, AppManagerUtilSupplier { + + public static enum Property implements Type { + ALIAS(alias()), // + // Battery Inverter + SAFETY_COUNTRY(AppDef.copyOfGeneric(safetyCountry(), def -> def // + .setRequired(true))), // + + FEED_IN_TYPE(IntegratedSystemProps.feedInType()), // + MAX_FEED_IN_POWER(maxFeedInPower(FEED_IN_TYPE)), // + FEED_IN_SETTING(IntegratedSystemProps.feedInSetting()), // + + NA_PROTECTION_ENABLED(IntegratedSystemProps.naProtectionEnabled()), // + + GRID_METER_CATEGORY(IntegratedSystemProps.gridMeterType()), // + CT_RATIO_FIRST(IntegratedSystemProps.ctRatioFirst(GRID_METER_CATEGORY)), // + + HAS_ESS_LIMITER_14A(hasEssLimiter14a()), // + + // DC PV Charger 1 + HAS_DC_PV1(IntegratedSystemProps.hasDcPv(1)), // + DC_PV1_ALIAS(IntegratedSystemProps.dcPvAlias(1, HAS_DC_PV1)), // + + // DC PV Charger 2 + HAS_DC_PV2(IntegratedSystemProps.hasDcPv(2)), // + DC_PV2_ALIAS(IntegratedSystemProps.dcPvAlias(2, HAS_DC_PV2)), // + + // DC PV Charger 3 + HAS_DC_PV3(IntegratedSystemProps.hasDcPv(3)), // + DC_PV3_ALIAS(IntegratedSystemProps.dcPvAlias(3, HAS_DC_PV3)), // + + // Emergency Reserve SoC + HAS_EMERGENCY_RESERVE(IntegratedSystemProps.hasEmergencyReserve()), // + EMERGENCY_RESERVE_ENABLED(IntegratedSystemProps.emergencyReserveEnabled(HAS_EMERGENCY_RESERVE)), // + EMERGENCY_RESERVE_SOC(IntegratedSystemProps.emergencyReserveSoc(EMERGENCY_RESERVE_ENABLED)), // + + // Shadow management + SHADOW_MANAGEMENT_DISABLED(IntegratedSystemProps.shadowManagementDisabled()), // + ; + + private final AppDef def; + + private Property(AppDef def) { + this.def = def; + } + + @Override + public Type self() { + return this; + } + + @Override + public AppDef def() { + return this.def; + } + + @Override + public Function, BundleParameter> getParamter() { + return t -> new BundleParameter(// + AbstractOpenemsApp.getTranslationBundle(t.language) // + ); + } + } + + private final AppManagerUtil appManagerUtil; + + @Activate + public FeneconHome15(// + @Reference ComponentManager componentManager, // + ComponentContext context, // + @Reference ConfigurationAdmin cm, // + @Reference ComponentUtil componentUtil, // + @Reference AppManagerUtil appManagerUtil // + ) { + super(componentManager, context, cm, componentUtil); + this.appManagerUtil = appManagerUtil; + } + + @Override + public AppDescriptor getAppDescriptor(OpenemsEdgeOem oem) { + return AppDescriptor.create() // + .setWebsiteUrl(oem.getAppWebsiteUrl(this.getAppId())) // + .build(); + } + + @Override + protected ThrowingTriFunction, Language, // + AppConfiguration, OpenemsNamedException> appPropertyConfigurationFactory() { + return (t, p, l) -> { + final var essId = "ess0"; + final var modbusIdInternal = "modbus0"; + final var modbusIdExternal = "modbus1"; + final var modbusIdExternalMeters = "modbus2"; + + final var hasEmergencyReserve = this.getBoolean(p, Property.HAS_EMERGENCY_RESERVE); + final var emergencyReserveEnabled = this.getBoolean(p, Property.EMERGENCY_RESERVE_ENABLED); + + final var feedInType = this.getEnum(p, FeedInType.class, Property.FEED_IN_TYPE); + final var maxFeedInPower = feedInType == FeedInType.DYNAMIC_LIMITATION + ? this.getInt(p, Property.MAX_FEED_IN_POWER) + : 0; + + final var shadowManagmentDisabled = this.getBoolean(p, Property.SHADOW_MANAGEMENT_DISABLED); + + final var gridMeterCategory = this.getEnum(p, GoodWeGridMeterCategory.class, Property.GRID_METER_CATEGORY); + + final Integer ctRatioFirst; + if (gridMeterCategory == GoodWeGridMeterCategory.COMMERCIAL_METER) { + ctRatioFirst = this.getInt(p, Property.CT_RATIO_FIRST); + } else { + ctRatioFirst = null; + } + final var hasEssLimiter14a = this.getBoolean(p, Property.HAS_ESS_LIMITER_14A); + + final var safetyCountry = this.getEnum(p, SafetyCountry.class, Property.SAFETY_COUNTRY); + final var feedInSetting = this.getString(p, Property.FEED_IN_SETTING); + final var naProtection = this.getBoolean(p, Property.NA_PROTECTION_ENABLED); + + final var deviceHardware = this.appManagerUtil + .getFirstInstantiatedAppByCategories(OpenemsAppCategory.OPENEMS_DEVICE_HARDWARE); + + var bundle = AbstractOpenemsApp.getTranslationBundle(l); + var components = Lists.newArrayList(// + // modbus + FeneconHomeComponents.modbusInternal(bundle, t, modbusIdInternal), + FeneconHomeComponents.modbusExternal(bundle, t, modbusIdExternal), + modbusForExternalMeters(bundle, t, modbusIdExternalMeters, deviceHardware), // + // ess + FeneconHomeComponents.ess(bundle, essId, "battery0", "batteryInverter0"), + FeneconHomeComponents.ctrlEssSurplusFeedToGrid(bundle, essId), predictor(bundle, t), // + // battery + FeneconHomeComponents.battery(bundle, "battery0", modbusIdInternal), + batteryInverter(bundle, "batteryInverter0", hasEmergencyReserve, feedInType, maxFeedInPower, + modbusIdExternal, shadowManagmentDisabled, safetyCountry, feedInSetting, naProtection), // + // meter + FeneconHomeComponents.gridMeter(bundle, "meter0", modbusIdExternal, gridMeterCategory, + ctRatioFirst), + // other + FeneconHomeComponents.power(), FeneconHomeComponents.io(bundle, modbusIdInternal)); + + for (int i = 0; i < 3; i++) { + final var oneBase = i + 1; + if (this.getBoolean(p, Property.valueOf("HAS_DC_PV" + oneBase))) { + components.add(FeneconHomeComponents.chargerPv("charger" + i, oneBase, + this.getString(p, l, Property.valueOf("DC_PV" + oneBase + "_ALIAS")), // + modbusIdExternal, "batteryInverter0")); + } + } + + if (hasEmergencyReserve) { + components.add(FeneconHomeComponents.emergencyMeter(bundle, modbusIdExternal)); + + // use 5(minimum value) as reserveSoc if emergencyReserveEnabled is not enabled + final var emergencyReserveSoc = this.getInt(p, Property.EMERGENCY_RESERVE_SOC); + components.add(FeneconHomeComponents.ctrlEmergencyCapacityReserve(bundle, t, essId, + emergencyReserveEnabled, emergencyReserveSoc)); + } + final var dependencies = Lists.newArrayList(// + gridOptimizedCharge(t, feedInType, maxFeedInPower), // + selfConsumptionOptimization(t, essId, "meter0"), // + prepareBatteryExtension() // + ); + + if (hasEssLimiter14a) { + dependencies.add(essLimiter14aToHardware(this.appManagerUtil, deviceHardware)); + } + + final var schedulerComponents = new ArrayList(); + if (hasEmergencyReserve) { + schedulerComponents.add(new SchedulerComponent("ctrlEmergencyCapacityReserve0", + "Controller.Ess.EmergencyCapacityReserve", this.getAppId())); + } + schedulerComponents.add(new SchedulerComponent("ctrlEssSurplusFeedToGrid0", + "Controller.Ess.Hybrid.Surplus-Feed-To-Grid", this.getAppId())); + + return AppConfiguration.create() // + .addTask(Tasks.component(components)) // + .addTask(Tasks.schedulerByCentralOrder(schedulerComponents)) // + .addDependencies(dependencies) // + .build(); + }; + } + + @Override + public OpenemsAppCategory[] getCategories() { + return new OpenemsAppCategory[] { OpenemsAppCategory.INTEGRATED_SYSTEM }; + } + + @Override + public OpenemsAppCardinality getCardinality() { + return OpenemsAppCardinality.SINGLE_IN_CATEGORY; + } + + @Override + public OpenemsAppPermissions getAppPermissions() { + return OpenemsAppPermissions.create() // + .setCanSee(Role.INSTALLER) // + .setCanDelete(Role.INSTALLER) // + .build(); + } + + @Override + protected FeneconHome15 getApp() { + return this; + } + + @Override + protected Property[] propertyValues() { + return Property.values(); + } + + @Override + public AppManagerUtil getAppManagerUtil() { + return this.appManagerUtil; + } + +} diff --git a/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome20.java b/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome20.java index e557fd7ebb4..48fb89434f0 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome20.java +++ b/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome20.java @@ -150,7 +150,9 @@ public enum Property implements PropertyParent { MAX_FEED_IN_POWER(maxFeedInPower(FEED_IN_TYPE)), // FEED_IN_SETTING(feedInSetting()), // - GRID_METER_CATEGORY(gridMeterType()), // + NA_PROTECTION_ENABLED(IntegratedSystemProps.naProtectionEnabled()), // + + GRID_METER_CATEGORY(gridMeterType(GoodWeGridMeterCategory.INTEGRATED_METER)), // CT_RATIO_FIRST(ctRatioFirst(GRID_METER_CATEGORY)), // HAS_ESS_LIMITER_14A(hasEssLimiter14a()), // @@ -306,17 +308,21 @@ protected ThrowingTriFunctionnewArrayList(// battery(bundle, batteryId, modbusIdInternal), // batteryInverter(bundle, batteryInverterId, hasEmergencyReserve, feedInType, maxFeedInPower, - modbusIdExternal, shadowManagementDisabled, safetyCountry, feedInSetting), // + modbusIdExternal, shadowManagementDisabled, safetyCountry, feedInSetting, naProtection), // ess(bundle, essId, batteryId, batteryInverterId), // io(bundle, modbusIdInternal), // gridMeter(bundle, gridMeterId, modbusIdExternal, gridMeterCategory, ctRatioFirst), // modbusInternal(bundle, t, modbusIdInternal), // modbusExternal(bundle, t, modbusIdExternal), // - modbusForExternalMeters(bundle, t, modbusIdExternalMeters), // + modbusForExternalMeters(bundle, t, modbusIdExternalMeters, deviceHardware), // predictor(bundle, t), // ctrlEssSurplusFeedToGrid(bundle, essId), // power() // @@ -359,7 +365,7 @@ protected ThrowingTriFunction(); diff --git a/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome30.java b/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome30.java index 019e73af9c2..78718482c0e 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome30.java +++ b/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome30.java @@ -150,7 +150,9 @@ public enum Property implements PropertyParent { MAX_FEED_IN_POWER(maxFeedInPower(FEED_IN_TYPE)), // FEED_IN_SETTING(feedInSetting()), // - GRID_METER_CATEGORY(gridMeterType()), // + NA_PROTECTION_ENABLED(IntegratedSystemProps.naProtectionEnabled()), // + + GRID_METER_CATEGORY(gridMeterType(GoodWeGridMeterCategory.INTEGRATED_METER)), // CT_RATIO_FIRST(ctRatioFirst(GRID_METER_CATEGORY)), // HAS_ESS_LIMITER_14A(hasEssLimiter14a()), // @@ -307,17 +309,21 @@ protected ThrowingTriFunctionnewArrayList(// battery(bundle, batteryId, modbusIdInternal), // batteryInverter(bundle, batteryInverterId, hasEmergencyReserve, feedInType, maxFeedInPower, - modbusIdExternal, shadowManagementDisabled, safetyCountry, feedInSetting), // + modbusIdExternal, shadowManagementDisabled, safetyCountry, feedInSetting, naProtection), // ess(bundle, essId, batteryId, batteryInverterId), // io(bundle, modbusIdInternal), // gridMeter(bundle, gridMeterId, modbusIdExternal, gridMeterCategory, ctRatioFirst), // modbusInternal(bundle, t, modbusIdInternal), // modbusExternal(bundle, t, modbusIdExternal), // - modbusForExternalMeters(bundle, t, modbusIdExternalMeters), // + modbusForExternalMeters(bundle, t, modbusIdExternalMeters, deviceHardware), // predictor(bundle, t), // ctrlEssSurplusFeedToGrid(bundle, essId), // power() // @@ -360,7 +366,7 @@ protected ThrowingTriFunction(); diff --git a/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome6.java b/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome6.java new file mode 100644 index 00000000000..1cbaaf23e25 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome6.java @@ -0,0 +1,270 @@ +package io.openems.edge.app.integratedsystem; + +import static io.openems.edge.app.common.props.CommonProps.alias; +import static io.openems.edge.app.integratedsystem.FeneconHomeComponents.batteryInverter; +import static io.openems.edge.app.integratedsystem.FeneconHomeComponents.essLimiter14aToHardware; +import static io.openems.edge.app.integratedsystem.FeneconHomeComponents.gridOptimizedCharge; +import static io.openems.edge.app.integratedsystem.FeneconHomeComponents.modbusForExternalMeters; +import static io.openems.edge.app.integratedsystem.FeneconHomeComponents.predictor; +import static io.openems.edge.app.integratedsystem.FeneconHomeComponents.prepareBatteryExtension; +import static io.openems.edge.app.integratedsystem.FeneconHomeComponents.selfConsumptionOptimization; +import static io.openems.edge.app.integratedsystem.IntegratedSystemProps.hasEssLimiter14a; +import static io.openems.edge.app.integratedsystem.IntegratedSystemProps.maxFeedInPower; +import static io.openems.edge.app.integratedsystem.IntegratedSystemProps.safetyCountry; + +import java.util.ArrayList; +import java.util.Map; +import java.util.function.Function; + +import org.osgi.service.cm.ConfigurationAdmin; +import org.osgi.service.component.ComponentContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +import com.google.common.collect.Lists; +import com.google.gson.JsonElement; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.function.ThrowingTriFunction; +import io.openems.common.oem.OpenemsEdgeOem; +import io.openems.common.session.Language; +import io.openems.common.session.Role; +import io.openems.edge.app.enums.FeedInType; +import io.openems.edge.app.enums.SafetyCountry; +import io.openems.edge.app.integratedsystem.FeneconHome6.Property; +import io.openems.edge.common.component.ComponentManager; +import io.openems.edge.core.appmanager.AbstractOpenemsApp; +import io.openems.edge.core.appmanager.AbstractOpenemsAppWithProps; +import io.openems.edge.core.appmanager.AppConfiguration; +import io.openems.edge.core.appmanager.AppDef; +import io.openems.edge.core.appmanager.AppDescriptor; +import io.openems.edge.core.appmanager.AppManagerUtil; +import io.openems.edge.core.appmanager.AppManagerUtilSupplier; +import io.openems.edge.core.appmanager.ComponentUtil; +import io.openems.edge.core.appmanager.ConfigurationTarget; +import io.openems.edge.core.appmanager.OpenemsApp; +import io.openems.edge.core.appmanager.OpenemsAppCardinality; +import io.openems.edge.core.appmanager.OpenemsAppCategory; +import io.openems.edge.core.appmanager.OpenemsAppPermissions; +import io.openems.edge.core.appmanager.Type; +import io.openems.edge.core.appmanager.Type.Parameter.BundleParameter; +import io.openems.edge.core.appmanager.dependency.Tasks; +import io.openems.edge.core.appmanager.dependency.aggregatetask.SchedulerByCentralOrderConfiguration.SchedulerComponent; + +@Component(name = "App.FENECON.Home6") +public class FeneconHome6 extends AbstractOpenemsAppWithProps + implements OpenemsApp, AppManagerUtilSupplier { + + public static enum Property implements Type { + ALIAS(alias()), // + // Battery Inverter + SAFETY_COUNTRY(AppDef.copyOfGeneric(safetyCountry(), def -> def // + .setRequired(true))), // + + FEED_IN_TYPE(IntegratedSystemProps.feedInType()), // + MAX_FEED_IN_POWER(maxFeedInPower(FEED_IN_TYPE)), // + FEED_IN_SETTING(IntegratedSystemProps.feedInSetting()), // + + NA_PROTECTION_ENABLED(IntegratedSystemProps.naProtectionEnabled()), // + + GRID_METER_CATEGORY(IntegratedSystemProps.gridMeterType()), // + CT_RATIO_FIRST(IntegratedSystemProps.ctRatioFirst(GRID_METER_CATEGORY)), // + + HAS_ESS_LIMITER_14A(hasEssLimiter14a()), // + + // DC PV Charger 1 + HAS_DC_PV1(IntegratedSystemProps.hasDcPv(1)), // + DC_PV1_ALIAS(IntegratedSystemProps.dcPvAlias(1, HAS_DC_PV1)), // + + // DC PV Charger 2 + HAS_DC_PV2(IntegratedSystemProps.hasDcPv(2)), // + DC_PV2_ALIAS(IntegratedSystemProps.dcPvAlias(2, HAS_DC_PV2)), // + + // Emergency Reserve SoC + HAS_EMERGENCY_RESERVE(IntegratedSystemProps.hasEmergencyReserve()), // + EMERGENCY_RESERVE_ENABLED(IntegratedSystemProps.emergencyReserveEnabled(HAS_EMERGENCY_RESERVE)), // + EMERGENCY_RESERVE_SOC(IntegratedSystemProps.emergencyReserveSoc(EMERGENCY_RESERVE_ENABLED)), // + + // Shadow management + SHADOW_MANAGEMENT_DISABLED(IntegratedSystemProps.shadowManagementDisabled()), // + ; + + private final AppDef def; + + private Property(AppDef def) { + this.def = def; + } + + @Override + public Type self() { + return this; + } + + @Override + public AppDef def() { + return this.def; + } + + @Override + public Function, BundleParameter> getParamter() { + return t -> new BundleParameter(// + AbstractOpenemsApp.getTranslationBundle(t.language) // + ); + } + } + + private final AppManagerUtil appManagerUtil; + + @Activate + public FeneconHome6(// + @Reference ComponentManager componentManager, // + ComponentContext context, // + @Reference ConfigurationAdmin cm, // + @Reference ComponentUtil componentUtil, // + @Reference AppManagerUtil appManagerUtil // + ) { + super(componentManager, context, cm, componentUtil); + this.appManagerUtil = appManagerUtil; + } + + @Override + public AppDescriptor getAppDescriptor(OpenemsEdgeOem oem) { + return AppDescriptor.create() // + .setWebsiteUrl(oem.getAppWebsiteUrl(this.getAppId())) // + .build(); + } + + @Override + protected ThrowingTriFunction, Language, // + AppConfiguration, OpenemsNamedException> appPropertyConfigurationFactory() { + return (t, p, l) -> { + final var essId = "ess0"; + final var modbusIdInternal = "modbus0"; + final var modbusIdExternal = "modbus1"; + final var modbusIdExternalMeters = "modbus2"; + + final var hasEmergencyReserve = this.getBoolean(p, Property.HAS_EMERGENCY_RESERVE); + final var emergencyReserveEnabled = this.getBoolean(p, Property.EMERGENCY_RESERVE_ENABLED); + + final var feedInType = this.getEnum(p, FeedInType.class, Property.FEED_IN_TYPE); + final var maxFeedInPower = feedInType == FeedInType.DYNAMIC_LIMITATION + ? this.getInt(p, Property.MAX_FEED_IN_POWER) + : 0; + + final var gridMeterCategory = this.getEnum(p, GoodWeGridMeterCategory.class, Property.GRID_METER_CATEGORY); + + final Integer ctRatioFirst; + if (gridMeterCategory == GoodWeGridMeterCategory.COMMERCIAL_METER) { + ctRatioFirst = this.getInt(p, Property.CT_RATIO_FIRST); + } else { + ctRatioFirst = null; + } + final var hasEssLimiter14a = this.getBoolean(p, Property.HAS_ESS_LIMITER_14A); + + final var shadowManagmentDisabled = this.getBoolean(p, Property.SHADOW_MANAGEMENT_DISABLED); + + final var safetyCountry = this.getEnum(p, SafetyCountry.class, Property.SAFETY_COUNTRY); + final var feedInSetting = this.getString(p, Property.FEED_IN_SETTING); + final var naProtection = this.getBoolean(p, Property.NA_PROTECTION_ENABLED); + + final var deviceHardware = this.appManagerUtil + .getFirstInstantiatedAppByCategories(OpenemsAppCategory.OPENEMS_DEVICE_HARDWARE); + + var bundle = AbstractOpenemsApp.getTranslationBundle(l); + var components = Lists.newArrayList(// + // modbus + FeneconHomeComponents.modbusInternal(bundle, t, modbusIdInternal), + FeneconHomeComponents.modbusExternal(bundle, t, modbusIdExternal), + modbusForExternalMeters(bundle, t, modbusIdExternalMeters, deviceHardware), // + // ess + FeneconHomeComponents.ess(bundle, essId, "battery0", "batteryInverter0"), + FeneconHomeComponents.ctrlEssSurplusFeedToGrid(bundle, essId), predictor(bundle, t), // + // battery + FeneconHomeComponents.battery(bundle, "battery0", modbusIdInternal), + batteryInverter(bundle, "batteryInverter0", hasEmergencyReserve, feedInType, maxFeedInPower, + modbusIdExternal, shadowManagmentDisabled, safetyCountry, feedInSetting, naProtection), // + // meter + FeneconHomeComponents.gridMeter(bundle, "meter0", modbusIdExternal, gridMeterCategory, + ctRatioFirst), + // other + FeneconHomeComponents.power(), FeneconHomeComponents.io(bundle, modbusIdInternal)); + + for (int i = 0; i < 2; i++) { + final var oneBase = i + 1; + if (this.getBoolean(p, Property.valueOf("HAS_DC_PV" + oneBase))) { + components.add(FeneconHomeComponents.chargerPv("charger" + i, oneBase, + this.getString(p, l, Property.valueOf("DC_PV" + oneBase + "_ALIAS")), // + modbusIdExternal, "batteryInverter0")); + } + } + + if (hasEmergencyReserve) { + components.add(FeneconHomeComponents.emergencyMeter(bundle, modbusIdExternal)); + + // use 5(minimum value) as reserveSoc if emergencyReserveEnabled is not enabled + final var emergencyReserveSoc = this.getInt(p, Property.EMERGENCY_RESERVE_SOC); + components.add(FeneconHomeComponents.ctrlEmergencyCapacityReserve(bundle, t, essId, + emergencyReserveEnabled, emergencyReserveSoc)); + } + + final var dependencies = Lists.newArrayList(// + gridOptimizedCharge(t, feedInType, maxFeedInPower), // + selfConsumptionOptimization(t, essId, "meter0"), // + prepareBatteryExtension() // + ); + + if (hasEssLimiter14a) { + dependencies.add(essLimiter14aToHardware(this.appManagerUtil, deviceHardware)); + } + + final var schedulerComponents = new ArrayList(); + if (hasEmergencyReserve) { + schedulerComponents.add(new SchedulerComponent("ctrlEmergencyCapacityReserve0", + "Controller.Ess.EmergencyCapacityReserve", this.getAppId())); + } + schedulerComponents.add(new SchedulerComponent("ctrlEssSurplusFeedToGrid0", + "Controller.Ess.Hybrid.Surplus-Feed-To-Grid", this.getAppId())); + + return AppConfiguration.create() // + .addTask(Tasks.component(components)) // + .addTask(Tasks.schedulerByCentralOrder(schedulerComponents)) // + .addDependencies(dependencies) // + .build(); + }; + } + + @Override + public OpenemsAppCategory[] getCategories() { + return new OpenemsAppCategory[] { OpenemsAppCategory.INTEGRATED_SYSTEM }; + } + + @Override + public OpenemsAppCardinality getCardinality() { + return OpenemsAppCardinality.SINGLE_IN_CATEGORY; + } + + @Override + public OpenemsAppPermissions getAppPermissions() { + return OpenemsAppPermissions.create() // + .setCanSee(Role.INSTALLER) // + .setCanDelete(Role.INSTALLER) // + .build(); + } + + @Override + protected FeneconHome6 getApp() { + return this; + } + + @Override + protected Property[] propertyValues() { + return Property.values(); + } + + @Override + public AppManagerUtil getAppManagerUtil() { + return this.appManagerUtil; + } + +} diff --git a/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHomeComponents.java b/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHomeComponents.java index 72f62c234ba..66abf571383 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHomeComponents.java +++ b/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHomeComponents.java @@ -18,6 +18,7 @@ import io.openems.edge.core.appmanager.AppManagerUtil; import io.openems.edge.core.appmanager.ConfigurationTarget; import io.openems.edge.core.appmanager.OpenemsAppCategory; +import io.openems.edge.core.appmanager.OpenemsAppInstance; import io.openems.edge.core.appmanager.TranslationUtil; import io.openems.edge.core.appmanager.dependency.DependencyDeclaration; @@ -35,6 +36,24 @@ public static EdgeConfig.Component battery(// final ResourceBundle bundle, // final String batteryId, // final String modbusIdInternal // + ) { + return battery(bundle, batteryId, modbusIdInternal, "AUTO"); + } + + /** + * Creates a default battery component for a FENECON Home. + * + * @param bundle the translation bundle + * @param batteryId the id of the battery + * @param modbusIdInternal the id of the internal modbus bridge + * @param batteryStartStop the startStop target of the bridge + * @return the {@link Component} + */ + public static EdgeConfig.Component battery(// + final ResourceBundle bundle, // + final String batteryId, // + final String modbusIdInternal, // + final String batteryStartStop // ) { return new EdgeConfig.Component(batteryId, TranslationUtil.getTranslation(bundle, "App.IntegratedSystem.battery0.alias"), "Battery.Fenecon.Home", // @@ -43,7 +62,7 @@ public static EdgeConfig.Component battery(// .addProperty("batteryStartUpRelay", "io0/Relay4") // .addProperty("modbus.id", modbusIdInternal) // .addProperty("modbusUnitId", 1) // - .addProperty("startStop", "AUTO") // + .addProperty("startStop", batteryStartStop) // .build()); } @@ -59,6 +78,7 @@ public static EdgeConfig.Component battery(// * @param shadowManagementDisabled if shadowmanagement is disabled * @param safetyCountry the {@link SafetyCountry} * @param feedInSetting the feedInSetting + * @param naProtectionEnabled if NA-protection is enabled * @return the {@link Component} */ public static EdgeConfig.Component batteryInverter(// @@ -70,7 +90,8 @@ public static EdgeConfig.Component batteryInverter(// final String modbusIdExternal, // final boolean shadowManagementDisabled, // final SafetyCountry safetyCountry, // - final String feedInSetting // + final String feedInSetting, // + final boolean naProtectionEnabled // ) { return new EdgeConfig.Component(batteryInverterId, TranslationUtil.getTranslation(bundle, "App.IntegratedSystem.batteryInverter0.alias"), @@ -88,6 +109,7 @@ public static EdgeConfig.Component batteryInverter(// .addProperty("safetyCountry", safetyCountry) // .addProperty("setfeedInPowerSettings", feedInSetting) // .addProperty("rcrEnable", feedInType == FeedInType.EXTERNAL_LIMITATION ? "ENABLE" : "DISABLE") // + .addProperty("naProtectionEnable", naProtectionEnabled ? "ENABLE" : "DISABLE") // .build()); } @@ -181,7 +203,8 @@ public static EdgeConfig.Component modbusInternal(// final String modbusIdInternal // ) { return new EdgeConfig.Component(modbusIdInternal, - TranslationUtil.getTranslation(bundle, "App.IntegratedSystem.modbus0.alias"), "Bridge.Modbus.Serial", // + TranslationUtil.getTranslation(bundle, "App.IntegratedSystem.modbusToBattery.alias"), + "Bridge.Modbus.Serial", // JsonUtils.buildJsonObject() // .addProperty("enabled", true) // .addProperty("baudRate", 19200) // @@ -235,6 +258,30 @@ public static EdgeConfig.Component modbusForExternalMeters(// final ConfigurationTarget t, // final String modbusIdExternal // ) { + return modbusForExternalMeters(bundle, t, modbusIdExternal, null); + } + + /** + * Creates a default external modbus component for external meters for a FENECON + * Home. + * + * @param bundle the translation bundle + * @param t the current {@link ConfigurationTarget} + * @param modbusIdExternal the id of the external modbus bridge + * @param deviceHardware the current device hardware; can be null if not + * available or needed + * @return the {@link Component} + */ + public static EdgeConfig.Component modbusForExternalMeters(// + final ResourceBundle bundle, // + final ConfigurationTarget t, // + final String modbusIdExternal, // + final OpenemsAppInstance deviceHardware // + ) { + final var portName = deviceHardware == null || !deviceHardware.appId.equals("App.OpenemsHardware.CM4S.Gen2") + ? "/dev/bus0" + : "/dev/busUSB3"; + return new EdgeConfig.Component(modbusIdExternal, TranslationUtil.getTranslation(bundle, "App.IntegratedSystem.modbus2.alias"), "Bridge.Modbus.Serial", // JsonUtils.buildJsonObject() // @@ -242,7 +289,7 @@ public static EdgeConfig.Component modbusForExternalMeters(// .addProperty("baudRate", 9600) // .addProperty("databits", 8) // .addProperty("parity", Parity.NONE) // - .addProperty("portName", "/dev/bus0") // + .addProperty("portName", portName) // .addProperty("stopbits", "ONE") // .onlyIf(t == ConfigurationTarget.ADD, b -> { b.addProperty("invalidateElementsAfterReadErrors", 1) // @@ -404,6 +451,27 @@ public static EdgeConfig.Component charger(// .build()); } + /** + * Creates a goodwe charger component for a FENECON Home Gen2. + * + * @param chargerId the id of the charger + * @param pvNumber the string number of the charger + * @param alias the alias for the charger + * @param modbusIdExternal the id of the modbus external + * @param batteryInverterId the battery inver id + * @return the component + */ + public static EdgeConfig.Component chargerPv(String chargerId, int pvNumber, String alias, + final String modbusIdExternal, final String batteryInverterId) { + return new EdgeConfig.Component(chargerId, alias, "GoodWe.Charger-PV" + pvNumber, // + JsonUtils.buildJsonObject() // + .addProperty("enabled", true) // + .addProperty("essOrBatteryInverter.id", batteryInverterId) // + .addProperty("modbus.id", modbusIdExternal) // + .addProperty("modbusUnitId", 247) // + .build()); + } + /** * Creates a default gridOptimizedCharge dependency for a FENECON Home. * @@ -522,22 +590,58 @@ public static DependencyDeclaration essLimiter14a(// public static DependencyDeclaration essLimiter14aToHardware(AppManagerUtil appManagerUtil) throws OpenemsNamedException { final var deviceHardware = appManagerUtil - .getInstantiatedAppsByCategories(OpenemsAppCategory.OPENEMS_DEVICE_HARDWARE); - final var app = deviceHardware.stream().findAny().orElse(null); - switch (app == null ? "" : app.appId) { - case "App.OpenemsHardware.CM3", "App.OpenemsHardware.CM4S" -> { - for (var dependency : app.dependencies) { - if (!"IO_GPIO".equals(dependency.key)) { - continue; - } - final var instance = appManagerUtil.findInstanceByIdOrError(dependency.instanceId); - final var ioId = instance.properties.get(IoGpio.Property.IO_ID.name()).getAsString(); - return essLimiter14a(ioId); + .getFirstInstantiatedAppByCategories(OpenemsAppCategory.OPENEMS_DEVICE_HARDWARE); + return essLimiter14aToHardware(appManagerUtil, deviceHardware); + } + + /** + * Creates a default essLimiter14a dependency for a FENECON Home which can be + * different depending on the hardware type. + * + * @param appManagerUtil the {@link AppManagerUtil} to get the hardware type + * @param deviceHardware the hardware app which is installed + * @return the {@link DependencyDeclaration} of the specific hardware or null if + * not specified for the current hardware + * @throws OpenemsNamedException on error + */ + public static DependencyDeclaration essLimiter14aToHardware(// + AppManagerUtil appManagerUtil, // + OpenemsAppInstance deviceHardware // + ) throws OpenemsNamedException { + if (deviceHardware == null) { + throw new OpenemsException("Hardware 'null' not supported for ess limiter 14a."); + } + + if (!isLimiter14aCompatible(deviceHardware)) { + throw new OpenemsException("Hardware '" + deviceHardware.appId + "' not supported for ess limiter 14a."); + } + + for (var dependency : deviceHardware.dependencies) { + if (!"IO_GPIO".equals(dependency.key)) { + continue; } + final var instance = appManagerUtil.findInstanceByIdOrError(dependency.instanceId); + final var ioId = instance.properties.get(IoGpio.Property.IO_ID.name()).getAsString(); + return essLimiter14a(ioId); } + throw new OpenemsException("Unable to get limiter14a dependency for hardware '" + deviceHardware.appId + "'."); + } + + /** + * Checks if the provided id of the app is compatible with the + * {@link Limiter14a}. + * + * @param hardwareInstance the current installed hardware instance; nullable + * @return true if there is a default relay for it; else false + */ + public static final boolean isLimiter14aCompatible(OpenemsAppInstance hardwareInstance) { + if (hardwareInstance == null) { + return false; } - throw new OpenemsException( - "Hardware " + (app == null ? "UNDEFINED" : app.appId) + " not supported for ess limiter 14a."); + return switch (hardwareInstance.appId) { + case "App.OpenemsHardware.CM3", "App.OpenemsHardware.CM4S", "App.OpenemsHardware.CM4S.Gen2" -> true; + default -> false; + }; } private FeneconHomeComponents() { diff --git a/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/GoodWeGridMeterCategory.java b/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/GoodWeGridMeterCategory.java index 18fa0acaa9f..71db0fa982e 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/GoodWeGridMeterCategory.java +++ b/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/GoodWeGridMeterCategory.java @@ -7,6 +7,7 @@ import io.openems.edge.core.appmanager.AbstractOpenemsApp; public enum GoodWeGridMeterCategory implements TranslatableEnum { + INTEGRATED_METER("App.IntegratedSystem.gridMeterTypeGen2.option.integrated"), // SMART_METER("App.IntegratedSystem.gridMeterType.option.smartMeter"), // COMMERCIAL_METER("App.IntegratedSystem.gridMeterType.option.commercialMeter"), // ; diff --git a/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/IntegratedSystemProps.java b/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/IntegratedSystemProps.java index 4170c49226b..5ac3b520d9d 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/IntegratedSystemProps.java +++ b/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/IntegratedSystemProps.java @@ -8,6 +8,8 @@ import java.util.function.Consumer; import java.util.function.Function; +import com.google.gson.JsonPrimitive; + import io.openems.edge.app.enums.FeedInType; import io.openems.edge.app.enums.OptionsFactory; import io.openems.edge.app.enums.SafetyCountry; @@ -53,6 +55,18 @@ public static final AppDef feedInType(Feed })); } + /** + * Creates a {@link AppDef} for a NA-protection (ger. Netz- und Anlagenschutz). + * + * @return the created {@link AppDef} + */ + public static final AppDef naProtectionEnabled() { + return AppDef.copyOfGeneric(defaultDef(), def -> def // + .setTranslatedLabel("App.IntegratedSystem.naProtectionEnabled.label") // + .setDefaultValue(false) // + .setField(JsonFormlyUtil::buildCheckboxFromNameable)); + } + /** * Creates a {@link AppDef} for the max feed in power. * @@ -111,17 +125,49 @@ public static final AppDef feedInSetting() })); } + /** + * Creates a {@link AppDef} for feed in setting for the checkbox of a dcpv. + * + * @param number the number which dc pv it is + * @return the created {@link AppDef} + */ + public static final AppDef hasDcPv(int number) { + return AppDef.copyOfGeneric(defaultDef(), def -> def // + .setTranslatedLabel("App.FENECON.Home.hasDcPV" + number + ".label") // + .setDefaultValue(false) // + .setField(JsonFormlyUtil::buildCheckboxFromNameable)); + } + + /** + * Creates a {@link AppDef} for the alias of a dcpv charger. + * + * @param number the number which dc pv it is + * @param hasDcPv the property for the hasDcPv + * @return the created {@link AppDef} + */ + public static final AppDef dcPvAlias(int number, Nameable hasDcPv) { + return AppDef.copyOfGeneric(defaultDef(), def -> def // + .setTranslatedLabel("App.FENECON.Home.dcPv" + number + ".alias.label") // + .setDefaultValue((app, property, l, parameter) -> { + return new JsonPrimitive("DC-PV" + number); + }) // + .setField(JsonFormlyUtil::buildInputFromNameable, (app, property, l, parameter, field) -> { + field.onlyShowIf(Exp.currentModelValue(hasDcPv).notNull()); + })); + } + /** * Creates a {@link AppDef} for the type of the grid meter. * + * @param exclude Category to be excluded * @return the created {@link AppDef} */ - public static final AppDef gridMeterType() { + public static final AppDef gridMeterType(GoodWeGridMeterCategory... exclude) { return AppDef.copyOfGeneric(defaultDef(), def -> def // .setTranslatedLabel("App.IntegratedSystem.gridMeterType.label") // .setDefaultValue(GoodWeGridMeterCategory.SMART_METER) // .setField(JsonFormlyUtil::buildSelectFromNameable, (app, property, l, parameter, field) -> { - field.setOptions(OptionsFactory.of(GoodWeGridMeterCategory.class), l); + field.setOptions(OptionsFactory.of(GoodWeGridMeterCategory.class, exclude), l); })); } @@ -286,16 +332,10 @@ AppDef hasEssLimiter14a() { .setTranslatedLabel("App.IntegratedSystem.hasEssLimiter14a.label") // .setDefaultValue(false) // .setField(JsonFormlyUtil::buildCheckboxFromNameable, (app, property, l, parameter, field) -> { - final var hardwareTypes = app.getAppManagerUtil() - .getInstantiatedAppsByCategories(OpenemsAppCategory.OPENEMS_DEVICE_HARDWARE); - - final var isSupported = hardwareTypes.stream()// - .anyMatch(t -> switch (t.appId) { - case "App.OpenemsHardware.CM3", "App.OpenemsHardware.CM4S" -> true; - default -> false; - }); + final var hardwareType = app.getAppManagerUtil() + .getFirstInstantiatedAppByCategories(OpenemsAppCategory.OPENEMS_DEVICE_HARDWARE); - if (!isSupported) { + if (!FeneconHomeComponents.isLimiter14aCompatible(hardwareType)) { field.disabled(true); } })); diff --git a/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/fenecon/commercial/FeneconCommercial92.java b/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/fenecon/commercial/FeneconCommercial92.java index 0bc05c28b03..f1051568c94 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/fenecon/commercial/FeneconCommercial92.java +++ b/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/fenecon/commercial/FeneconCommercial92.java @@ -1,10 +1,14 @@ package io.openems.edge.app.integratedsystem.fenecon.commercial; import static io.openems.edge.app.common.props.CommonProps.alias; +import static io.openems.edge.app.common.props.CommonProps.defaultDef; +import static io.openems.edge.app.integratedsystem.FeneconHomeComponents.essLimiter14aToHardware; import static io.openems.edge.app.integratedsystem.IntegratedSystemProps.feedInType; +import static io.openems.edge.app.integratedsystem.IntegratedSystemProps.hasEssLimiter14a; import static io.openems.edge.app.integratedsystem.IntegratedSystemProps.maxFeedInPower; import static io.openems.edge.app.integratedsystem.IntegratedSystemProps.safetyCountry; +import java.util.List; import java.util.Map; import java.util.function.Function; @@ -31,6 +35,8 @@ import io.openems.edge.core.appmanager.AppConfiguration; import io.openems.edge.core.appmanager.AppDef; import io.openems.edge.core.appmanager.AppDescriptor; +import io.openems.edge.core.appmanager.AppManagerUtil; +import io.openems.edge.core.appmanager.AppManagerUtilSupplier; import io.openems.edge.core.appmanager.ComponentUtil; import io.openems.edge.core.appmanager.ConfigurationTarget; import io.openems.edge.core.appmanager.InterfaceConfiguration; @@ -42,10 +48,12 @@ import io.openems.edge.core.appmanager.Type.Parameter; import io.openems.edge.core.appmanager.Type.Parameter.BundleParameter; import io.openems.edge.core.appmanager.dependency.Tasks; +import io.openems.edge.core.appmanager.formly.JsonFormlyUtil; @Component(name = "App.FENECON.Commercial.92") -public class FeneconCommercial92 extends - AbstractOpenemsAppWithProps implements OpenemsApp { +public class FeneconCommercial92 + extends AbstractOpenemsAppWithProps + implements OpenemsApp, AppManagerUtilSupplier { public enum Property implements Type { ALIAS(alias()), // @@ -55,6 +63,16 @@ public enum Property implements Type def // + .setLabel("Battery Start/Stop Target") // + .setDefaultValue("AUTO") // + .setField(JsonFormlyUtil::buildSelect, (app, property, l, parameter, field) -> { + field.setOptions(List.of("START", "AUTO")); + }) // + .appendIsAllowedToSee(AppDef.ofLeastRole(Role.ADMIN)))), // ; private final AppDef def; @@ -80,14 +98,18 @@ public Function, BundleParameter> getPar } + private final AppManagerUtil appManagerUtil; + @Activate public FeneconCommercial92(// @Reference final ComponentManager componentManager, // final ComponentContext componentContext, // @Reference final ConfigurationAdmin cm, // - @Reference final ComponentUtil componentUtil // + @Reference final ComponentUtil componentUtil, // + @Reference final AppManagerUtil appManagerUtil // ) { super(componentManager, componentContext, cm, componentUtil); + this.appManagerUtil = appManagerUtil; } @Override @@ -131,8 +153,15 @@ protected ThrowingTriFunction, L ? this.getInt(p, Property.MAX_FEED_IN_POWER) : 0; + final var hasEssLimiter14a = this.getBoolean(p, Property.HAS_ESS_LIMITER_14A); + + final var batteryTarget = this.getString(p, Property.BATTERY_TARGET); + + final var deviceHardware = this.appManagerUtil + .getFirstInstantiatedAppByCategories(OpenemsAppCategory.OPENEMS_DEVICE_HARDWARE); + final var components = Lists.newArrayList(// - FeneconHomeComponents.battery(bundle, batteryId, modbusToBatteryId), // + FeneconHomeComponents.battery(bundle, batteryId, modbusToBatteryId, batteryTarget), // FeneconCommercialComponents.batteryInverter(bundle, batteryInverterId, modbusToBatteryInverterId), // FeneconHomeComponents.ess(bundle, essId, batteryId, batteryInverterId), // FeneconHomeComponents.io(bundle, modbusToBatteryId), // @@ -140,7 +169,7 @@ protected ThrowingTriFunction, L FeneconHomeComponents.predictor(bundle, t), // FeneconCommercialComponents.modbusToBatteryInverter(bundle, t, modbusToBatteryInverterId), // FeneconCommercialComponents.modbusToGridMeter(bundle, t, modbusToGridMeterId), // - FeneconHomeComponents.modbusForExternalMeters(bundle, t, modbusToExternalDevicesId) // + FeneconHomeComponents.modbusForExternalMeters(bundle, t, modbusToExternalDevicesId, deviceHardware) // ); final var dependencies = Lists.newArrayList(// @@ -150,6 +179,10 @@ protected ThrowingTriFunction, L FeneconCommercialComponents.gridMeter(bundle, gridMeterId, modbusToGridMeterId) // ); + if (hasEssLimiter14a) { + dependencies.add(essLimiter14aToHardware(this.appManagerUtil, deviceHardware)); + } + return AppConfiguration.create() // .addTask(Tasks.component(components)) // .addTask(Tasks.staticIp(new InterfaceConfiguration("eth1") // @@ -172,4 +205,9 @@ public OpenemsAppPermissions getAppPermissions() { .build(); } + @Override + public AppManagerUtil getAppManagerUtil() { + return this.appManagerUtil; + } + } diff --git a/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/AwattarHourly.java b/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/AwattarHourly.java index 31200d39881..9e0ee9fdeed 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/AwattarHourly.java +++ b/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/AwattarHourly.java @@ -4,6 +4,7 @@ import static io.openems.edge.app.common.props.CommonProps.defaultDef; import static io.openems.edge.core.appmanager.validator.Checkables.checkCommercial92; import static io.openems.edge.core.appmanager.validator.Checkables.checkHome; +import static io.openems.edge.core.appmanager.validator.Checkables.checkOr; import java.util.Map; import java.util.function.Function; @@ -169,7 +170,7 @@ public OpenemsAppCardinality getCardinality() { @Override protected ValidatorConfig.Builder getValidateBuilder() { return ValidatorConfig.create() // - .setCompatibleCheckableConfigs(checkHome().or(checkCommercial92())); + .setCompatibleCheckableConfigs(checkOr(checkHome(), checkCommercial92())); } @Override diff --git a/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/EntsoE.java b/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/EntsoE.java index 0c2f6d2f10d..b1bf936cabb 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/EntsoE.java +++ b/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/EntsoE.java @@ -1,7 +1,9 @@ package io.openems.edge.app.timeofusetariff; +import static io.openems.edge.app.common.props.CommonProps.defaultDef; import static io.openems.edge.core.appmanager.validator.Checkables.checkCommercial92; import static io.openems.edge.core.appmanager.validator.Checkables.checkHome; +import static io.openems.edge.core.appmanager.validator.Checkables.checkOr; import java.util.Map; import java.util.function.Function; @@ -41,6 +43,7 @@ import io.openems.edge.core.appmanager.Type; import io.openems.edge.core.appmanager.dependency.Tasks; import io.openems.edge.core.appmanager.dependency.aggregatetask.SchedulerByCentralOrderConfiguration.SchedulerComponent; +import io.openems.edge.core.appmanager.formly.Exp; import io.openems.edge.core.appmanager.formly.JsonFormlyUtil; import io.openems.edge.core.appmanager.validator.ValidatorConfig; @@ -84,7 +87,20 @@ public static enum Property implements Type { field.setOptions(BiddingZone.optionsFactory(), l); field.isRequired(true); - })); + })), + + RESOLUTION(AppDef.copyOfGeneric(defaultDef(), def -> def // + .setTranslatedLabelWithAppPrefix(".resolution.label") // + .setTranslatedDescriptionWithAppPrefix(".resolution.description") // + .setRequired(true)// + .setDefaultValue(Resolution.HOURLY)// + .setField(JsonFormlyUtil::buildSelectFromNameable, (app, property, l, parameter, field) -> { + field.setOptions(Resolution.optionsFactory(), l); + final var isInBiddingZone = Exp + .array(Exp.staticValue(BiddingZone.GERMANY), Exp.staticValue(BiddingZone.AUSTRIA)) + .some(t -> t.equal(Exp.currentModelValue(BIDDING_ZONE))); + field.onlyShowIf(isInBiddingZone); + }))); private final AppDef def; @@ -168,7 +184,7 @@ public OpenemsAppCardinality getCardinality() { @Override protected ValidatorConfig.Builder getValidateBuilder() { return ValidatorConfig.create() // - .setCompatibleCheckableConfigs(checkHome().or(checkCommercial92())); + .setCompatibleCheckableConfigs(checkOr(checkHome(), checkCommercial92())); } @Override @@ -211,4 +227,32 @@ public static final OptionsFactory optionsFactory() { } } + public enum Resolution implements TranslatableEnum { + HOURLY("hourly"), // + QUARTERLY("quarterly"); + + private static final String TRANSLATION_PREFIX = "App.TimeOfUseTariff.ENTSO-E.resolution.option."; + + private final String translationKey; + + private Resolution(String translationKey) { + this.translationKey = TRANSLATION_PREFIX + translationKey; + } + + @Override + public final String getTranslation(Language l) { + final var bundle = AbstractOpenemsApp.getTranslationBundle(l); + return TranslationUtil.getTranslation(bundle, this.translationKey); + } + + /** + * Creates a {@link OptionsFactory} of this enum. + * + * @return the {@link OptionsFactory} + */ + public static final OptionsFactory optionsFactory() { + return OptionsFactory.of(values()); + } + } + } diff --git a/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/GroupeE.java b/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/GroupeE.java index b466c39ff10..7a868a62d1c 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/GroupeE.java +++ b/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/GroupeE.java @@ -2,6 +2,7 @@ import static io.openems.edge.core.appmanager.validator.Checkables.checkCommercial92; import static io.openems.edge.core.appmanager.validator.Checkables.checkHome; +import static io.openems.edge.core.appmanager.validator.Checkables.checkOr; import java.util.Map; import java.util.function.Function; @@ -151,7 +152,7 @@ public OpenemsAppCardinality getCardinality() { @Override protected ValidatorConfig.Builder getValidateBuilder() { return ValidatorConfig.create() // - .setCompatibleCheckableConfigs(checkHome().or(checkCommercial92())); + .setCompatibleCheckableConfigs(checkOr(checkHome(), checkCommercial92())); } @Override diff --git a/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/RabotCharge.java b/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/RabotCharge.java index de9a446a214..94a818c02ed 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/RabotCharge.java +++ b/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/RabotCharge.java @@ -1,8 +1,8 @@ package io.openems.edge.app.timeofusetariff; -import static io.openems.edge.core.appmanager.formly.enums.InputType.PASSWORD; import static io.openems.edge.core.appmanager.validator.Checkables.checkCommercial92; import static io.openems.edge.core.appmanager.validator.Checkables.checkHome; +import static io.openems.edge.core.appmanager.validator.Checkables.checkOr; import java.util.Map; import java.util.function.Function; @@ -15,7 +15,6 @@ import com.google.common.collect.Lists; import com.google.gson.JsonElement; -import com.google.gson.JsonPrimitive; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.function.ThrowingTriFunction; @@ -31,7 +30,6 @@ import io.openems.edge.core.appmanager.AppConfiguration; import io.openems.edge.core.appmanager.AppDef; import io.openems.edge.core.appmanager.AppDescriptor; -import io.openems.edge.core.appmanager.ComponentManagerSupplier; import io.openems.edge.core.appmanager.ComponentUtil; import io.openems.edge.core.appmanager.ConfigurationTarget; import io.openems.edge.core.appmanager.Nameable; @@ -41,7 +39,6 @@ import io.openems.edge.core.appmanager.Type; import io.openems.edge.core.appmanager.dependency.Tasks; import io.openems.edge.core.appmanager.dependency.aggregatetask.SchedulerByCentralOrderConfiguration.SchedulerComponent; -import io.openems.edge.core.appmanager.formly.JsonFormlyUtil; import io.openems.edge.core.appmanager.validator.ValidatorConfig; /** @@ -75,24 +72,8 @@ public static enum Property implements Type def// - .setTranslatedLabelWithAppPrefix(".accessToken.label") // - .setTranslatedDescriptionWithAppPrefix(".accessToken.description") // - .setRequired(true) // - .setField(JsonFormlyUtil::buildInput, (app, prop, l, params, field) -> { - field.setInputType(PASSWORD); - }) // - .bidirectional(TIME_OF_USE_TARIFF_PROVIDER_ID, "accessToken", - ComponentManagerSupplier::getComponentManager, t -> { - return JsonUtils.getAsOptionalString(t) // - .map(s -> { - if (s.isEmpty()) { - return null; - } - return new JsonPrimitive("xxx"); - }) // - .orElse(null); - }))); + ZIP_CODE(TimeOfUseProps.zipCode()), // + ; private final AppDef def; @@ -129,18 +110,16 @@ protected ThrowingTriFunction, L final var timeOfUseTariffProviderId = this.getId(t, p, Property.TIME_OF_USE_TARIFF_PROVIDER_ID); final var alias = this.getString(p, l, Property.ALIAS); - final var accessToken = this.getValueOrDefault(p, Property.ACCESS_TOKEN, null); + final var zipCode = this.getString(p, Property.ZIP_CODE); - var components = Lists.newArrayList(// + final var components = Lists.newArrayList(// new EdgeConfig.Component(ctrlEssTimeOfUseTariffId, alias, "Controller.Ess.Time-Of-Use-Tariff", JsonUtils.buildJsonObject() // .addProperty("ess.id", "ess0") // .build()), // new EdgeConfig.Component(timeOfUseTariffProviderId, this.getName(l), "TimeOfUseTariff.RabotCharge", JsonUtils.buildJsonObject() // - .onlyIf(accessToken != null && !accessToken.equals("xxx"), b -> { - b.addProperty("accessToken", accessToken); - }) // + .addProperty("zipcode", zipCode) // .build())// ); @@ -178,7 +157,7 @@ public OpenemsAppCardinality getCardinality() { @Override protected ValidatorConfig.Builder getValidateBuilder() { return ValidatorConfig.create() // - .setCompatibleCheckableConfigs(checkHome().or(checkCommercial92())); + .setCompatibleCheckableConfigs(checkOr(checkHome(), checkCommercial92())); } @Override diff --git a/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/StadtwerkHassfurt.java b/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/StadtwerkHassfurt.java index 8bf099ba335..09cd81281b6 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/StadtwerkHassfurt.java +++ b/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/StadtwerkHassfurt.java @@ -3,6 +3,7 @@ import static io.openems.edge.app.common.props.CommonProps.defaultDef; import static io.openems.edge.core.appmanager.validator.Checkables.checkCommercial92; import static io.openems.edge.core.appmanager.validator.Checkables.checkHome; +import static io.openems.edge.core.appmanager.validator.Checkables.checkOr; import java.util.Map; import java.util.function.Function; @@ -167,7 +168,7 @@ public OpenemsAppCardinality getCardinality() { @Override protected ValidatorConfig.Builder getValidateBuilder() { return ValidatorConfig.create() // - .setCompatibleCheckableConfigs(checkHome().or(checkCommercial92())); + .setCompatibleCheckableConfigs(checkOr(checkHome(), checkCommercial92())); } @Override diff --git a/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/StromdaoCorrently.java b/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/StromdaoCorrently.java index fbbeb951333..8603da06a70 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/StromdaoCorrently.java +++ b/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/StromdaoCorrently.java @@ -2,6 +2,7 @@ import static io.openems.edge.core.appmanager.validator.Checkables.checkCommercial92; import static io.openems.edge.core.appmanager.validator.Checkables.checkHome; +import static io.openems.edge.core.appmanager.validator.Checkables.checkOr; import java.util.Map; import java.util.function.Function; @@ -38,8 +39,6 @@ import io.openems.edge.core.appmanager.Type; import io.openems.edge.core.appmanager.dependency.Tasks; import io.openems.edge.core.appmanager.dependency.aggregatetask.SchedulerByCentralOrderConfiguration.SchedulerComponent; -import io.openems.edge.core.appmanager.formly.JsonFormlyUtil; -import io.openems.edge.core.appmanager.formly.enums.InputType; import io.openems.edge.core.appmanager.validator.ValidatorConfig; /** @@ -74,12 +73,7 @@ public static enum Property implements Type // - f.setInputType(InputType.NUMBER) // - .isRequired(true))); + ZIP_CODE(TimeOfUseProps.zipCode()); private final AppDef def; @@ -164,7 +158,7 @@ public OpenemsAppCardinality getCardinality() { @Override protected ValidatorConfig.Builder getValidateBuilder() { return ValidatorConfig.create() // - .setCompatibleCheckableConfigs(checkHome().or(checkCommercial92())); + .setCompatibleCheckableConfigs(checkOr(checkHome(), checkCommercial92())); } @Override diff --git a/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/Swisspower.java b/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/Swisspower.java new file mode 100644 index 00000000000..8726789cf11 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/Swisspower.java @@ -0,0 +1,197 @@ +package io.openems.edge.app.timeofusetariff; + +import static io.openems.edge.core.appmanager.formly.enums.InputType.PASSWORD; +import static io.openems.edge.core.appmanager.validator.Checkables.checkCommercial92; +import static io.openems.edge.core.appmanager.validator.Checkables.checkHome; + +import java.util.Map; +import java.util.function.Function; + +import org.osgi.service.cm.ConfigurationAdmin; +import org.osgi.service.component.ComponentContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +import com.google.common.collect.Lists; +import com.google.gson.JsonElement; +import com.google.gson.JsonPrimitive; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.function.ThrowingTriFunction; +import io.openems.common.oem.OpenemsEdgeOem; +import io.openems.common.session.Language; +import io.openems.common.types.EdgeConfig; +import io.openems.common.utils.JsonUtils; +import io.openems.edge.app.common.props.CommonProps; +import io.openems.edge.app.timeofusetariff.Swisspower.Property; +import io.openems.edge.common.component.ComponentManager; +import io.openems.edge.core.appmanager.AbstractOpenemsApp; +import io.openems.edge.core.appmanager.AbstractOpenemsAppWithProps; +import io.openems.edge.core.appmanager.AppConfiguration; +import io.openems.edge.core.appmanager.AppDef; +import io.openems.edge.core.appmanager.AppDescriptor; +import io.openems.edge.core.appmanager.ComponentManagerSupplier; +import io.openems.edge.core.appmanager.ComponentUtil; +import io.openems.edge.core.appmanager.ConfigurationTarget; +import io.openems.edge.core.appmanager.Nameable; +import io.openems.edge.core.appmanager.OpenemsApp; +import io.openems.edge.core.appmanager.OpenemsAppCardinality; +import io.openems.edge.core.appmanager.OpenemsAppCategory; +import io.openems.edge.core.appmanager.Type; +import io.openems.edge.core.appmanager.dependency.Tasks; +import io.openems.edge.core.appmanager.dependency.aggregatetask.SchedulerByCentralOrderConfiguration.SchedulerComponent; +import io.openems.edge.core.appmanager.formly.JsonFormlyUtil; +import io.openems.edge.core.appmanager.validator.ValidatorConfig; + +/** + * Describes a App for Swisspower. + * + *
+  {
+    "appId":"App.TimeOfUseTariff.Swisspower",
+    "alias":"Swisspower",
+    "instanceId": UUID,
+    "image": base64,
+    "properties":{
+    	"CTRL_ESS_TIME_OF_USE_TARIFF_ID": "ctrlEssTimeOfUseTariff0",
+    	"TIME_OF_USE_TARIFF_PROVIDER_ID": "timeOfUseTariff0",
+    	"ACCESS_TOKEN": {token},
+    	"METERING_CODE": {code}
+    },
+    "appDescriptor": {
+    	"websiteUrl": {@link AppDescriptor#getWebsiteUrl()}
+    }
+  }
+ * 
+ */ +@Component(name = "App.TimeOfUseTariff.Swisspower") +public class Swisspower extends AbstractOpenemsAppWithProps + implements OpenemsApp { + + public static enum Property implements Type, Nameable { + // Component-IDs + CTRL_ESS_TIME_OF_USE_TARIFF_ID(AppDef.componentId("ctrlEssTimeOfUseTariff0")), // + TIME_OF_USE_TARIFF_PROVIDER_ID(AppDef.componentId("timeOfUseTariff0")), // + + // Properties + ALIAS(CommonProps.alias()), // + ACCESS_TOKEN(AppDef.copyOfGeneric(CommonProps.defaultDef(), def -> def// + .setTranslatedLabelWithAppPrefix(".accessToken.label") // + .setTranslatedDescriptionWithAppPrefix(".accessToken.description") // + .setRequired(true) // + .setField(JsonFormlyUtil::buildInput, (app, prop, l, params, field) -> { + field.setInputType(PASSWORD); + }) // + .bidirectional(TIME_OF_USE_TARIFF_PROVIDER_ID, "accessToken", + ComponentManagerSupplier::getComponentManager, t -> { + return JsonUtils.getAsOptionalString(t) // + .map(s -> { + if (s.isEmpty()) { + return null; + } + return new JsonPrimitive("xxx"); + }) // + .orElse(null); + }))), + METERING_CODE(AppDef.copyOfGeneric(CommonProps.defaultDef(), def -> def// + .setTranslatedLabelWithAppPrefix(".meteringCode.label") // + .setTranslatedDescriptionWithAppPrefix(".meteringCode.description") // + .setRequired(true) // + .setField(JsonFormlyUtil::buildInput))); + + private final AppDef def; + + private Property(AppDef def) { + this.def = def; + } + + @Override + public Property self() { + return this; + } + + @Override + public AppDef def() { + return this.def; + } + + @Override + public Function, Type.Parameter.BundleParameter> getParamter() { + return Type.Parameter.functionOf(AbstractOpenemsApp::getTranslationBundle); + } + } + + @Activate + public Swisspower(@Reference ComponentManager componentManager, ComponentContext context, + @Reference ConfigurationAdmin cm, @Reference ComponentUtil componentUtil) { + super(componentManager, context, cm, componentUtil); + } + + @Override + protected ThrowingTriFunction, Language, AppConfiguration, OpenemsNamedException> appPropertyConfigurationFactory() { + return (t, p, l) -> { + final var ctrlEssTimeOfUseTariffId = this.getId(t, p, Property.CTRL_ESS_TIME_OF_USE_TARIFF_ID); + final var timeOfUseTariffProviderId = this.getId(t, p, Property.TIME_OF_USE_TARIFF_PROVIDER_ID); + + final var alias = this.getString(p, l, Property.ALIAS); + final var accessToken = this.getValueOrDefault(p, Property.ACCESS_TOKEN, null); + final var meteringCode = this.getString(p, l, Property.METERING_CODE); + + var components = Lists.newArrayList(// + new EdgeConfig.Component(ctrlEssTimeOfUseTariffId, alias, "Controller.Ess.Time-Of-Use-Tariff", + JsonUtils.buildJsonObject() // + .addProperty("ess.id", "ess0") // + .build()), // + new EdgeConfig.Component(timeOfUseTariffProviderId, this.getName(l), "TimeOfUseTariff.Swisspower", + JsonUtils.buildJsonObject() // + .addPropertyIfNotNull("meteringCode", meteringCode) // + .onlyIf(accessToken != null && !accessToken.equals("xxx"), b -> { + b.addProperty("accessToken", accessToken); + }) // + .build())// + ); + + return AppConfiguration.create() // + .addTask(Tasks.component(components)) // + .addTask(Tasks.schedulerByCentralOrder(new SchedulerComponent(ctrlEssTimeOfUseTariffId, + "Controller.Ess.Time-Of-Use-Tariff", this.getAppId()))) // + .addTask(Tasks.persistencePredictor("_sum/UnmanagedConsumptionActivePower")) // + .build(); + }; + } + + @Override + public AppDescriptor getAppDescriptor(OpenemsEdgeOem oem) { + return AppDescriptor.create() // + .setWebsiteUrl(oem.getAppWebsiteUrl(this.getAppId())) // + .build(); + } + + @Override + public OpenemsAppCategory[] getCategories() { + return new OpenemsAppCategory[] { OpenemsAppCategory.TIME_OF_USE_TARIFF }; + } + + @Override + protected Property[] propertyValues() { + return Property.values(); + } + + @Override + public OpenemsAppCardinality getCardinality() { + return OpenemsAppCardinality.SINGLE_IN_CATEGORY; + } + + @Override + protected ValidatorConfig.Builder getValidateBuilder() { + return ValidatorConfig.create() // + .setCompatibleCheckableConfigs(checkHome().or(checkCommercial92())); + } + + @Override + protected Swisspower getApp() { + return this; + } + +} diff --git a/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/Tibber.java b/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/Tibber.java index 3eeeb094bab..608b2a4ec4b 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/Tibber.java +++ b/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/Tibber.java @@ -3,6 +3,7 @@ import static io.openems.edge.core.appmanager.formly.enums.InputType.PASSWORD; import static io.openems.edge.core.appmanager.validator.Checkables.checkCommercial92; import static io.openems.edge.core.appmanager.validator.Checkables.checkHome; +import static io.openems.edge.core.appmanager.validator.Checkables.checkOr; import java.util.Map; import java.util.function.Function; @@ -197,7 +198,7 @@ public OpenemsAppCardinality getCardinality() { @Override protected ValidatorConfig.Builder getValidateBuilder() { return ValidatorConfig.create() // - .setCompatibleCheckableConfigs(checkHome().or(checkCommercial92())); + .setCompatibleCheckableConfigs(checkOr(checkHome(), checkCommercial92())); } @Override diff --git a/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/TimeOfUseProps.java b/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/TimeOfUseProps.java index 55ad9a46287..c2955a41328 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/TimeOfUseProps.java +++ b/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/TimeOfUseProps.java @@ -1,5 +1,7 @@ package io.openems.edge.app.timeofusetariff; +import static io.openems.edge.app.common.props.CommonProps.defaultDef; + import java.util.ArrayList; import java.util.function.Consumer; @@ -9,10 +11,30 @@ import io.openems.common.types.EdgeConfig.Component; import io.openems.common.utils.JsonUtils; import io.openems.common.utils.JsonUtils.JsonObjectBuilder; +import io.openems.edge.core.appmanager.AppDef; import io.openems.edge.core.appmanager.ConfigurationTarget; +import io.openems.edge.core.appmanager.Nameable; +import io.openems.edge.core.appmanager.OpenemsApp; +import io.openems.edge.core.appmanager.Type.Parameter.BundleProvider; +import io.openems.edge.core.appmanager.formly.JsonFormlyUtil; +import io.openems.edge.core.appmanager.formly.enums.InputType; public final class TimeOfUseProps { + /** + * Creates a {@link AppDef} for a zipcode. + * + * @return the {@link AppDef} + */ + public static AppDef zipCode() { + return AppDef.copyOfGeneric(defaultDef(), def -> def// + .setTranslatedLabel("App.TimeOfUseTariff.zipCode.label") // + .setTranslatedDescription("App.TimeOfUseTariff.zipCode.description") // + .setField(JsonFormlyUtil::buildInputFromNameable, (app, property, l, parameter, field) -> { + field.setInputType(InputType.NUMBER); + })); + } + private TimeOfUseProps() { } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerUtil.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerUtil.java index e1e7d3f1fe1..17d8c739e08 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerUtil.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerUtil.java @@ -52,7 +52,7 @@ public default List getInstantiatedAppsOf(String... appIds) } /** - * Gets the installed apps which match any of the provided + * Gets the installed apps which matches at least one of the provided * {@link OpenemsAppCategory OpenemsAppCategories}. * * @param categories the {@link OpenemsAppCategory} to be contained by the app @@ -60,6 +60,18 @@ public default List getInstantiatedAppsOf(String... appIds) */ public List getInstantiatedAppsByCategories(OpenemsAppCategory... categories); + /** + * Gets the first found installed app which matches at least one of the provided + * {@link OpenemsAppCategory OpenemsAppCategories}. + * + * @param categories the {@link OpenemsAppCategory} to be contained by the app + * @return the found {@link OpenemsAppInstance}; or null if non found + */ + public default OpenemsAppInstance getFirstInstantiatedAppByCategories(OpenemsAppCategory... categories) { + final var instances = this.getInstantiatedAppsByCategories(categories); + return instances.isEmpty() ? null : instances.get(0); + } + /** * Finds the {@link OpenemsApp} with the given id. * diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/HostSupplier.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/HostSupplier.java new file mode 100644 index 00000000000..32da8503bc4 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/HostSupplier.java @@ -0,0 +1,14 @@ +package io.openems.edge.core.appmanager; + +import io.openems.edge.common.host.Host; + +public interface HostSupplier { + + /** + * Gets a {@link Host}. + * + * @return the {@link Host} + */ + Host getHost(); + +} diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsAppCategory.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsAppCategory.java index 9f11f6825c7..16d616748e9 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsAppCategory.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsAppCategory.java @@ -24,6 +24,11 @@ public enum OpenemsAppCategory { */ EVCS("evcs"), + /** + * Read only Electric vehicle charging station. + */ + EVCS_READ_ONLY("evcsReadOnly"), + /** * Heat. */ @@ -79,7 +84,7 @@ public enum OpenemsAppCategory { * */ TIMEDATA("timedata"), - + /** * Category for test apps. * diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/aggregatetask/ComponentAggregateTaskImpl.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/aggregatetask/ComponentAggregateTaskImpl.java index b7c8349e4ed..87fad0519b6 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/aggregatetask/ComponentAggregateTaskImpl.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/aggregatetask/ComponentAggregateTaskImpl.java @@ -69,7 +69,10 @@ public void aggregate(ComponentConfiguration config, ComponentConfiguration oldC if (oldConfig != null) { var componentDiff = new ArrayList<>(oldConfig.components()); if (config != null) { - componentDiff.removeIf(t -> config.components().stream().anyMatch(c -> c.getId().equals(t.getId()))); + componentDiff.removeIf(t -> config.components().stream().anyMatch(c -> { + return c.getId().equals(t.getId()) // + && c.getFactoryId().equals(t.getFactoryId()); + })); } this.components2Delete.addAll(componentDiff); } @@ -94,8 +97,24 @@ public void create(User user, List otherAppConfigurations) thr if (foundComponentWithSameId != null) { // check if the found component has the same factory id if (!foundComponentWithSameId.getFactoryId().equals(comp.getFactoryId())) { - errors.add("Configuration of component with id '" + foundComponentWithSameId.getId() - + "' can not be rewritten. Because the component has a different factoryId."); + if (this.components2Delete.stream().anyMatch(t -> t.getId().equals(comp.getId()))) { + // if the component was intended to be deleted anyway delete it directly and + // create the new component directly afterwards + try { + this.deleteComponent(user, comp); + this.deletedComponents.add(comp.getId()); + this.components2Delete.removeIf(t -> t.getId().equals(comp.getId())); + this.createComponent(user, comp); + this.createdComponents.add(comp); + } catch (OpenemsNamedException e) { + final var error = "Component[" + comp.getFactoryId() + "] cant be created!"; + errors.add(error); + errors.add(e.getMessage()); + } + } else { + errors.add("Configuration of component with id '" + foundComponentWithSameId.getId() + + "' can not be rewritten. Because the component has a different factoryId."); + } continue; } @@ -179,8 +198,7 @@ public void delete(User user, List otherAppConfigurations) thr } try { - this.componentManager.handleDeleteComponentConfigRequest(user, - new DeleteComponentConfigRequest(comp.getId())); + this.deleteComponent(user, comp); this.deletedComponents.add(comp.getId()); } catch (OpenemsNamedException e) { errors.add(e.toString()); @@ -235,6 +253,10 @@ private final boolean anyChanges() { || !this.components2Delete.isEmpty(); } + private void deleteComponent(User user, EdgeConfig.Component comp) throws OpenemsNamedException { + this.componentManager.handleDeleteComponentConfigRequest(user, new DeleteComponentConfigRequest(comp.getId())); + } + private void createComponent(User user, EdgeConfig.Component comp) throws OpenemsNamedException { List properties = comp.getProperties().entrySet().stream() .map(t -> new Property(t.getKey(), t.getValue())).collect(Collectors.toList()); diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/aggregatetask/SchedulerByCentralOrderAggregateTaskImpl.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/aggregatetask/SchedulerByCentralOrderAggregateTaskImpl.java index c7b69815ce1..253f2b98c45 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/aggregatetask/SchedulerByCentralOrderAggregateTaskImpl.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/aggregatetask/SchedulerByCentralOrderAggregateTaskImpl.java @@ -58,11 +58,12 @@ public class SchedulerByCentralOrderAggregateTaskImpl implements SchedulerByCent public static final class ProductionSchedulerOrderDefinition extends SchedulerOrderDefinition { public ProductionSchedulerOrderDefinition() { - this.thenByFactoryId("Controller.Ess.PrepareBatteryExtension") // + this// + .thenByFactoryId("Controller.Ess.Limiter14a") // + .thenByFactoryId("Controller.Ess.PrepareBatteryExtension") // .thenByFactoryId("Controller.Ess.FixActivePower") // .thenByFactoryId("Controller.Ess.FixStateOfCharge")// .thenByFactoryId("Controller.Ess.EmergencyCapacityReserve") // - .thenByFactoryId("Controller.Ess.Limiter14a") // .thenBy(new SchedulerOrderDefinition() // .filterByFactoryId("Controller.Api.ModbusTcp.ReadWrite") // .thenByCreatedAppId("App.Ess.GeneratingPlantController") // diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/InputBuilder.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/InputBuilder.java index 4f3457f1751..65e5eb0b1b9 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/InputBuilder.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/InputBuilder.java @@ -140,9 +140,9 @@ public InputBuilder setMaxLenght(int maxLength) { /** * Sets the validation of the Input. + * *

* e. g. to set the validation of an IP use {@link Validation#IP} - *

* * @param validation the validation to be set * @return this @@ -153,6 +153,19 @@ public InputBuilder setValidation(Validation validation) { return this; } + /** + * Sets the validation of the Input. + * + * @param pattern the pattern to be set + * @param msg the error message + * @return this + */ + public InputBuilder setValidation(String pattern, String msg) { + this.setPattern(pattern); + this.setValidationMessage("pattern", msg); + return this; + } + /** * Only allows positive number as a input. * diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_de.properties b/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_de.properties index b0e860ca53e..e9795f28fb0 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_de.properties +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_de.properties @@ -2,6 +2,7 @@ integratedSystems = Integrierte Systeme timeOfUseTariff = Dynamische Stromtarife evcs = E-Mobilität +evcsReadOnly = E-Mobilität (nur lesend) heat = Power-to-Heat loadControl = Laststeuerung hardware = Hardware @@ -33,6 +34,7 @@ switzerland = Schweiz sweden = Schweden czech = Tschechische Republik netherlands = Niederlande +greece = Griechenland username = Benutzername password = Passwort left = Links @@ -44,6 +46,7 @@ unofficialAppWarning.text1 =
Hinweis: Bei dieser App ha unofficialAppWarning.text2 =
  • Es wird keine 100% Funktionsfähigkeit der App gewährleistet.
  • Für diese App besteht kein Anspruch auf Service-Unterstützung jeglicher Form. Bei Fragen und technischen Problemen nutzen Sie bitte das OpenEMS Community Forum.
  • Auswirkungen jeglicher Art auf das Gesamtsystem können nicht ausgeschlossen werden.
# communication +communication.excludingIp = Eingabe ist keine valide IP oder die IP des EMS! communication.ipAddress = IP-Adresse communication.ipAddress.description = IP-Adresse des Geräts. communication.port = Port @@ -123,6 +126,8 @@ App.Evcs.controller.alias = Ladestation Steuerung App.Evcs.ip.description = Die IP-Adresse der Ladestation. App.Evcs.chargingStation.label = Ladepunkt {0} App.Evcs.numberOfChargingStations.label = Anzahl Ladepunkte +App.Evcs.phaseRotation.label = Phasenrotation +App.Evcs.phaseRotation.description = Verkabelung der einzelnen Phasen der Ladestation zu den Phasen im Netz App.Evcs.Cluster.Name = Multiladepunkt Management App.Evcs.Cluster.Name.short = Multiladepunkt Management @@ -152,6 +157,9 @@ App.Evcs.IesKeywatt.connector.description = Die Anschlusskennung der Stromzapfs App.Evcs.Keba.Name = KEBA Ladestation App.Evcs.Keba.Name.short = KEBA +App.Evcs.Mennekes.ReadOnly.Name = Mennekes Ladestation +App.Evcs.Mennekes.ReadOnly.Name.short = Mennekes + App.Evcs.Alpitronic.Name = Alpitronic Ladestation App.Evcs.Alpitronic.Name.short = Alpitronic App.Evcs.Alpitronic.chargingStation.label = Alpitronic Ladestation - Ladepunkt {0} @@ -245,9 +253,11 @@ App.IntegratedSystem.ctRatioFirst.label = Wandler-Primärstrom (200A - 5000A/5A) App.IntegratedSystem.shadowManagementDisabled.label = Schattenmanagement deaktivieren App.IntegratedSystem.shadowManagementDisabled.description = Nur wenn Optimierer verbaut sind, muss das Schattenmanagement deaktiviert werden App.IntegratedSystem.hasEssLimiter14a.label = Hat Limitierer für §14a +App.IntegratedSystem.naProtectionEnabled.label = NA-Schutz aktiviert? -App.IntegratedSystem.modbus0.alias = Kommunikation mit der Batterie -App.IntegratedSystem.modbus0N.alias = Kommunikation mit den Batterien +App.IntegratedSystem.modbusToBattery.alias = Kommunikation mit der Batterie +App.IntegratedSystem.modbusToBatteryN.alias = Kommunikation mit den Batterien +App.IntegratedSystem.modbusToBattery0.alias = Kommunikation mit der Batterie {0} App.IntegratedSystem.modbus1.alias = Kommunikation mit dem Batterie-Wechselrichter App.IntegratedSystem.modbus1N.alias = Kommunikation mit dem Batterie-Wechselrichter {0} App.IntegratedSystem.modbus2.alias = externe RS485 Schnittstelle @@ -260,6 +270,7 @@ App.IntegratedSystem.batteryParallelClusterN.alias = Parallel-Cluster {0} App.IntegratedSystem.batteryInverter0.alias = Batterie-Wechselrichter App.IntegratedSystem.batteryInverterN.alias = Batterie-Wechselrichter {0} App.IntegratedSystem.ess0.alias = Speichersystem +App.IntegratedSystem.essCluster0.alias = Batteriespeicher-Cluster App.IntegratedSystem.predictor0.alias = Prognose App.IntegratedSystem.ctrlEssSurplusFeedToGrid0.alias = Überschusseinspeisung App.IntegratedSystem.emergencyMeter.alias = Notstromverbraucher @@ -272,9 +283,11 @@ App.IntegratedSystem.mpptAlias.label = MPPT {0} Alias App.IntegratedSystem.mpptAlias.alias = MPPT {0} App.IntegratedSystem.hasAcMeter.label = Hat AC-Zähler App.IntegratedSystem.acMeterType.label = AC-Zähler Typ +App.IntegratedSystem.gridMeterTypeGen2.option.integrated = Integrierter Home 3-Phasensensor mit Stromwandler (Standardlieferumfang) +App.IntegratedSystem.gridMeterTypeGen2.option.external = Home 3-Phasensensor mit Stromwandler 120A (Optional) -App.FENECON.Home.Name = FENECON Home -App.FENECON.Home.Name.short = FENECON Home +App.FENECON.Home.Name = FENECON Home 10 +App.FENECON.Home.Name.short = FENECON Home 10 App.FENECON.Home.safetyCountry.label = Batterie-Wechselrichter Ländereinstellung App.FENECON.Home.rippleControlReceiver.label = Rundsteuerempfänger aktiviert? App.FENECON.Home.rippleControlReceiver.description = Externe Abregelung durch Netzbetreiber @@ -284,6 +297,10 @@ App.FENECON.Home.hasAcMeter.label = Hat AC-Zähler App.FENECON.Home.acMeterType.label = AC-Zähler Typ App.FENECON.Home.hasDcPV1.label = Hat DC-PV 1 (MPPT 1) App.FENECON.Home.hasDcPV2.label = Hat DC-PV 2 (MPPT 2) +App.FENECON.Home.hasDcPV3.label = Hat DC-PV 3 (MPPT 3) +App.FENECON.Home.dcPv1.alias.label = DC-PV 1 Alias +App.FENECON.Home.dcPv2.alias.label = DC-PV 2 Alias +App.FENECON.Home.dcPv3.alias.label = DC-PV 3 Alias App.FENECON.Home.emergencyPowerSupply.label = Aktivieren der Notstromversorgung App.FENECON.Home.emergencyPowerEnergy.label = Aktivieren der Notfallreserve Energie App.FENECON.Home.reserveEnergy.label = Notfallreserve Energie (State-of-Charge) @@ -309,8 +326,18 @@ App.FENECON.Home.30.Name.short = FENECON Home 30 App.FENECON.Home.20.Name = FENECON Home 20 App.FENECON.Home.20.Name.short = FENECON Home 20 +App.FENECON.Home6.Name = FENECON Home 6 +App.FENECON.Home6.Name.short = FENECON Home 6 + +App.FENECON.Home10.Gen2.Name = FENECON Home 10 (Gen2) +App.FENECON.Home10.Gen2.Name.short = FENECON Home 10 (Gen2) + +App.FENECON.Home15.Name = FENECON Home 15 +App.FENECON.Home15.Name.short = FENECON Home 15 + App.FENECON.Commercial.92.Name = FENECON Commercial App.FENECON.Commercial.92.Name.short = FENECON Commercial + App.FENECON.Industrial.L.io0 = Relais App.FENECON.Industrial.L.Name = FENECON Industrial L App.FENECON.Industrial.L.Name.short = FENECON Industrial L @@ -325,7 +352,6 @@ App.FENECON.Industrial.L.ILK710.batteryFirmwareVersion.label = Battery Firmware App.FENECON.Industrial.S.io0 = Relais App.FENECON.Industrial.S.ess0.alias = Batteriespeicher App.FENECON.Industrial.S.essN.alias = Batteriespeicher {0} -App.FENECON.Industrial.S.essCluster0.alias = Batteriespeicher-Cluster App.FENECON.Industrial.S.hasGridMeter.label = Hat Netzzähler App.FENECON.Industrial.S.hasSelfConsumptionOptimization.label = Hat Eigenverbrauchsoptimierung App.FENECON.Industrial.S.modbusToGridMeter.alias = Kommunikation mit den Netzzähler @@ -416,6 +442,9 @@ App.PvInverter.SolarEdge.Name = SolarEdge PV-Wechselrichter App.PvInverter.SolarEdge.Name.short = SolarEdge # Time of use Tariff +App.TimeOfUseTariff.zipCode.label = PLZ +App.TimeOfUseTariff.zipCode.description = Deutsche Postleitzahl des Wohnorts + App.TimeOfUseTariff.Awattar.Name = Dynamischer Stromtarif (Awattar HOURLY) App.TimeOfUseTariff.Awattar.Name.short = Awattar HOURLY App.TimeOfUseTariff.Awattar.zone.label = Zone @@ -434,6 +463,10 @@ App.TimeOfUseTariff.ENTSO-E.biddingZone.option.sweden_se3 = Schweden (SE3) App.TimeOfUseTariff.ENTSO-E.biddingZone.option.sweden_se4 = Schweden (SE4) App.TimeOfUseTariff.ENTSO-E.biddingZone.option.belgium = Belgien App.TimeOfUseTariff.ENTSO-E.biddingZone.option.netherlands = Niederlande +App.TimeOfUseTariff.ENTSO-E.resolution.label = Auflösung +App.TimeOfUseTariff.ENTSO-E.resolution.description = Auflösung entsprechend dem Preisintervall +App.TimeOfUseTariff.ENTSO-E.resolution.option.hourly = Stundenpreise +App.TimeOfUseTariff.ENTSO-E.resolution.option.quarterly = Viertelstundenpreise App.TimeOfUseTariff.GroupeE.Name = Dynamischer Stromtarif (Groupe-E) App.TimeOfUseTariff.GroupeE.Name.short = Groupe-E App.TimeOfUseTariff.Hassfurt.Name = Dynamischer Stromtarif (Stadtwerk Haßfurt) @@ -446,8 +479,12 @@ App.TimeOfUseTariff.RabotCharge.accessToken.label = Token App.TimeOfUseTariff.RabotCharge.accessToken.description = Bitte stellen Sie das von rabot.charge bereitgestellte persönliche Zugangstoken bereit. Um ein Token zu erhalten, wenden Sie sich bitte an rabot.charge. App.TimeOfUseTariff.Stromdao.Name = Dynamischer Stromtarif (Stromdao Corrently) App.TimeOfUseTariff.Stromdao.Name.short = Stromdao Corrently -App.TimeOfUseTariff.Stromdao.zipCode.label = PLZ -App.TimeOfUseTariff.Stromdao.zipCode.description = Deutsche Postleitzahl des Wohnorts +App.TimeOfUseTariff.Swisspower.Name = Dynamischer Stromtarif (Swisspower) +App.TimeOfUseTariff.Swisspower.Name.short = Swisspower +App.TimeOfUseTariff.Swisspower.accessToken.label = Token +App.TimeOfUseTariff.Swisspower.accessToken.description = Bitte stellen Sie den von Swisspower bereitgestellte Zugangstoken bereit. Um ein Token zu erhalten, wenden Sie sich bitte an Swisspower. +App.TimeOfUseTariff.Swisspower.meteringCode.label = Messpunktnummer (z.B. CH1018601234500000000000000011642) +App.TimeOfUseTariff.Swisspower.meteringCode.description = Bitte stellen Sie die von Swisspower bereitgestellte Messpunktnummer bereit. Um ein Messpunktnummer zu erhalten, wenden Sie sich bitte an Swisspower. App.TimeOfUseTariff.Tibber.Name = Dynamischer Stromtarif (Tibber) App.TimeOfUseTariff.Tibber.Name.short = Tibber App.TimeOfUseTariff.Tibber.accessToken.label = Token diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_en.properties b/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_en.properties index 460116c4f56..03f34b1bafa 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_en.properties +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_en.properties @@ -2,6 +2,7 @@ integratedSystems = Integrated Systems timeOfUseTariff = Time-of-use tariffs evcs = E-Mobility +evcsReadOnly = E-Mobility (Read Only) heat = Power-to-Heat loadControl = Load control hardware = Hardware @@ -33,6 +34,7 @@ switzerland = Switzerland sweden = Sweden czech = Czech Republic netherlands = Netherlands +greece = Greece username = Username password = Password left = Left @@ -44,6 +46,7 @@ unofficialAppWarning.text1 = Note: This app is not an o unofficialAppWarning.text2 =
  • 100% functionality of the app is not guaranteed
  • There is no entitlement to service of any kind for this app. If you have any questions or technical problems, please use the OpenEMS Community Forum.
  • Effects of any kind on the overall system cannot be ruled out.
# communication +communication.excludingIp = Input is not a valid IP Address or is the IP of the EMS itself! communication.ipAddress = IP-Address communication.ipAddress.description = The IP address of the device. communication.port = Port @@ -123,6 +126,8 @@ App.Evcs.controller.alias = Charging station control App.Evcs.ip.description = The IP address of the charging station. App.Evcs.chargingStation.label = Charging point {0} App.Evcs.numberOfChargingStations.label = Number of charging points +App.Evcs.phaseRotation.label = Phase rotation +App.Evcs.phaseRotation.description = Wiring of the individual phases of the charging station to actual phases of the grid App.Evcs.Cluster.Name = Multi-charging point management App.Evcs.Cluster.Name.short = Multi-charging point management @@ -152,6 +157,9 @@ App.Evcs.IesKeywatt.connector.description = The connector id of the chargepoint App.Evcs.Keba.Name = KEBA charging station App.Evcs.Keba.Name.short = KEBA +App.Evcs.Mennekes.ReadOnly.Name = Mennekes charging station +App.Evcs.Mennekes.ReadOnly.Name.short = Mennekes + App.Evcs.Alpitronic.Name = Alpitronic charging station App.Evcs.Alpitronic.Name.short = Alpitronic App.Evcs.Alpitronic.chargingStation.label = Alpitronic charging station - Charging point {0} @@ -244,10 +252,12 @@ App.IntegratedSystem.gridMeterType.option.commercialMeter = Home 3-phase sensor App.IntegratedSystem.ctRatioFirst.label = CT-Ratio (200A - 5000A/5A) App.IntegratedSystem.shadowManagementDisabled.label = Deactivate shadow management App.IntegratedSystem.shadowManagementDisabled.description = Only if optimisers are installed, shadow management must be deactivated -App.IntegratedSystem.hasEssLimiter14a.label = Has limiter for §14a +App.IntegratedSystem.hasEssLimiter14a.label = Has limiter for 14a +App.IntegratedSystem.naProtectionEnabled.label = NA-protection enabled? -App.IntegratedSystem.modbus0.alias = Communication with the battery -App.IntegratedSystem.modbus0N.alias = Communication with the batteries +App.IntegratedSystem.modbusToBattery.alias = Communication with the battery +App.IntegratedSystem.modbusToBatteryN.alias = Communication with the batteries +App.IntegratedSystem.modbusToBattery0.alias = Communication with the battery {0} App.IntegratedSystem.modbus1.alias = Communication with the battery inverter App.IntegratedSystem.modbus1N.alias = Communication with the battery inverter {0} App.IntegratedSystem.modbus2.alias = external RS485 interface @@ -260,6 +270,7 @@ App.IntegratedSystem.batteryParallelClusterN.alias = parallel-cluster {0} App.IntegratedSystem.batteryInverter0.alias = battery inverter App.IntegratedSystem.batteryInverterN.alias = battery inverter {0} App.IntegratedSystem.ess0.alias = Storage system +App.IntegratedSystem.essCluster0.alias = Storage system-Cluster App.IntegratedSystem.predictor0.alias = Forecast App.IntegratedSystem.ctrlEssSurplusFeedToGrid0.alias = Excess feed-in App.IntegratedSystem.emergencyMeter.alias = Emergency power consumers @@ -272,9 +283,11 @@ App.IntegratedSystem.mpptAlias.label = MPPT {0} Alias App.IntegratedSystem.mpptAlias.alias = MPPT {0} App.IntegratedSystem.hasAcMeter.label = Has AC meter App.IntegratedSystem.acMeterType.label = AC-Meter Type +App.IntegratedSystem.gridMeterTypeGen2.option.integrated = Integrated Home 3 phase sensor with current transformer (standard scope of delivery) +App.IntegratedSystem.gridMeterTypeGen2.option.external = Home 3 phase sensor with 120A current transformer (optional) -App.FENECON.Home.Name = FENECON Home -App.FENECON.Home.Name.short = FENECON Home +App.FENECON.Home.Name = FENECON Home 10 +App.FENECON.Home.Name.short = FENECON Home 10 App.FENECON.Home.safetyCountry.label = Battery-Inverter Safety Country App.FENECON.Home.rippleControlReceiver.label = Ripple control receiver active? App.FENECON.Home.rippleControlReceiver.description = External balancing by grid operator @@ -284,6 +297,10 @@ App.FENECON.Home.hasAcMeter.label = Has AC meter App.FENECON.Home.acMeterType.label = AC-Meter Type App.FENECON.Home.hasDcPV1.label = Has DC-PV 1 (MPPT 1) App.FENECON.Home.hasDcPV2.label = Has DC-PV 2 (MPPT 2) +App.FENECON.Home.hasDcPV3.label = Has DC-PV 3 (MPPT 3) +App.FENECON.Home.dcPv1.alias.label = DC-PV 1 alias +App.FENECON.Home.dcPv2.alias.label = DC-PV 2 alias +App.FENECON.Home.dcPv3.alias.label = DC-PV 3 alias App.FENECON.Home.emergencyPowerSupply.label = Activate Emergency power supply App.FENECON.Home.emergencyPowerEnergy.label = Activate Emergency Reserve Energy App.FENECON.Home.reserveEnergy.label = Emergency Reserve Energy (State-of-Charge) @@ -309,8 +326,18 @@ App.FENECON.Home.30.Name.short = FENECON Home 30 App.FENECON.Home.20.Name = FENECON Home 20 App.FENECON.Home.20.Name.short = FENECON Home 20 +App.FENECON.Home6.Name = FENECON Home 6 +App.FENECON.Home6.Name.short = FENECON Home 6 + +App.FENECON.Home10.Gen2.Name = FENECON Home 10 (Gen2) +App.FENECON.Home10.Gen2.Name.short = FENECON Home 10 (Gen2) + +App.FENECON.Home15.Name = FENECON Home 15 +App.FENECON.Home15.Name.short = FENECON Home 15 + App.FENECON.Commercial.92.Name = FENECON Commercial App.FENECON.Commercial.92.Name.short = FENECON Commercial + App.FENECON.Industrial.L.io0 = Relay App.FENECON.Industrial.L.Name = FENECON Industrial L App.FENECON.Industrial.L.Name.short = FENECON Industrial L @@ -325,7 +352,6 @@ App.FENECON.Industrial.L.ILK710.batteryFirmwareVersion.label = Battery Firmware App.FENECON.Industrial.S.io0 = Relay App.FENECON.Industrial.S.ess0.alias = Battery Storage App.FENECON.Industrial.S.essN.alias = Battery Storage {0} -App.FENECON.Industrial.S.essCluster0.alias = Battery Storage-Cluster App.FENECON.Industrial.S.hasGridMeter.label = Has Grid-Meter App.FENECON.Industrial.S.hasSelfConsumptionOptimization.label = Has Self-consumption optimisation App.FENECON.Industrial.S.modbusToGridMeter.alias = Communication with the Grid-Meter @@ -416,6 +442,9 @@ App.PvInverter.SolarEdge.Name = SolarEdge PV inverter App.PvInverter.SolarEdge.Name.short = SolarEdge # Time of use Tariff +App.TimeOfUseTariff.zipCode.label = ZIP Code +App.TimeOfUseTariff.zipCode.description = German postal code of place of residence + App.TimeOfUseTariff.Awattar.Name = Time-of-Use Tariff (Awattar HOURLY) App.TimeOfUseTariff.Awattar.Name.short = Awattar HOURLY App.TimeOfUseTariff.Awattar.zone.label = Zone @@ -434,6 +463,10 @@ App.TimeOfUseTariff.ENTSO-E.biddingZone.option.sweden_se3 = Sweden (SE3) App.TimeOfUseTariff.ENTSO-E.biddingZone.option.sweden_se4 = Sweden (SE4) App.TimeOfUseTariff.ENTSO-E.biddingZone.option.belgium = Belgium App.TimeOfUseTariff.ENTSO-E.biddingZone.option.netherlands = Netherlands +App.TimeOfUseTariff.ENTSO-E.resolution.label = Resolution +App.TimeOfUseTariff.ENTSO-E.resolution.description = Resolution corresponding to the price interval +App.TimeOfUseTariff.ENTSO-E.resolution.option.hourly = Hourly prices +App.TimeOfUseTariff.ENTSO-E.resolution.option.quarterly = Quarter-hourly prices App.TimeOfUseTariff.GroupeE.Name = Time-of-Use Tariff (Groupe-E) App.TimeOfUseTariff.GroupeE.Name.short = Groupe-E App.TimeOfUseTariff.Hassfurt.Name = Time-of-Use Tariff (Stadtwerk Hassfurt) @@ -446,8 +479,12 @@ App.TimeOfUseTariff.RabotCharge.accessToken.label = Token App.TimeOfUseTariff.RabotCharge.accessToken.description = Please provide personal access token provided by rabot.charge. to get one, please contact rabot.charge. App.TimeOfUseTariff.Stromdao.Name = Time-of-Use Tariff (STROMDAO Corrently) App.TimeOfUseTariff.Stromdao.Name.short = STROMDAO Corrently -App.TimeOfUseTariff.Stromdao.zipCode.label = ZIP Code -App.TimeOfUseTariff.Stromdao.zipCode.description = German postal code of place of residence +App.TimeOfUseTariff.Swisspower.Name = Time-of-Use Tariff (Swisspower) +App.TimeOfUseTariff.Swisspower.Name.short = Swisspower +App.TimeOfUseTariff.Swisspower.accessToken.label = Token +App.TimeOfUseTariff.Swisspower.accessToken.description = Please provide personal access token provided by Swisspower. To get one, please contact Swisspower. +App.TimeOfUseTariff.Swisspower.meteringCode.label = Measuring point number (e.g. CH1018601234500000000000000011642) +App.TimeOfUseTariff.Swisspower.meteringCode.description = Please provide Measuring point number received by Swisspower. To get one, please contact Swisspower. App.TimeOfUseTariff.Tibber.Name = Time-of-Use Tariff (Tibber) App.TimeOfUseTariff.Tibber.Name.short = Tibber App.TimeOfUseTariff.Tibber.accessToken.label = Token diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckHome.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckHome.java index 45dcf2223e4..87668a4d6ec 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckHome.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckHome.java @@ -34,7 +34,10 @@ public boolean check() { this.checkAppsNotInstalled.setProperties(Checkables.checkAppsNotInstalled(// "App.FENECON.Home", // "App.FENECON.Home.20", // - "App.FENECON.Home.30" // + "App.FENECON.Home.30", // + "App.FENECON.Home6", // + "App.FENECON.Home10.Gen2", // + "App.FENECON.Home15" // ).properties()); return !this.checkAppsNotInstalled.check(); diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/Checkables.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/Checkables.java index 32783117bed..993cc7d84d3 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/Checkables.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/Checkables.java @@ -1,6 +1,7 @@ package io.openems.edge.core.appmanager.validator; import java.util.Collections; +import java.util.Objects; import java.util.TreeMap; import io.openems.edge.core.appmanager.validator.ValidatorConfig.CheckableConfig; @@ -35,14 +36,25 @@ public static CheckableConfig checkCommercial92() { * * @param check1 the first check * @param check2 the second check + * @param other the additional checks to combine with 'or' operator * @return the {@link CheckableConfig} */ - public static CheckableConfig checkOr(CheckableConfig check1, CheckableConfig check2) { - return new ValidatorConfig.CheckableConfig(CheckOr.COMPONENT_NAME, + public static CheckableConfig checkOr(// + CheckableConfig check1, // + CheckableConfig check2, // + CheckableConfig... other // + ) { + var config = new ValidatorConfig.CheckableConfig(CheckOr.COMPONENT_NAME, new ValidatorConfig.MapBuilder<>(new TreeMap()) // - .put("check1", check1) // - .put("check2", check2) // + .put("check1", Objects.requireNonNull(check1)) // + .put("check2", Objects.requireNonNull(check2)) // .build()); + if (other != null && other.length > 0) { + for (var check : other) { + config = config.or(check); + } + } + return config; } /** diff --git a/io.openems.edge.core/src/io/openems/edge/core/componentmanager/EdgeConfigWorker.java b/io.openems.edge.core/src/io/openems/edge/core/componentmanager/EdgeConfigWorker.java index 9679cfb5a6a..6c90e120dab 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/componentmanager/EdgeConfigWorker.java +++ b/io.openems.edge.core/src/io/openems/edge/core/componentmanager/EdgeConfigWorker.java @@ -595,7 +595,7 @@ private static TreeMap convertProperties(String componentId for (EdgeConfig.Factory.Property property : factory.getProperties()) { var key = property.getId(); - if (EdgeConfig.ignorePropertyKey(key) || EdgeConfig.ignoreComponentPropertyKey(componentId, key)) { + if (EdgeConfig.ignorePropertyKey(key)) { // Ignore this Property continue; } diff --git a/io.openems.edge.core/src/io/openems/edge/core/componentmanager/jsonrpc/ChannelExportXlsxResponse.java b/io.openems.edge.core/src/io/openems/edge/core/componentmanager/jsonrpc/ChannelExportXlsxResponse.java index da0f00be519..6722f9c9fc0 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/componentmanager/jsonrpc/ChannelExportXlsxResponse.java +++ b/io.openems.edge.core/src/io/openems/edge/core/componentmanager/jsonrpc/ChannelExportXlsxResponse.java @@ -28,8 +28,6 @@ /** * Represents a JSON-RPC Response for 'channelExportXlsxRequest'. * - *

- * *

  * {
  *   "jsonrpc": "2.0",
diff --git a/io.openems.edge.core/src/io/openems/edge/core/host/HostImpl.java b/io.openems.edge.core/src/io/openems/edge/core/host/HostImpl.java
index bc18b13fab4..e9b516ae383 100644
--- a/io.openems.edge.core/src/io/openems/edge/core/host/HostImpl.java
+++ b/io.openems.edge.core/src/io/openems/edge/core/host/HostImpl.java
@@ -1,8 +1,10 @@
 package io.openems.edge.core.host;
 
 import java.io.IOException;
+import java.net.Inet4Address;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
+import java.util.List;
 import java.util.Scanner;
 import java.util.concurrent.CompletableFuture;
 
@@ -16,6 +18,7 @@
 import org.osgi.service.component.annotations.Reference;
 import org.osgi.service.metatype.annotations.Designate;
 import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
 import io.openems.common.exceptions.OpenemsException;
@@ -27,12 +30,15 @@
 import io.openems.edge.common.component.OpenemsComponent;
 import io.openems.edge.common.host.Host;
 import io.openems.edge.common.jsonapi.ComponentJsonApi;
+import io.openems.edge.common.jsonapi.EdgeGuards;
 import io.openems.edge.common.jsonapi.EdgeKeys;
 import io.openems.edge.common.jsonapi.JsonApiBuilder;
 import io.openems.edge.common.user.User;
 import io.openems.edge.core.host.jsonrpc.ExecuteSystemCommandRequest;
 import io.openems.edge.core.host.jsonrpc.ExecuteSystemRestartRequest;
 import io.openems.edge.core.host.jsonrpc.ExecuteSystemUpdateRequest;
+import io.openems.edge.core.host.jsonrpc.GetIpAddresses;
+import io.openems.edge.core.host.jsonrpc.GetIpAddresses.Response;
 import io.openems.edge.core.host.jsonrpc.GetNetworkConfigRequest;
 import io.openems.edge.core.host.jsonrpc.GetNetworkConfigResponse;
 import io.openems.edge.core.host.jsonrpc.GetSystemUpdateStateRequest;
@@ -50,6 +56,8 @@
 		})
 public class HostImpl extends AbstractOpenemsComponent implements Host, OpenemsComponent, ComponentJsonApi {
 
+	private final Logger log = LoggerFactory.getLogger(HostImpl.class);
+
 	protected final OperatingSystem operatingSystem;
 
 	private final DiskSpaceWorker diskSpaceWorker;
@@ -74,6 +82,8 @@ public HostImpl() {
 		// Initialize correct Operating System handler
 		if (System.getProperty("os.name").startsWith("Windows")) {
 			this.operatingSystem = new OperatingSystemWindows();
+		} else if (System.getProperty("os.name").startsWith("Mac")) {
+			this.operatingSystem = new OperatingSystemMac();
 		} else {
 			this.operatingSystem = new OperatingSystemDebianSystemd(this);
 		}
@@ -93,6 +103,13 @@ public HostImpl() {
 				e1.printStackTrace();
 			}
 		}
+
+		this.operatingSystem.getOperatingSystemVersion().whenComplete((name, error) -> {
+			this._setOsVersion(name);
+			if (error != null) {
+				this.log.info("Error while trying to get operating system version", error);
+			}
+		});
 	}
 
 	@Activate
@@ -170,6 +187,20 @@ public void buildJsonApiRoutes(JsonApiBuilder builder) {
 			return this.handleExecuteSystemRestartRequest(call.get(EdgeKeys.USER_KEY),
 					ExecuteSystemRestartRequest.from(call.getRequest())).get();
 		});
+
+		builder.handleRequest(new GetIpAddresses(), endpoint -> {
+			endpoint.setDescription("""
+					Gets the current ip addresses.
+					""".stripIndent());
+
+			endpoint.setGuards(EdgeGuards.roleIsAtleast(Role.OWNER));
+		}, call -> new Response(this.getSystemIPs()));
+
+	}
+
+	@Override
+	public List getSystemIPs() throws OpenemsNamedException {
+		return this.operatingSystem.getSystemIPs();
 	}
 
 	/**
@@ -288,8 +319,13 @@ protected void logError(Logger log, String message) {
 	 * @throws IOException on error
 	 */
 	private static String execReadToString(String execCommand) throws IOException {
-		try (var s = new Scanner(Runtime.getRuntime().exec(execCommand).getInputStream()).useDelimiter("\\A")) {
+		ProcessBuilder processBuilder = new ProcessBuilder(execCommand.split(" "));
+		processBuilder.redirectErrorStream(true);
+		Process process = processBuilder.start();
+
+		try (var s = new Scanner(process.getInputStream()).useDelimiter("\\A")) {
 			return s.hasNext() ? s.next().trim() : "";
 		}
 	}
+
 }
diff --git a/io.openems.edge.core/src/io/openems/edge/core/host/NetworkConfiguration.java b/io.openems.edge.core/src/io/openems/edge/core/host/NetworkConfiguration.java
index bb280376c0f..0ce1a666cf6 100644
--- a/io.openems.edge.core/src/io/openems/edge/core/host/NetworkConfiguration.java
+++ b/io.openems.edge.core/src/io/openems/edge/core/host/NetworkConfiguration.java
@@ -20,8 +20,6 @@ public NetworkConfiguration(TreeMap> interfaces) {
 	/**
 	 * Return this NetworkConfiguration as a JSON object.
 	 *
-	 * 

- * *

 	 * {
 	 *   "interfaces": {
diff --git a/io.openems.edge.core/src/io/openems/edge/core/host/OperatingSystem.java b/io.openems.edge.core/src/io/openems/edge/core/host/OperatingSystem.java
index ef8c3cacbb3..8be6e7d652d 100644
--- a/io.openems.edge.core/src/io/openems/edge/core/host/OperatingSystem.java
+++ b/io.openems.edge.core/src/io/openems/edge/core/host/OperatingSystem.java
@@ -1,5 +1,7 @@
 package io.openems.edge.core.host;
 
+import java.net.Inet4Address;
+import java.util.List;
 import java.util.concurrent.CompletableFuture;
 
 import io.openems.common.exceptions.NotImplementedException;
@@ -61,4 +63,19 @@ public CompletableFuture handleExecuteSystemComman
 	public CompletableFuture handleExecuteSystemRestartRequest(
 			ExecuteSystemRestartRequest request) throws NotImplementedException;
 
+	/**
+	 * Gets the System IPs.
+	 * 
+	 * @return a list of all ips of the system
+	 * @throws OpenemsNamedException on error
+	 */
+	public List getSystemIPs() throws OpenemsNamedException;
+
+	/**
+	 * Gets the current operating system version.
+	 * 
+	 * @return a future with the result
+	 */
+	public CompletableFuture getOperatingSystemVersion();
+
 }
diff --git a/io.openems.edge.core/src/io/openems/edge/core/host/OperatingSystemDebianSystemd.java b/io.openems.edge.core/src/io/openems/edge/core/host/OperatingSystemDebianSystemd.java
index f74bd442b16..46dbeb79b59 100644
--- a/io.openems.edge.core/src/io/openems/edge/core/host/OperatingSystemDebianSystemd.java
+++ b/io.openems.edge.core/src/io/openems/edge/core/host/OperatingSystemDebianSystemd.java
@@ -10,6 +10,7 @@
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.net.Inet4Address;
+import java.net.UnknownHostException;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -17,6 +18,7 @@
 import java.time.LocalDateTime;
 import java.time.temporal.ChronoUnit;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map.Entry;
@@ -24,20 +26,26 @@
 import java.util.Set;
 import java.util.TreeMap;
 import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Consumer;
 import java.util.function.Supplier;
 import java.util.regex.Pattern;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.gson.JsonElement;
+
 import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
 import io.openems.common.exceptions.OpenemsException;
 import io.openems.common.function.ThrowingConsumer;
 import io.openems.common.types.ConfigurationProperty;
 import io.openems.common.utils.InetAddressUtils;
+import io.openems.common.utils.JsonUtils;
 import io.openems.common.utils.StringUtils;
 import io.openems.edge.common.type.TypeUtils;
 import io.openems.edge.common.user.User;
@@ -203,7 +211,7 @@ private List toFileFormat(User user, NetworkInterface iface) throws O
 				+ user.getName());
 		result.add("# changedAt: " //
 				+ LocalDateTime.now().truncatedTo(ChronoUnit.MINUTES).toString());
-		
+
 		// Match Section
 		result.add(MATCH_SECTION);
 		result.add("Name=" + iface.getName());
@@ -220,12 +228,12 @@ private List toFileFormat(User user, NetworkInterface iface) throws O
 		if (iface.getLinkLocalAddressing().isSetAndNotNull()) {
 			result.add("LinkLocalAddressing=" + (iface.getLinkLocalAddressing().getValue() ? "yes" : "no"));
 		}
-		
+
 		var metric = DEFAULT_METRIC;
 		if (iface.getMetric().isSetAndNotNull()) {
 			metric = iface.getMetric().getValue().intValue();
 		}
-		
+
 		if (iface.getDhcp().isSetAndNotNull()) {
 			var dhcp = iface.getDhcp().getValue();
 			result.add(EMPTY_SECTION);
@@ -577,4 +585,81 @@ protected static  NetworkInterface parseSystemdNetworkdConfigurationFile(L
 				dhcp.get(), linkLocalAddressing.get(), gateway.get(), dns.get(), addresses.get(), metric.get(),
 				attachment);
 	}
+
+	@Override
+	public List getSystemIPs() throws OpenemsNamedException {
+		var req = ExecuteSystemCommandRequest.withoutAuthentication("ip -j -4 address show", false, 5);
+		try {
+			var result = this.handleExecuteSystemCommandRequest(req).get().getResult().toString();
+			return parseIpJson(result);
+		} catch (InterruptedException | ExecutionException e) {
+			return Collections.emptyList();
+		}
+
+	}
+
+	/**
+	 * Parses the json returned by ip address get command.
+	 * 
+	 * @param result the json to be parsed
+	 * @return a list of parsed ips
+	 * @throws OpenemsNamedException on error
+	 */
+	protected static List parseIpJson(String result) throws OpenemsNamedException {
+		final var stdout = JsonUtils.getAsJsonArray(JsonUtils.getAsJsonObject(JsonUtils.parse(result)), "stdout");
+		final var networkData = stdout.get(0).getAsString();
+		final var networkDataJson = JsonUtils.parseOptional(networkData);
+		if (networkDataJson.isPresent() && networkDataJson.get().isJsonArray()) {
+			final var networkInterfaces = JsonUtils.getAsJsonArray(JsonUtils.parse(networkData));
+
+			if (networkData.startsWith("[")) {
+				return networkInterfaces.asList().stream().map(JsonElement::getAsJsonObject)
+						.map(interfaceObject -> interfaceObject.getAsJsonArray("addr_info"))
+						.flatMap(addrInfoArray -> StreamSupport.stream(addrInfoArray.spliterator(), false))
+						.map(JsonElement::getAsJsonObject)
+						.filter(addrInfoObject -> "inet".equals(addrInfoObject.get("family").getAsString()))
+						.map(addrInfoObject -> addrInfoObject.get("local").getAsString()) //
+						.mapMulti((t, u) -> {
+							try {
+								u.accept((Inet4Address) Inet4Address.getByName(t));
+							} catch (UnknownHostException e) {
+								// do nothing
+							}
+						}) //
+						.toList();//
+			}
+		}
+		return Collections.emptyList();
+	}
+
+	@Override
+	public CompletableFuture getOperatingSystemVersion() {
+		final var sc = new SystemCommand(//
+				"cat /etc/os-release", //
+				false, // runInBackground
+				5, // timeoutSeconds
+				Optional.empty(), // username
+				Optional.empty()); // password
+
+		final var versionFuture = new CompletableFuture();
+		this.execute(sc, success -> {
+			final var osVersionName = Stream.of(success.stdout()) //
+					.map(t -> t.split("=", 2)) //
+					.filter(t -> t.length == 2) //
+					.filter(t -> t[0].equals("PRETTY_NAME")) //
+					.map(t -> t[1]) //
+					.map(t -> {
+						if (t.startsWith("\"") && t.endsWith("\"")) {
+							return t.substring(1, t.length() - 1);
+						}
+						return t;
+					}) //
+					.findAny();
+
+			osVersionName.ifPresentOrElse(versionFuture::complete, () -> versionFuture
+					.completeExceptionally(new OpenemsException("OS-Version name not found in /etc/os-release")));
+		}, versionFuture::completeExceptionally);
+		return versionFuture;
+	}
+
 }
diff --git a/io.openems.edge.core/src/io/openems/edge/core/host/OperatingSystemMac.java b/io.openems.edge.core/src/io/openems/edge/core/host/OperatingSystemMac.java
new file mode 100644
index 00000000000..362a7046086
--- /dev/null
+++ b/io.openems.edge.core/src/io/openems/edge/core/host/OperatingSystemMac.java
@@ -0,0 +1,60 @@
+package io.openems.edge.core.host;
+
+import java.net.Inet4Address;
+import java.util.Collections;
+import java.util.List;
+import java.util.TreeMap;
+import java.util.concurrent.CompletableFuture;
+
+import io.openems.common.exceptions.NotImplementedException;
+import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
+import io.openems.common.jsonrpc.base.JsonrpcResponseSuccess;
+import io.openems.edge.common.user.User;
+import io.openems.edge.core.host.jsonrpc.ExecuteSystemCommandRequest;
+import io.openems.edge.core.host.jsonrpc.ExecuteSystemCommandResponse;
+import io.openems.edge.core.host.jsonrpc.ExecuteSystemRestartRequest;
+import io.openems.edge.core.host.jsonrpc.SetNetworkConfigRequest;
+
+public class OperatingSystemMac implements OperatingSystem {
+
+	@Override
+	public NetworkConfiguration getNetworkConfiguration() throws OpenemsNamedException {
+		return new NetworkConfiguration(new TreeMap<>());
+	}
+
+	@Override
+	public void handleSetNetworkConfigRequest(User user, NetworkConfiguration oldNetworkConfiguration,
+			SetNetworkConfigRequest request) throws OpenemsNamedException {
+		throw new NotImplementedException("SetNetworkConfigRequest is not implemented for Mac");
+
+	}
+
+	@Override
+	public String getUsbConfiguration() throws OpenemsNamedException {
+		// not implemented
+		return "";
+	}
+
+	@Override
+	public CompletableFuture handleExecuteSystemCommandRequest(
+			ExecuteSystemCommandRequest request) throws OpenemsNamedException {
+		throw new NotImplementedException("ExecuteSystemCommandRequest is not implemented for Mac");
+	}
+
+	@Override
+	public CompletableFuture handleExecuteSystemRestartRequest(
+			ExecuteSystemRestartRequest request) throws NotImplementedException {
+		throw new NotImplementedException("ExecuteSystemRestartRequest is not implemented for Mac");
+	}
+
+	@Override
+	public List getSystemIPs() throws OpenemsNamedException {
+		return Collections.emptyList();
+	}
+
+	@Override
+	public CompletableFuture getOperatingSystemVersion() {
+		return CompletableFuture.completedFuture(System.getProperty("os.name"));
+	}
+
+}
diff --git a/io.openems.edge.core/src/io/openems/edge/core/host/OperatingSystemWindows.java b/io.openems.edge.core/src/io/openems/edge/core/host/OperatingSystemWindows.java
index 68c509a86a4..b59c72f7f5b 100644
--- a/io.openems.edge.core/src/io/openems/edge/core/host/OperatingSystemWindows.java
+++ b/io.openems.edge.core/src/io/openems/edge/core/host/OperatingSystemWindows.java
@@ -1,5 +1,8 @@
 package io.openems.edge.core.host;
 
+import java.net.Inet4Address;
+import java.util.Collections;
+import java.util.List;
 import java.util.TreeMap;
 import java.util.concurrent.CompletableFuture;
 
@@ -50,4 +53,13 @@ public CompletableFuture handleExecuteSystemRe
 		throw new NotImplementedException("ExecuteSystemRestartRequest is not implemented for Windows");
 	}
 
+	@Override
+	public List getSystemIPs() throws OpenemsNamedException {
+		return Collections.emptyList();
+	}
+
+	public CompletableFuture getOperatingSystemVersion() {
+		return CompletableFuture.completedFuture(System.getProperty("os.name"));
+	}
+
 }
diff --git a/io.openems.edge.core/src/io/openems/edge/core/host/jsonrpc/ExecuteSystemCommandResponse.java b/io.openems.edge.core/src/io/openems/edge/core/host/jsonrpc/ExecuteSystemCommandResponse.java
index fc3b40b57a8..42cf5c4b909 100644
--- a/io.openems.edge.core/src/io/openems/edge/core/host/jsonrpc/ExecuteSystemCommandResponse.java
+++ b/io.openems.edge.core/src/io/openems/edge/core/host/jsonrpc/ExecuteSystemCommandResponse.java
@@ -13,8 +13,6 @@
 /**
  * JSON-RPC Response to {@link ExecuteSystemCommandRequest}.
  *
- * 

- * *

  * {
  *   "jsonrpc": "2.0",
diff --git a/io.openems.edge.core/src/io/openems/edge/core/host/jsonrpc/ExecuteSystemRestartResponse.java b/io.openems.edge.core/src/io/openems/edge/core/host/jsonrpc/ExecuteSystemRestartResponse.java
index 6bc3b532cfa..7b8f1064daf 100644
--- a/io.openems.edge.core/src/io/openems/edge/core/host/jsonrpc/ExecuteSystemRestartResponse.java
+++ b/io.openems.edge.core/src/io/openems/edge/core/host/jsonrpc/ExecuteSystemRestartResponse.java
@@ -10,8 +10,6 @@
 /**
  * JSON-RPC Response to {@link ExecuteSystemRestartRequest}.
  *
- * 

- * *

  * {
  *   "jsonrpc": "2.0",
diff --git a/io.openems.edge.core/src/io/openems/edge/core/host/jsonrpc/GetIpAddresses.java b/io.openems.edge.core/src/io/openems/edge/core/host/jsonrpc/GetIpAddresses.java
new file mode 100644
index 00000000000..c3a69854015
--- /dev/null
+++ b/io.openems.edge.core/src/io/openems/edge/core/host/jsonrpc/GetIpAddresses.java
@@ -0,0 +1,68 @@
+package io.openems.edge.core.host.jsonrpc;
+
+import static io.openems.common.jsonrpc.serialization.JsonSerializerUtil.emptyObjectSerializer;
+import static io.openems.common.jsonrpc.serialization.JsonSerializerUtil.jsonObjectSerializer;
+
+import java.net.Inet4Address;
+import java.util.List;
+
+import com.google.gson.JsonArray;
+
+import io.openems.common.jsonrpc.serialization.JsonSerializer;
+import io.openems.common.utils.JsonUtils;
+import io.openems.edge.common.jsonapi.EndpointRequestType;
+import io.openems.edge.core.host.jsonrpc.GetIpAddresses.Request;
+import io.openems.edge.core.host.jsonrpc.GetIpAddresses.Response;
+
+public class GetIpAddresses implements EndpointRequestType {
+	public record Request() {
+
+		/**
+		 * Returns a {@link JsonSerializer} for a {@link GetIpAddresses.Request}.
+		 * 
+		 * @return the created {@link JsonSerializer}
+		 */
+		public static JsonSerializer serializer() {
+			return emptyObjectSerializer(Request::new);
+		}
+	}
+
+	public record Response(List ips) {
+
+		/**
+		 * Returns a {@link JsonSerializer} for a {@link GetIpAddresses.Response}.
+		 * 
+		 * @return the created {@link JsonSerializer}
+		 */
+		public static JsonSerializer seriliazer() {
+			return jsonObjectSerializer(GetIpAddresses.Response.class, json -> {
+				return new Response(List.of());
+			}, obj -> {
+				return JsonUtils.buildJsonObject()//
+						.add("ips", buildInterfaceArray(obj.ips()))//
+						.build();
+			});
+		}
+
+		private static JsonArray buildInterfaceArray(List ips) {
+			var builder = JsonUtils.buildJsonArray();
+			ips.stream().map((ip) -> ip.getHostAddress()).forEach(builder::add);
+			return builder.build();
+		}
+	}
+
+	@Override
+	public String getMethod() {
+		return "getIpAddresses";
+	}
+
+	@Override
+	public JsonSerializer getRequestSerializer() {
+		return Request.serializer();
+	}
+
+	@Override
+	public JsonSerializer getResponseSerializer() {
+		return Response.seriliazer();
+	}
+}
diff --git a/io.openems.edge.core/src/io/openems/edge/core/host/jsonrpc/GetNetworkConfigResponse.java b/io.openems.edge.core/src/io/openems/edge/core/host/jsonrpc/GetNetworkConfigResponse.java
index 83097f7cea5..bb9b62a4a0e 100644
--- a/io.openems.edge.core/src/io/openems/edge/core/host/jsonrpc/GetNetworkConfigResponse.java
+++ b/io.openems.edge.core/src/io/openems/edge/core/host/jsonrpc/GetNetworkConfigResponse.java
@@ -10,8 +10,6 @@
 /**
  * JSON-RPC Response to "getNetworkConfig" Request.
  *
- * 

- * *

  * {
  *   "jsonrpc": "2.0",
diff --git a/io.openems.edge.core/src/io/openems/edge/core/host/jsonrpc/GetSystemUpdateStateResponse.java b/io.openems.edge.core/src/io/openems/edge/core/host/jsonrpc/GetSystemUpdateStateResponse.java
index 38ea2900958..ad6162d4ad6 100644
--- a/io.openems.edge.core/src/io/openems/edge/core/host/jsonrpc/GetSystemUpdateStateResponse.java
+++ b/io.openems.edge.core/src/io/openems/edge/core/host/jsonrpc/GetSystemUpdateStateResponse.java
@@ -21,8 +21,6 @@
 /**
  * JSON-RPC Response to {@link GetSystemUpdateStateRequest}.
  *
- * 

- * *

  * {
  *   "jsonrpc": "2.0",
diff --git a/io.openems.edge.core/src/io/openems/edge/core/meta/Config.java b/io.openems.edge.core/src/io/openems/edge/core/meta/Config.java
index 41b9dd0f734..b8745593b85 100644
--- a/io.openems.edge.core/src/io/openems/edge/core/meta/Config.java
+++ b/io.openems.edge.core/src/io/openems/edge/core/meta/Config.java
@@ -3,7 +3,7 @@
 import org.osgi.service.metatype.annotations.AttributeDefinition;
 import org.osgi.service.metatype.annotations.ObjectClassDefinition;
 
-import io.openems.edge.common.currency.CurrencyConfig;
+import io.openems.common.types.CurrencyConfig;
 
 @ObjectClassDefinition(//
 		name = "Core Meta", //
@@ -15,4 +15,7 @@
 	@AttributeDefinition(name = "Currency", description = "Every monetary value is inherently expressed in this Currency. Values obtained in a different currency (e.g. energy prices from a web service) are internally converted to this Currency using the current exchange rate.")
 	CurrencyConfig currency() default CurrencyConfig.EUR;
 
+	@AttributeDefinition(name = "Is Ess Charge From Grid Allowed", description = "Charging the battery from grid is allowed.")
+	boolean isEssChargeFromGridAllowed() default false;
+
 }
\ No newline at end of file
diff --git a/io.openems.edge.core/src/io/openems/edge/core/meta/MetaImpl.java b/io.openems.edge.core/src/io/openems/edge/core/meta/MetaImpl.java
index 8ae2c8457f9..0516b76643e 100644
--- a/io.openems.edge.core/src/io/openems/edge/core/meta/MetaImpl.java
+++ b/io.openems.edge.core/src/io/openems/edge/core/meta/MetaImpl.java
@@ -1,5 +1,12 @@
 package io.openems.edge.core.meta;
 
+import static io.openems.common.utils.ThreadPoolUtils.shutdownAndAwaitTermination;
+
+import java.time.Instant;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
 import org.osgi.service.cm.ConfigurationAdmin;
 import org.osgi.service.component.ComponentContext;
 import org.osgi.service.component.annotations.Activate;
@@ -12,8 +19,10 @@
 import io.openems.common.OpenemsConstants;
 import io.openems.common.channel.AccessMode;
 import io.openems.common.oem.OpenemsEdgeOem;
+import io.openems.edge.common.channel.LongReadChannel;
 import io.openems.edge.common.component.AbstractOpenemsComponent;
 import io.openems.edge.common.component.OpenemsComponent;
+import io.openems.edge.common.currency.Currency;
 import io.openems.edge.common.meta.Meta;
 import io.openems.edge.common.modbusslave.ModbusSlave;
 import io.openems.edge.common.modbusslave.ModbusSlaveTable;
@@ -27,6 +36,8 @@
 		})
 public class MetaImpl extends AbstractOpenemsComponent implements Meta, OpenemsComponent, ModbusSlave {
 
+	private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
+
 	@Reference
 	private ConfigurationAdmin cm;
 
@@ -45,6 +56,12 @@ public MetaImpl() {
 	private void activate(ComponentContext context, Config config) {
 		super.activate(context, SINGLETON_COMPONENT_ID, Meta.SINGLETON_SERVICE_PID, true);
 
+		// Update the Channel _meta/SystemTimeUtc after every second
+		final var systemTimeUtcChannel = this.channel(Meta.ChannelId.SYSTEM_TIME_UTC);
+		this.executor.scheduleAtFixedRate(() -> {
+			systemTimeUtcChannel.setNextValue(Instant.now().getEpochSecond());
+		}, 0, 1000, TimeUnit.MILLISECONDS);
+
 		this.applyConfig(config);
 		if (OpenemsComponent.validateSingleton(this.cm, Meta.SINGLETON_SERVICE_PID, SINGLETON_COMPONENT_ID)) {
 			return;
@@ -64,6 +81,7 @@ private void modified(ComponentContext context, Config config) {
 	@Override
 	@Deactivate
 	protected void deactivate() {
+		shutdownAndAwaitTermination(this.executor, 0);
 		super.deactivate();
 	}
 
@@ -73,6 +91,7 @@ public ModbusSlaveTable getModbusSlaveTable(AccessMode accessMode) {
 	}
 
 	private void applyConfig(Config config) {
-		this._setCurrency(config.currency().toCurrency());
+		this._setCurrency(Currency.fromCurrencyConfig(config.currency()));
+		this._setIsEssChargeFromGridAllowed(config.isEssChargeFromGridAllowed());
 	}
 }
diff --git a/io.openems.edge.core/src/io/openems/edge/core/predictormanager/PredictorManagerImpl.java b/io.openems.edge.core/src/io/openems/edge/core/predictormanager/PredictorManagerImpl.java
index 934c788a24f..a674023dd02 100644
--- a/io.openems.edge.core/src/io/openems/edge/core/predictormanager/PredictorManagerImpl.java
+++ b/io.openems.edge.core/src/io/openems/edge/core/predictormanager/PredictorManagerImpl.java
@@ -166,6 +166,7 @@ private Prediction getPredictionSum(Sum.ChannelId channelId) {
 						switch (meter.getMeterType()) {
 						case GRID:
 						case CONSUMPTION_METERED:
+						case MANAGED_CONSUMPTION_METERED:
 						case CONSUMPTION_NOT_METERED:
 							return false;
 						case PRODUCTION:
diff --git a/io.openems.edge.core/src/io/openems/edge/core/sum/SumImpl.java b/io.openems.edge.core/src/io/openems/edge/core/sum/SumImpl.java
index 6bf8c875d46..4910b1f965e 100644
--- a/io.openems.edge.core/src/io/openems/edge/core/sum/SumImpl.java
+++ b/io.openems.edge.core/src/io/openems/edge/core/sum/SumImpl.java
@@ -39,7 +39,6 @@
 import io.openems.edge.ess.api.MetaEss;
 import io.openems.edge.ess.api.SymmetricEss;
 import io.openems.edge.ess.dccharger.api.EssDcCharger;
-import io.openems.edge.evcs.api.Evcs;
 import io.openems.edge.evcs.api.MetaEvcs;
 import io.openems.edge.meter.api.ElectricityMeter;
 import io.openems.edge.meter.api.VirtualMeter;
@@ -267,6 +266,14 @@ private void calculateChannelValues() {
 					// Consumption is positive
 					break;
 
+				case MANAGED_CONSUMPTION_METERED: //
+					if (meter instanceof MetaEvcs) {
+						// ignore this Evcs
+					} else {
+						managedConsumptionActivePower.addValue(meter.getActivePowerChannel());
+					}
+					break;
+
 				case CONSUMPTION_NOT_METERED:
 					// TODO CONSUMPTION_NOT_METERED
 					// Consumption is positive
@@ -306,17 +313,6 @@ private void calculateChannelValues() {
 				productionDcActualPower.addValue(charger.getActualPowerChannel());
 				productionDcActiveEnergy.addValue(charger.getActualEnergyChannel());
 
-			} else if (component instanceof Evcs evcs) {
-				/*
-				 * Electric Vehicle Charging Station
-				 */
-				if (evcs instanceof MetaEvcs) {
-					// ignore this Evcs
-					continue;
-				}
-
-				managedConsumptionActivePower.addValue(evcs.getChargePowerChannel());
-
 			} else if (component instanceof TimeOfUseTariff tou) {
 				/*
 				 * Time-of-Use-Tariff
@@ -464,7 +460,6 @@ private void calculateState() {
 				highestLevel = Level.INFO;
 			}
 		}
-
 		this.getStateChannel().setNextValue(highestLevel);
 	}
 
diff --git a/io.openems.edge.core/test/io/openems/edge/app/heat/TestHeatPump.java b/io.openems.edge.core/test/io/openems/edge/app/heat/TestHeatPump.java
index 4831dc04896..fbba642f889 100644
--- a/io.openems.edge.core/test/io/openems/edge/app/heat/TestHeatPump.java
+++ b/io.openems.edge.core/test/io/openems/edge/app/heat/TestHeatPump.java
@@ -11,8 +11,8 @@
 import io.openems.common.utils.JsonUtils;
 import io.openems.edge.app.api.ModbusTcpApiReadOnly;
 import io.openems.edge.app.api.RestJsonApiReadOnly;
-import io.openems.edge.app.integratedsystem.FeneconHome;
-import io.openems.edge.app.integratedsystem.TestFeneconHome;
+import io.openems.edge.app.integratedsystem.FeneconHome10;
+import io.openems.edge.app.integratedsystem.TestFeneconHome10;
 import io.openems.edge.common.test.ComponentTest;
 import io.openems.edge.core.appmanager.AppManagerTestBundle;
 import io.openems.edge.core.appmanager.Apps;
@@ -26,7 +26,7 @@ public class TestHeatPump {
 
 	private HeatPump heatPump;
 
-	private FeneconHome homeApp;
+	private FeneconHome10 homeApp;
 
 	private ModbusTcpApiReadOnly modbusTcpApiReadOnly;
 	private RestJsonApiReadOnly restJsonApiReadOnly;
@@ -37,7 +37,7 @@ public void beforeEach() throws Exception {
 		this.appManagerTestBundle = new AppManagerTestBundle(null, null, t -> {
 			return ImmutableList.of(//
 					this.heatPump = Apps.heatPump(t), //
-					this.homeApp = Apps.feneconHome(t), //
+					this.homeApp = Apps.feneconHome10(t), //
 					Apps.gridOptimizedCharge(t), //
 					Apps.selfConsumptionOptimization(t), //
 					Apps.socomecMeter(t), //
@@ -65,7 +65,7 @@ public void testNotRemovingDependenciesFromRelay() throws Exception {
 
 		// install home
 		this.appManagerTestBundle.sut.handleAddAppInstanceRequest(DUMMY_ADMIN,
-				new AddAppInstance.Request(this.homeApp.getAppId(), "key", "alias", TestFeneconHome.fullSettings()));
+				new AddAppInstance.Request(this.homeApp.getAppId(), "key", "alias", TestFeneconHome10.fullSettings()));
 
 		assertEquals(6, this.appManagerTestBundle.sut.getInstantiatedApps().size());
 
diff --git a/io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHome.java b/io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHome10.java
similarity index 97%
rename from io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHome.java
rename to io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHome10.java
index 91ea3e5a532..89254321153 100644
--- a/io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHome.java
+++ b/io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHome10.java
@@ -22,7 +22,7 @@
 import io.openems.edge.core.appmanager.jsonrpc.AddAppInstance;
 import io.openems.edge.core.appmanager.jsonrpc.UpdateAppInstance;
 
-public class TestFeneconHome {
+public class TestFeneconHome10 {
 
 	private AppManagerTestBundle appManagerTestBundle;
 
@@ -30,7 +30,7 @@ public class TestFeneconHome {
 	public void beforeEach() throws Exception {
 		this.appManagerTestBundle = new AppManagerTestBundle(null, null, t -> {
 			return Apps.of(t, //
-					Apps::feneconHome, //
+					Apps::feneconHome10, //
 					Apps::gridOptimizedCharge, //
 					Apps::selfConsumptionOptimization, //
 					Apps::socomecMeter, //
@@ -166,7 +166,7 @@ public void testFeedInTypeNoLimitation() throws Exception {
 	@Ignore
 	public void testEnableLimiter14a() throws Exception {
 		final var createSettings = fullSettings();
-		createSettings.addProperty(FeneconHome.Property.HAS_ESS_LIMITER_14A.name(), false);
+		createSettings.addProperty(FeneconHome10.Property.HAS_ESS_LIMITER_14A.name(), false);
 
 		final var createResponse = this.appManagerTestBundle.sut.handleAddAppInstanceRequest(DUMMY_ADMIN,
 				new AddAppInstance.Request("App.FENECON.Home", "key", "alias", createSettings));
@@ -176,7 +176,7 @@ public void testEnableLimiter14a() throws Exception {
 				.anyMatch(a -> a.appId.equals("App.Ess.Limiter14a")));
 
 		final var updateSettings = fullSettings();
-		createSettings.addProperty(FeneconHome.Property.HAS_ESS_LIMITER_14A.name(), true);
+		createSettings.addProperty(FeneconHome10.Property.HAS_ESS_LIMITER_14A.name(), true);
 		final var updateResponse = this.appManagerTestBundle.sut.handleUpdateAppInstanceRequest(DUMMY_ADMIN,
 				new UpdateAppInstance.Request(createResponse.instance().instanceId, "alias", updateSettings));
 
@@ -234,7 +234,7 @@ public static final OpenemsAppInstance createFullHome(AppManagerTestBundle appMa
 	}
 
 	/**
-	 * Gets a {@link JsonObject} with the full settings for a {@link FeneconHome}.
+	 * Gets a {@link JsonObject} with the full settings for a {@link FeneconHome10}.
 	 * 
 	 * @return the settings object
 	 */
@@ -259,7 +259,7 @@ public static final JsonObject fullSettings() {
 
 	/**
 	 * Gets a {@link JsonObject} with the minimum settings for a
-	 * {@link FeneconHome}.
+	 * {@link FeneconHome10}.
 	 * 
 	 * @return the settings object
 	 */
diff --git a/io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHome10Gen2.java b/io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHome10Gen2.java
new file mode 100644
index 00000000000..7cae58845f1
--- /dev/null
+++ b/io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHome10Gen2.java
@@ -0,0 +1,125 @@
+package io.openems.edge.app.integratedsystem;
+
+import static io.openems.edge.common.test.DummyUser.DUMMY_ADMIN;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.google.gson.JsonObject;
+
+import io.openems.common.utils.JsonUtils;
+import io.openems.edge.common.test.DummyUser;
+import io.openems.edge.common.user.User;
+import io.openems.edge.core.appmanager.AppManagerTestBundle;
+import io.openems.edge.core.appmanager.AppManagerTestBundle.PseudoComponentManagerFactory;
+import io.openems.edge.core.appmanager.Apps;
+import io.openems.edge.core.appmanager.OpenemsAppInstance;
+import io.openems.edge.core.appmanager.jsonrpc.AddAppInstance;
+import io.openems.edge.core.appmanager.jsonrpc.UpdateAppInstance;
+
+public class TestFeneconHome10Gen2 {
+	private AppManagerTestBundle appManagerTestBundle;
+
+	@Before
+	public void beforeEach() throws Exception {
+		this.appManagerTestBundle = new AppManagerTestBundle(null, null, t -> {
+			return Apps.of(t, //
+					Apps::feneconHome10Gen2, //
+					Apps::gridOptimizedCharge, //
+					Apps::selfConsumptionOptimization, //
+					Apps::prepareBatteryExtension //
+			);
+		}, null, new PseudoComponentManagerFactory());
+
+		final var componentTask = this.appManagerTestBundle.addComponentAggregateTask();
+		this.appManagerTestBundle.addSchedulerByCentralOrderAggregateTask(componentTask);
+	}
+
+	@Test
+	public void testCreate() throws Exception {
+		this.createFullHome(this.appManagerTestBundle, DummyUser.DUMMY_ADMIN);
+	}
+
+	private final OpenemsAppInstance createFullHome(AppManagerTestBundle appManagerTestBundle, User user)
+			throws Exception {
+		var fullConfig = fullSettings();
+
+		appManagerTestBundle.sut.handleAddAppInstanceRequest(user,
+				new AddAppInstance.Request("App.FENECON.Home10.Gen2", "key", "alias", fullConfig));
+
+		assertEquals(this.appManagerTestBundle.sut.getInstantiatedApps().size(), 4);
+
+		for (var instance : appManagerTestBundle.sut.getInstantiatedApps()) {
+			final var expectedDependencies = switch (instance.appId) {
+			case "App.FENECON.Home10.Gen2" -> 3;
+			case "App.PvSelfConsumption.GridOptimizedCharge" -> 0;
+			case "App.PvSelfConsumption.SelfConsumptionOptimization" -> 0;
+			case "App.Ess.PrepareBatteryExtension" -> 0;
+			default -> throw new Exception("App with ID[" + instance.appId + "] should not have been created!");
+			};
+			if (expectedDependencies == 0 && instance.dependencies == null) {
+				continue;
+			}
+			assertEquals(expectedDependencies, instance.dependencies.size());
+		}
+
+		var homeInstance = appManagerTestBundle.sut.getInstantiatedApps().stream()
+				.filter(t -> t.appId.equals("App.FENECON.Home10.Gen2")).findAny().orElse(null);
+
+		assertNotNull(homeInstance);
+
+		return homeInstance;
+	}
+
+	@Test
+	public final void testCreateAndUpdateFullHome() throws Exception {
+		var fullSettings = fullSettings();
+		var homeInstance = this.createFullHome(this.appManagerTestBundle, DummyUser.DUMMY_ADMIN);
+		this.appManagerTestBundle.sut.handleUpdateAppInstanceRequest(DUMMY_ADMIN,
+				new UpdateAppInstance.Request(homeInstance.instanceId, "aliasrename", fullSettings));
+		// expect the same as before
+		// make sure every dependency got installed
+		assertEquals(this.appManagerTestBundle.sut.getInstantiatedApps().size(), 4);
+		for (var instance : this.appManagerTestBundle.sut.getInstantiatedApps()) {
+			final var expectedDependencies = switch (instance.appId) {
+			case "App.FENECON.Home10.Gen2" -> 3;
+			case "App.PvSelfConsumption.GridOptimizedCharge" -> 0;
+			case "App.PvSelfConsumption.SelfConsumptionOptimization" -> 0;
+			case "App.Ess.PrepareBatteryExtension" -> 0;
+			default -> throw new Exception("App with ID[" + instance.appId + "] should not have been created!");
+			};
+			if (expectedDependencies == 0 && instance.dependencies == null) {
+				continue;
+			}
+			assertEquals(expectedDependencies, instance.dependencies.size());
+		}
+
+	}
+
+	/**
+	 * Gets a {@link JsonObject} with the full settings for a
+	 * {@link FeneconHome10Gen2}.
+	 * 
+	 * @return the settings object
+	 */
+	public static final JsonObject fullSettings() {
+		return JsonUtils.buildJsonObject() //
+				.addProperty("SAFETY_COUNTRY", "GERMANY") //
+				.addProperty("MAX_FEED_IN_POWER", 1000) //
+				.addProperty("FEED_IN_SETTING", "LAGGING_0_95") //
+				.addProperty("HAS_DC_PV1", true) //
+				.addProperty("DC_PV1_ALIAS", "alias pv 1") //
+				.addProperty("HAS_DC_PV2", true) //
+				.addProperty("DC_PV2_ALIAS", "alias pv 2") //
+				.addProperty("HAS_DC_PV3", true) //
+				.addProperty("DC_PV2_ALIAS", "alias pv 3") //
+				.addProperty("HAS_EMERGENCY_RESERVE", true) //
+				.addProperty("GRID_METER_CATEGORY", GoodWeGridMeterCategory.INTEGRATED_METER)
+				.addProperty("EMERGENCY_RESERVE_ENABLED", true) //
+				.addProperty("EMERGENCY_RESERVE_SOC", 15) //
+				.addProperty("SHADOW_MANAGEMENT_DISABLED", false) //
+				.build();
+	}
+}
diff --git a/io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHome15.java b/io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHome15.java
new file mode 100644
index 00000000000..2973b75ed36
--- /dev/null
+++ b/io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHome15.java
@@ -0,0 +1,124 @@
+package io.openems.edge.app.integratedsystem;
+
+import static io.openems.edge.common.test.DummyUser.DUMMY_ADMIN;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.google.gson.JsonObject;
+
+import io.openems.common.utils.JsonUtils;
+import io.openems.edge.common.test.DummyUser;
+import io.openems.edge.common.user.User;
+import io.openems.edge.core.appmanager.AppManagerTestBundle;
+import io.openems.edge.core.appmanager.AppManagerTestBundle.PseudoComponentManagerFactory;
+import io.openems.edge.core.appmanager.Apps;
+import io.openems.edge.core.appmanager.OpenemsAppInstance;
+import io.openems.edge.core.appmanager.jsonrpc.AddAppInstance;
+import io.openems.edge.core.appmanager.jsonrpc.UpdateAppInstance;
+
+public class TestFeneconHome15 {
+	private AppManagerTestBundle appManagerTestBundle;
+
+	@Before
+	public void beforeEach() throws Exception {
+		this.appManagerTestBundle = new AppManagerTestBundle(null, null, t -> {
+			return Apps.of(t, //
+					Apps::feneconHome15, //
+					Apps::gridOptimizedCharge, //
+					Apps::selfConsumptionOptimization, //
+					Apps::prepareBatteryExtension //
+			);
+		}, null, new PseudoComponentManagerFactory());
+
+		final var componentTask = this.appManagerTestBundle.addComponentAggregateTask();
+		this.appManagerTestBundle.addSchedulerByCentralOrderAggregateTask(componentTask);
+	}
+
+	@Test
+	public void testCreate() throws Exception {
+		this.createFullHome(this.appManagerTestBundle, DummyUser.DUMMY_ADMIN);
+	}
+
+	private final OpenemsAppInstance createFullHome(AppManagerTestBundle appManagerTestBundle, User user)
+			throws Exception {
+		var fullConfig = fullSettings();
+
+		appManagerTestBundle.sut.handleAddAppInstanceRequest(user,
+				new AddAppInstance.Request("App.FENECON.Home15", "key", "alias", fullConfig));
+
+		assertEquals(this.appManagerTestBundle.sut.getInstantiatedApps().size(), 4);
+
+		for (var instance : appManagerTestBundle.sut.getInstantiatedApps()) {
+			final var expectedDependencies = switch (instance.appId) {
+			case "App.FENECON.Home15" -> 3;
+			case "App.PvSelfConsumption.GridOptimizedCharge" -> 0;
+			case "App.PvSelfConsumption.SelfConsumptionOptimization" -> 0;
+			case "App.Ess.PrepareBatteryExtension" -> 0;
+			default -> throw new Exception("App with ID[" + instance.appId + "] should not have been created!");
+			};
+			if (expectedDependencies == 0 && instance.dependencies == null) {
+				continue;
+			}
+			assertEquals(expectedDependencies, instance.dependencies.size());
+		}
+
+		var homeInstance = appManagerTestBundle.sut.getInstantiatedApps().stream()
+				.filter(t -> t.appId.equals("App.FENECON.Home15")).findAny().orElse(null);
+
+		assertNotNull(homeInstance);
+
+		return homeInstance;
+	}
+
+	@Test
+	public final void testCreateAndUpdateFullHome() throws Exception {
+		var fullSettings = fullSettings();
+		var homeInstance = this.createFullHome(this.appManagerTestBundle, DummyUser.DUMMY_ADMIN);
+		this.appManagerTestBundle.sut.handleUpdateAppInstanceRequest(DUMMY_ADMIN,
+				new UpdateAppInstance.Request(homeInstance.instanceId, "aliasrename", fullSettings));
+		// expect the same as before
+		// make sure every dependency got installed
+		assertEquals(this.appManagerTestBundle.sut.getInstantiatedApps().size(), 4);
+		for (var instance : this.appManagerTestBundle.sut.getInstantiatedApps()) {
+			final var expectedDependencies = switch (instance.appId) {
+			case "App.FENECON.Home15" -> 3;
+			case "App.PvSelfConsumption.GridOptimizedCharge" -> 0;
+			case "App.PvSelfConsumption.SelfConsumptionOptimization" -> 0;
+			case "App.Ess.PrepareBatteryExtension" -> 0;
+			default -> throw new Exception("App with ID[" + instance.appId + "] should not have been created!");
+			};
+			if (expectedDependencies == 0 && instance.dependencies == null) {
+				continue;
+			}
+			assertEquals(expectedDependencies, instance.dependencies.size());
+		}
+
+	}
+
+	/**
+	 * Gets a {@link JsonObject} with the full settings for a {@link FeneconHome15}.
+	 * 
+	 * @return the settings object
+	 */
+	public static final JsonObject fullSettings() {
+		return JsonUtils.buildJsonObject() //
+				.addProperty("SAFETY_COUNTRY", "GERMANY") //
+				.addProperty("MAX_FEED_IN_POWER", 1000) //
+				.addProperty("FEED_IN_SETTING", "LAGGING_0_95") //
+				.addProperty("HAS_DC_PV1", true) //
+				.addProperty("DC_PV1_ALIAS", "alias pv 1") //
+				.addProperty("HAS_DC_PV2", true) //
+				.addProperty("DC_PV2_ALIAS", "alias pv 2") //
+				.addProperty("HAS_DC_PV3", true) //
+				.addProperty("DC_PV3_ALIAS", "alias pv 3") //
+				.addProperty("HAS_EMERGENCY_RESERVE", true) //
+				.addProperty("GRID_METER_CATEGORY", GoodWeGridMeterCategory.INTEGRATED_METER)
+				.addProperty("EMERGENCY_RESERVE_ENABLED", true) //
+				.addProperty("EMERGENCY_RESERVE_SOC", 15) //
+				.addProperty("SHADOW_MANAGEMENT_DISABLED", false) //
+				.build();
+	}
+}
diff --git a/io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHome20.java b/io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHome20.java
index f192548fe84..609fd1b8a38 100644
--- a/io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHome20.java
+++ b/io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHome20.java
@@ -192,7 +192,7 @@ private final OpenemsAppInstance createFullHome() throws Exception {
 	}
 
 	/**
-	 * Gets a {@link JsonObject} with the full settings for a {@link FeneconHome}.
+	 * Gets a {@link JsonObject} with the full settings for a {@link FeneconHome20}.
 	 * 
 	 * @return the settings object
 	 */
diff --git a/io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHome30.java b/io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHome30.java
index ed6fd66154c..0c349b74428 100644
--- a/io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHome30.java
+++ b/io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHome30.java
@@ -8,32 +8,41 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import com.google.common.collect.ImmutableList;
 import com.google.gson.JsonObject;
 
 import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
+import io.openems.common.session.Language;
 import io.openems.common.utils.JsonUtils;
 import io.openems.edge.app.enums.FeedInType;
 import io.openems.edge.common.user.User;
 import io.openems.edge.core.appmanager.AppManagerTestBundle;
 import io.openems.edge.core.appmanager.AppManagerTestBundle.PseudoComponentManagerFactory;
 import io.openems.edge.core.appmanager.Apps;
+import io.openems.edge.core.appmanager.ConfigurationTarget;
+import io.openems.edge.core.appmanager.OpenemsApp;
 import io.openems.edge.core.appmanager.OpenemsAppInstance;
 import io.openems.edge.core.appmanager.jsonrpc.AddAppInstance;
+import io.openems.edge.core.appmanager.jsonrpc.DeleteAppInstance;
 import io.openems.edge.core.appmanager.jsonrpc.UpdateAppInstance;
 
 public class TestFeneconHome30 {
 
 	private AppManagerTestBundle appManagerTestBundle;
 
+	private OpenemsApp integratedSystemApp;
+
 	@Before
 	public void beforeEach() throws Exception {
 		this.appManagerTestBundle = new AppManagerTestBundle(null, null, t -> {
-			return Apps.of(t, //
-					Apps::feneconHome30, //
-					Apps::gridOptimizedCharge, //
-					Apps::selfConsumptionOptimization, //
-					Apps::socomecMeter, //
-					Apps::prepareBatteryExtension //
+			return ImmutableList.of(//
+					this.integratedSystemApp = Apps.feneconHome30(t), //
+					Apps.gridOptimizedCharge(t), //
+					Apps.selfConsumptionOptimization(t), //
+					Apps.socomecMeter(t), //
+					Apps.prepareBatteryExtension(t), //
+					Apps.techbaseCm3(t), //
+					Apps.techbaseCm4sGen2(t) //
 			);
 		}, null, new PseudoComponentManagerFactory());
 
@@ -221,6 +230,34 @@ public void testFeedInTypeNoLimitation() throws Exception {
 		assertEquals("DISABLE", batteryInverterProps.get("rcrEnable"));
 	}
 
+	@Test
+	public void testNewHardwareExternalModbusPort() throws Exception {
+		final var hardwareResponse = this.appManagerTestBundle.sut.handleAddAppInstanceRequest(DUMMY_ADMIN,
+				new AddAppInstance.Request("App.OpenemsHardware.CM3", "key", "alias",
+						JsonUtils.buildJsonObject().build()));
+
+		// old/no hardware
+		final var oldConfig = this.integratedSystemApp.getAppConfiguration(ConfigurationTarget.ADD, fullSettings(),
+				Language.DEFAULT);
+		final var oldExternalModbus = oldConfig.getComponents().stream() //
+				.filter(t -> t.getId().equals("modbus2")) //
+				.findAny().orElse(null);
+		assertEquals("/dev/bus0", oldExternalModbus.getProperty("portName").orElse(null).getAsString());
+
+		this.appManagerTestBundle.sut.handleDeleteAppInstanceRequest(DUMMY_ADMIN,
+				new DeleteAppInstance.Request(hardwareResponse.instance().instanceId));
+		// install new hardware
+		this.appManagerTestBundle.sut.handleAddAppInstanceRequest(DUMMY_ADMIN, new AddAppInstance.Request(
+				"App.OpenemsHardware.CM4S.Gen2", "key", "alias", JsonUtils.buildJsonObject().build()));
+
+		final var newConfig = this.integratedSystemApp.getAppConfiguration(ConfigurationTarget.ADD, fullSettings(),
+				Language.DEFAULT);
+		final var newExternalModbus = newConfig.getComponents().stream() //
+				.filter(t -> t.getId().equals("modbus2")) //
+				.findAny().orElse(null);
+		assertEquals("/dev/busUSB3", newExternalModbus.getProperty("portName").orElse(null).getAsString());
+	}
+
 	private final OpenemsAppInstance createFullHome30() throws Exception {
 		return createFullHome30(this.appManagerTestBundle, DUMMY_ADMIN);
 	}
@@ -275,7 +312,7 @@ public static final OpenemsAppInstance createFullHome30(AppManagerTestBundle app
 	}
 
 	/**
-	 * Gets a {@link JsonObject} with the full settings for a {@link FeneconHome}.
+	 * Gets a {@link JsonObject} with the full settings for a {@link FeneconHome30}.
 	 * 
 	 * @return the settings object
 	 */
@@ -300,7 +337,7 @@ public static final JsonObject fullSettings() {
 	}
 
 	/**
-	 * Gets a {@link JsonObject} with the full settings for a {@link FeneconHome}.
+	 * Gets a {@link JsonObject} with the full settings for a {@link FeneconHome30}.
 	 * 
 	 * @return the settings object
 	 */
diff --git a/io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHome30DefaultRelays.java b/io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHome30DefaultRelays.java
index 699921aa573..c67423251bc 100644
--- a/io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHome30DefaultRelays.java
+++ b/io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHome30DefaultRelays.java
@@ -18,7 +18,7 @@
 import io.openems.edge.core.appmanager.Apps;
 import io.openems.edge.core.appmanager.DummyPseudoComponentManager;
 import io.openems.edge.core.appmanager.OpenemsAppInstance;
-import io.openems.edge.io.test.DummyInputOutput;
+import io.openems.edge.io.test.DummyCustomInputOutput;
 
 public class TestFeneconHome30DefaultRelays {
 
@@ -96,10 +96,10 @@ private final OpenemsAppInstance createFullHomeWithDummyIo() throws Exception {
 		final var instance = TestFeneconHome30.createFullHome30(this.appManagerTestBundle, DUMMY_ADMIN);
 		this.appManagerTestBundle.componentManger.handleDeleteComponentConfigRequest(DUMMY_ADMIN,
 				new DeleteComponentConfigRequest("io0"));
-		final var dummyRelay = new DummyInputOutput("io0", "RELAY", 1, 8);
+		final var dummyRelay = new DummyCustomInputOutput("io0", "RELAY", 1, 8);
 		this.appManagerTestBundle.cm.getOrCreateEmptyConfiguration(dummyRelay.id());
 		((DummyPseudoComponentManager) this.appManagerTestBundle.componentManger).addComponent(dummyRelay);
-		final var dummyRelay1 = new DummyInputOutput("io1", "RELAY", 1, 8);
+		final var dummyRelay1 = new DummyCustomInputOutput("io1", "RELAY", 1, 8);
 		this.appManagerTestBundle.cm.getOrCreateEmptyConfiguration(dummyRelay1.id());
 		((DummyPseudoComponentManager) this.appManagerTestBundle.componentManger).addComponent(dummyRelay1);
 		return instance;
diff --git a/io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHome6.java b/io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHome6.java
new file mode 100644
index 00000000000..9bb963b40b9
--- /dev/null
+++ b/io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHome6.java
@@ -0,0 +1,122 @@
+package io.openems.edge.app.integratedsystem;
+
+import static io.openems.edge.common.test.DummyUser.DUMMY_ADMIN;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.google.gson.JsonObject;
+
+import io.openems.common.utils.JsonUtils;
+import io.openems.edge.common.test.DummyUser;
+import io.openems.edge.common.user.User;
+import io.openems.edge.core.appmanager.AppManagerTestBundle;
+import io.openems.edge.core.appmanager.AppManagerTestBundle.PseudoComponentManagerFactory;
+import io.openems.edge.core.appmanager.Apps;
+import io.openems.edge.core.appmanager.OpenemsAppInstance;
+import io.openems.edge.core.appmanager.jsonrpc.AddAppInstance;
+import io.openems.edge.core.appmanager.jsonrpc.UpdateAppInstance;
+
+public class TestFeneconHome6 {
+	private AppManagerTestBundle appManagerTestBundle;
+
+	@Before
+	public void beforeEach() throws Exception {
+		this.appManagerTestBundle = new AppManagerTestBundle(null, null, t -> {
+			return Apps.of(t, //
+					Apps::feneconHome6, //
+					Apps::gridOptimizedCharge, //
+					Apps::selfConsumptionOptimization, //
+					Apps::prepareBatteryExtension //
+			);
+		}, null, new PseudoComponentManagerFactory());
+
+		final var componentTask = this.appManagerTestBundle.addComponentAggregateTask();
+		this.appManagerTestBundle.addSchedulerByCentralOrderAggregateTask(componentTask);
+	}
+
+	@Test
+	public void testCreate() throws Exception {
+		this.createFullHome(this.appManagerTestBundle, DummyUser.DUMMY_ADMIN);
+	}
+
+	private final OpenemsAppInstance createFullHome(AppManagerTestBundle appManagerTestBundle, User user)
+			throws Exception {
+		var fullConfig = fullSettings();
+
+		appManagerTestBundle.sut.handleAddAppInstanceRequest(user,
+				new AddAppInstance.Request("App.FENECON.Home6", "key", "alias", fullConfig));
+
+		assertEquals(this.appManagerTestBundle.sut.getInstantiatedApps().size(), 4);
+
+		for (var instance : appManagerTestBundle.sut.getInstantiatedApps()) {
+			final var expectedDependencies = switch (instance.appId) {
+			case "App.FENECON.Home6" -> 3;
+			case "App.PvSelfConsumption.GridOptimizedCharge" -> 0;
+			case "App.PvSelfConsumption.SelfConsumptionOptimization" -> 0;
+			case "App.Ess.PrepareBatteryExtension" -> 0;
+			default -> throw new Exception("App with ID[" + instance.appId + "] should not have been created!");
+			};
+			if (expectedDependencies == 0 && instance.dependencies == null) {
+				continue;
+			}
+			assertEquals(expectedDependencies, instance.dependencies.size());
+		}
+
+		var homeInstance = appManagerTestBundle.sut.getInstantiatedApps().stream()
+				.filter(t -> t.appId.equals("App.FENECON.Home6")).findAny().orElse(null);
+
+		assertNotNull(homeInstance);
+
+		return homeInstance;
+	}
+
+	@Test
+	public final void testCreateAndUpdateFullHome() throws Exception {
+		var fullSettings = fullSettings();
+		var homeInstance = this.createFullHome(this.appManagerTestBundle, DummyUser.DUMMY_ADMIN);
+		this.appManagerTestBundle.sut.handleUpdateAppInstanceRequest(DUMMY_ADMIN,
+				new UpdateAppInstance.Request(homeInstance.instanceId, "aliasrename", fullSettings));
+		// expect the same as before
+		// make sure every dependency got installed
+		assertEquals(this.appManagerTestBundle.sut.getInstantiatedApps().size(), 4);
+		for (var instance : this.appManagerTestBundle.sut.getInstantiatedApps()) {
+			final var expectedDependencies = switch (instance.appId) {
+			case "App.FENECON.Home6" -> 3;
+			case "App.PvSelfConsumption.GridOptimizedCharge" -> 0;
+			case "App.PvSelfConsumption.SelfConsumptionOptimization" -> 0;
+			case "App.Ess.PrepareBatteryExtension" -> 0;
+			default -> throw new Exception("App with ID[" + instance.appId + "] should not have been created!");
+			};
+			if (expectedDependencies == 0 && instance.dependencies == null) {
+				continue;
+			}
+			assertEquals(expectedDependencies, instance.dependencies.size());
+		}
+
+	}
+
+	/**
+	 * Gets a {@link JsonObject} with the full settings for a {@link FeneconHome6}.
+	 * 
+	 * @return the settings object
+	 */
+	public static final JsonObject fullSettings() {
+		return JsonUtils.buildJsonObject() //
+				.addProperty("SAFETY_COUNTRY", "GERMANY") //
+				.addProperty("MAX_FEED_IN_POWER", 1000) //
+				.addProperty("FEED_IN_SETTING", "LAGGING_0_95") //
+				.addProperty("HAS_DC_PV1", true) //
+				.addProperty("DC_PV1_ALIAS", "alias pv 1") //
+				.addProperty("HAS_DC_PV2", true) //
+				.addProperty("DC_PV2_ALIAS", "alias pv 2") //
+				.addProperty("HAS_EMERGENCY_RESERVE", true) //
+				.addProperty("GRID_METER_CATEGORY", GoodWeGridMeterCategory.INTEGRATED_METER)
+				.addProperty("EMERGENCY_RESERVE_ENABLED", true) //
+				.addProperty("EMERGENCY_RESERVE_SOC", 15) //
+				.addProperty("SHADOW_MANAGEMENT_DISABLED", false) //
+				.build();
+	}
+}
diff --git a/io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHomeDefaultRelays.java b/io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHomeDefaultRelays.java
index 983785d6092..7127e6b227c 100644
--- a/io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHomeDefaultRelays.java
+++ b/io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHomeDefaultRelays.java
@@ -18,7 +18,7 @@
 import io.openems.edge.core.appmanager.Apps;
 import io.openems.edge.core.appmanager.DummyPseudoComponentManager;
 import io.openems.edge.core.appmanager.OpenemsAppInstance;
-import io.openems.edge.io.test.DummyInputOutput;
+import io.openems.edge.io.test.DummyCustomInputOutput;
 
 public class TestFeneconHomeDefaultRelays {
 
@@ -28,7 +28,7 @@ public class TestFeneconHomeDefaultRelays {
 	public void beforeEach() throws Exception {
 		this.appManagerTestBundle = new AppManagerTestBundle(null, null, t -> {
 			return Apps.of(t, //
-					Apps::feneconHome, //
+					Apps::feneconHome10, //
 					Apps::gridOptimizedCharge, //
 					Apps::selfConsumptionOptimization, //
 					Apps::socomecMeter, //
@@ -89,13 +89,13 @@ public void testDefaultRelayValuesThresholdControl() throws Exception {
 	}
 
 	private final OpenemsAppInstance createFullHomeWithDummyIo() throws Exception {
-		final var instance = TestFeneconHome.createFullHome(this.appManagerTestBundle, DUMMY_ADMIN);
+		final var instance = TestFeneconHome10.createFullHome(this.appManagerTestBundle, DUMMY_ADMIN);
 		this.appManagerTestBundle.componentManger.handleDeleteComponentConfigRequest(DUMMY_ADMIN,
 				new DeleteComponentConfigRequest("io0"));
-		final var dummyRelay = new DummyInputOutput("io0", "RELAY", 1, 4);
+		final var dummyRelay = new DummyCustomInputOutput("io0", "RELAY", 1, 4);
 		this.appManagerTestBundle.cm.getOrCreateEmptyConfiguration(dummyRelay.id());
 		((DummyPseudoComponentManager) this.appManagerTestBundle.componentManger).addComponent(dummyRelay);
-		final var dummyRelay1 = new DummyInputOutput("io1", "RELAY", 1, 4);
+		final var dummyRelay1 = new DummyCustomInputOutput("io1", "RELAY", 1, 4);
 		this.appManagerTestBundle.cm.getOrCreateEmptyConfiguration(dummyRelay1.id());
 		((DummyPseudoComponentManager) this.appManagerTestBundle.componentManger).addComponent(dummyRelay1);
 		return instance;
diff --git a/io.openems.edge.core/test/io/openems/edge/core/appmanager/AppManagerAppHelperImplTest.java b/io.openems.edge.core/test/io/openems/edge/core/appmanager/AppManagerAppHelperImplTest.java
index 98637a96d29..6980cb250cc 100644
--- a/io.openems.edge.core/test/io/openems/edge/core/appmanager/AppManagerAppHelperImplTest.java
+++ b/io.openems.edge.core/test/io/openems/edge/core/appmanager/AppManagerAppHelperImplTest.java
@@ -41,7 +41,7 @@ public class AppManagerAppHelperImplTest {
 	public void beforeEach() throws Exception {
 		this.appManagerTestBundle = new AppManagerTestBundle(null, null, t -> {
 			return ImmutableList.of(//
-					Apps.feneconHome(t), //
+					Apps.feneconHome10(t), //
 					Apps.kebaEvcs(t), //
 					Apps.awattarHourly(t), //
 					Apps.entsoE(t), //
diff --git a/io.openems.edge.core/test/io/openems/edge/core/appmanager/AppManagerImplTest.java b/io.openems.edge.core/test/io/openems/edge/core/appmanager/AppManagerImplTest.java
index ca463981dd4..d206fb1ffdc 100644
--- a/io.openems.edge.core/test/io/openems/edge/core/appmanager/AppManagerImplTest.java
+++ b/io.openems.edge.core/test/io/openems/edge/core/appmanager/AppManagerImplTest.java
@@ -18,7 +18,7 @@
 import io.openems.common.session.Language;
 import io.openems.common.utils.JsonUtils;
 import io.openems.edge.app.evcs.KebaEvcs;
-import io.openems.edge.app.integratedsystem.FeneconHome;
+import io.openems.edge.app.integratedsystem.FeneconHome10;
 import io.openems.edge.app.timeofusetariff.AwattarHourly;
 import io.openems.edge.app.timeofusetariff.StromdaoCorrently;
 import io.openems.edge.common.host.Host;
@@ -28,7 +28,7 @@ public class AppManagerImplTest {
 
 	private AppManagerTestBundle appManagerTestBundle;
 
-	private FeneconHome homeApp;
+	private FeneconHome10 homeApp;
 
 	private KebaEvcs kebaEvcsApp;
 	private AwattarHourly awattarApp;
@@ -46,6 +46,7 @@ public void beforeEach() throws Exception {
 		final var emergencyReserveEnabled = false;
 
 		final var shadowManagmentDisabled = false;
+		final var naProtectionEnabled = false;
 
 		// Battery-Inverter Settings
 		final var safetyCountry = "AUSTRIA";
@@ -130,6 +131,7 @@ public void beforeEach() throws Exception {
 								.addProperty("feedPowerEnable", "ENABLE") //
 								.addProperty("feedPowerPara", 10000) //
 								.addProperty("controlMode", "SMART") //
+								.addProperty("naProtectionEnable", naProtectionEnabled ? "ENABLE" : "DISABLE") //
 								.addProperty("setfeedInPowerSettings", "LAGGING_0_95") //
 								.addProperty("mpptForShadowEnable", shadowManagmentDisabled ? "DISABLE" : "ENABLE") //
 								.addProperty("rcrEnable", "DISABLE") //
@@ -255,6 +257,7 @@ public void beforeEach() throws Exception {
 										.addProperty("HAS_DC_PV2", false) //
 										.addProperty("EMERGENCY_RESERVE_ENABLED", emergencyReserveEnabled) //
 										.addProperty("SHADOW_MANAGEMENT_DISABLED", shadowManagmentDisabled) //
+										.addProperty("NA_PROTECTION_ENABLED", naProtectionEnabled) //
 										.build()) //
 								.build()) //
 						.add(JsonUtils.buildJsonObject() //
@@ -289,7 +292,7 @@ public void beforeEach() throws Exception {
 
 		this.appManagerTestBundle = new AppManagerTestBundle(componentConfig, initialConfig, t -> {
 			return ImmutableList.of(//
-					this.homeApp = Apps.feneconHome(t), //
+					this.homeApp = Apps.feneconHome10(t), //
 					Apps.gridOptimizedCharge(t), //
 					Apps.selfConsumptionOptimization(t), //
 					Apps.prepareBatteryExtension(t), //
diff --git a/io.openems.edge.core/test/io/openems/edge/core/appmanager/AppManagerTestBundle.java b/io.openems.edge.core/test/io/openems/edge/core/appmanager/AppManagerTestBundle.java
index 1252dc6b574..7701feb9a12 100644
--- a/io.openems.edge.core/test/io/openems/edge/core/appmanager/AppManagerTestBundle.java
+++ b/io.openems.edge.core/test/io/openems/edge/core/appmanager/AppManagerTestBundle.java
@@ -1,5 +1,7 @@
 package io.openems.edge.core.appmanager;
 
+import static io.openems.common.utils.ReflectionUtils.setAttributeViaReflection;
+import static io.openems.common.utils.ReflectionUtils.setStaticAttributeViaReflection;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 
@@ -33,8 +35,8 @@
 import io.openems.common.exceptions.OpenemsException;
 import io.openems.common.types.EdgeConfig;
 import io.openems.common.utils.JsonUtils;
-import io.openems.common.utils.ReflectionUtils;
 import io.openems.edge.common.component.ComponentManager;
+import io.openems.edge.common.host.DummyHost;
 import io.openems.edge.common.host.Host;
 import io.openems.edge.common.test.ComponentTest;
 import io.openems.edge.common.test.DummyComponentContext;
@@ -73,6 +75,7 @@ public class AppManagerTestBundle {
 	public final ComponentManager componentManger;
 	public final ComponentUtil componentUtil;
 	public final Validator validator;
+	public final DummyHost host = new DummyHost();
 
 	public final DummyAppManagerAppHelper appHelper;
 	public final AppManagerImpl sut;
@@ -183,8 +186,6 @@ public  AppManagerTestBundle(//
 		this.appManagerUtil = new AppManagerUtilImpl(this.componentManger);
 		this.appCenterBackendUtil = new DummyAppCenterBackendUtil();
 
-		ReflectionUtils.setAttribute(this.appManagerUtil.getClass(), this.appManagerUtil, "appManager", this.sut);
-
 		this.addCheckable(TestCheckable.COMPONENT_NAME, t -> new TestCheckable());
 		this.addCheckable(CheckOr.COMPONENT_NAME, t -> new CheckOr(t, this.checkableFactory));
 		this.checkCardinality = this.addCheckable(CheckCardinality.COMPONENT_NAME,
@@ -198,20 +199,13 @@ public  AppManagerTestBundle(//
 		this.appValidateWorker = new AppValidateWorker();
 		final var appConfigValidator = new AppConfigValidator();
 
-		ReflectionUtils.setAttribute(AppValidateWorker.class, this.appValidateWorker, "appManagerUtil",
-				this.appManagerUtil);
-		ReflectionUtils.setAttribute(AppValidateWorker.class, this.appValidateWorker, "validator", appConfigValidator);
-
-		ReflectionUtils.setAttribute(AppConfigValidator.class, appConfigValidator, "appManagerUtil",
-				this.appManagerUtil);
-		ReflectionUtils.setAttribute(AppConfigValidator.class, appConfigValidator, "tasks", this.appHelper.getTasks());
+		setAttributeViaReflection(this.appValidateWorker, "appManagerUtil", this.appManagerUtil);
+		setAttributeViaReflection(this.appValidateWorker, "validator", appConfigValidator);
 
-		// use this so the appManagerAppHelper does not has to be a OpenemsComponent and
-		// the attribute can still be private
-		ReflectionUtils.setAttribute(this.appHelper.getClass(), this.appHelper, "appManager", this.sut);
-		ReflectionUtils.setAttribute(this.appHelper.getClass(), this.appHelper, "appManagerUtil", this.appManagerUtil);
+		setAttributeViaReflection(appConfigValidator, "appManagerUtil", this.appManagerUtil);
+		setAttributeViaReflection(appConfigValidator, "tasks", this.appHelper.getTasks());
 
-		ReflectionUtils.setAttribute(DependencyUtil.class, null, "appHelper", this.appHelper);
+		setStaticAttributeViaReflection(DependencyUtil.class, "appHelper", this.appHelper);
 
 		new ComponentTest(this.sut) //
 				.addReference("cm", this.cm) //
diff --git a/io.openems.edge.core/test/io/openems/edge/core/appmanager/Apps.java b/io.openems.edge.core/test/io/openems/edge/core/appmanager/Apps.java
index 4fa625f043d..3d776848849 100644
--- a/io.openems.edge.core/test/io/openems/edge/core/appmanager/Apps.java
+++ b/io.openems.edge.core/test/io/openems/edge/core/appmanager/Apps.java
@@ -29,12 +29,16 @@
 import io.openems.edge.app.evcs.KebaEvcs;
 import io.openems.edge.app.evcs.WebastoNextEvcs;
 import io.openems.edge.app.evcs.WebastoUniteEvcs;
+import io.openems.edge.app.evcs.readonly.MennekesEvcsReadOnly;
 import io.openems.edge.app.heat.CombinedHeatAndPower;
 import io.openems.edge.app.heat.HeatPump;
 import io.openems.edge.app.heat.HeatingElement;
-import io.openems.edge.app.integratedsystem.FeneconHome;
+import io.openems.edge.app.integratedsystem.FeneconHome10;
+import io.openems.edge.app.integratedsystem.FeneconHome10Gen2;
+import io.openems.edge.app.integratedsystem.FeneconHome15;
 import io.openems.edge.app.integratedsystem.FeneconHome20;
 import io.openems.edge.app.integratedsystem.FeneconHome30;
+import io.openems.edge.app.integratedsystem.FeneconHome6;
 import io.openems.edge.app.integratedsystem.fenecon.commercial.FeneconCommercial92;
 import io.openems.edge.app.loadcontrol.ManualRelayControl;
 import io.openems.edge.app.loadcontrol.ThresholdControl;
@@ -67,8 +71,10 @@
 import io.openems.edge.app.timeofusetariff.RabotCharge;
 import io.openems.edge.app.timeofusetariff.StadtwerkHassfurt;
 import io.openems.edge.app.timeofusetariff.StromdaoCorrently;
+import io.openems.edge.app.timeofusetariff.Swisspower;
 import io.openems.edge.app.timeofusetariff.Tibber;
 import io.openems.edge.common.component.ComponentManager;
+import io.openems.edge.common.host.Host;
 
 public final class Apps {
 
@@ -94,13 +100,43 @@ public static final List of(AppManagerTestBundle t,
 	// Integrated Systems
 
 	/**
-	 * Test method for creating a {@link FeneconHome}.
+	 * Test method for creating a {@link FeneconHome10}.
 	 * 
 	 * @param t the {@link AppManagerTestBundle}
 	 * @return the {@link OpenemsApp} instance
 	 */
-	public static final FeneconHome feneconHome(AppManagerTestBundle t) {
-		return app(t, FeneconHome::new, "App.FENECON.Home");
+	public static final FeneconHome10 feneconHome10(AppManagerTestBundle t) {
+		return app(t, FeneconHome10::new, "App.FENECON.Home");
+	}
+
+	/**
+	 * Test method for creating a {@link FeneconHome6}.
+	 * 
+	 * @param t the {@link AppManagerTestBundle}
+	 * @return the {@link OpenemsApp} instance
+	 */
+	public static final FeneconHome6 feneconHome6(AppManagerTestBundle t) {
+		return app(t, FeneconHome6::new, "App.FENECON.Home6");
+	}
+
+	/**
+	 * Test method for creating a {@link FeneconHome10Gen2}.
+	 * 
+	 * @param t the {@link AppManagerTestBundle}
+	 * @return the {@link OpenemsApp} instance
+	 */
+	public static final FeneconHome10Gen2 feneconHome10Gen2(AppManagerTestBundle t) {
+		return app(t, FeneconHome10Gen2::new, "App.FENECON.Home10.Gen2");
+	}
+
+	/**
+	 * Test method for creating a {@link FeneconHome15}.
+	 * 
+	 * @param t the {@link AppManagerTestBundle}
+	 * @return the {@link OpenemsApp} instance
+	 */
+	public static final FeneconHome15 feneconHome15(AppManagerTestBundle t) {
+		return app(t, FeneconHome15::new, "App.FENECON.Home15");
 	}
 
 	/**
@@ -175,6 +211,16 @@ public static final StadtwerkHassfurt stadtwerkHassfurt(AppManagerTestBundle t)
 		return app(t, StadtwerkHassfurt::new, "App.TimeOfUseTariff.Hassfurt");
 	}
 
+	/**
+	 * Test method for creating a {@link Swisspower}.
+	 * 
+	 * @param t the {@link AppManagerTestBundle}
+	 * @return the {@link OpenemsApp} instance
+	 */
+	public static final Swisspower swisspower(AppManagerTestBundle t) {
+		return app(t, Swisspower::new, "App.TimeOfUseTariff.Swisspower");
+	}
+
 	/**
 	 * Test method for creating a {@link RabotCharge}.
 	 * 
@@ -381,6 +427,16 @@ public static final KebaEvcs kebaEvcs(AppManagerTestBundle t) {
 		return app(t, KebaEvcs::new, "App.Evcs.Keba");
 	}
 
+	/**
+	 * Test method for creating a {@link MennekesEvcsReadOnly}.
+	 * 
+	 * @param t the {@link AppManagerTestBundle}
+	 * @return the {@link OpenemsApp} instance
+	 */
+	public static final MennekesEvcsReadOnly mennekesEvcsReadOnlyEvcs(AppManagerTestBundle t) {
+		return app(t, MennekesEvcsReadOnly::new, "App.Evcs.Mennekes.ReadOnly");
+	}
+
 	/**
 	 * Test method for creating a {@link IesKeywattEvcs}.
 	 * 
@@ -726,6 +782,11 @@ private static final  T app(AppManagerTestBundle t, DefaultAppConstructorWith
 				t.componentUtil, t.appManagerUtil);
 	}
 
+	private static final  T app(AppManagerTestBundle t, DefaultAppConstructorWithHost constructor, String appId) {
+		return constructor.create(t.componentManger, AppManagerTestBundle.getComponentContext(appId), t.cm,
+				t.componentUtil, t.host);
+	}
+
 	private static interface DefaultAppConstructor {
 
 		public A create(ComponentManager componentManager, ComponentContext componentContext, ConfigurationAdmin cm,
@@ -740,4 +801,11 @@ public A create(ComponentManager componentManager, ComponentContext componentCon
 
 	}
 
+	private static interface DefaultAppConstructorWithHost {
+
+		public A create(ComponentManager componentManager, ComponentContext componentContext, ConfigurationAdmin cm,
+				ComponentUtil componentUtil, Host host);
+
+	}
+
 }
diff --git a/io.openems.edge.core/test/io/openems/edge/core/appmanager/DummyAppManagerAppHelper.java b/io.openems.edge.core/test/io/openems/edge/core/appmanager/DummyAppManagerAppHelper.java
index 9fe35111886..9ad1f56bc01 100644
--- a/io.openems.edge.core/test/io/openems/edge/core/appmanager/DummyAppManagerAppHelper.java
+++ b/io.openems.edge.core/test/io/openems/edge/core/appmanager/DummyAppManagerAppHelper.java
@@ -1,11 +1,12 @@
 package io.openems.edge.core.appmanager;
 
-import java.lang.reflect.InvocationTargetException;
+import static io.openems.common.utils.ReflectionUtils.setAttributeViaReflection;
+
 import java.util.ArrayList;
 import java.util.List;
 
 import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
-import io.openems.common.utils.ReflectionUtils;
+import io.openems.common.utils.ReflectionUtils.ReflectionException;
 import io.openems.edge.common.component.ComponentManager;
 import io.openems.edge.common.user.User;
 import io.openems.edge.core.appmanager.dependency.AppManagerAppHelper;
@@ -24,12 +25,12 @@ public DummyAppManagerAppHelper(//
 			ComponentManager componentManager, //
 			ComponentUtil componentUtil, //
 			AppManagerUtil util //
-	) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
+	) throws ReflectionException {
 		this.tasks = new ArrayList>();
 		this.impl = new AppManagerAppHelperImpl(componentManager, componentUtil);
 
-		ReflectionUtils.setAttribute(AppManagerAppHelperImpl.class, this.impl, "tasks", this.tasks);
-		ReflectionUtils.setAttribute(AppManagerAppHelperImpl.class, this.impl, "appManagerUtil", util);
+		setAttributeViaReflection(this.impl, "tasks", this.tasks);
+		setAttributeViaReflection(this.impl, "appManagerUtil", util);
 	}
 
 	/**
diff --git a/io.openems.edge.core/test/io/openems/edge/core/appmanager/GetAppAssistantTest.java b/io.openems.edge.core/test/io/openems/edge/core/appmanager/GetAppAssistantTest.java
index 66e888c8e66..0ba857d97d4 100644
--- a/io.openems.edge.core/test/io/openems/edge/core/appmanager/GetAppAssistantTest.java
+++ b/io.openems.edge.core/test/io/openems/edge/core/appmanager/GetAppAssistantTest.java
@@ -15,7 +15,7 @@ public class GetAppAssistantTest {
 	public void before() throws Exception {
 		this.testBundle = new AppManagerTestBundle(null, null, t -> {
 			return Apps.of(t, //
-					Apps::feneconHome, //
+					Apps::feneconHome10, //
 					Apps::awattarHourly, //
 					Apps::entsoE, //
 					Apps::stromdaoCorrently, //
diff --git a/io.openems.edge.core/test/io/openems/edge/core/appmanager/ResolveDependenciesTest.java b/io.openems.edge.core/test/io/openems/edge/core/appmanager/ResolveDependenciesTest.java
index ffd20f881d7..ad04e452202 100644
--- a/io.openems.edge.core/test/io/openems/edge/core/appmanager/ResolveDependenciesTest.java
+++ b/io.openems.edge.core/test/io/openems/edge/core/appmanager/ResolveDependenciesTest.java
@@ -41,7 +41,7 @@ public void before() throws Exception {
 						.build(),
 				t -> {
 					return Apps.of(t, //
-							Apps::feneconHome, //
+							Apps::feneconHome10, //
 							Apps::gridOptimizedCharge, //
 							Apps::selfConsumptionOptimization, //
 							Apps::prepareBatteryExtension //
diff --git a/io.openems.edge.core/test/io/openems/edge/core/appmanager/TestTranslations.java b/io.openems.edge.core/test/io/openems/edge/core/appmanager/TestTranslations.java
index c14160b59af..96b30fe1d5b 100644
--- a/io.openems.edge.core/test/io/openems/edge/core/appmanager/TestTranslations.java
+++ b/io.openems.edge.core/test/io/openems/edge/core/appmanager/TestTranslations.java
@@ -15,9 +15,12 @@
 import io.openems.common.oem.DummyOpenemsEdgeOem;
 import io.openems.common.session.Language;
 import io.openems.common.utils.JsonUtils;
-import io.openems.edge.app.integratedsystem.TestFeneconHome;
+import io.openems.edge.app.integratedsystem.TestFeneconHome10;
+import io.openems.edge.app.integratedsystem.TestFeneconHome10Gen2;
+import io.openems.edge.app.integratedsystem.TestFeneconHome15;
 import io.openems.edge.app.integratedsystem.TestFeneconHome20;
 import io.openems.edge.app.integratedsystem.TestFeneconHome30;
+import io.openems.edge.app.integratedsystem.TestFeneconHome6;
 
 public class TestTranslations {
 
@@ -31,9 +34,12 @@ private record TestTranslation(OpenemsApp app, boolean validateAppAssistant, Jso
 	public void beforeEach() throws Exception {
 		this.apps = new ArrayList<>();
 		new AppManagerTestBundle(null, null, t -> {
-			this.apps.add(new TestTranslation(Apps.feneconHome(t), true, TestFeneconHome.fullSettings()));
+			this.apps.add(new TestTranslation(Apps.feneconHome10(t), true, TestFeneconHome10.fullSettings()));
 			this.apps.add(new TestTranslation(Apps.feneconHome20(t), true, TestFeneconHome20.fullSettings()));
 			this.apps.add(new TestTranslation(Apps.feneconHome30(t), true, TestFeneconHome30.fullSettings()));
+			this.apps.add(new TestTranslation(Apps.feneconHome6(t), true, TestFeneconHome6.fullSettings()));
+			this.apps.add(new TestTranslation(Apps.feneconHome10Gen2(t), true, TestFeneconHome10Gen2.fullSettings()));
+			this.apps.add(new TestTranslation(Apps.feneconHome15(t), true, TestFeneconHome15.fullSettings()));
 			this.apps.add(new TestTranslation(Apps.feneconCommercial92(t), true, new JsonObject()));
 			this.apps.add(new TestTranslation(Apps.awattarHourly(t), true, new JsonObject()));
 			this.apps.add(new TestTranslation(Apps.entsoE(t), true, JsonUtils.buildJsonObject() //
@@ -42,10 +48,13 @@ public void beforeEach() throws Exception {
 			this.apps.add(new TestTranslation(Apps.groupeE(t), true, JsonUtils.buildJsonObject() //
 					.build()));
 			this.apps.add(new TestTranslation(Apps.rabotCharge(t), true, JsonUtils.buildJsonObject() //
-					.addProperty("ACCESS_TOKEN", "123456789") //
+					.addProperty("ZIP_CODE", "123456789") //
 					.build()));
 			this.apps.add(new TestTranslation(Apps.stadtwerkHassfurt(t), true, JsonUtils.buildJsonObject() //
 					.build()));
+			this.apps.add(new TestTranslation(Apps.swisspower(t), true, JsonUtils.buildJsonObject() //
+					.addProperty("METERING_CODE", "bf7777") //
+					.build()));
 			this.apps.add(new TestTranslation(Apps.stromdaoCorrently(t), true, JsonUtils.buildJsonObject() //
 					.addProperty("ZIP_CODE", "123456789") //
 					.build()));
@@ -74,6 +83,7 @@ public void beforeEach() throws Exception {
 					.build()));
 			this.apps.add(new TestTranslation(Apps.hardyBarthEvcs(t), true, new JsonObject()));
 			this.apps.add(new TestTranslation(Apps.kebaEvcs(t), true, new JsonObject()));
+			this.apps.add(new TestTranslation(Apps.mennekesEvcsReadOnlyEvcs(t), true, new JsonObject()));
 			this.apps.add(new TestTranslation(Apps.iesKeywattEvcs(t), true, new JsonObject()));
 			this.apps.add(new TestTranslation(Apps.alpitronicEvcs(t), true, new JsonObject()));
 			this.apps.add(new TestTranslation(Apps.webastoNext(t), true, new JsonObject()));
diff --git a/io.openems.edge.core/test/io/openems/edge/core/appmanager/validator/CheckHomeTest.java b/io.openems.edge.core/test/io/openems/edge/core/appmanager/validator/CheckHomeTest.java
index 54504dd2026..2f473f500ec 100644
--- a/io.openems.edge.core/test/io/openems/edge/core/appmanager/validator/CheckHomeTest.java
+++ b/io.openems.edge.core/test/io/openems/edge/core/appmanager/validator/CheckHomeTest.java
@@ -11,7 +11,7 @@
 
 import io.openems.common.session.Language;
 import io.openems.edge.app.common.props.PropsUtil;
-import io.openems.edge.app.integratedsystem.TestFeneconHome;
+import io.openems.edge.app.integratedsystem.TestFeneconHome10;
 import io.openems.edge.app.integratedsystem.TestFeneconHome20;
 import io.openems.edge.app.integratedsystem.TestFeneconHome30;
 import io.openems.edge.core.appmanager.AppManagerTestBundle;
@@ -30,7 +30,7 @@ public class CheckHomeTest {
 	public void setUp() throws Exception {
 		this.appManagerTestBundle = new AppManagerTestBundle(null, null, t -> {
 			return ImmutableList.of(//
-					Apps.feneconHome(t), //
+					Apps.feneconHome10(t), //
 					Apps.feneconHome20(t), //
 					Apps.feneconHome30(t) //
 			);
@@ -49,7 +49,7 @@ public void testCheck() {
 	@Test
 	public void testCheckWithInstalledHome10() throws Exception {
 		final var response = this.appManagerTestBundle.sut.handleAddAppInstanceRequest(DUMMY_ADMIN,
-				new AddAppInstance.Request("App.FENECON.Home", "key", "alias", TestFeneconHome.fullSettings()));
+				new AddAppInstance.Request("App.FENECON.Home", "key", "alias", TestFeneconHome10.fullSettings()));
 
 		assertTrue(response.warnings().isEmpty());
 		assertTrue(this.checkHome.check());
diff --git a/io.openems.edge.core/test/io/openems/edge/core/host/IpAddressTest.java b/io.openems.edge.core/test/io/openems/edge/core/host/IpAddressTest.java
new file mode 100644
index 00000000000..292e2533934
--- /dev/null
+++ b/io.openems.edge.core/test/io/openems/edge/core/host/IpAddressTest.java
@@ -0,0 +1,32 @@
+package io.openems.edge.core.host;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
+
+public class IpAddressTest {
+
+	@Test
+	public void test() throws OpenemsNamedException {
+		String jsonInput = "{\"stdout\":[\"[{\\\"ifindex\\\":1,\\\"ifname\\\":\\\"lo\\\",\\\"flags\\\":[\\\"LOOPBACK\\\",\\\"UP\\\",\\\"LOWER_UP\\\"],\\\"mtu\\\":65536,\\\"qdisc\\\":\\\"noqueue\\\",\\\"operstate\\\":\\\"UNKNOWN\\\",\\\"group\\\":\\\"default\\\",\\\"txqlen\\\":1000,\\\"addr_info\\\":[{\\\"family\\\":\\\"inet\\\",\\\"local\\\":\\\"127.0.0.1\\\",\\\"prefixlen\\\":8,\\\"scope\\\":\\\"host\\\",\\\"label\\\":\\\"lo\\\",\\\"valid_life_time\\\":4294967295,\\\"preferred_life_time\\\":4294967295}]},{\\\"ifindex\\\":2,\\\"ifname\\\":\\\"eth0\\\",\\\"flags\\\":[\\\"BROADCAST\\\",\\\"MULTICAST\\\",\\\"UP\\\",\\\"LOWER_UP\\\"],\\\"mtu\\\":1500,\\\"qdisc\\\":\\\"pfifo_fast\\\",\\\"operstate\\\":\\\"UP\\\",\\\"group\\\":\\\"default\\\",\\\"txqlen\\\":1000,\\\"addr_info\\\":[{\\\"family\\\":\\\"inet\\\",\\\"local\\\":\\\"169.254.97.140\\\",\\\"prefixlen\\\":16,\\\"broadcast\\\":\\\"169.254.255.255\\\",\\\"scope\\\":\\\"link\\\",\\\"label\\\":\\\"eth0\\\",\\\"valid_life_time\\\":4294967295,\\\"preferred_life_time\\\":4294967295},{\\\"family\\\":\\\"inet\\\",\\\"local\\\":\\\"192.168.100.100\\\",\\\"prefixlen\\\":24,\\\"broadcast\\\":\\\"192.168.100.255\\\",\\\"scope\\\":\\\"global\\\",\\\"label\\\":\\\"eth0\\\",\\\"valid_life_time\\\":4294967295,\\\"preferred_life_time\\\":4294967295},{\\\"family\\\":\\\"inet\\\",\\\"local\\\":\\\"192.168.25.10\\\",\\\"prefixlen\\\":24,\\\"broadcast\\\":\\\"192.168.25.255\\\",\\\"scope\\\":\\\"global\\\",\\\"label\\\":\\\"Evcs\\\",\\\"valid_life_time\\\":4294967295,\\\"preferred_life_time\\\":4294967295},{\\\"family\\\":\\\"inet\\\",\\\"local\\\":\\\"10.0.3.217\\\",\\\"prefixlen\\\":16,\\\"broadcast\\\":\\\"10.0.255.255\\\",\\\"scope\\\":\\\"global\\\",\\\"dynamic\\\":true,\\\"label\\\":\\\"eth0\\\",\\\"valid_life_time\\\":21474407,\\\"preferred_life_time\\\":21474407}]}]\"],\"stderr\":[],\"exitcode\":0}\n";
+		assertEquals("5 Ip Addresses", 5, OperatingSystemDebianSystemd.parseIpJson(jsonInput).size());
+		String emptyInput = "{" //
+				+ "    \"stdout\": [" //
+				+ "        \"[]\"" //
+				+ "    ]," //
+				+ "    \"stderr\": []," //
+				+ "    \"exitcode\": 0" //
+				+ "}"; //
+		assertEquals("0 Ip Addresses", 0, OperatingSystemDebianSystemd.parseIpJson(emptyInput).size());
+		String failedInput = "{" //
+				+ "    \"stdout\": [" //
+				+ "        \"Device eth4 does not exist\"" //
+				+ "    ]," //
+				+ "    \"stderr\": []," //
+				+ "    \"exitcode\": 0" //
+				+ "}"; //
+		assertEquals("0 Ip Addresses", 0, OperatingSystemDebianSystemd.parseIpJson(failedInput).size());
+	}
+}
diff --git a/io.openems.edge.core/test/io/openems/edge/core/meta/MetaImplTest.java b/io.openems.edge.core/test/io/openems/edge/core/meta/MetaImplTest.java
index 946dafc96a0..3c6a00657d5 100644
--- a/io.openems.edge.core/test/io/openems/edge/core/meta/MetaImplTest.java
+++ b/io.openems.edge.core/test/io/openems/edge/core/meta/MetaImplTest.java
@@ -1,6 +1,6 @@
 package io.openems.edge.core.meta;
 
-import static io.openems.edge.common.currency.CurrencyConfig.EUR;
+import static io.openems.common.types.CurrencyConfig.EUR;
 
 import org.junit.Test;
 
diff --git a/io.openems.edge.core/test/io/openems/edge/core/meta/MyConfig.java b/io.openems.edge.core/test/io/openems/edge/core/meta/MyConfig.java
index b99b018b74a..a2c92b9de2b 100644
--- a/io.openems.edge.core/test/io/openems/edge/core/meta/MyConfig.java
+++ b/io.openems.edge.core/test/io/openems/edge/core/meta/MyConfig.java
@@ -1,7 +1,7 @@
 package io.openems.edge.core.meta;
 
 import io.openems.common.test.AbstractComponentConfig;
-import io.openems.edge.common.currency.CurrencyConfig;
+import io.openems.common.types.CurrencyConfig;
 import io.openems.edge.common.meta.Meta;
 
 @SuppressWarnings("all")
@@ -10,6 +10,7 @@ public class MyConfig extends AbstractComponentConfig implements Config {
 	public static class Builder {
 
 		private CurrencyConfig currency;
+		private boolean isEssChargeFromGridAllowed;
 
 		private Builder() {
 		}
@@ -19,6 +20,11 @@ public Builder setCurrency(CurrencyConfig currency) {
 			return this;
 		}
 
+		public Builder setIsEssChargeFromGridAllowed(boolean isEssChargeFromGridAllowed) {
+			this.isEssChargeFromGridAllowed = isEssChargeFromGridAllowed;
+			return this;
+		}
+
 		public MyConfig build() {
 			return new MyConfig(this);
 		}
@@ -45,4 +51,9 @@ public CurrencyConfig currency() {
 		return this.builder.currency;
 	}
 
+	@Override
+	public boolean isEssChargeFromGridAllowed() {
+		return this.builder.isEssChargeFromGridAllowed;
+	}
+
 }
diff --git a/io.openems.edge.core/test/io/openems/edge/core/predictormanager/PredictorManagerImplTest.java b/io.openems.edge.core/test/io/openems/edge/core/predictormanager/PredictorManagerImplTest.java
index 934e934489e..9075da90e4f 100644
--- a/io.openems.edge.core/test/io/openems/edge/core/predictormanager/PredictorManagerImplTest.java
+++ b/io.openems.edge.core/test/io/openems/edge/core/predictormanager/PredictorManagerImplTest.java
@@ -1,19 +1,17 @@
 package io.openems.edge.core.predictormanager;
 
+import static io.openems.common.test.TestUtils.createDummyClock;
 import static io.openems.edge.predictor.api.prediction.Prediction.EMPTY_PREDICTION;
 import static java.time.temporal.ChronoUnit.DAYS;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 
-import java.time.Instant;
-import java.time.ZoneOffset;
 import java.time.ZonedDateTime;
 import java.util.List;
 
 import org.junit.Test;
 
 import io.openems.common.exceptions.OpenemsException;
-import io.openems.common.test.TimeLeapClock;
 import io.openems.common.types.ChannelAddress;
 import io.openems.edge.common.sum.DummySum;
 import io.openems.edge.common.test.ComponentTest;
@@ -60,7 +58,7 @@ public class PredictorManagerImplTest {
 
 	@Test
 	public void test() throws OpenemsException, Exception {
-		final var clock = new TimeLeapClock(Instant.parse("2020-01-01T00:00:00.00Z"), ZoneOffset.UTC);
+		final var clock = createDummyClock();
 		final var cm = new DummyComponentManager(clock);
 		final var sum = new DummySum();
 		final var midnight = ZonedDateTime.now(clock).truncatedTo(DAYS);
diff --git a/io.openems.edge.core/test/io/openems/edge/core/sum/ExtremeEverValuesTest.java b/io.openems.edge.core/test/io/openems/edge/core/sum/ExtremeEverValuesTest.java
index 86641c06232..aa35aa7da51 100644
--- a/io.openems.edge.core/test/io/openems/edge/core/sum/ExtremeEverValuesTest.java
+++ b/io.openems.edge.core/test/io/openems/edge/core/sum/ExtremeEverValuesTest.java
@@ -1,21 +1,20 @@
 package io.openems.edge.core.sum;
 
+import static io.openems.common.test.TestUtils.createDummyClock;
 import static io.openems.edge.common.test.TestUtils.activateNextProcessImage;
 import static io.openems.edge.common.test.TestUtils.withValue;
 import static io.openems.edge.core.sum.ExtremeEverValues.Range.NEGATIVE;
 import static io.openems.edge.core.sum.ExtremeEverValues.Range.POSTIVE;
+import static java.time.temporal.ChronoUnit.HOURS;
+import static java.time.temporal.ChronoUnit.SECONDS;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 
 import java.io.IOException;
-import java.time.Instant;
-import java.time.ZoneOffset;
-import java.time.temporal.ChronoUnit;
 
 import org.junit.Test;
 
 import io.openems.common.exceptions.OpenemsException;
-import io.openems.common.test.TimeLeapClock;
 import io.openems.edge.common.sum.Sum;
 import io.openems.edge.common.test.ComponentTest;
 import io.openems.edge.common.test.DummyComponentManager;
@@ -25,12 +24,12 @@ public class ExtremeEverValuesTest {
 
 	@Test
 	public void test() throws OpenemsException, Exception {
-		final var clock = new TimeLeapClock(Instant.parse("2020-01-01T20:00:00.00Z"), ZoneOffset.UTC);
+		final var clock = createDummyClock();
 		var cm = new DummyConfigurationAdmin();
 		var sum = new SumImpl();
 		new ComponentTest(sum) //
 				.addReference("cm", cm) //
-				.addReference("componentManager", new DummyComponentManager()) //
+				.addReference("componentManager", new DummyComponentManager(clock)) //
 				.activate(MyConfig.create() //
 						.setGridMinActivePower(0) //
 						.setIgnoreStateComponents() //
@@ -63,14 +62,14 @@ public void test() throws OpenemsException, Exception {
 		assertEquals(0, getProperty(cm, "gridMinActivePower"));
 
 		// Still the same
-		clock.leap(24, ChronoUnit.HOURS);
+		clock.leap(24, HOURS);
 		withValue(sum, Sum.ChannelId.GRID_ACTIVE_POWER, -100);
 		sut.update(sum, cm);
 		activateNextProcessImage(sum);
 		assertEquals(0, getProperty(cm, "gridMinActivePower"));
 
 		// 24 hours passed -> update config
-		clock.leap(1, ChronoUnit.SECONDS);
+		clock.leap(1, SECONDS);
 		withValue(sum, Sum.ChannelId.GRID_ACTIVE_POWER, -101);
 		sut.update(sum, cm);
 		activateNextProcessImage(sum);
@@ -78,7 +77,7 @@ public void test() throws OpenemsException, Exception {
 		assertEquals(-101, getProperty(cm, "gridMinActivePower"));
 
 		// Update Channel; not the config
-		clock.leap(1, ChronoUnit.SECONDS);
+		clock.leap(1, SECONDS);
 		withValue(sum, Sum.ChannelId.GRID_ACTIVE_POWER, -101);
 		sut.update(sum, cm);
 		activateNextProcessImage(sum);
diff --git a/io.openems.edge.core/test/io/openems/edge/core/sum/SumImplTest.java b/io.openems.edge.core/test/io/openems/edge/core/sum/SumImplTest.java
index 77a25ef888b..2ef7670b28d 100644
--- a/io.openems.edge.core/test/io/openems/edge/core/sum/SumImplTest.java
+++ b/io.openems.edge.core/test/io/openems/edge/core/sum/SumImplTest.java
@@ -1,39 +1,46 @@
 package io.openems.edge.core.sum;
 
-import static io.openems.edge.meter.api.MeterType.GRID;
+import static io.openems.common.types.MeterType.GRID;
+import static io.openems.edge.common.sum.Sum.ChannelId.CONSUMPTION_ACTIVE_POWER;
+import static io.openems.edge.common.sum.Sum.ChannelId.CONSUMPTION_MAX_ACTIVE_POWER;
+import static io.openems.edge.common.sum.Sum.ChannelId.ESS_ACTIVE_POWER;
+import static io.openems.edge.common.sum.Sum.ChannelId.ESS_DISCHARGE_POWER;
+import static io.openems.edge.common.sum.Sum.ChannelId.GRID_ACTIVE_POWER;
+import static io.openems.edge.common.sum.Sum.ChannelId.GRID_MAX_ACTIVE_POWER;
+import static io.openems.edge.common.sum.Sum.ChannelId.GRID_MIN_ACTIVE_POWER;
+import static io.openems.edge.common.sum.Sum.ChannelId.PRODUCTION_ACTIVE_POWER;
+import static io.openems.edge.common.sum.Sum.ChannelId.PRODUCTION_MAX_ACTIVE_POWER;
+import static io.openems.edge.common.sum.Sum.ChannelId.UNMANAGED_CONSUMPTION_ACTIVE_POWER;
 
 import org.junit.Test;
 
 import io.openems.common.exceptions.OpenemsException;
-import io.openems.common.types.ChannelAddress;
+import io.openems.common.types.MeterType;
+import io.openems.edge.common.filter.DisabledRampFilter;
 import io.openems.edge.common.test.AbstractComponentTest.TestCase;
 import io.openems.edge.common.test.ComponentTest;
 import io.openems.edge.common.test.DummyComponentManager;
 import io.openems.edge.common.test.DummyConfigurationAdmin;
-import io.openems.edge.meter.api.MeterType;
+import io.openems.edge.evcs.test.DummyEvcsPower;
+import io.openems.edge.evcs.test.DummyManagedEvcs;
 import io.openems.edge.meter.test.DummyElectricityMeter;
 
 public class SumImplTest {
 
-	private static final ChannelAddress GRID_MIN_ACTIVE_POWER = new ChannelAddress("_sum", "GridMinActivePower");
-	private static final ChannelAddress GRID_MAX_ACTIVE_POWER = new ChannelAddress("_sum", "GridMaxActivePower");
-	private static final ChannelAddress PRODUCTION_MAX_ACTIVE_POWER = new ChannelAddress("_sum",
-			"ProductionMaxActivePower");
-	private static final ChannelAddress CONSUMPTION_MAX_ACTIVE_POWER = new ChannelAddress("_sum",
-			"ConsumptionMaxActivePower");
-
 	@Test
 	public void test() throws OpenemsException, Exception {
-		var sut = new SumImpl();
-		var cm = new DummyConfigurationAdmin();
-		var grid = new DummyElectricityMeter("meter0") //
+		final var sut = new SumImpl();
+		final var grid = new DummyElectricityMeter("meter0") //
 				.withMeterType(GRID); //
-		var pv = new DummyElectricityMeter("meter1") //
+		final var pv = new DummyElectricityMeter("meter1") //
 				.withMeterType(MeterType.PRODUCTION); //
-		var test = new ComponentTest(sut) //
+		final var evcs = new DummyManagedEvcs("evcs0", new DummyEvcsPower(new DisabledRampFilter())) //
+				.withMeterType(MeterType.MANAGED_CONSUMPTION_METERED);
+		final var test = new ComponentTest(sut) //
 				.addComponent(grid) //
 				.addComponent(pv) //
-				.addReference("cm", cm) //
+				.addComponent(evcs) //
+				.addReference("cm", new DummyConfigurationAdmin()) //
 				.addReference("componentManager", new DummyComponentManager()) //
 				.activate(MyConfig.create() //
 						.setGridMinActivePower(0) //
@@ -42,8 +49,16 @@ public void test() throws OpenemsException, Exception {
 
 		grid.withActivePower(-1000);
 		pv.withActivePower(5555);
+		evcs.withActivePower(1000);
 		test.next(new TestCase() //
 				.onBeforeProcessImage(() -> sut.updateChannelsBeforeProcessImage()) //
+				.output(GRID_ACTIVE_POWER, -1000) //
+				.output(PRODUCTION_ACTIVE_POWER, 5555) //
+				.output(ESS_ACTIVE_POWER, null) //
+				.output(ESS_DISCHARGE_POWER, null) //
+				.output(CONSUMPTION_ACTIVE_POWER, 4555) //
+				.output(UNMANAGED_CONSUMPTION_ACTIVE_POWER, 3555) //
+
 				.output(GRID_MIN_ACTIVE_POWER, -1000) //
 				.output(GRID_MAX_ACTIVE_POWER, 0) //
 				.output(PRODUCTION_MAX_ACTIVE_POWER, 5555) //
@@ -53,6 +68,13 @@ public void test() throws OpenemsException, Exception {
 		pv.withActivePower(6666);
 		test.next(new TestCase() //
 				.onBeforeProcessImage(() -> sut.updateChannelsBeforeProcessImage()) //
+				.output(GRID_ACTIVE_POWER, -2000) //
+				.output(PRODUCTION_ACTIVE_POWER, 6666) //
+				.output(ESS_ACTIVE_POWER, null) //
+				.output(ESS_DISCHARGE_POWER, null) //
+				.output(CONSUMPTION_ACTIVE_POWER, 4666) //
+				.output(UNMANAGED_CONSUMPTION_ACTIVE_POWER, 3666) //
+
 				.output(GRID_MIN_ACTIVE_POWER, -2000) //
 				.output(GRID_MAX_ACTIVE_POWER, 0) //
 				.output(PRODUCTION_MAX_ACTIVE_POWER, 6666) //
@@ -61,6 +83,13 @@ public void test() throws OpenemsException, Exception {
 		grid.withActivePower(3000);
 		test.next(new TestCase() //
 				.onBeforeProcessImage(() -> sut.updateChannelsBeforeProcessImage()) //
+				.output(GRID_ACTIVE_POWER, 3000) //
+				.output(PRODUCTION_ACTIVE_POWER, 6666) //
+				.output(ESS_ACTIVE_POWER, null) //
+				.output(ESS_DISCHARGE_POWER, null) //
+				.output(CONSUMPTION_ACTIVE_POWER, 9666) //
+				.output(UNMANAGED_CONSUMPTION_ACTIVE_POWER, 8666) //
+
 				.output(GRID_MIN_ACTIVE_POWER, -2000) //
 				.output(GRID_MAX_ACTIVE_POWER, 3000) //
 				.output(PRODUCTION_MAX_ACTIVE_POWER, 6666) //
diff --git a/io.openems.edge.edge2edge/.classpath b/io.openems.edge.edge2edge/.classpath
index bbfbdbe40e7..b4cffd0fe60 100644
--- a/io.openems.edge.edge2edge/.classpath
+++ b/io.openems.edge.edge2edge/.classpath
@@ -1,7 +1,7 @@
 
 
 	
-	
+	
 	
 	
 		
diff --git a/io.openems.edge.edge2edge/src/io/openems/edge/edge2edge/common/AbstractEdge2Edge.java b/io.openems.edge.edge2edge/src/io/openems/edge/edge2edge/common/AbstractEdge2Edge.java
index 9dae53bdbeb..e700699800e 100644
--- a/io.openems.edge.edge2edge/src/io/openems/edge/edge2edge/common/AbstractEdge2Edge.java
+++ b/io.openems.edge.edge2edge/src/io/openems/edge/edge2edge/common/AbstractEdge2Edge.java
@@ -1,6 +1,7 @@
 package io.openems.edge.edge2edge.common;
 
 import static io.openems.edge.bridge.modbus.api.ModbusUtils.readElementOnce;
+import static io.openems.edge.bridge.modbus.api.ModbusUtils.FunctionCode.FC3;
 import static java.util.concurrent.CompletableFuture.completedFuture;
 
 import java.util.ArrayDeque;
@@ -79,7 +80,7 @@ protected boolean activate(ComponentContext context, String id, String alias, bo
 				return;
 			}
 
-			readElementOnce(this.modbusProtocol, ModbusUtils::retryOnNull, new UnsignedWordElement(1))
+			readElementOnce(FC3, this.modbusProtocol, ModbusUtils::retryOnNull, new UnsignedWordElement(1))
 					.thenAccept(value -> {
 						if (value == null) {
 							return;
@@ -124,7 +125,7 @@ protected void deactivate() {
 	 * @return a future true if it is OpenEMS; otherwise false
 	 */
 	private CompletableFuture isOpenems() {
-		return readElementOnce(this.modbusProtocol, ModbusUtils::retryOnNull, new UnsignedWordElement(0)) //
+		return readElementOnce(FC3, this.modbusProtocol, ModbusUtils::retryOnNull, new UnsignedWordElement(0)) //
 				.thenCompose(value -> completedFuture(isHashEqual(value, "OpenEMS")));
 	}
 
@@ -161,7 +162,7 @@ private CompletableFuture findComponentBlock(String componentId, int st
 	}
 
 	private void _findComponentBlock(CompletableFuture result, String componentId, int startAddress) {
-		readElementOnce(this.modbusProtocol, ModbusUtils::retryOnNull, new StringWordElement(startAddress, 16)) //
+		readElementOnce(FC3, this.modbusProtocol, ModbusUtils::retryOnNull, new StringWordElement(startAddress, 16)) //
 				.thenAccept(remoteComponentId -> {
 					if (remoteComponentId == null) {
 						result.completeExceptionally(
@@ -173,7 +174,7 @@ private void _findComponentBlock(CompletableFuture result, String compo
 						result.complete(startAddress);
 						return;
 					}
-					readElementOnce(this.modbusProtocol, ModbusUtils::retryOnNull,
+					readElementOnce(FC3, this.modbusProtocol, ModbusUtils::retryOnNull,
 							new UnsignedWordElement(startAddress + 16)) //
 							.thenAccept(lengthOfBlock -> {
 								this._findComponentBlock(result, componentId, startAddress + lengthOfBlock);
@@ -182,7 +183,8 @@ private void _findComponentBlock(CompletableFuture result, String compo
 	}
 
 	private CompletableFuture readNatureBlocks(int startAddress) {
-		return readElementOnce(this.modbusProtocol, ModbusUtils::doNotRetry, new UnsignedWordElement(startAddress + 16))
+		return readElementOnce(FC3, this.modbusProtocol, ModbusUtils::doNotRetry,
+				new UnsignedWordElement(startAddress + 16)) //
 				.thenCompose(lengthOfComponentBlock ->
 				// TODO fix length of last component blocks in Slave Modbus/TCP-Api
 				this.readNatureStartAddresses(startAddress + 20,
@@ -344,20 +346,14 @@ protected abstract io.openems.edge.common.channel.ChannelId getWriteChannelId(
 	 * @return the {@link AbstractModbusElement}
 	 */
 	private static ModbusElement generateModbusElement(ModbusType type, int address) {
-		switch (type) {
-		case ENUM16:
-		case UINT16:
-			return new UnsignedWordElement(address);
-		case UINT32:
-			return new UnsignedDoublewordElement(address);
-		case FLOAT32:
-			return new FloatDoublewordElement(address);
-		case FLOAT64:
-			return new UnsignedQuadruplewordElement(address);
-		case STRING16:
-			return new StringWordElement(address, 16);
-		}
-		return null;
+		return switch (type) {
+		case ENUM16, UINT16 -> new UnsignedWordElement(address);
+		case UINT32 -> new UnsignedDoublewordElement(address);
+		case UINT64 -> new UnsignedQuadruplewordElement(address);
+		case FLOAT32 -> new FloatDoublewordElement(address);
+		case FLOAT64 -> new UnsignedQuadruplewordElement(address);
+		case STRING16 -> new StringWordElement(address, 16);
+		};
 	}
 
 	/**
@@ -419,7 +415,7 @@ private CompletableFuture> readNatureStartAddresses(int
 
 	private void _readNatureStartAddresses(CompletableFuture> result, int startAddress,
 			int lastAddress, final TreeMap natureStartAddresses) {
-		readElementOnce(this.modbusProtocol, ModbusUtils::retryOnNull, new UnsignedWordElement(startAddress))
+		readElementOnce(FC3, this.modbusProtocol, ModbusUtils::retryOnNull, new UnsignedWordElement(startAddress))
 				.thenAccept(rawHash -> {
 					if (rawHash == null) {
 						result.completeExceptionally(new OpenemsException("Unable to read hash at " + startAddress));
@@ -427,7 +423,7 @@ private void _readNatureStartAddresses(CompletableFuture
 					}
 					var hash = (short) (int) rawHash;
 
-					readElementOnce(this.modbusProtocol, ModbusUtils::doNotRetry,
+					readElementOnce(FC3, this.modbusProtocol, ModbusUtils::doNotRetry,
 							new UnsignedWordElement(startAddress + 1)).thenAccept(lengthOfNatureBlock -> {
 								this.logInfo(this.log, "Found Remote-Nature '0x" + Integer.toHexString(hash & 0xffff)
 										+ "' on address " + startAddress);
diff --git a/io.openems.edge.edge2edge/src/io/openems/edge/edge2edge/meter/Config.java b/io.openems.edge.edge2edge/src/io/openems/edge/edge2edge/meter/Config.java
index 2622f6c7e6d..81e8c94500b 100644
--- a/io.openems.edge.edge2edge/src/io/openems/edge/edge2edge/meter/Config.java
+++ b/io.openems.edge.edge2edge/src/io/openems/edge/edge2edge/meter/Config.java
@@ -3,7 +3,7 @@
 import org.osgi.service.metatype.annotations.AttributeDefinition;
 import org.osgi.service.metatype.annotations.ObjectClassDefinition;
 
-import io.openems.edge.meter.api.MeterType;
+import io.openems.common.types.MeterType;
 
 @ObjectClassDefinition(//
 		name = "Edge-2-Edge Meter", //
diff --git a/io.openems.edge.edge2edge/src/io/openems/edge/edge2edge/meter/Edge2EdgeMeterImpl.java b/io.openems.edge.edge2edge/src/io/openems/edge/edge2edge/meter/Edge2EdgeMeterImpl.java
index 7890cb4b9a5..5f9fd7402ea 100644
--- a/io.openems.edge.edge2edge/src/io/openems/edge/edge2edge/meter/Edge2EdgeMeterImpl.java
+++ b/io.openems.edge.edge2edge/src/io/openems/edge/edge2edge/meter/Edge2EdgeMeterImpl.java
@@ -17,6 +17,7 @@
 
 import io.openems.common.channel.AccessMode;
 import io.openems.common.exceptions.OpenemsException;
+import io.openems.common.types.MeterType;
 import io.openems.edge.bridge.modbus.api.BridgeModbus;
 import io.openems.edge.bridge.modbus.api.ModbusComponent;
 import io.openems.edge.common.component.OpenemsComponent;
@@ -25,7 +26,6 @@
 import io.openems.edge.edge2edge.common.AbstractEdge2Edge;
 import io.openems.edge.edge2edge.common.Edge2Edge;
 import io.openems.edge.meter.api.ElectricityMeter;
-import io.openems.edge.meter.api.MeterType;
 
 @Designate(ocd = Config.class, factory = true)
 @Component(//
diff --git a/io.openems.edge.edge2edge/test/io/openems/edge/edge2edge/ess/Edge2EdgeEssImplTest.java b/io.openems.edge.edge2edge/test/io/openems/edge/edge2edge/ess/Edge2EdgeEssImplTest.java
index 90317ad9e80..6b200696166 100644
--- a/io.openems.edge.edge2edge/test/io/openems/edge/edge2edge/ess/Edge2EdgeEssImplTest.java
+++ b/io.openems.edge.edge2edge/test/io/openems/edge/edge2edge/ess/Edge2EdgeEssImplTest.java
@@ -10,19 +10,16 @@
 
 public class Edge2EdgeEssImplTest {
 
-	private static final String COMPONENT_ID = "ess0";
-	private static final String MODBUS_ID = "modbus0";
-
 	@Test
 	public void test() throws Exception {
 		new ComponentTest(new Edge2EdgeEssImpl()) //
 				.addReference("cm", new DummyConfigurationAdmin()) //
-				.addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) //
+				.addReference("setModbus", new DummyModbusBridge("modbus0")) //
 				.activate(MyConfig.create() //
-						.setId(COMPONENT_ID) //
-						.setModbusId(MODBUS_ID) //
+						.setId("ess0") //
+						.setModbusId("modbus0") //
 						.setRemoteAccessMode(AccessMode.READ_WRITE) //
-						.setRemoteComponentId(COMPONENT_ID) //
+						.setRemoteComponentId("ess0") //
 						.build())
 				.next(new TestCase());
 	}
diff --git a/io.openems.edge.edge2edge/test/io/openems/edge/edge2edge/meter/Edge2EdgeEssMeterImplTest.java b/io.openems.edge.edge2edge/test/io/openems/edge/edge2edge/meter/Edge2EdgeEssMeterImplTest.java
index a52aed0a22b..ab0e6bd99ac 100644
--- a/io.openems.edge.edge2edge/test/io/openems/edge/edge2edge/meter/Edge2EdgeEssMeterImplTest.java
+++ b/io.openems.edge.edge2edge/test/io/openems/edge/edge2edge/meter/Edge2EdgeEssMeterImplTest.java
@@ -1,28 +1,26 @@
 package io.openems.edge.edge2edge.meter;
 
+import static io.openems.common.types.MeterType.PRODUCTION;
+
 import org.junit.Test;
 
 import io.openems.edge.bridge.modbus.test.DummyModbusBridge;
 import io.openems.edge.common.test.AbstractComponentTest.TestCase;
 import io.openems.edge.common.test.ComponentTest;
 import io.openems.edge.common.test.DummyConfigurationAdmin;
-import io.openems.edge.meter.api.MeterType;
 
 public class Edge2EdgeEssMeterImplTest {
 
-	private static final String COMPONENT_ID = "meter0";
-	private static final String MODBUS_ID = "modbus0";
-
 	@Test
 	public void test() throws Exception {
 		new ComponentTest(new Edge2EdgeMeterImpl()) //
 				.addReference("cm", new DummyConfigurationAdmin()) //
-				.addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) //
+				.addReference("setModbus", new DummyModbusBridge("modbus0")) //
 				.activate(MyConfig.create() //
-						.setId(COMPONENT_ID) //
-						.setModbusId(MODBUS_ID) //
-						.setRemoteComponentId(COMPONENT_ID) //
-						.setMeterType(MeterType.PRODUCTION) //
+						.setId("meter0") //
+						.setModbusId("modbus0") //
+						.setRemoteComponentId("meter0") //
+						.setMeterType(PRODUCTION) //
 						.build())
 				.next(new TestCase());
 	}
diff --git a/io.openems.edge.edge2edge/test/io/openems/edge/edge2edge/meter/MyConfig.java b/io.openems.edge.edge2edge/test/io/openems/edge/edge2edge/meter/MyConfig.java
index a78c1af3e8a..5a10b708e13 100644
--- a/io.openems.edge.edge2edge/test/io/openems/edge/edge2edge/meter/MyConfig.java
+++ b/io.openems.edge.edge2edge/test/io/openems/edge/edge2edge/meter/MyConfig.java
@@ -1,8 +1,8 @@
 package io.openems.edge.edge2edge.meter;
 
 import io.openems.common.test.AbstractComponentConfig;
+import io.openems.common.types.MeterType;
 import io.openems.common.utils.ConfigUtils;
-import io.openems.edge.meter.api.MeterType;
 
 @SuppressWarnings("all")
 public class MyConfig extends AbstractComponentConfig implements Config {
diff --git a/io.openems.edge.energy.api/.classpath b/io.openems.edge.energy.api/.classpath
index bbfbdbe40e7..b4cffd0fe60 100644
--- a/io.openems.edge.energy.api/.classpath
+++ b/io.openems.edge.energy.api/.classpath
@@ -1,7 +1,7 @@
 
 
 	
-	
+	
 	
 	
 		
diff --git a/io.openems.edge.energy.api/bnd.bnd b/io.openems.edge.energy.api/bnd.bnd
index 49a30e8f94b..5dc57c9e038 100644
--- a/io.openems.edge.energy.api/bnd.bnd
+++ b/io.openems.edge.energy.api/bnd.bnd
@@ -8,8 +8,12 @@ Bundle-Version: 1.0.0.${tstamp}
 	io.openems.common,\
 	io.openems.edge.common,\
 	io.openems.edge.controller.api,\
+	io.openems.edge.ess.api,\
+	io.openems.edge.evcs.api,\
+	io.openems.edge.meter.api,\
 	io.openems.edge.predictor.api,\
 	io.openems.edge.timeofusetariff.api,\
+	org.apache.commons.math3,\
 	
 -testpath: \
 	${testpath},\
diff --git a/io.openems.edge.energy.api/src/io/openems/edge/energy/api/EnergyConstants.java b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/EnergyConstants.java
new file mode 100644
index 00000000000..0cca2cc68df
--- /dev/null
+++ b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/EnergyConstants.java
@@ -0,0 +1,15 @@
+package io.openems.edge.energy.api;
+
+import io.openems.common.types.ChannelAddress;
+
+public class EnergyConstants {
+
+	public static final int PERIODS_PER_HOUR = 4;
+
+	public static final ChannelAddress SUM_PRODUCTION = new ChannelAddress("_sum", "ProductionActivePower");
+	public static final ChannelAddress SUM_UNMANAGED_CONSUMPTION = new ChannelAddress("_sum",
+			"UnmanagedConsumptionActivePower");
+
+	private EnergyConstants() {
+	}
+}
diff --git a/io.openems.edge.energy.api/src/io/openems/edge/energy/api/EnergySchedulable.java b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/EnergySchedulable.java
index cf2f7e66e0d..32f662788f1 100644
--- a/io.openems.edge.energy.api/src/io/openems/edge/energy/api/EnergySchedulable.java
+++ b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/EnergySchedulable.java
@@ -1,13 +1,13 @@
 package io.openems.edge.energy.api;
 
-import io.openems.edge.controller.api.Controller;
+import io.openems.edge.common.component.OpenemsComponent;
 
-public interface EnergySchedulable extends Controller {
+public interface EnergySchedulable extends OpenemsComponent {
 
 	/**
 	 * Get the {@link EnergyScheduleHandler}.
 	 * 
 	 * @return {@link EnergyScheduleHandler}
 	 */
-	public EnergyScheduleHandler getEnergyScheduleHandler();
+	public EnergyScheduleHandler getEnergyScheduleHandler();
 }
diff --git a/io.openems.edge.energy.api/src/io/openems/edge/energy/api/EnergyScheduleHandler.java b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/EnergyScheduleHandler.java
index ca5ca2ebc45..5bd30a68fb6 100644
--- a/io.openems.edge.energy.api/src/io/openems/edge/energy/api/EnergyScheduleHandler.java
+++ b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/EnergyScheduleHandler.java
@@ -1,76 +1,609 @@
 package io.openems.edge.energy.api;
 
+import static com.google.common.base.MoreObjects.toStringHelper;
 import static io.openems.common.utils.DateUtils.roundDownToQuarter;
 
+import java.time.Clock;
 import java.time.ZonedDateTime;
-import java.util.Optional;
+import java.util.Arrays;
+import java.util.List;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.IntFunction;
 import java.util.function.Supplier;
+import java.util.stream.IntStream;
 
-import com.google.common.collect.ImmutableMap;
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableSortedMap;
 
-public class EnergyScheduleHandler {
+import io.openems.edge.controller.api.Controller;
+import io.openems.edge.energy.api.simulation.EnergyFlow;
+import io.openems.edge.energy.api.simulation.GlobalSimulationsContext;
+import io.openems.edge.energy.api.simulation.OneSimulationContext;
 
-	private final Supplier availableStates;
-	private final Supplier context;
-
-	private ImmutableMap> schedule = ImmutableMap.of();
-
-	public EnergyScheduleHandler(Supplier availableStates, Supplier context) {
-		this.availableStates = availableStates;
-		this.context = context;
-	}
+public sealed interface EnergyScheduleHandler {
 
 	/**
-	 * Gets the available States.
+	 * Triggers Rescheduling by the Energy Scheduler.
 	 * 
-	 * @return an Array of States
+	 * @param reason a reason
 	 */
-	public STATE[] getAvailableStates() {
-		return this.availableStates.get();
-	}
+	public void triggerReschedule(String reason);
 
-	/**
-	 * Gets the Context.
-	 * 
-	 * @return the Context
-	 */
-	public CONTEXT getContext() {
-		return this.context.get();
-	}
+	public abstract static sealed class AbstractEnergyScheduleHandler implements EnergyScheduleHandler {
 
-	public static record Period(STATE state, Integer essChargeInChargeGrid) {
-	}
+		private final Function contextFunction;
 
-	/**
-	 * Sets the Schedule. Called by Optimizer.
-	 * 
-	 * @param schedule the Schedule
-	 */
-	public synchronized void setSchedule(ImmutableMap> schedule) {
-		this.schedule = schedule;
-	}
+		protected Clock clock;
+		protected CONTEXT context;
+		private Consumer onRescheduleCallback;
 
-	/**
-	 * Gets the current State or null.
-	 * 
-	 * @return the State or null
-	 */
-	public synchronized STATE getCurrentState() {
-		return Optional.ofNullable(this.schedule.get(roundDownToQuarter(ZonedDateTime.now()))) //
-				.map(Period::state) //
-				.orElse(null);
+		public AbstractEnergyScheduleHandler(Function contextFunction) {
+			this.contextFunction = contextFunction;
+		}
+
+		/**
+		 * Initialize the {@link EnergyScheduleHandler}.
+		 * 
+		 * 

+ * This method is called internally before a Simulation is executed. + * + * @param asc the {@link GlobalSimulationsContext} + */ + public void initialize(GlobalSimulationsContext asc) { + this.clock = asc.clock(); + this.context = this.contextFunction.apply(asc); + } + + /** + * This method sets the callback for events that require Rescheduling. + * + * @param callback the {@link Consumer} callback with a reason + */ + public synchronized void setOnRescheduleCallback(Consumer callback) { + this.onRescheduleCallback = callback; + } + + /** + * This method removes the callback. + */ + public synchronized void removeOnRescheduleCallback() { + this.onRescheduleCallback = null; + } + + @Override + public void triggerReschedule(String reason) { + var onRescheduleCallback = this.onRescheduleCallback; + if (onRescheduleCallback != null) { + onRescheduleCallback.accept(reason); + } + } + + protected ZonedDateTime getNow() { + var clock = this.clock; + if (clock != null) { + return ZonedDateTime.now(clock); + } + return ZonedDateTime.now(); + } + + protected void buildToString(MoreObjects.ToStringHelper toStringHelper) { + var context = this.context; + if (context != null) { + toStringHelper.addValue(context); + } + } } - // TODO hacky... find a better way! - /** - * Gets the current essChargeInChargeGrid or null. - * - * @return the essChargeInChargeGrid or null - */ - public synchronized Integer getCurrentEssChargeInChargeGrid() { - return Optional.ofNullable(this.schedule.get(roundDownToQuarter(ZonedDateTime.now()))) // - .map(Period::essChargeInChargeGrid) // - .orElse(null); + public static final class WithDifferentStates extends AbstractEnergyScheduleHandler { + + public static final class Builder { + + private STATE defaultState; + private Supplier availableStatesSupplier; + private Function contextFunction; + private Function>> initialPopulationsFunction = gsc -> List + .of(); + private WithDifferentStates.Simulator simulator; + private WithDifferentStates.PostProcessor postProcessor = PostProcessor.doNothing(); + + /** + * Sets the default State if no other is explicitly scheduled. + * + * @param state a state + * @return myself + */ + public Builder setDefaultState(STATE state) { + this.defaultState = state; + return this; + } + + /** + * Sets a {@link Function} to create a {@link GlobalSimulationsContext}. + * + * @param contextFunction the context function + * @return myself + */ + public Builder setContextFunction( + Function contextFunction) { + this.contextFunction = contextFunction; + return this; + } + + /** + * Sets a {@link Function} to provide {@link InitialPopulation}s. + * + * @param initialPopulationsFunction the function + * @return myself + */ + public Builder setInitialPopulationsFunction( + Function>> initialPopulationsFunction) { + this.initialPopulationsFunction = initialPopulationsFunction; + return this; + } + + /** + * Sets a {@link Supplier} for available States. + * + * @param supplier a states supplier + * @return myself + */ + public Builder setAvailableStates(Supplier supplier) { + this.availableStatesSupplier = supplier; + return this; + } + + /** + * Sets a {@link WithDifferentStates.Simulator} that modifies a given + * {@link EnergyFlow}. + * + * @param simulator a simulator + * @return myself + */ + public Builder setSimulator(WithDifferentStates.Simulator simulator) { + this.simulator = simulator; + return this; + } + + /** + * Sets a {@link PostProcessor}. + * + * @param postProcessor a {@link PostProcessor} + * @return myself + */ + public Builder setPostProcessor( + WithDifferentStates.PostProcessor postProcessor) { + this.postProcessor = postProcessor; + return this; + } + + /** + * Builds the {@link EnergyScheduleHandler.WithDifferentStates} instance. + * + * @return a {@link EnergyScheduleHandler.WithDifferentStates} + */ + public WithDifferentStates build() { + return new EnergyScheduleHandler.WithDifferentStates(this.defaultState, + this.availableStatesSupplier, this.contextFunction, this.initialPopulationsFunction, + this.simulator, this.postProcessor); + } + } + + /** + * Create a {@link EnergyScheduleHandler.WithDifferentStates} for a + * {@link Controller} with different states that can be evaluated. + * + * @param the type of the State + * @param the type of the Context + * @return a {@link Builder} + */ + public static Builder create() { + return new Builder(); + } + + public static interface Simulator { + /** + * Simulates a Period. + * + * @param osc the {@link OneSimulationContext} + * @param period the {@link GlobalSimulationsContext.Period} + * @param model the {@link EnergyFlow.Model} + * @param context the Controller Context + * @param state the simulated State + * @return additional cost to be considered by the cost function + */ + public double simulate(OneSimulationContext osc, GlobalSimulationsContext.Period period, + EnergyFlow.Model model, CONTEXT context, STATE state); + } + + public static interface PostProcessor { + + /** + * A 'do-nothing' {@link PostProcessor}. + * + * @param the type of the State + * @param the type of the Context + * @return the same State + */ + public static PostProcessor doNothing() { + return (osc, energyFlow, context, state) -> state; + } + + /** + * Post-Process a state of a Period during Simulation, i.e. replace with + * 'better' state with the equivalent behaviour. + * + *

+ * NOTE: heavy computation is ok here, because this method is called only at the + * end with the best Schedule. + * + * @param osc the {@link OneSimulationContext} + * @param energyFlow the {@link EnergyFlow} + * @param context the Controller Context + * @param state the initial state + * @return the new state + */ + public STATE postProcess(OneSimulationContext osc, EnergyFlow energyFlow, CONTEXT context, STATE state); + } + + private final STATE defaultState; + private final Supplier availableStatesSupplier; + private final Function>> initialPopulationsFunction; + private final Simulator simulator; + private final WithDifferentStates.PostProcessor postProcessor; + private final SortedMap> schedule = new TreeMap<>(); + + private STATE[] availableStates; + + private WithDifferentStates(// + STATE defaultState, // + Supplier availableStatesSupplier, // + Function contextFunction, // + Function>> initialPopulationsFunction, // + Simulator simulator, // + WithDifferentStates.PostProcessor postProcessor) { + super(contextFunction); + this.defaultState = defaultState; + this.availableStatesSupplier = availableStatesSupplier; + this.initialPopulationsFunction = initialPopulationsFunction; + this.simulator = simulator; + this.postProcessor = postProcessor; + } + + @Override + public void initialize(GlobalSimulationsContext asc) { + super.initialize(asc); + this.availableStates = this.availableStatesSupplier.get(); + } + + public static record InitialPopulation(List periods, STATE state) { + + /** + * Creates a {@link InitialPopulation} record. + * + * @param the type of the State + * @param periods a List of {@link ZonedDateTime}s + * @param state the state + * @return a {@link InitialPopulation} record + */ + public static InitialPopulation of(List periods, STATE state) { + return new InitialPopulation(periods, state); + } + } + + /** + * Generates {@link InitialPopulation}s for this + * {@link EnergyScheduleHandler.WithDifferentStates}. + * + * @param gsc the {@link GlobalSimulationsContext} + * @return a List of {@link InitialPopulation}s + */ + public List> getInitialPopulations(GlobalSimulationsContext gsc) { + return this.initialPopulationsFunction.apply(gsc); + } + + /** + * Gets the default State. + * + * @return the default State + */ + public STATE getDefaultState() { + return this.defaultState; + } + + /** + * Gets the index of the default State. + * + * @return the index of the default State + */ + public int getDefaultStateIndex() { + var states = this.availableStates; + if (states == null) { + throw new IllegalAccessError( + "EnergySchedulerHandler is uninitialized. `initialize()` must be called first."); + } + return IntStream.range(0, states.length) // + .filter(i -> states[i] == this.defaultState) // + .findFirst() // + .orElse(0 /* fallback */); + } + + /** + * Gets the available States. + * + * @return an Array of States + */ + public STATE[] getAvailableStates() { + return this.availableStates; + } + + /** + * Simulates a Period. + * + * @param osc the {@link OneSimulationContext} + * @param period the simulated {@link GlobalSimulationsContext.Period} + * @param model the {@link EnergyFlow.Model} + * @param stateIndex the index of the simulated state + * @return additional cost to be considered by the cost function + */ + public double simulatePeriod(OneSimulationContext osc, GlobalSimulationsContext.Period period, + EnergyFlow.Model model, int stateIndex) { + return this.simulator.simulate(osc, period, model, this.context, this.availableStates[stateIndex]); + } + + /** + * Post-processes a Period of the best Schedule. + * + *

+ * This method is called internally after the Simulations are executed with the + * found best Schedule. + * + * @param period the {@link GlobalSimulationsContext.Period} + * @param osc the {@link OneSimulationContext} + * @param energyFlow the {@link EnergyFlow} + * @param stateIndex the index of the simulated state + * @return the post-processed state index + */ + public int postProcessPeriod(GlobalSimulationsContext.Period period, OneSimulationContext osc, + EnergyFlow energyFlow, int stateIndex) { + return this.getStateIndex( + this.postProcessor.postProcess(osc, energyFlow, this.context, this.availableStates[stateIndex])); + } + + public static record Period( + /** STATE of the Period */ + STATE state, + /** Price [1/MWh] */ + double price, // + /** EnergyScheduleHandler Context */ + CONTEXT context, // + /** Simulated EnergyFlow */ + EnergyFlow energyFlow, // + /** the initial ESS energy in the beginning of the period in [Wh] */ + int essInitialEnergy) { + + /** + * This class is only used internally to apply the Schedule. + */ + public static record Transition(int stateIndex, double price, EnergyFlow energyFlow, int essInitialEnergy) { + } + + /** + * Builds a {@link EnergyScheduleHandler.WithDifferentStates.Period} from a + * {@link EnergyScheduleHandler.WithDifferentStates.Period.Transition} record. + * + * @param the type of the State + * @param the type of the Context + * @param t the + * {@link EnergyScheduleHandler.WithDifferentStates.Period.Transition} + * record + * @param getState a method to translate a 'stateIndex' to a STATE + * @param context the CONTEXT used during simulation + * @return a {@link Period} record + */ + public static Period fromTransitionRecord(Period.Transition t, + IntFunction getState, CONTEXT context) { + return new Period<>(getState.apply(t.stateIndex), t.price, context, t.energyFlow, t.essInitialEnergy); + } + } + + /** + * Applies a new Schedule. + * + *

+ * This method is called by the {@link EnergyScheduler}. + * + * @param schedule the new Schedule as Map of ZonedDateTime to State-Index + */ + public void applySchedule(ImmutableSortedMap schedule) { + final var thisQuarter = roundDownToQuarter(this.getNow()); + final var nextQuarter = thisQuarter.plusMinutes(15); + final var currentContext = this.context; + synchronized (this.schedule) { + // Clear outdated entries + this.schedule.headMap(thisQuarter).clear(); + + // Remove future entries + this.schedule.tailMap(nextQuarter).clear(); + + // Update entries from param + var states = this.availableStates; + if (states.length == 0) { + System.err.println("States is empty!"); // TODO proper log + return; + } + schedule.forEach((k, t) -> { + this.schedule.put(k, Period.fromTransitionRecord(t, this::getState, currentContext)); + }); + } + } + + /** + * Gets a copy of the current Schedule. + * + * @return the Schedule + */ + public ImmutableSortedMap> getSchedule() { + synchronized (this.schedule) { + return ImmutableSortedMap.copyOfSorted(this.schedule); + } + } + + /** + * Gets the current {@link Period} record. + * + * @return the record of the currently scheduled Period; possibly null + */ + public Period getCurrentPeriod() { + synchronized (this.schedule) { + final var thisQuarter = roundDownToQuarter(this.getNow()); + return this.schedule.get(thisQuarter); + } + } + + /** + * Gets the string representation for the given stateIndex. + * + * @param stateIndex the index of the state + * @return string representation + */ + public String toStateString(int stateIndex) { + return this.getState(stateIndex).toString(); + } + + /** + * Gets the STATE for the given stateIndex. + * + * @param stateIndex the stateIndex + * @return the STATE + */ + private STATE getState(int stateIndex) { + var states = this.availableStates; + return stateIndex < states.length // + ? states[stateIndex] // + : this.defaultState; + } + + /** + * Gets the stateIndex for the given STATE. + * + * @param state the STATE + * @return the stateIndex; or zero if not found + */ + private int getStateIndex(STATE state) { + var states = this.availableStates; + for (var i = 0; i < states.length; i++) { + if (states[i] == state) { + return i; + } + } + return 0; + } + + @Override + public String toString() { + var toStringHelper = toStringHelper("ESH.WithDifferentStates"); + var availableStates = this.availableStates; + if (availableStates != null) { + toStringHelper.add("availableStates", Arrays.toString(availableStates)); + } + super.buildToString(toStringHelper); + return toStringHelper.toString(); + } } + public static final class WithOnlyOneState extends AbstractEnergyScheduleHandler { + + public static final class Builder { + + private Function contextFunction; + private WithOnlyOneState.Simulator simulator; + + /** + * Sets a {@link Function} to create a {@link GlobalSimulationsContext}. + * + * @param contextFunction the context function + * @return myself + */ + public Builder setContextFunction(Function contextFunction) { + this.contextFunction = contextFunction; + return this; + } + + /** + * Sets a {@link WithDifferentStates.Simulator} that modifies a given + * {@link EnergyFlow}. + * + * @param simulator a simulator + * @return myself + */ + public Builder setSimulator(WithOnlyOneState.Simulator simulator) { + this.simulator = simulator; + return this; + } + + /** + * Builds the {@link EnergyScheduleHandler.WithDifferentStates} instance. + * + * @return a {@link EnergyScheduleHandler.WithDifferentStates} + */ + public WithOnlyOneState build() { + return new EnergyScheduleHandler.WithOnlyOneState(this.contextFunction, this.simulator); + } + } + + /** + * Create a {@link EnergyScheduleHandler.WithOnlyOneState} for a + * {@link Controller} with only a single state. + * + * @param the type of the Context + * @return a {@link Builder} + */ + public static Builder create() { + return new Builder(); + } + + public static interface Simulator { + /** + * Simulates a Period. + * + * @param osc the {@link OneSimulationContext} + * @param period the {@link GlobalSimulationsContext.Period} + * @param model the {@link EnergyFlow.Model} + * @param context the Controller Context + */ + public void simulate(OneSimulationContext osc, GlobalSimulationsContext.Period period, + EnergyFlow.Model model, CONTEXT context); + } + + private final Simulator simulator; + + private WithOnlyOneState(// + Function contextFunction, // + Simulator simulator) { + super(contextFunction); + this.simulator = simulator; + } + + /** + * Simulates a Period. + * + * @param simContext the {@link OneSimulationContext} + * @param period the {@link GlobalSimulationsContext.Period} + * @param model the {@link EnergyFlow.Model} + */ + public void simulatePeriod(OneSimulationContext simContext, GlobalSimulationsContext.Period period, + EnergyFlow.Model model) { + this.simulator.simulate(simContext, period, model, this.context); + } + + @Override + public String toString() { + var toStringHelper = toStringHelper("ESH.WithOnlyOneState"); + super.buildToString(toStringHelper); + return toStringHelper.toString(); + } + } } diff --git a/io.openems.edge.energy.api/src/io/openems/edge/energy/api/EnergyScheduler.java b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/EnergyScheduler.java index 7d58264b26b..07fa09550e8 100644 --- a/io.openems.edge.energy.api/src/io/openems/edge/energy/api/EnergyScheduler.java +++ b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/EnergyScheduler.java @@ -1,19 +1,22 @@ package io.openems.edge.energy.api; +import io.openems.common.jsonrpc.base.JsonrpcRequest; +import io.openems.common.jsonrpc.base.JsonrpcResponse; +import io.openems.common.types.OpenemsType; import io.openems.edge.common.channel.Doc; import io.openems.edge.common.component.OpenemsComponent; -import io.openems.edge.common.jsonapi.ComponentJsonApi; +import io.openems.edge.common.jsonapi.Call; /** * The global Energy Schedule optimizer singleton. */ -public interface EnergyScheduler extends OpenemsComponent, ComponentJsonApi { +public interface EnergyScheduler extends OpenemsComponent { public static final String SINGLETON_SERVICE_PID = "Core.Energy"; public static final String SINGLETON_COMPONENT_ID = "_energy"; public enum ChannelId implements io.openems.edge.common.channel.ChannelId { - ; + SIMULATIONS_PER_QUARTER(Doc.of(OpenemsType.INTEGER)); private final Doc doc; @@ -26,4 +29,22 @@ public Doc doc() { return this.doc; } } + + /** + * Handles a GetScheduleRequest. + * + * @param call the JsonApi {@link Call} + * @param id the Component-ID of the Controller + * @return the GetScheduleResponse + */ + @Deprecated + public JsonrpcResponse handleGetScheduleRequestV1(Call call, String id); + + /** + * Gets the configured implementation {@link Version}. + * + * @return {@link Version} or null + */ + @Deprecated + public Version getImplementationVersion(); } diff --git a/io.openems.edge.energy.api/src/io/openems/edge/energy/api/EnergyUtils.java b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/EnergyUtils.java new file mode 100644 index 00000000000..9caf820f816 --- /dev/null +++ b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/EnergyUtils.java @@ -0,0 +1,106 @@ +package io.openems.edge.energy.api; + +import static io.openems.edge.common.type.TypeUtils.multiply; +import static io.openems.edge.energy.api.EnergyConstants.PERIODS_PER_HOUR; + +import java.util.stream.Stream; + +import com.google.common.collect.ImmutableList; + +public class EnergyUtils { + + private EnergyUtils() { + } + + /** + * Converts a State-of-Charge [%] to Energy [Wh]. + * + * @param totalEnergy the total energy in [Wh] + * @param soc the State-of-Charge in [%] + * @return the energy in [Wh] + */ + public static int socToEnergy(int totalEnergy, int soc) { + return totalEnergy /* [Wh] */ / 100 * soc; + } + + /** + * Finds the first valley in an array of doubles, e.g. prices. + * + * @param fromIndex start searching from this index + * @param values the values array + * @return the index of the valley + */ + public static int findFirstValleyIndex(int fromIndex, double[] values) { + if (values.length <= fromIndex) { + return fromIndex; + } else { + var previous = values[fromIndex]; + for (var i = fromIndex + 1; i < values.length; i++) { + var value = values[i]; + if (value > previous) { + return i - 1; + } + previous = value; + } + } + return values.length - 1; + } + + /** + * Finds the first peak in an array of doubles, e.g. prices. + * + * @param fromIndex start searching from this index + * @param values the values array + * @return the index of the peak + */ + public static int findFirstPeakIndex(int fromIndex, double[] values) { + if (values.length <= fromIndex) { + return fromIndex; + } else { + var previous = values[fromIndex]; + for (var i = fromIndex + 1; i < values.length; i++) { + var value = values[i]; + if (value < previous) { + return i - 1; + } + previous = value; + } + } + return values.length - 1; + } + + /** + * Converts power [W] to energy [Wh/15 min]. + * + * @param power the power value + * @return the energy value + */ + public static int toEnergy(int power) { + return power / PERIODS_PER_HOUR; + } + + /** + * Converts energy [Wh/15 min] to power [W]. + * + * @param energy the energy value + * @return the power value + */ + public static Integer toPower(Integer energy) { + return multiply(energy, PERIODS_PER_HOUR); + } + + /** + * From a list of {@link EnergyScheduleHandler}s, filters only the ones of type + * {@link EnergyScheduleHandler.WithDifferentStates}. + * + * @param eshs list of {@link EnergyScheduleHandler}s + * @return new stream of {@link EnergyScheduleHandler.WithDifferentStates}s + */ + public static Stream> filterEshsWithDifferentStates( + ImmutableList eshs) { + return eshs.stream() // + .filter(EnergyScheduleHandler.WithDifferentStates.class::isInstance) // + .>map( + EnergyScheduleHandler.WithDifferentStates.class::cast); + } +} diff --git a/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/RiskLevel.java b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/RiskLevel.java similarity index 69% rename from io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/RiskLevel.java rename to io.openems.edge.energy.api/src/io/openems/edge/energy/api/RiskLevel.java index d3623400efa..6fa76b32ff2 100644 --- a/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/RiskLevel.java +++ b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/RiskLevel.java @@ -1,4 +1,4 @@ -package io.openems.edge.controller.ess.timeofusetariff; +package io.openems.edge.energy.api; public enum RiskLevel { @@ -6,14 +6,14 @@ public enum RiskLevel { * Less dependent on predictions. The storage system behavior is less likely to * deviate from the predicted behavior. */ - LOW, + LOW(1.20), /** * Moderately dependent on predictions. The storage system behavior may * occasionally deviate from the predicted behavior but generally stays within * expected parameters. */ - MEDIUM, + MEDIUM(1.17), /** * Heavily reliant on predictions. The storage system behavior is expected to @@ -21,6 +21,12 @@ public enum RiskLevel { * during peak pricing hours or under-consumption for self-sufficiency may still * occur. */ - HIGH + HIGH(1.10); + /** Used to incorporate charge/discharge efficiency. */ + public final double efficiencyFactor; + + private RiskLevel(double efficiencyFactor) { + this.efficiencyFactor = efficiencyFactor; + } } diff --git a/io.openems.edge.energy.api/src/io/openems/edge/energy/api/Version.java b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/Version.java new file mode 100644 index 00000000000..41012cf10de --- /dev/null +++ b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/Version.java @@ -0,0 +1,20 @@ +package io.openems.edge.energy.api; + +public enum Version { + /** + * Version 1. + * + *

+ * Well tested and production ready, but applies only to + * "Controller.Ess.Time-Of-Use-Tariff". + */ + V1_ESS_ONLY, // + /** + * Version 1. + * + *

+ * Work-in-progress that uses new EnergySchedulable interface to provide real + * multi-objective optimization. + */ + V2_ENERGY_SCHEDULABLE +} diff --git a/io.openems.edge.energy.api/src/io/openems/edge/energy/api/simulation/Coefficient.java b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/simulation/Coefficient.java new file mode 100644 index 00000000000..14fa453d462 --- /dev/null +++ b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/simulation/Coefficient.java @@ -0,0 +1,36 @@ +package io.openems.edge.energy.api.simulation; + +import static com.google.common.base.CaseFormat.UPPER_CAMEL; +import static com.google.common.base.CaseFormat.UPPER_UNDERSCORE; + +public enum Coefficient { + /* _sum/Production; positive */ + PROD, + /* _sum/Consumption; positive */ + CONS, + /* _sum/EssActivePower; charge negative; discharge positive */ + ESS, + /* _sum/EssActivePower; sell-to-grid negative, buy-from-grid positive */ + GRID, + /* Production -> Consumption, positive */ + PROD_TO_CONS, + /* Production -> Grid, positive */ + PROD_TO_GRID, + /* Production -> ESS, positive */ + PROD_TO_ESS, + /* Grid -> Consumption, positive */ + GRID_TO_CONS, + /* ESS -> Consumption, positive */ + ESS_TO_CONS, + /* Grid -> ESS, discharge-to-grid negative, charge-from-grid positive */ + GRID_TO_ESS; + + /** + * Gets the {@link Coefficient#name()} in CamelCase. + * + * @return name + */ + public String toCamelCase() { + return UPPER_UNDERSCORE.to(UPPER_CAMEL, this.name()); + } +} \ No newline at end of file diff --git a/io.openems.edge.energy.api/src/io/openems/edge/energy/api/simulation/EnergyFlow.java b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/simulation/EnergyFlow.java new file mode 100644 index 00000000000..c5fe1c3774f --- /dev/null +++ b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/simulation/EnergyFlow.java @@ -0,0 +1,688 @@ +package io.openems.edge.energy.api.simulation; + +import static com.google.common.base.MoreObjects.toStringHelper; +import static io.openems.edge.energy.api.simulation.Coefficient.CONS; +import static io.openems.edge.energy.api.simulation.Coefficient.ESS; +import static io.openems.edge.energy.api.simulation.Coefficient.ESS_TO_CONS; +import static io.openems.edge.energy.api.simulation.Coefficient.GRID; +import static io.openems.edge.energy.api.simulation.Coefficient.GRID_TO_CONS; +import static io.openems.edge.energy.api.simulation.Coefficient.GRID_TO_ESS; +import static io.openems.edge.energy.api.simulation.Coefficient.PROD; +import static io.openems.edge.energy.api.simulation.Coefficient.PROD_TO_CONS; +import static io.openems.edge.energy.api.simulation.Coefficient.PROD_TO_ESS; +import static io.openems.edge.energy.api.simulation.Coefficient.PROD_TO_GRID; +import static java.lang.Double.NaN; +import static java.lang.Math.min; +import static java.util.Arrays.stream; +import static java.util.stream.Collectors.joining; +import static org.apache.commons.math3.optim.linear.Relationship.EQ; +import static org.apache.commons.math3.optim.linear.Relationship.GEQ; +import static org.apache.commons.math3.optim.linear.Relationship.LEQ; +import static org.apache.commons.math3.optim.nonlinear.scalar.GoalType.MAXIMIZE; +import static org.apache.commons.math3.optim.nonlinear.scalar.GoalType.MINIMIZE; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.function.Function; + +import org.apache.commons.math3.exception.MathIllegalStateException; +import org.apache.commons.math3.optim.PointValuePair; +import org.apache.commons.math3.optim.linear.LinearConstraint; +import org.apache.commons.math3.optim.linear.LinearConstraintSet; +import org.apache.commons.math3.optim.linear.LinearObjectiveFunction; +import org.apache.commons.math3.optim.linear.Relationship; +import org.apache.commons.math3.optim.linear.SimplexSolver; +import org.apache.commons.math3.optim.nonlinear.scalar.GoalType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.openems.edge.energy.api.simulation.GlobalSimulationsContext.Period; + +/** + * Holds the {@link Solution} of an {@link EnergyFlow.Model} and provides helper + * functions to access the individual {@link Coefficient}s. + */ +public class EnergyFlow { + + private static final Logger LOG = LoggerFactory.getLogger(EnergyFlow.class); + + private final double[] point; + private final int managedConsumption; + + private EnergyFlow(PointValuePair pvp, int managedConsumption) { + this.point = pvp.getPointRef(); + this.managedConsumption = managedConsumption; + } + + /** + * Gets {@link Coefficient#PROD}. + * + * @return the value + */ + public int getProd() { + return this.getValue(PROD); + } + + /** + * Gets {@link Coefficient#CONS}. + * + * @return the value + */ + public int getCons() { + return this.getValue(CONS); + } + + /** + * Gets the part of {@link Coefficient#CONS} that is actively managed. + * + * @return the value + */ + public int getManagedCons() { + return this.managedConsumption; + } + + /** + * Gets {@link Coefficient#ESS}. + * + * @return the value + */ + public int getEss() { + return this.getValue(ESS); + } + + /** + * Gets {@link Coefficient#GRID}. + * + * @return the value + */ + public int getGrid() { + return this.getValue(GRID); + } + + /** + * Gets {@link Coefficient#PROD_TO_CONS}. + * + * @return the value + */ + public int getProdToCons() { + return this.getValue(PROD_TO_CONS); + } + + /** + * Gets {@link Coefficient#PROD_TO_ESS}. + * + * @return the value + */ + public int getProdToEss() { + return this.getValue(PROD_TO_ESS); + } + + /** + * Gets {@link Coefficient#PROD_TO_GRID}. + * + * @return the value + */ + public int getProdToGrid() { + return this.getValue(PROD_TO_GRID); + } + + /** + * Gets {@link Coefficient#GRID_TO_CONS}. + * + * @return the value + */ + public int getGridToCons() { + return this.getValue(GRID_TO_CONS); + } + + /** + * Gets {@link Coefficient#GRID_TO_ESS}. + * + * @return the value + */ + public int getGridToEss() { + return this.getValue(GRID_TO_ESS); + } + + /** + * Gets {@link Coefficient#ESS_TO_CONS}. + * + * @return the value + */ + public int getEssToCons() { + return this.getValue(ESS_TO_CONS); + } + + private int getValue(Coefficient coefficient) { + return toInt(this.point[coefficient.ordinal()]); + } + + /** + * Prints all {@link Coefficient}s and their values line by line. + */ + public void print() { + for (var c : Coefficient.values()) { + LOG.info(c.toCamelCase() + ": " + this.getValue(c)); + } + } + + @Override + public String toString() { + return toStringHelper(this) // + .addValue(stream(Coefficient.values()) // + .map(c -> new StringBuilder() // + .append(c.toCamelCase()) // + .append("=") // + .append(this.getValue(c)) // + .toString()) // + .collect(joining(", "))) // + .toString(); + } + + /** + * Models an EnergyFlow as a Linear Equation System with defined + * {@link Coefficient}s for GRID, ESS, CONS, etc. + */ + public static class Model { + + /** + * Generates a {@link EnergyFlow.Model} from a {@link OneSimulationContext} and + * a {@link Period}. + * + * @param osc the {@link OneSimulationContext} + * @param period the {@link Period} + * @return a new {@link EnergyFlow.Model} + */ + public static EnergyFlow.Model from(OneSimulationContext osc, Period period) { + final int factor; // TODO replace with switch in Java 21 + if (period instanceof GlobalSimulationsContext.Period.Hour) { + factor = 4; + } else { + factor = 1; + } + final var essGlobal = osc.global.ess(); + final var essOne = osc.ess; + final var grid = osc.global.grid(); + return new EnergyFlow.Model(// + /* production */ period.production(), // + /* consumption */ period.consumption(), // + /* essMaxCharge */ min(essGlobal.maxChargeEnergy() * factor, + essGlobal.totalEnergy() - essOne.getInitialEnergy()), // + /* essMaxDischarge */ min(essGlobal.maxDischargeEnergy() * factor, osc.ess.getInitialEnergy()), // + /* gridMaxBuy */ grid.maxBuy() * factor, // + /* gridMaxSell */ grid.maxSell() * factor); + } + + public final int production; + public final int unmanagedConsumption; + public final int essMaxCharge; + public final int essMaxDischarge; + public final int gridMaxBuy; + public final int gridMaxSell; + + private final List constraints = new ArrayList(); + + private int managedConsumption = 0; + + public Model(int production, int unmanagedConsumption, int essMaxCharge, int essMaxDischarge, int gridMaxBuy, + int gridMaxSell) { + this.production = production; + this.unmanagedConsumption = unmanagedConsumption; + this.essMaxCharge = essMaxCharge; + this.essMaxDischarge = essMaxDischarge; + this.gridMaxBuy = gridMaxBuy; + this.gridMaxSell = gridMaxSell; + + this + // Internal Relationships + .addConstraint(c -> c // Sum + .setCoefficient(PROD, 1) // + .setCoefficient(ESS, 1) // + .setCoefficient(GRID, 1) // + .setCoefficient(CONS, -1) // + .toLinearConstraint(EQ, 0)) // + .addConstraint(c -> c // Distribute Production + .setCoefficient(PROD, -1) // + .setCoefficient(PROD_TO_CONS, 1) // + .setCoefficient(PROD_TO_ESS, 1) // + .setCoefficient(PROD_TO_GRID, 1) // + .toLinearConstraint(EQ, 0)) // + .addConstraint(b -> b // Distribute Consumption + .setCoefficient(CONS, 1) // + .setCoefficient(ESS_TO_CONS, -1) // + .setCoefficient(GRID_TO_CONS, -1) // + .setCoefficient(PROD_TO_CONS, -1) // + .toLinearConstraint(EQ, 0)) // + .addConstraint(b -> b // Distribute Grid + .setCoefficient(GRID, -1) // + .setCoefficient(PROD_TO_GRID, -1) // + .setCoefficient(GRID_TO_CONS, 1) // + .setCoefficient(GRID_TO_ESS, 1) // + .toLinearConstraint(EQ, 0)) // + .addConstraint(b -> b // Distribute ESS + .setCoefficient(ESS, -1) // + .setCoefficient(PROD_TO_ESS, -1) // + .setCoefficient(ESS_TO_CONS, 1) // + .setCoefficient(GRID_TO_ESS, -1) // + .toLinearConstraint(EQ, 0)) // + .addConstraint(b -> b // Only Positive PROD_TO_ESS + .setCoefficient(PROD_TO_ESS, 1) // + .toLinearConstraint(GEQ, 0)) // + .addConstraint(b -> b // Only Positive PROD_TO_GRID + .setCoefficient(PROD_TO_GRID, 1) // + .toLinearConstraint(GEQ, 0)) // + .addConstraint(b -> b // Only Positive ESS_TO_CONS + .setCoefficient(ESS_TO_CONS, 1) // + .toLinearConstraint(GEQ, 0)) // + .addConstraint(b -> b // Only Positive GRID_TO_CONS + .setCoefficient(GRID_TO_CONS, 1) // + .toLinearConstraint(GEQ, 0)) + + // Production & Consumption + .addConstraint(c -> c // + .setCoefficient(PROD, 1) // + .toLinearConstraint(EQ, production)) // + .addConstraint(c -> c // + .setCoefficient(CONS, 1) // + .toLinearConstraint(GEQ, unmanagedConsumption)) + .addConstraint(b -> b // + .setCoefficient(PROD_TO_CONS, 1) // + .toLinearConstraint(GEQ, min(production, unmanagedConsumption))) + + // ESS Max Charge/Discharge + .addConstraint(c -> c // + .setCoefficient(ESS, 1) // + .toLinearConstraint(GEQ, -essMaxCharge)) // + .addConstraint(c -> c // + .setCoefficient(ESS, 1) // + .toLinearConstraint(LEQ, essMaxDischarge)) // + // Grid Max Buy/Sell + .addConstraint(c -> c // + .setCoefficient(GRID, 1) // + .toLinearConstraint(LEQ, gridMaxBuy)) // + .addConstraint(c -> c // + .setCoefficient(GRID, 1) // + .toLinearConstraint(GEQ, -gridMaxSell)); + } + + /** + * Sets the {@link Coefficient#ESS} Charge/Discharge Energy to the given value, + * while making sure the value fits in the active constraints. + * + * @param value the value + * @return actually set value; {@link Double#NaN} on error + */ + public double setEss(int value) { + return this.setFittingCoefficientValue(ESS, EQ, value); + } + + /** + * Limits the {@link Coefficient#ESS} Charge Energy to the given value, while + * making sure the value fits in the active constraints. + * + * @param value the value + * @return actually set value; {@link Double#NaN} on error + */ + public double setEssMaxCharge(int value) { + return this.setFittingCoefficientValue(ESS, GEQ, -value); + } + + /** + * Limits the {@link Coefficient#ESS} Discharge Energy to the given value, while + * making sure the value fits in the active constraints. + * + * @param value the value + * @return actually set value; {@link Double#NaN} on error + */ + public double setEssMaxDischarge(int value) { + return this.setFittingCoefficientValue(ESS, LEQ, value); + } + + /** + * Limits the {@link Coefficient#GRID} Buy Energy to the given value, while + * making sure the value fits in the active constraints. + * + * @param value the value + * @return actually set value; {@link Double#NaN} on error + */ + public double setGridMaxBuy(int value) { + return this.setFittingCoefficientValue(ESS, LEQ, value); + } + + /** + * Limits the {@link Coefficient#GRID} Sell Energy to the given value, while + * making sure the value fits in the active constraints. + * + * @param value the value + * @return actually set value; {@link Double#NaN} on error + */ + public double setGridMaxSell(int value) { + return this.setFittingCoefficientValue(ESS, GEQ, -value); + } + + /** + * Adds {@link Coefficient#CONS} Energy, while making sure the value fits in the + * active constraints. + * + * @param value the added consumption value + * @return actually set value; {@link Double#NaN} on error + */ + public synchronized double addConsumption(int value) { + this.managedConsumption += value; + return this.setFittingCoefficientValue(CONS, GEQ, this.unmanagedConsumption + this.managedConsumption); + } + + /** + * Prints a table with all constraints. + */ + public void logConstraints() { + { + var b = new StringBuilder(); + for (var coefficient : Coefficient.values()) { + b.append(String.format("%s ", coefficient.toCamelCase())); + } + LOG.info(b.toString()); + } + for (var constraint : this.constraints) { + var b = new StringBuilder(); + var equation = constraint.getCoefficients(); + for (var coefficient : Coefficient.values()) { + b.append(String.format("% " + coefficient.name().length() + ".0f ", + equation.getEntry(coefficient.ordinal()))); + } + b.append(String.format("%2s % 10.0f", constraint.getRelationship(), constraint.getValue())); + LOG.info(b.toString()); + } + } + + /** + * Prints min/max values for a {@link Coefficient}. + * + * @param coefficient the {@link Coefficient} + */ + public void logMinMaxValues(Coefficient coefficient) { + var values = this.calculateMinMaxValues(coefficient); + var min = values[0]; + var max = values[1]; + LOG.info(String.format("%-12s % 5.0f % 5.0f %s", coefficient.toCamelCase(), min, max, + min == max ? "fixed" : "")); + } + + /** + * Prints a table with all constraints. + */ + public void logMinMaxValues() { + LOG.info(String.format("%-12s %5s %5s", "Coefficient", "Min", "Max")); + for (var coefficient : Coefficient.values()) { + this.logMinMaxValues(coefficient); + } + } + + @Override + public String toString() { + return "EnergyFlow.Model[" // + + Arrays.stream(Coefficient.values()) // + .map(coefficient -> { + var values = this.calculateMinMaxValues(coefficient); + var min = values[0]; + var max = values[1]; + var b = new StringBuilder().append(coefficient.toCamelCase()) // + .append("=") // + .append(min); + if (min == max) { + b // + .append("|fixed"); + } else { + b // + .append("|") // + .append(max); // + } + return b.toString(); + }) // + .collect(joining(",")) // + + "]"; + } + + /** + * Calculates the current Min and Max values for a given {@link Coefficient}. + * + * @param coefficient the {@link Coefficient} + * @return result[0] is the Min value; result[1] is the Max value + */ + private double[] calculateMinMaxValues(Coefficient coefficient) { + final double[] result = new double[2]; + try { + result[0] = this.getExtremeCoefficientValue(coefficient, MINIMIZE); + } catch (MathIllegalStateException e) { + result[0] = NaN; + } + try { + result[1] = this.getExtremeCoefficientValue(coefficient, MAXIMIZE); + } catch (MathIllegalStateException e) { + result[1] = NaN; + } + return result; + } + + private EnergyFlow.Model addConstraint(Function coefficients) { + this.constraints.add(coefficients.apply(new Coefficients())); + return this; + } + + /** + * Gets the minimum or maximum allowed value for the given {@link Coefficient}. + * + * @param coefficient the {@link Coefficient} + * @param goalType the {@link GoalType} + * @return the value + * @throws MathIllegalStateException if this {@link EnergyFlow.Model} is + * unsolvable + */ + public double getExtremeCoefficientValue(Coefficient coefficient, GoalType goalType) + throws MathIllegalStateException { + return solve(goalType, this.constraints, Coefficients.create() // + .setCoefficient(coefficient, 1) // + .toLinearObjectiveFunction(0)) // + .getPointRef()[coefficient.ordinal()]; + } + + /** + * Adds a {@link LinearConstraint} that sets the given {@link Coefficient} to + * the minimum or maximum allowed value. + * + * @param coefficient the {@link Coefficient} + * @param goalType the {@link GoalType} + * @return actually set value; {@link Double#NaN} on error + */ + public double setExtremeCoefficientValue(Coefficient coefficient, GoalType goalType) { + try { + var value = this.getExtremeCoefficientValue(coefficient, goalType); + this.setCoefficientValue(coefficient, value); + return value; + } catch (MathIllegalStateException e) { + LOG.warn("[setExtremeCoefficientValue] " // + + "Unable to " + goalType + " " + coefficient + ": " + e.getMessage() + " " // + + this.toString()); + return NaN; + } + } + + /** + * Adds a {@link LinearConstraint} that sets the given {@link Coefficient} to + * the given value, while making sure the value fits in the active constraints. + * + * @param coefficient the {@link Coefficient} + * @param relationship the {@link Relationship}l + * @param value the value + * @return actually set value; {@link Double#NaN} on error + */ + public double setFittingCoefficientValue(Coefficient coefficient, Relationship relationship, double value) { + // Fit to MIN value + try { + var min = this.getExtremeCoefficientValue(coefficient, MINIMIZE); + if (value <= min) { + this.setCoefficientValue(coefficient, relationship, min); + return min; + } + } catch (MathIllegalStateException e) { + LOG.warn("[setFittingCoefficientValue] " // + + "Unable to MINIMIZE " + coefficient + ": " + e.getMessage() + " " // + + this.toString()); + return NaN; + } + + // Fit to MAX value + try { + var max = this.getExtremeCoefficientValue(coefficient, MAXIMIZE); + if (value > max) { + this.setCoefficientValue(coefficient, relationship, max); + return max; + } + } catch (MathIllegalStateException e) { + LOG.warn("[setFittingCoefficientValue] " // + + "Unable to MAXIMIZE " + coefficient + ": " + e.getMessage() + " " // + + this.toString()); + return NaN; + } + + // Apply coefficient value + this.setCoefficientValue(coefficient, relationship, value); + return value; + } + + /** + * Adds a {@link LinearConstraint} that sets the given {@link Coefficient} to + * the given value. + * + * @param coefficient the {@link Coefficient} + * @param value the value + */ + private void setCoefficientValue(Coefficient coefficient, double value) { + this.setCoefficientValue(coefficient, Relationship.EQ, value); + } + + /** + * Adds a {@link LinearConstraint} that constrains the given {@link Coefficient} + * to the given value and {@link Relationship}. + * + * @param coefficient the {@link Coefficient} + * @param relationship the {@link Relationship} + * @param value the value + */ + private void setCoefficientValue(Coefficient coefficient, Relationship relationship, double value) { + this.addConstraint(c -> c // + .setCoefficient(coefficient, 1) // + .toLinearConstraint(relationship, value)); + } + + /** + * Solves the {@link EnergyFlow.Model} and returns an {@link EnergyFlow}. + * + * @return the {@link EnergyFlow}; null if this {@link EnergyFlow.Model} is + * unsolvable + */ + public EnergyFlow solve() { + final double ess; + try { + ess = this.getExtremeCoefficientValue(ESS, MAXIMIZE); + } catch (MathIllegalStateException e) { + LOG.warn("[solve] " // + + "Unable to MAXIMIZE ESS: " + e.getMessage() + " " // + + this.toString()); + return null; + } + if (ess <= 0) { + // ESS Charge or Zero; GRID_TO_ESS must be >= 0 + this.setFittingCoefficientValue(GRID_TO_ESS, GEQ, 0); + this.setFittingCoefficientValue(ESS_TO_CONS, EQ, 0); + } + if (ess >= 0) { + // ESS Discharge or Zero + // Maximize ESS_TO_CONS (1st prio: PROD_TO_CONS; 3rd prio: GRID_TO_CONS) + final double essMax; + try { + essMax = this.getExtremeCoefficientValue(ESS_TO_CONS, MAXIMIZE); + } catch (MathIllegalStateException e) { + LOG.warn("[solve] " // + + "Unable to MAXIMIZE ESS_TO_CONS: " + e.getMessage() + " " // + + this.toString()); + return null; + } + this.setCoefficientValue(ESS_TO_CONS, min(essMax, ess)); + } + + var coefficients = initializeCoefficients(); + Arrays.fill(coefficients, 1); + try { + return new EnergyFlow(solve(MINIMIZE, this.constraints, new LinearObjectiveFunction(coefficients, 0)), + this.managedConsumption); + } catch (MathIllegalStateException e) { + LOG.warn("[solve] " // + + "Unable to solve EnergyFlow.Model: " + e.getMessage() + " " // + + this.toString()); + return null; + } + } + + /** + * Solves the linear equation system. + * + * @param goalType {@link GoalType#MINIMIZE} or + * {@link GoalType#MAXIMIZE} the objective function + * @param constraints the {@link LinearConstraint}s + * @param objectiveFunction the {@link LinearObjectiveFunction} + * @return the {@link PointValuePair} + * @throws MathIllegalStateException if this {@link EnergyFlow.Model} is + * unsolvable + */ + private static PointValuePair solve(GoalType goalType, Collection constraints, + LinearObjectiveFunction objectiveFunction) throws MathIllegalStateException { + return new SimplexSolver().optimize(// + objectiveFunction, // + new LinearConstraintSet(constraints), // + goalType); + } + } + + /** + * Helper class to provides a Builder-Pattern like way to create a coefficients + * array suitable for a {@link LinearConstraint} or + * {@link LinearObjectiveFunction}. + */ + private static class Coefficients { + + private static Coefficients create() { + return new Coefficients(); + } + + private final double[] coefficients; + + private Coefficients() { + this.coefficients = initializeCoefficients(); + } + + private Coefficients setCoefficient(Coefficient coefficient, int value) { + this.coefficients[coefficient.ordinal()] = value; + return this; + } + + private LinearConstraint toLinearConstraint(Relationship relationship, double value) { + return new LinearConstraint(this.coefficients, relationship, value); + } + + private LinearObjectiveFunction toLinearObjectiveFunction(int constantTerm) { + return new LinearObjectiveFunction(this.coefficients, constantTerm); + } + + } + + private static double[] initializeCoefficients() { + return new double[Coefficient.values().length]; + } + + private static int toInt(double value) { + return (int) Math.round(value); + } +} diff --git a/io.openems.edge.energy.api/src/io/openems/edge/energy/api/simulation/GlobalSimulationsContext.java b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/simulation/GlobalSimulationsContext.java new file mode 100644 index 00000000000..9e74dee2dd4 --- /dev/null +++ b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/simulation/GlobalSimulationsContext.java @@ -0,0 +1,377 @@ +package io.openems.edge.energy.api.simulation; + +import static com.google.common.base.MoreObjects.toStringHelper; +import static com.google.common.collect.ImmutableList.toImmutableList; +import static io.openems.edge.common.type.TypeUtils.assertNull; +import static io.openems.edge.energy.api.EnergyConstants.SUM_PRODUCTION; +import static io.openems.edge.energy.api.EnergyConstants.SUM_UNMANAGED_CONSUMPTION; +import static io.openems.edge.energy.api.EnergyUtils.filterEshsWithDifferentStates; +import static io.openems.edge.energy.api.EnergyUtils.socToEnergy; +import static io.openems.edge.energy.api.EnergyUtils.toEnergy; +import static java.lang.Math.abs; +import static java.util.Arrays.stream; + +import java.time.Clock; +import java.time.ZonedDateTime; +import java.util.Objects; +import java.util.function.IntFunction; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +import io.openems.common.exceptions.OpenemsException; +import io.openems.common.utils.DateUtils; +import io.openems.edge.common.component.ComponentManager; +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.common.sum.Sum; +import io.openems.edge.common.type.TypeUtils; +import io.openems.edge.energy.api.EnergySchedulable; +import io.openems.edge.energy.api.EnergyScheduleHandler; +import io.openems.edge.energy.api.RiskLevel; +import io.openems.edge.energy.api.simulation.GlobalSimulationsContext.Period.Hour; +import io.openems.edge.energy.api.simulation.GlobalSimulationsContext.Period.Quarter; +import io.openems.edge.evcs.api.Status; +import io.openems.edge.predictor.api.manager.PredictorManager; +import io.openems.edge.timeofusetariff.api.TimeOfUseTariff; + +/** + * Holds the simulation context that is used globally for all simulations in one + * optimization run. + * + *

+ * This record is usually created once per quarter. + */ +public record GlobalSimulationsContext(// + Clock clock, // + RiskLevel riskLevel, + /** Start-Timestamp */ + ZonedDateTime startTime, // + ImmutableList eshs, // + ImmutableList> eshsWithDifferentStates, // + Grid grid, // + Ess ess, // + ImmutableMap evcss, // + /** + * Period is either mixed, with {@link Hour}s and {@link Quarter}s, or + * {@link Quarter}s only. + */ + ImmutableList periods) { + + @Override + public String toString() { + return toStringHelper(this) // + .add("startTime", this.startTime) // + .addValue(this.grid) // + .addValue(this.ess) // + .add("evcss", this.evcss) // + .add("eshs", this.eshs) // + .toString(); + } + + public static record Ess(// + /** ESS Currently Available Energy (SoC in [Wh]) */ + int currentEnergy, // + /** ESS Total Energy (Capacity) [Wh] */ + int totalEnergy, // + /** ESS Max Charge Energy [Wh] */ + int maxChargeEnergy, // + /** ESS Max Discharge Energy [Wh] */ + int maxDischargeEnergy) { + } + + public static record Evcs(// + // Plugged state + Status status, // + // Energy Session [Wh] + int energySession) { + } + + public static record Grid(// + /** Max Buy-From-Grid Energy [Wh] */ + int maxBuy, // + /** Max Sell-To-Grid Energy [Wh] */ + int maxSell) { + } + + public static sealed interface Period { + /** + * Start-Timestamp of the Period. + * + * @return the {@link ZonedDateTime} + */ + public ZonedDateTime time(); + + /** + * Production prediction for the Period in [Wh]. + * + * @return the production prediction + */ + public int production(); + + /** + * Consumption prediction for the Period in [Wh]. + * + * @return the consumption prediction + */ + public int consumption(); + + /** + * (Average) Grid-Buy-Price for the Period in [1/MWh]. + * + * @return the price + */ + public double price(); + + public static record Quarter(// + ZonedDateTime time, // + int production, // + int consumption, // + double price // + ) implements Period { + } + + public static record Hour(// + ZonedDateTime time, // + int production, // + int consumption, // + double price, // + /** Raw Periods, representing one QUARTER. */ + ImmutableList quarterPeriods // + ) implements Period { + } + } + + public static class Builder { + private ComponentManager componentManager; + private RiskLevel riskLevel; + private ImmutableList eshs; + private Sum sum; + private PredictorManager predictorManager; + private TimeOfUseTariff timeOfUseTariff; + + /** + * The {@link ComponentManager}. + * + * @param componentManager the {@link ComponentManager} + * @return myself + */ + public Builder setComponentManager(ComponentManager componentManager) { + this.componentManager = componentManager; + return this; + } + + /** + * The {@link RiskLevel}. + * + * @param riskLevel the {@link RiskLevel} + * @return myself + */ + public Builder setRiskLevel(RiskLevel riskLevel) { + this.riskLevel = riskLevel; + return this; + } + + /** + * The {@link EnergyScheduleHandler}s of the {@link EnergySchedulable}s. + * + *

+ * The list is sorted by Scheduler. + * + * @param eshs the list of {@link EnergyScheduleHandler}s + * @return myself + */ + public Builder setEnergyScheduleHandlers(ImmutableList eshs) { + this.eshs = eshs; + return this; + } + + /** + * The {@link Sum}. + * + * @param sum the {@link Sum} + * @return myself + */ + public Builder setSum(Sum sum) { + this.sum = sum; + return this; + } + + /** + * The {@link PredictorManager}. + * + * @param predictorManager the {@link PredictorManager} + * @return myself + */ + public Builder setPredictorManager(PredictorManager predictorManager) { + this.predictorManager = predictorManager; + return this; + } + + /** + * The {@link TimeOfUseTariff}. + * + * @param timeOfUseTariff the {@link TimeOfUseTariff} + * @return myself + */ + public Builder setTimeOfUseTariff(TimeOfUseTariff timeOfUseTariff) { + this.timeOfUseTariff = timeOfUseTariff; + return this; + } + + /** + * Builds the {@link GlobalSimulationsContext}. + * + * @return the {@link GlobalSimulationsContext} record + */ + public GlobalSimulationsContext build() throws OpenemsException, IllegalArgumentException { + try { + System.out.println("OPTIMIZER GlobalSimulationsContext::build()"); + + assertNull("ComponentManager is not available", this.componentManager); + assertNull("EnergyScheduleHandlers are not available", this.eshs); + assertNull("Sum is not available", this.sum); + assertNull("Predictor-Manager is not available", this.predictorManager); + assertNull("TimeOfUseTariff is not available", this.timeOfUseTariff); + + final var clock = this.componentManager.getClock(); + final var startTime = DateUtils.roundDownToQuarter(ZonedDateTime.now(clock)); + System.out.println("OPTIMIZER GlobalSimulationsContext::build() startTime=" + startTime); + + // Prediction values + final var consumptions = this.predictorManager.getPrediction(SUM_UNMANAGED_CONSUMPTION); + System.out.println( + "OPTIMIZER GlobalSimulationsContext::build() consumptions=" + consumptions.asArray().length); + final var productions = this.predictorManager.getPrediction(SUM_PRODUCTION); + System.out.println( + "OPTIMIZER GlobalSimulationsContext::build() productions=" + productions.asArray().length); + + // Prices contains the price values and the time it is retrieved. + final var prices = this.timeOfUseTariff.getPrices(); + System.out.println("OPTIMIZER GlobalSimulationsContext::build() prices=" + prices.asArray().length); + + // Helpers + final IntFunction toQuarterPeriod = (i) -> { + final var time = startTime.plusMinutes(i * 15); + final var consumption = consumptions.getAt(time); + final var price = prices.getAt(time); + if (consumption == null || price == null) { + return null; + } + final var production = productions.getAtOrElse(time, 0); + return new Period.Quarter(time, toEnergy(production), toEnergy(consumption), price); + }; + final IntFunction toHourPeriod = (i) -> { + final var rangeStart = startTime.plusMinutes(i * 15); + final var rangeEnd = startTime.plusMinutes(i * 15).plusMinutes(60); + + final var consumption = consumptions // + .getBetween(rangeStart, rangeEnd) // + .mapToInt(Integer::intValue) // + .toArray(); + final var priceRange = prices // + .getBetween(rangeStart, rangeEnd) // + .mapToDouble(Double::doubleValue) // + .toArray(); + if (consumption.length == 0 || priceRange.length == 0) { + return null; + } + final var price = stream(priceRange).average().getAsDouble(); + final var production = productions // + .getBetween(rangeStart, rangeEnd) // + .mapToInt(Integer::intValue) // + .sum(); + final var quarterPeriods = IntStream.range(i, i + 4) // + .mapToObj(toQuarterPeriod) // + .filter(Objects::nonNull) // + .collect(toImmutableList()); + return new Period.Hour(rangeStart, // + toEnergy(production), toEnergy(stream(consumption).sum()), // + price, quarterPeriods); + }; + + final var periodLengthHourFromIndex = calculatePeriodDurationHourFromIndex(startTime); + + var periods = Stream.concat(// + IntStream.range(0, periodLengthHourFromIndex) // + .mapToObj(toQuarterPeriod), // + IntStream.iterate(periodLengthHourFromIndex, i -> i + 4) // + .mapToObj(toHourPeriod) // + .takeWhile(Objects::nonNull)) // + .filter(Objects::nonNull) // + .collect(toImmutableList()); + System.out.println("OPTIMIZER GlobalSimulationsContext::build() periods:" + periods.size()); + + if (periods.isEmpty()) { + throw new IllegalArgumentException("No forecast periods available. " // + + "Consumptions[" + consumptions.asArray().length + "] " // + + "Productions[" + productions.asArray().length + "] " // + + "Prices[" + prices.asArray().length + "]"); + } + + final Ess ess; + { + var essTotalEnergy = this.sum.getEssCapacity().getOrError(); + var essInitialEnergy = socToEnergy(essTotalEnergy, this.sum.getEssSoc().getOrError()); + + // Power Values for scheduling battery for individual periods. + var maxDischargePower = TypeUtils.max(1000 /* at least 1000 W */, // + this.sum.getEssMaxDischargePower().get()); + var maxChargePower = TypeUtils.min(-1000 /* at least 1000 W */, // + this.sum.getEssMinDischargePower().get()); + + ess = new Ess(essInitialEnergy, essTotalEnergy, toEnergy(abs(maxChargePower)), + toEnergy(maxDischargePower)); + } + final var grid = new Grid(40000 /* TODO */, 20000 /* TODO */); + + final var evcss = this.componentManager.getEnabledComponentsOfType(io.openems.edge.evcs.api.Evcs.class) + .stream() // + .collect(ImmutableMap.toImmutableMap(// + OpenemsComponent::id, // + evcs -> new Evcs(// + evcs.getStatus(), // + evcs.getEnergySession().orElse(0)))); + + System.out.println("OPTIMIZER GlobalSimulationsContext::build() finished"); + return new GlobalSimulationsContext(clock, this.riskLevel, startTime, // + this.eshs, filterEshsWithDifferentStates(this.eshs).collect(toImmutableList()), // + grid, ess, evcss, periods); + } catch (Exception e) { + e.printStackTrace(); + throw e; + } + } + } + + /** + * Create a {@link GlobalSimulationsContext} {@link Builder}. + * + * @return a {@link Builder} + */ + public static Builder create() { + return new GlobalSimulationsContext.Builder(); + } + + /** + * Calculates the index when Period duration switches from {@link Hour} to + * {@link Quarter}. + * + *

+ * The index is calculated as "6 hours" plus remaining quarters of the current + * hour. + * + * @param time Start-Timestamp of the Schedule + * @return the index + */ + // TODO this should be set depending on the actual calculation time and + // quality of the best schedule result + protected static int calculatePeriodDurationHourFromIndex(ZonedDateTime time) { + var minute = time.getMinute(); + if (minute == 0) { + minute = 60; + } + return 6 * 4 + (60 - minute) / 15; + } +} diff --git a/io.openems.edge.energy.api/src/io/openems/edge/energy/api/simulation/OneSimulationContext.java b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/simulation/OneSimulationContext.java new file mode 100644 index 00000000000..72656c93ed0 --- /dev/null +++ b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/simulation/OneSimulationContext.java @@ -0,0 +1,111 @@ +package io.openems.edge.energy.api.simulation; + +import static com.google.common.base.MoreObjects.toStringHelper; +import static java.lang.Math.max; + +import java.util.Map.Entry; + +import com.google.common.collect.ImmutableMap; + +/** + * Holds the simulation context that is used for one simulation of a full + * schedule with multiple periods. + * + *

+ * This record is usually created multiple times per second. + */ +public class OneSimulationContext { + + public static class Ess { + /** ESS Currently Available Energy (SoC in [Wh]). */ + private int initialEnergy; + + protected static Ess from(GlobalSimulationsContext.Ess ess) { + return new Ess(ess.currentEnergy()); + } + + private Ess(int initialEnergy) { + this.initialEnergy = initialEnergy; + } + + /** + * Calculates the initial SoC-Energy of the next Period. + * + * @param ess the ess charge/discharge energy of the current Period + */ + public synchronized void calculateInitialEnergy(int ess) { + this.initialEnergy = max(0, this.initialEnergy - ess); // always at least '0' + } + + /** + * The initial SoC-Energy of the Period. + * + * @return the value + */ + public int getInitialEnergy() { + return this.initialEnergy; + } + } + + public static class Evcs { + // Energy Session [Wh] + private int initialEnergySession; + + protected static Evcs from(GlobalSimulationsContext.Evcs evcs) { + return new Evcs(evcs.energySession()); + } + + private Evcs(int initialEnergySession) { + this.initialEnergySession = initialEnergySession; + } + + /** + * Calculates the initial EnergySession of the next Period. + * + * @param evcs the evcs charge energy of the current Period + */ + public synchronized void calculateInitialEnergySession(int evcs) { + this.initialEnergySession = this.initialEnergySession + max(0, evcs); + } + + /** + * The initial EnergySession of the Period. + * + * @return the value + */ + public int getInitialEnergySession() { + return this.initialEnergySession; + } + } + + /** + * Builds a {@link OneSimulationContext}. + * + * @param asc the {@link GlobalSimulationsContext} + * @return the {@link OneSimulationContext} + */ + public static OneSimulationContext from(GlobalSimulationsContext asc) { + return new OneSimulationContext(asc, Ess.from(asc.ess()), + asc.evcss().entrySet().stream().collect(ImmutableMap.toImmutableMap(// + Entry::getKey, // + e -> Evcs.from(e.getValue())))); + } + + public final GlobalSimulationsContext global; + public final Ess ess; + public final ImmutableMap evcss; + + private OneSimulationContext(GlobalSimulationsContext gsc, Ess ess, ImmutableMap evcss) { + this.global = gsc; + this.ess = ess; + this.evcss = evcss; + } + + @Override + public String toString() { + return toStringHelper(this) // + .add("ess", this.ess) // + .addValue(this.global) // + .toString(); + } +} diff --git a/io.openems.edge.energy.api/src/io/openems/edge/energy/api/simulation/package-info.java b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/simulation/package-info.java new file mode 100644 index 00000000000..479bc6029cb --- /dev/null +++ b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/simulation/package-info.java @@ -0,0 +1,3 @@ +@org.osgi.annotation.versioning.Version("1.0.0") +@org.osgi.annotation.bundle.Export +package io.openems.edge.energy.api.simulation; diff --git a/io.openems.edge.energy.api/src/io/openems/edge/energy/api/test/AbstractDummyEnergySchedulable.java b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/test/AbstractDummyEnergySchedulable.java new file mode 100644 index 00000000000..0880e69eb33 --- /dev/null +++ b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/test/AbstractDummyEnergySchedulable.java @@ -0,0 +1,16 @@ +package io.openems.edge.energy.api.test; + +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.common.test.AbstractDummyOpenemsComponent; +import io.openems.edge.energy.api.EnergySchedulable; + +public abstract class AbstractDummyEnergySchedulable> + extends AbstractDummyOpenemsComponent implements EnergySchedulable, OpenemsComponent { + + protected AbstractDummyEnergySchedulable(String id, + io.openems.edge.common.channel.ChannelId[] firstInitialChannelIds, + io.openems.edge.common.channel.ChannelId[]... furtherInitialChannelIds) { + super(id, firstInitialChannelIds, furtherInitialChannelIds); + } + +} diff --git a/io.openems.edge.energy.api/src/io/openems/edge/energy/api/test/DummyEnergySchedulable.java b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/test/DummyEnergySchedulable.java new file mode 100644 index 00000000000..468849402bc --- /dev/null +++ b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/test/DummyEnergySchedulable.java @@ -0,0 +1,32 @@ +package io.openems.edge.energy.api.test; + +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.energy.api.EnergySchedulable; +import io.openems.edge.energy.api.EnergyScheduleHandler; + +/** + * Provides a simple, simulated {@link EnergySchedulable} component that can be + * used together with the OpenEMS Component test framework. + */ +public class DummyEnergySchedulable extends AbstractDummyEnergySchedulable + implements EnergySchedulable, OpenemsComponent { + + private final EnergyScheduleHandler esh; + + public DummyEnergySchedulable(String id, EnergyScheduleHandler esh) { + super(id, // + OpenemsComponent.ChannelId.values() // + ); + this.esh = esh; + } + + @Override + protected final DummyEnergySchedulable self() { + return this; + } + + @Override + public EnergyScheduleHandler getEnergyScheduleHandler() { + return this.esh; + } +} diff --git a/io.openems.edge.energy.api/src/io/openems/edge/energy/api/test/DummyGlobalSimulationsContext.java b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/test/DummyGlobalSimulationsContext.java new file mode 100644 index 00000000000..e6b87c2935c --- /dev/null +++ b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/test/DummyGlobalSimulationsContext.java @@ -0,0 +1,106 @@ +package io.openems.edge.energy.api.test; + +import static com.google.common.collect.ImmutableList.toImmutableList; +import static io.openems.common.test.TestUtils.createDummyClock; +import static io.openems.edge.energy.api.EnergyUtils.filterEshsWithDifferentStates; + +import java.time.ZonedDateTime; +import java.util.Arrays; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +import io.openems.common.test.TimeLeapClock; +import io.openems.edge.energy.api.EnergyScheduleHandler; +import io.openems.edge.energy.api.RiskLevel; +import io.openems.edge.energy.api.simulation.GlobalSimulationsContext; +import io.openems.edge.energy.api.simulation.GlobalSimulationsContext.Period; +import io.openems.edge.evcs.api.Status; + +public class DummyGlobalSimulationsContext { + + private DummyGlobalSimulationsContext() { + } + + public static final TimeLeapClock CLOCK = createDummyClock(); + public static final ZonedDateTime TIME = ZonedDateTime.now(CLOCK); + + /** + * Generates a {@link GlobalSimulationsContext} with the given + * {@link EnergyScheduleHandler}s. + * + * @param handlers the {@link EnergyScheduleHandler}s + * @return a {@link GlobalSimulationsContext} + */ + public static GlobalSimulationsContext fromHandlers(EnergyScheduleHandler... handlers) { + final var eshs = Arrays.stream(handlers).collect(toImmutableList()); + + return new GlobalSimulationsContext(// + CLOCK, RiskLevel.MEDIUM, TIME, // + eshs, filterEshsWithDifferentStates(eshs).collect(toImmutableList()), // + new GlobalSimulationsContext.Grid(4000, 20000), // + new GlobalSimulationsContext.Ess(5000, 22000, 4000, 4000), // + ImmutableMap.of(// + "evcs0", new GlobalSimulationsContext.Evcs(Status.CHARGING, 0)), // + ImmutableList.of(// + new Period.Quarter(time(0, 0), 0, 106, 293.70), // + new Period.Quarter(time(0, 15), 0, 86, 293.70), // + new Period.Quarter(time(0, 30), 0, 88, 293.70), // + new Period.Quarter(time(0, 45), 0, 81, 293.70), // + new Period.Quarter(time(1, 0), 0, 73, 294.30), // + new Period.Quarter(time(1, 15), 0, 68, 294.30), // + new Period.Quarter(time(1, 30), 0, 76, 294.30), // + new Period.Quarter(time(1, 45), 0, 149, 294.30), // + new Period.Quarter(time(2, 0), 0, 333, 289.30), // + new Period.Quarter(time(2, 15), 0, 61, 289.30), // + new Period.Quarter(time(2, 30), 0, 74, 289.30), // + new Period.Quarter(time(2, 45), 0, 73, 289.30), // + new Period.Quarter(time(3, 0), 0, 68, 288.00), // + new Period.Quarter(time(3, 15), 0, 66, 288.00), // + new Period.Quarter(time(3, 30), 0, 82, 288.00), // + new Period.Quarter(time(3, 45), 0, 99, 288.00), // + new Period.Quarter(time(4, 0), 0, 84, 288.80), // + new Period.Quarter(time(4, 15), 0, 80, 288.80), // + new Period.Quarter(time(4, 30), 0, 97, 288.80), // + new Period.Quarter(time(4, 45), 0, 85, 288.80), // + new Period.Quarter(time(5, 0), 0, 65, 302.90), // + new Period.Quarter(time(5, 15), 0, 69, 302.90), // + new Period.Quarter(time(5, 30), 0, 75, 302.90), // + new Period.Quarter(time(5, 45), 3, 90, 302.90), // + new Period.Quarter(time(6, 0), 6, 394, 331.70), // + new Period.Quarter(time(6, 15), 36, 106, 331.70), // + new Period.Quarter(time(6, 30), 112, 94, 331.70), // + new Period.Quarter(time(6, 45), 205, 74, 331.70), // + new Period.Quarter(time(7, 0), 342, 62, 342.50), // + new Period.Quarter(time(7, 15), 437, 74, 342.50), // + new Period.Quarter(time(7, 30), 518, 72, 342.50), // + new Period.Quarter(time(7, 45), 628, 60, 342.50), // + new Period.Quarter(time(8, 0), 931, 46, 332.70), // + new Period.Quarter(time(8, 15), 1159, 45, 332.70), // + new Period.Quarter(time(8, 30), 1349, 40, 332.70), // + new Period.Quarter(time(8, 45), 1543, 26, 332.70), // + new Period.Quarter(time(9, 0), 1743, 46, 311.80), // + new Period.Quarter(time(9, 15), 1920, 472, 311.80), // + new Period.Quarter(time(9, 30), 2112, 498, 311.80), // + new Period.Quarter(time(9, 45), 2209, 83, 311.80), // + new Period.Quarter(time(10, 0), 2436, 105, 292.10), // + new Period.Quarter(time(10, 15), 2671, 92, 292.10), // + new Period.Quarter(time(10, 30), 2723, 133, 292.10), // + new Period.Quarter(time(10, 45), 2824, 88, 292.10), // + new Period.Hour(time(11, 0), 11610, 716, 282.90, ImmutableList.of(// + new Period.Quarter(time(11, 0), 2878, 86, 282.90), // + new Period.Quarter(time(11, 15), 2871, 245, 282.90), // + new Period.Quarter(time(11, 30), 2883, 308, 282.90), // + new Period.Quarter(time(11, 45), 2978, 77, 282.90))), // + new Period.Hour(time(12, 0), 6118, 241, 260.70, ImmutableList.of(// + new Period.Quarter(time(12, 0), 3044, 54, 260.70), // + new Period.Quarter(time(12, 15), 3022, 64, 260.70), // + new Period.Quarter(time(12, 30), 3036, 64, 260.70), // + new Period.Quarter(time(12, 45), 3045, 59, 260.70))) // + )); + } + + private static ZonedDateTime time(int hours, int minutes) { + return TIME.plusHours(hours).plusMinutes(minutes); + } +} diff --git a/io.openems.edge.energy.api/src/io/openems/edge/energy/api/test/package-info.java b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/test/package-info.java new file mode 100644 index 00000000000..b8f87f83ce3 --- /dev/null +++ b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/test/package-info.java @@ -0,0 +1,3 @@ +@org.osgi.annotation.versioning.Version("1.0.0") +@org.osgi.annotation.bundle.Export +package io.openems.edge.energy.api.test; diff --git a/io.openems.edge.energy.api/test/io/openems/edge/energy/api/EnergyUtilsTest.java b/io.openems.edge.energy.api/test/io/openems/edge/energy/api/EnergyUtilsTest.java new file mode 100644 index 00000000000..7e27d9f59a1 --- /dev/null +++ b/io.openems.edge.energy.api/test/io/openems/edge/energy/api/EnergyUtilsTest.java @@ -0,0 +1,38 @@ +package io.openems.edge.energy.api; + +import static io.openems.edge.energy.api.EnergyUtils.findFirstPeakIndex; +import static io.openems.edge.energy.api.EnergyUtils.findFirstValleyIndex; +import static io.openems.edge.energy.api.EnergyUtils.toPower; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import org.junit.Test; + +public class EnergyUtilsTest { + + @Test + public void testFindFirstPeakIndex() { + assertEquals(0, findFirstPeakIndex(0, new double[0])); + assertEquals(0, findFirstPeakIndex(0, new double[] { 1 })); + assertEquals(0, findFirstPeakIndex(0, new double[] { 1, 0 })); + assertEquals(1, findFirstPeakIndex(0, new double[] { 0, 1, 0 })); + assertEquals(1, findFirstPeakIndex(0, new double[] { 0, 1, 0, 1 })); + assertEquals(5, findFirstPeakIndex(5, new double[0])); + } + + @Test + public void testFindFirstValleyIndex() { + assertEquals(0, findFirstValleyIndex(0, new double[0])); + assertEquals(0, findFirstValleyIndex(0, new double[] { 1 })); + assertEquals(1, findFirstValleyIndex(0, new double[] { 1, 0 })); + assertEquals(0, findFirstValleyIndex(0, new double[] { 0, 1, 0 })); + assertEquals(2, findFirstValleyIndex(1, new double[] { 0, 1, 0, 1 })); + assertEquals(5, findFirstValleyIndex(5, new double[0])); + } + + @Test + public void testToPower() { + assertEquals(2000, (int) toPower(500)); + assertNull(toPower(null)); + } +} diff --git a/io.openems.edge.energy.api/test/io/openems/edge/energy/api/simulation/GlobalSimulationsContextTest.java b/io.openems.edge.energy.api/test/io/openems/edge/energy/api/simulation/GlobalSimulationsContextTest.java new file mode 100644 index 00000000000..74e988989f8 --- /dev/null +++ b/io.openems.edge.energy.api/test/io/openems/edge/energy/api/simulation/GlobalSimulationsContextTest.java @@ -0,0 +1,88 @@ +package io.openems.edge.energy.api.simulation; + +import static io.openems.edge.energy.api.RiskLevel.MEDIUM; +import static io.openems.edge.energy.api.simulation.GlobalSimulationsContext.calculatePeriodDurationHourFromIndex; +import static org.junit.Assert.assertEquals; + +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; + +import org.junit.Test; + +import com.google.common.collect.ImmutableList; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.test.TimeLeapClock; +import io.openems.edge.common.sum.DummySum; +import io.openems.edge.common.test.DummyComponentManager; +import io.openems.edge.energy.api.EnergyConstants; +import io.openems.edge.predictor.api.prediction.Prediction; +import io.openems.edge.predictor.api.test.DummyPredictor; +import io.openems.edge.predictor.api.test.DummyPredictorManager; +import io.openems.edge.timeofusetariff.test.DummyTimeOfUseTariffProvider; + +public class GlobalSimulationsContextTest { + + private static final TimeLeapClock CLOCK = new TimeLeapClock(Instant.ofEpochSecond(946684800), ZoneId.of("UTC")); + + @Test + public void testBuild() throws OpenemsNamedException { + final var cm = new DummyComponentManager(CLOCK); + final var now = ZonedDateTime.now(CLOCK); + final var sum = new DummySum() // + .withEssCapacity(10000) // + .withEssSoc(50) // + .withEssMinDischargePower(-4000) // + .withEssMaxDischargePower(5000); + final var predictorManager = new DummyPredictorManager(// + new DummyPredictor("predictor0", cm, Prediction.from(sum, // + EnergyConstants.SUM_UNMANAGED_CONSUMPTION, now, new Integer[] { // + 4000, 8000, 6000, 2000, 3000, 5000, 7000, 9000, // + 4001, 8001, 6001, 2001, 3001, 5001, 7001, 9001, // + 4002, 8002, 6002, 2002, 3002, 5002, 7002, 9002, // + 4003, 8003, 6003, 2003, 3003, 5003, 7003, 9003, // + 4004, 8004, 6004, 2004, 3004, 5004, 7004, 9004, // + }), EnergyConstants.SUM_UNMANAGED_CONSUMPTION), + new DummyPredictor("predictor1", cm, Prediction.from(sum, // + EnergyConstants.SUM_PRODUCTION, now, + new Integer[] { 8000, 9000, 10000, 11000, 7000, 4000, 3000, 5000, // + 8001, 9001, 10001, 11001, 7001, 4001, 3001, 5001, // + 8002, 9002, 10002, 11002, 7002, 4002, 3002, 5002, // + 8003, 9003, 10003, 11003, 7003, 4003, 3003, 5003, // + 8004, 9004, 10004, 11004, 7004, 4004, 3004, 5004, // + }), EnergyConstants.SUM_PRODUCTION)); + final var prices = DummyTimeOfUseTariffProvider.fromQuarterlyPrices(CLOCK, // + 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, // + 11.1, 12.1, 13.1, 14.1, 15.1, 16.1, 17.1, 18.1, // + 11.2, 12.2, 13.2, 14.2, 15.2, 16.2, 17.2, 18.2, // + 11.3, 12.3, 13.3, 14.3, 15.3, 16.3, 17.3, 18.3, // + 11.4, 12.4, 13.4, 14.4, 15.4, 16.4, 17.4, 18.4 // + ); + + var gsc = GlobalSimulationsContext.create() // + .setComponentManager(cm) // + .setRiskLevel(MEDIUM) // + .setEnergyScheduleHandlers(ImmutableList.of()) // + .setSum(sum) // + .setPredictorManager(predictorManager) // + .setTimeOfUseTariff(prices) // + .build(); + + assertEquals(1000 /* -4000 W */, gsc.ess().maxChargeEnergy()); + assertEquals(1250 /* 5000 W */, gsc.ess().maxDischargeEnergy()); + assertEquals(28, gsc.periods().size()); + var p0 = gsc.periods().get(0); + assertEquals(2000 /* Wh */, p0.production()); + assertEquals(1000 /* Wh */, p0.consumption()); + } + + @Test + public void testCalculatePeriodDurationHourFromIndex() { + assertEquals(24, calculatePeriodDurationHourFromIndex(ZonedDateTime.parse("2020-03-04T14:00:00.00Z"))); + assertEquals(24 + 3, calculatePeriodDurationHourFromIndex(ZonedDateTime.parse("2020-03-04T14:15:00.00Z"))); + assertEquals(24 + 2, calculatePeriodDurationHourFromIndex(ZonedDateTime.parse("2020-03-04T14:30:00.00Z"))); + assertEquals(24 + 1, calculatePeriodDurationHourFromIndex(ZonedDateTime.parse("2020-03-04T14:45:00.00Z"))); + assertEquals(24, calculatePeriodDurationHourFromIndex(ZonedDateTime.parse("2020-03-04T15:00:00.00Z"))); + } +} diff --git a/io.openems.edge.energy/.classpath b/io.openems.edge.energy/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.energy/.classpath +++ b/io.openems.edge.energy/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.energy/bnd.bnd b/io.openems.edge.energy/bnd.bnd index 7df50f07c5e..95805b51ce6 100644 --- a/io.openems.edge.energy/bnd.bnd +++ b/io.openems.edge.energy/bnd.bnd @@ -3,20 +3,33 @@ Bundle-Vendor: FENECON GmbH Bundle-License: https://opensource.org/licenses/EPL-2.0 Bundle-Version: 1.0.0.${tstamp} +# TODO remove emergencycapacityreserve, limittotaldischarge and limiter14a from buildpath after v1 + -buildpath: \ ${buildpath},\ io.openems.common,\ io.openems.edge.common,\ io.openems.edge.controller.api,\ io.openems.edge.controller.ess.emergencycapacityreserve,\ + io.openems.edge.controller.ess.fixactivepower,\ + io.openems.edge.controller.ess.limiter14a,\ io.openems.edge.controller.ess.limittotaldischarge,\ io.openems.edge.controller.ess.timeofusetariff,\ io.openems.edge.energy.api,\ io.openems.edge.ess.api,\ io.openems.edge.predictor.api,\ + io.openems.edge.scheduler.api,\ io.openems.edge.timedata.api,\ io.openems.edge.timeofusetariff.api,\ io.openems.wrapper.jenetics,\ - + -testpath: \ - ${testpath} \ No newline at end of file + ${testpath},\ + io.openems.edge.controller.ess.emergencycapacityreserve,\ + io.openems.edge.controller.ess.gridoptimizedcharge,\ + io.openems.edge.controller.ess.limittotaldischarge,\ + io.openems.edge.controller.ess.timeofusetariff,\ + io.openems.edge.controller.evcs,\ + io.openems.edge.evcs.api,\ + io.openems.edge.meter.api,\ + org.apache.commons.math3,\ diff --git a/io.openems.edge.energy/src/io/openems/edge/energy/Config.java b/io.openems.edge.energy/src/io/openems/edge/energy/Config.java index 5b1344d5a72..df2d7d0d769 100644 --- a/io.openems.edge.energy/src/io/openems/edge/energy/Config.java +++ b/io.openems.edge.energy/src/io/openems/edge/energy/Config.java @@ -3,6 +3,9 @@ import org.osgi.service.metatype.annotations.AttributeDefinition; import org.osgi.service.metatype.annotations.ObjectClassDefinition; +import io.openems.edge.energy.api.RiskLevel; +import io.openems.edge.energy.api.Version; + @ObjectClassDefinition(// name = "Core Energy Scheduler", // description = "The global Energy Scheduler.") @@ -11,5 +14,14 @@ @AttributeDefinition(name = "Is enabled?", description = "Is this Component enabled?") boolean enabled() default true; + @AttributeDefinition(name = "Log-Verbosity", description = "The log verbosity") + LogVerbosity logVerbosity() default LogVerbosity.DEBUG_LOG; + + @AttributeDefinition(name = "Version", description = "Select version of implementation") + Version version() default Version.V1_ESS_ONLY; + + @AttributeDefinition(name = "Risk level", description = "") + RiskLevel riskLevel() default RiskLevel.MEDIUM; + String webconsole_configurationFactory_nameHint() default "Core Energy Scheduler"; } \ No newline at end of file diff --git a/io.openems.edge.energy/src/io/openems/edge/energy/EnergySchedulerImpl.java b/io.openems.edge.energy/src/io/openems/edge/energy/EnergySchedulerImpl.java index 1f0e892b447..8944a6d3946 100644 --- a/io.openems.edge.energy/src/io/openems/edge/energy/EnergySchedulerImpl.java +++ b/io.openems.edge.energy/src/io/openems/edge/energy/EnergySchedulerImpl.java @@ -1,9 +1,12 @@ package io.openems.edge.energy; -import static io.openems.edge.energy.optimizer.Utils.handleGetScheduleRequest; +import static com.google.common.collect.ImmutableList.toImmutableList; +import static io.openems.edge.energy.optimizer.Utils.sortByScheduler; +import static java.util.stream.Collectors.joining; import java.time.ZonedDateTime; import java.util.List; +import java.util.Optional; import java.util.concurrent.CopyOnWriteArrayList; import org.osgi.service.cm.ConfigurationAdmin; @@ -21,18 +24,24 @@ import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.jsonrpc.base.JsonrpcRequest; +import io.openems.common.jsonrpc.base.JsonrpcResponse; import io.openems.edge.common.component.AbstractOpenemsComponent; import io.openems.edge.common.component.ComponentManager; import io.openems.edge.common.component.OpenemsComponent; -import io.openems.edge.common.jsonapi.ComponentJsonApi; -import io.openems.edge.common.jsonapi.JsonApiBuilder; +import io.openems.edge.common.jsonapi.Call; import io.openems.edge.common.sum.Sum; -import io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffControllerImpl; +import io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffController; import io.openems.edge.energy.api.EnergySchedulable; +import io.openems.edge.energy.api.EnergyScheduleHandler.AbstractEnergyScheduleHandler; import io.openems.edge.energy.api.EnergyScheduler; -import io.openems.edge.energy.jsonrpc.GetScheduleRequest; -import io.openems.edge.energy.optimizer.GlobalContext; +import io.openems.edge.energy.api.Version; +import io.openems.edge.energy.api.simulation.GlobalSimulationsContext; import io.openems.edge.energy.optimizer.Optimizer; +import io.openems.edge.energy.v1.jsonrpc.GetScheduleResponse; +import io.openems.edge.energy.v1.optimizer.GlobalContextV1; +import io.openems.edge.energy.v1.optimizer.OptimizerV1; +import io.openems.edge.energy.v1.optimizer.UtilsV1; import io.openems.edge.predictor.api.manager.PredictorManager; import io.openems.edge.timedata.api.Timedata; import io.openems.edge.timeofusetariff.api.TimeOfUseTariff; @@ -43,10 +52,11 @@ immediate = true, // configurationPolicy = ConfigurationPolicy.OPTIONAL // ) -public class EnergySchedulerImpl extends AbstractOpenemsComponent - implements OpenemsComponent, EnergyScheduler, ComponentJsonApi { +@SuppressWarnings("deprecation") +public class EnergySchedulerImpl extends AbstractOpenemsComponent implements OpenemsComponent, EnergyScheduler { /** The hard working Optimizer. */ + private final OptimizerV1 optimizerV1; private final Optimizer optimizer; @Reference @@ -61,72 +71,140 @@ public class EnergySchedulerImpl extends AbstractOpenemsComponent @Reference(policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.OPTIONAL) private volatile TimeOfUseTariff timeOfUseTariff; + @Reference + private io.openems.edge.scheduler.api.Scheduler scheduler; + @Reference private Sum sum; + private final List schedulables = new CopyOnWriteArrayList<>(); + @Reference(policyOption = ReferencePolicyOption.GREEDY, // cardinality = ReferenceCardinality.MULTIPLE, // policy = ReferencePolicy.DYNAMIC, // target = "(enabled=true)") - private volatile List> schedulables = new CopyOnWriteArrayList<>(); + private void addSchedulable(EnergySchedulable schedulable) { + this.schedulables.add(schedulable); + var esh = (AbstractEnergyScheduleHandler) schedulable.getEnergyScheduleHandler(); // this is safe + esh.setOnRescheduleCallback(reason -> this.triggerReschedule(reason)); + this.triggerReschedule("EnergySchedulerImpl::addSchedulable() " + schedulable.id()); + } + + @SuppressWarnings("unused") + private void removeSchedulable(EnergySchedulable schedulable) { + this.schedulables.remove(schedulable); + var esh = (AbstractEnergyScheduleHandler) schedulable.getEnergyScheduleHandler(); // this is safe + esh.removeOnRescheduleCallback(); + this.triggerReschedule("EnergySchedulerImpl::removeSchedulable() " + schedulable.id()); + } @Reference(policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.OPTIONAL) private volatile Timedata timedata; + @Deprecated + @Reference(policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.OPTIONAL, target = "(enabled=true)") + private volatile TimeOfUseTariffController timeOfUseTariffController; + + private Config config; + public EnergySchedulerImpl() { super(// OpenemsComponent.ChannelId.values(), // EnergyScheduler.ChannelId.values() // ); - // Prepare Optimizer and Context - this.optimizer = new Optimizer(() -> { - if (this.timeOfUseTariff == null) { - throw new OpenemsException("TimeOfUseTariff is not available"); - } - var ctrl = this.schedulables.stream() // - .filter(TimeOfUseTariffControllerImpl.class::isInstance) // - .map(TimeOfUseTariffControllerImpl.class::cast) // - .findFirst().orElse(null); - if (ctrl == null) { - throw new OpenemsException("TimeOfUseTariffController is not available"); - } - var esh = ctrl.getEnergyScheduleHandler(); - // NOTE: This is a workaround while we refactor TimeOfUseTariffController - // This code assumes that the `EnergySchedulable` is a - // `TimeOfUseTariffController` - return GlobalContext.create() // - .setClock(this.componentManager.getClock()) // - .setEnergyScheduleHandler(esh) // - .setSum(this.sum) // - .setPredictorManager(this.predictorManager) // - .setTimeOfUseTariff(this.timeOfUseTariff) // - .build(); - }); + + this.optimizerV1 = new OptimizerV1(// + () -> this.config.logVerbosity(), // + () -> { + if (this.timeOfUseTariff == null) { + throw new OpenemsException("TimeOfUseTariff is not available"); + } + if (this.timeOfUseTariffController == null) { + throw new OpenemsException("TimeOfUseTariffController is not available"); + } + return GlobalContextV1.create() // + .setClock(this.componentManager.getClock()) // + .setEnergyScheduleHandler(this.timeOfUseTariffController.getEnergyScheduleHandlerV1()) // + .setSum(this.sum) // + .setPredictorManager(this.predictorManager) // + .setTimeOfUseTariff(this.timeOfUseTariff) // + .build(); + }); + + this.optimizer = new Optimizer(// + () -> this.config.logVerbosity(), // + () -> { + System.out.println("OPTIMIZER gscSupplier: " + + this.schedulables.stream().map(s -> s.id()).collect(joining(", "))); + // Sort Schedulables by the order in the Scheduler + var schedulables = sortByScheduler(this.scheduler, this.schedulables); + System.out.println("OPTIMIZER gscSupplier sorted: " + + schedulables.stream().map(s -> s.id()).collect(joining(", "))); + var eshs = schedulables.stream() // + .map(EnergySchedulable::getEnergyScheduleHandler) // + .collect(toImmutableList()); + System.out.println("OPTIMIZER gscSupplier eshs: " + + eshs.stream().map(e -> e.getClass().getSimpleName()).collect(joining(", "))); + + return GlobalSimulationsContext.create() // + .setComponentManager(this.componentManager) // + .setRiskLevel(this.config.riskLevel()) // + .setEnergyScheduleHandlers(eshs) // + .setSum(this.sum) // + .setPredictorManager(this.predictorManager) // + .setTimeOfUseTariff(this.timeOfUseTariff) // + .build(); + }, // + this.channel(EnergyScheduler.ChannelId.SIMULATIONS_PER_QUARTER)); } @Activate private void activate(ComponentContext context, Config config) throws OpenemsException { super.activate(context, SINGLETON_COMPONENT_ID, SINGLETON_SERVICE_PID, true); - if (this.applyConfig(config)) { - this.optimizer.activate(this.id()); + + if (this.applyConfig(config, "activate")) { + switch (config.version()) { + case V1_ESS_ONLY -> this.optimizerV1.activate(this.id()); + case V2_ENERGY_SCHEDULABLE -> this.optimizer.activate(); + } } } @Modified private void modified(ComponentContext context, Config config) throws OpenemsNamedException { super.modified(context, SINGLETON_COMPONENT_ID, SINGLETON_SERVICE_PID, true); - if (this.applyConfig(config)) { - this.optimizer.modified(this.id()); + this.applyConfig(config, "modified"); + } + + private void triggerReschedule(String reason) { + if (this.config == null) { + return; // Wait for @Activate + } + switch (this.config.version()) { + case V1_ESS_ONLY -> this.optimizerV1.activate(this.id()); + case V2_ENERGY_SCHEDULABLE -> this.optimizer.triggerReschedule(reason); + } + } + + @Override + public String debugLog() { + if (this.config != null && this.config.version() == Version.V2_ENERGY_SCHEDULABLE) { + return this.optimizer.debugLog(); } + return null; } - private synchronized boolean applyConfig(Config config) { + private synchronized boolean applyConfig(Config config, String reason) { + this.config = config; if (OpenemsComponent.validateSingleton(this.cm, SINGLETON_SERVICE_PID, SINGLETON_COMPONENT_ID)) { return false; } - if (!config.enabled()) { - this.optimizer.deactivate(); + if (config.enabled()) { + this.triggerReschedule("EnergySchedulerImpl::applyConfig()" + reason); + } else { + this.optimizerV1.deactivate(); + this.optimizer.interruptTask(); return false; } @@ -136,14 +214,24 @@ private synchronized boolean applyConfig(Config config) { @Override @Deactivate protected void deactivate() { + this.optimizerV1.deactivate(); this.optimizer.deactivate(); super.deactivate(); } @Override - public void buildJsonApiRoutes(JsonApiBuilder builder) { - builder.handleRequest(GetScheduleRequest.METHOD, call -> handleGetScheduleRequest(// - this.optimizer, call.getRequest().getId(), this.timedata, this.timeOfUseTariff, - "ctrlEssTimeOfUseTariff0", ZonedDateTime.now(this.componentManager.getClock()))); + public GetScheduleResponse handleGetScheduleRequestV1(Call call, String id) { + if (this.optimizerV1 != null) { + return UtilsV1.handleGetScheduleRequest(this.optimizerV1, call.getRequest().getId(), this.timedata, + this.timeOfUseTariff, id, ZonedDateTime.now(this.componentManager.getClock())); + } + throw new IllegalArgumentException("This should have been Version V1"); + } + + @Override + public Version getImplementationVersion() { + return Optional.ofNullable(this.config) // + .map(c -> c.version()) // + .orElse(null); } } diff --git a/io.openems.edge.energy/src/io/openems/edge/energy/LogVerbosity.java b/io.openems.edge.energy/src/io/openems/edge/energy/LogVerbosity.java new file mode 100644 index 00000000000..27f2d942c3b --- /dev/null +++ b/io.openems.edge.energy/src/io/openems/edge/energy/LogVerbosity.java @@ -0,0 +1,13 @@ +package io.openems.edge.energy; + +public enum LogVerbosity { + NONE, + /** + * Show basic information in Controller.Debug.Log. + */ + DEBUG_LOG, + /** + * Trace. + */ + TRACE; +} diff --git a/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/EshCodec.java b/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/EshCodec.java new file mode 100644 index 00000000000..f979a534d85 --- /dev/null +++ b/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/EshCodec.java @@ -0,0 +1,180 @@ +package io.openems.edge.energy.optimizer; + +import static io.jenetics.util.ISeq.toISeq; +import static java.lang.Math.min; +import static java.util.stream.Collectors.joining; +import static java.util.stream.IntStream.range; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Optional; +import java.util.function.Function; + +import io.jenetics.Genotype; +import io.jenetics.IntegerChromosome; +import io.jenetics.IntegerGene; +import io.jenetics.engine.InvertibleCodec; +import io.jenetics.util.Factory; +import io.openems.edge.energy.api.EnergyScheduleHandler; +import io.openems.edge.energy.api.simulation.GlobalSimulationsContext; + +/** + * {@link InvertibleCodec} implementation to convert between {@link Genotype} + * and int-array representation of a schedule. + * + *

+ * Genotype: + * + *

    + *
  • Separate Chromosome per EnergyScheduleHandler WithDifferentStates + *
  • Chromosome length = number of periods + *
  • Integer-Genes represent the state + *
+ * + *

+ * int-array schedule: + * + *

    + *
  • First dimension: period, i.e. [0] is first period + *
  • Second dimension: state index of + * {@link EnergyScheduleHandler.WithDifferentStates} + *
+ */ +public class EshCodec implements InvertibleCodec { + + private final Genotype gtf; + private final Function, int[][]> decoder; + private final Function> encoder; + + /** + * Creates an {@link EshCodec}. + * + * @param gsc the {@link GlobalSimulationsContext} + * @param previousResult the previous {@link SimulationResult} + * @param isFirstPeriodFixed is the first period fixed to the value of the + * previous result? + * @return new {@link EshCodec} or null + */ + public static EshCodec of(GlobalSimulationsContext gsc, SimulationResult previousResult, + boolean isFirstPeriodFixed) { + final var chromosomes = gsc.eshsWithDifferentStates().stream() // + .map(esh -> IntegerChromosome.of(0, esh.getAvailableStates().length, gsc.periods().size())) // + .collect(toISeq()); + if (chromosomes.isEmpty()) { + return null; + } + + return new EshCodec(// + // Genotype Factory + Genotype.of(chromosomes), // + + // Decoder + gt -> { + final var numberOfPeriods = gsc.periods().size(); + final var numberOfEshs = gsc.eshsWithDifferentStates().size(); + final var schedule = new int[numberOfPeriods][numberOfEshs]; + + for (var periodIndex = 0; periodIndex < numberOfPeriods; periodIndex++) { + for (var eshIndex = 0; eshIndex < numberOfEshs; eshIndex++) { + final int value = gt.get(eshIndex).get(periodIndex).intValue(); + final int state; + // TODO Valid States per Period should be read from ESH here. + // Example: might be limited by a SmartConfig with JSCalendar payload + if (isFirstPeriodFixed && periodIndex == 0) { + final var time = gsc.periods().get(periodIndex).time(); + final var esh = gsc.eshsWithDifferentStates().get(eshIndex); + state = Optional.ofNullable(previousResult.schedules().get(esh)) + .flatMap(s -> Optional.ofNullable(s.get(time)).map(t -> t.stateIndex())) + .orElse(value); + } else { + state = gt.get(eshIndex).get(periodIndex).intValue(); + } + schedule[periodIndex][eshIndex] = state; + } + } + return schedule; + }, + + // Encoder + schedule -> { + if (schedule.length == 0) { + return null; + } + var numberOfEshs = min(schedule[0].length, gsc.eshsWithDifferentStates().size()); + if (numberOfEshs == 0) { + return null; + } + var numberOfPeriods = min(schedule.length, gsc.periods().size()); + return Genotype.of(range(0, numberOfEshs) // + .mapToObj(eshIndex -> { + var esh = gsc.eshsWithDifferentStates().get(eshIndex); + var max = esh.getAvailableStates().length; + return IntegerChromosome.of(range(0, numberOfPeriods) // + .mapToObj(periodIndex -> IntegerGene.of(// + min(schedule[periodIndex][eshIndex], max - 1), 0, max)) // + .collect(toISeq())); + }) // + .collect(toISeq())); + }); + } + + private EshCodec(// + Genotype gtf, // + Function, int[][]> decoder, // + Function> encoder) { + this.gtf = gtf; + this.decoder = decoder; + this.encoder = encoder; + } + + @Override + public Factory> encoding() { + return this.gtf; + } + + @Override + public Function, int[][]> decoder() { + return this.decoder; + } + + @Override + public Function> encoder() { + return this.encoder; + } + + /** + * Converts a Schedule to a human readable String. + * + * @param schedule the Schedule + * @return the String + */ + public static String scheduleToString(int[][] schedule) { + return "[" + Arrays.stream(schedule) // + .map(arr -> "[" + Arrays.stream(arr).mapToObj(Integer::toString).collect(joining(",")) + "]") // + .collect(joining(",")) + "]"; + } + + /** + * Converts a Collection of Schedules to a human readable String. + * + * @param schedules Collection of Schedules + * @return the String + */ + public static String schedulesToString(Collection schedules) { + return schedules.stream() // + .map(EshCodec::scheduleToString) // + .collect(joining("\n")); + } + + /** + * Converts a Collection of Schedules to an array of human readable Strings. + * + * @param schedules Collection of Schedules + * @return the String array + */ + public static String[] schedulesToStringArray(Collection schedules) { + return schedules.stream() // + .map(EshCodec::scheduleToString) // + .toArray(String[]::new); + } +} \ No newline at end of file diff --git a/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/InitialPopulation.java b/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/InitialPopulation.java new file mode 100644 index 00000000000..db4bdacf63c --- /dev/null +++ b/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/InitialPopulation.java @@ -0,0 +1,145 @@ +package io.openems.edge.energy.optimizer; + +import static io.jenetics.util.ISeq.toISeq; + +import java.util.List; +import java.util.Objects; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import com.google.common.collect.ImmutableSortedMap; +import com.google.common.collect.Lists; + +import io.jenetics.Gene; +import io.jenetics.Genotype; +import io.jenetics.IntegerGene; +import io.jenetics.util.ISeq; +import io.openems.edge.energy.api.EnergyScheduleHandler; +import io.openems.edge.energy.api.simulation.GlobalSimulationsContext; + +/** + * This class helps finding good initial populations. + */ +public class InitialPopulation { + + private InitialPopulation() { + } + + /** + * Generate initial population. + * + * @param gsc the {@link GlobalSimulationsContext} + * @param codec the {@link EshCodec} + * @param previousResult the {@link SimulationResult} of the previous + * optimization run + * @param isCurrentPeriodFixed fixes the {@link Gene} of the current period to + * the previousResult + * @return a List of {@link Genotype}s, entries can be null + */ + public static ISeq> generateInitialPopulation(GlobalSimulationsContext gsc, EshCodec codec, + SimulationResult previousResult, boolean isCurrentPeriodFixed) { + // TODO read good variations from ESHs. + // Example: force charge car during cheapest hours + return Stream // + .concat(// + variationsOfAllStatesDefault(gsc, previousResult, isCurrentPeriodFixed), // + variationsFromExistingSimulationResult(gsc, previousResult, isCurrentPeriodFixed)) // + .filter(Objects::nonNull) // + .distinct() // + .map(codec::encode) // + .collect(toISeq()); + } + + /** + * Builds Schedules with all states default and all possible variations for + * first adjustable period(s). + * + * @param gsc the {@link GlobalSimulationsContext} + * @param previousResult the {@link SimulationResult} of the previous + * optimization run + * @param isCurrentPeriodFixed fixes the state of the first (current) period to + * the previousResult + * @return a Stream of Schedules + */ + protected static Stream variationsOfAllStatesDefault(GlobalSimulationsContext gsc, + SimulationResult previousResult, boolean isCurrentPeriodFixed) { + return generateAllVariations(gsc) // + .map(variation -> IntStream.range(0, gsc.periods().size()) // + .mapToObj(periodIndex -> { + final var period = gsc.periods().get(periodIndex); + + return IntStream.range(0, gsc.eshsWithDifferentStates().size()) // + .map(eshIndex -> { + final var esh = gsc.eshsWithDifferentStates().get(eshIndex); + final var previousSchedule = previousResult.schedules() + .getOrDefault(esh, ImmutableSortedMap.of()).get(period.time()); + + if (periodIndex == 0 && isCurrentPeriodFixed && previousSchedule != null) { + return previousSchedule.stateIndex(); // from previous result + } else if (periodIndex < 2) { + return variation.get(eshIndex); // variation of first period(s) + } else { + return esh.getDefaultStateIndex(); // ESH default state + } + }).toArray(); // + }) // + .toArray(int[][]::new)); + } + + /** + * Builds Schedules with all states from an existing {@link SimulationResult} + * and all possible variations for first adjustable period. + * + * @param gsc the {@link GlobalSimulationsContext} + * @param previousResult the {@link SimulationResult} of the previous + * optimization run + * @param isCurrentPeriodFixed fixes the state of the first (current) period to + * the previousResult + * @return a Stream of Schedules + */ + protected static Stream variationsFromExistingSimulationResult(GlobalSimulationsContext gsc, + SimulationResult previousResult, boolean isCurrentPeriodFixed) { + return generateAllVariations(gsc) // + .map(variation -> IntStream.range(0, gsc.periods().size()) // + .mapToObj(periodIndex -> { + final var period = gsc.periods().get(periodIndex); + + return IntStream.range(0, gsc.eshsWithDifferentStates().size()) // + .map(eshIndex -> { + final var esh = gsc.eshsWithDifferentStates().get(eshIndex); + final var previousSchedule = previousResult.schedules() + .getOrDefault(esh, ImmutableSortedMap.of()).get(period.time()); + + if (((periodIndex == 0 && isCurrentPeriodFixed) || periodIndex > 1) + && previousSchedule != null) { + return previousSchedule.stateIndex(); // from previous result + } else if (periodIndex < 2) { + return variation.get(eshIndex); // variation of first period(s) + } else { + return esh.getDefaultStateIndex(); // ESH default state + } + }).toArray(); // + }) // + .toArray(int[][]::new)); + + } + + /** + * Generates all possible variations of + * {@link EnergyScheduleHandler.WithDifferentStates} indexes and state-index for + * a Period. + * + * @param gsc {@link GlobalSimulationsContext} + * @return variations + */ + private static Stream> generateAllVariations(GlobalSimulationsContext gsc) { + return Lists.cartesianProduct(// + gsc.eshsWithDifferentStates().stream() // + .map(esh -> IntStream // + .range(0, esh.getAvailableStates().length) // + .mapToObj(Integer::valueOf) // + .toList()) // + .>toArray(List[]::new) // + ).stream(); + } +} diff --git a/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/Optimizer.java b/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/Optimizer.java index 030b0c3e4dc..3ba69ccbc9e 100644 --- a/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/Optimizer.java +++ b/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/Optimizer.java @@ -1,145 +1,296 @@ package io.openems.edge.energy.optimizer; -import static com.google.common.collect.ImmutableMap.toImmutableMap; -import static com.google.common.collect.ImmutableSortedMap.toImmutableSortedMap; -import static io.openems.edge.energy.optimizer.Simulator.simulate; +import static io.jenetics.engine.Limits.byExecutionTime; +import static io.jenetics.engine.Limits.byFixedGeneration; +import static io.openems.common.utils.ThreadPoolUtils.shutdownAndAwaitTermination; +import static io.openems.edge.energy.optimizer.SimulationResult.EMPTY; import static io.openems.edge.energy.optimizer.Utils.calculateExecutionLimitSeconds; -import static io.openems.edge.energy.optimizer.Utils.createSimulatorParams; +import static io.openems.edge.energy.optimizer.Utils.calculateSleepMillis; +import static io.openems.edge.energy.optimizer.Utils.createSimulator; import static io.openems.edge.energy.optimizer.Utils.initializeRandomRegistryForProduction; -import static io.openems.edge.energy.optimizer.Utils.logSchedule; -import static io.openems.edge.energy.optimizer.Utils.updateSchedule; +import static io.openems.edge.energy.optimizer.Utils.logSimulationResult; import static java.lang.Thread.sleep; +import static java.time.Duration.ofSeconds; -import java.time.Duration; -import java.time.Instant; -import java.time.ZonedDateTime; -import java.util.Map.Entry; -import java.util.TreeMap; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Predicate; +import java.util.function.Supplier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.collect.ImmutableSortedMap; - +import io.jenetics.IntegerGene; +import io.jenetics.engine.EvolutionResult; import io.openems.common.exceptions.OpenemsException; import io.openems.common.function.ThrowingSupplier; -import io.openems.common.test.TimeLeapClock; -import io.openems.common.worker.AbstractImmediateWorker; +import io.openems.common.utils.FunctionUtils; +import io.openems.edge.common.channel.Channel; +import io.openems.edge.energy.LogVerbosity; import io.openems.edge.energy.api.EnergyScheduleHandler; -import io.openems.edge.energy.optimizer.Simulator.Period; +import io.openems.edge.energy.api.simulation.GlobalSimulationsContext; /** * This task is executed once in the beginning and afterwards every full 15 * minutes. */ -public class Optimizer extends AbstractImmediateWorker { +public class Optimizer implements Runnable { private final Logger log = LoggerFactory.getLogger(Optimizer.class); + private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); - private final ThrowingSupplier globalContext; - private final TreeMap schedule = new TreeMap<>(); + private final Supplier logVerbosity; + private final ThrowingSupplier gscSupplier; + private final Channel simulationsPerQuarterChannel; + private final AtomicBoolean rescheduleCurrentPeriod = new AtomicBoolean(false); - private Params params = null; + private Simulator simulator = null; + private SimulationResult simulationResult = EMPTY; + private ScheduledFuture future; - public Optimizer(ThrowingSupplier globalContext) { - this.globalContext = globalContext; + public Optimizer(Supplier logVerbosity, + ThrowingSupplier gscSupplier, // + Channel simulationsPerQuarterChannel) { + this.logVerbosity = logVerbosity; + this.gscSupplier = gscSupplier; + this.simulationsPerQuarterChannel = simulationsPerQuarterChannel; initializeRandomRegistryForProduction(); + } - // Run Optimizer thread in LOW PRIORITY - this.setPriority(Thread.MIN_PRIORITY); + /** + * Interrupts the {@link Optimizer} Task. + */ + public synchronized void interruptTask() { + if (this.future != null) { + this.future.cancel(true); + } } - @Override - public void forever() throws InterruptedException, OpenemsException { - this.log.info("# Start next run of Optimizer"); + /** + * Activate and start the {@link Optimizer}. + */ + public synchronized void activate() { + this.interruptTask(); + this.future = this.executor.scheduleAtFixedRate(this, 0, 1, TimeUnit.SECONDS); + } - this.createParams(); // this possibly takes forever + /** + * Deactivate the {@link Optimizer}. + */ + public synchronized void deactivate() { + this.interruptTask(); + shutdownAndAwaitTermination(this.executor, 0); + } - final var globalContext = this.globalContext.get(); - final var start = Instant.now(globalContext.clock()); + /** + * Triggers Rescheduling. + * + * @param reason a reason + */ + public void triggerReschedule(String reason) { + // NOTE: This is what happens here: + // [_cycle ] INFO [dge.energy.optimizer.Optimizer] OPTIMIZER Trigger Reschedule. + // Reason: ControllerEvcsImpl::onEvcsStatusChange from 6:The charging limit + // reached to 1:Not ready for Charging + // [thread-1] INFO [dge.energy.optimizer.Optimizer] OPTIMIZER Optimizer::run() + // InterruptedException: null + // [thread-1] INFO [dge.energy.optimizer.Optimizer] OPTIMIZER Simulation gave no + // result! + // [thread-1] INFO [dge.energy.optimizer.Optimizer] OPTIMIZER Run Quick + // Optimization... + // [thread-1] INFO [dge.energy.optimizer.Optimizer] OPTIMIZER + // updateSimulator()... - long executionLimitSeconds; + // TODO On interrupt: keep best "regularOptimization" up till now as input for + // next InitialPopulation + this.traceLog(() -> "Trigger Reschedule. Reason: " + reason); + this.rescheduleCurrentPeriod.set(true); + this.activate(); // interrupt + reschedule + } - // Calculate max execution time till next quarter (with buffer) - executionLimitSeconds = calculateExecutionLimitSeconds(globalContext.clock()); + @Override + public void run() { + var simulationResult = SimulationResult.EMPTY; + try { + if (this.rescheduleCurrentPeriod.getAndSet(false) || this.simulationResult == EMPTY) { + this.traceLog(() -> "Run Quick Optimization..."); + simulationResult = this.runQuickOptimization(); + } else { + this.traceLog(() -> "Run Regular Optimization..."); + simulationResult = this.runRegularOptimization(); + } - // Find best Schedule - var schedule = Simulator.getBestSchedule(this.params, executionLimitSeconds); + } catch (InterruptedException | ExecutionException | IllegalArgumentException e) { + this.traceLog(() -> "Optimizer::run() " + e.getClass().getSimpleName() + ": " + e.getMessage()); + } - // Re-Simulate and keep best Schedule - var newSchedule = simulate(this.params, schedule); + this.applySimulationResult(simulationResult); + } - // Debug Log best Schedule - logSchedule(this.params, newSchedule); + /** + * Creates a new {@link Simulator} using the `gscSupplier` and updates + * `this.simulator`. + * + * @return a {@link Simulator} or null + * @throws InterruptedException on interrupted sleep + */ + private Simulator updateSimulator() throws InterruptedException { + try { + // Create the Simulator with GlobalSimulationsContext + this.traceLog(() -> "updateSimulator()..."); + createSimulator(this.gscSupplier, // + simulator -> this.simulator = simulator, // + error -> { + this.traceLog(error); + this.applySimulationResult(EMPTY); + }); + final var simulator = this.simulator; + if (simulator == null) { + this.traceLog(() -> "Simulator is null"); + } else { + this.traceLog(() -> "Simulator is " + simulator.toLogString("")); + } + return simulator; + } catch (Exception e) { + e.printStackTrace(); // TODO remove + throw e; + } + } - // Update Schedule from newly simulated Schedule - synchronized (this.schedule) { - updateSchedule(ZonedDateTime.now(globalContext.clock()), this.schedule, newSchedule); + /** + * Runs a quick optimization with only one generation. + * + * @return a {@link SimulationResult} or null + * @throws InterruptedException on interrupted sleep + * @throws ExecutionException on simulation error + */ + protected SimulationResult runQuickOptimization() throws InterruptedException, ExecutionException { + var simulator = this.updateSimulator(); + if (simulator == null) { + return SimulationResult.EMPTY; } - // Send Schedule to Controller - globalContext.energyScheduleHandler().setSchedule(this.schedule.entrySet().stream()// - .collect(toImmutableMap(// - Entry::getKey, // - e -> new EnergyScheduleHandler.Period<>(e.getValue().state(), - e.getValue().op().essChargeInChargeGrid())))); - - // Sleep remaining time - if (!(globalContext.clock() instanceof TimeLeapClock)) { - var remainingExecutionLimit = Duration - .between(Instant.now(globalContext.clock()), start.plusSeconds(executionLimitSeconds)).getSeconds(); - if (remainingExecutionLimit > 0) { - this.log.info("Sleep [" + remainingExecutionLimit + "s] till next run of Optimizer"); - sleep(remainingExecutionLimit * 1000); - } + if (this.simulationResult == EMPTY) { + this.traceLog(() -> "reschedule because previous simulationresult is EMPTY"); + } else { + this.traceLog(() -> "triggerReschedule() had been called -> reschedule"); } + return this.runSimulation(simulator, // + false, // current period can get adjusted + byFixedGeneration(1)) // simulate only one generation + .get(); } /** - * Try forever till all data is available (e.g. ESS Capacity) + * Runs a regular optimization for upcoming periods. * - * @throws InterruptedException during sleep + * @return a {@link SimulationResult} or null + * @throws InterruptedException on interrupted sleep + * @throws ExecutionException on simulation error */ - private void createParams() throws InterruptedException { - while (true) { - try { - synchronized (this.schedule) { - this.params = createSimulatorParams(this.globalContext.get(), // - this.schedule.entrySet().stream() // - .collect(toImmutableSortedMap(// - ZonedDateTime::compareTo, // - Entry::getKey, e -> e.getValue().state()))); - return; - } - - } catch (OpenemsException e) { - this.log.info("# Stuck trying to get Params. " + e.getMessage()); - this.params = null; - synchronized (this.schedule) { - this.schedule.clear(); - } - sleep(30_000); - } + protected SimulationResult runRegularOptimization() throws InterruptedException, ExecutionException { + // Run regular optimization for upcoming periods + var millisTillNextQuarter = calculateSleepMillis(); + if (millisTillNextQuarter < 60_000 /* 60s */) { + this.traceLog(() -> "Run Simulation in " + millisTillNextQuarter + "ms..."); + sleep(millisTillNextQuarter); + } + var simulator = this.updateSimulator(); + if (simulator == null) { + return SimulationResult.EMPTY; } + + this.traceLog(() -> "Run Simulation"); + return this.runSimulation(simulator, // + true, // current period should not get adjusted + byExecutionTime(ofSeconds(calculateExecutionLimitSeconds()))) // Limit by execution time + .get(); + } + + protected CompletableFuture runSimulation(Simulator simulator, boolean isCurrentPeriodFixed, + Predicate> executionLimit) { + this.traceLog(() -> "Run next Simulation"); + return CompletableFuture.supplyAsync(() -> { + this.traceLog(() -> "Executing async Simulation"); + + var bestSchedule = simulator.getBestSchedule(this.simulationResult, isCurrentPeriodFixed, null, // + stream -> stream // + // Stop till next quarter + .limit(executionLimit)); + + return bestSchedule; + }); } /** - * Gets the current {@link Params} or null. - * - * @return the {@link Params} or null + * Applies the Schedule to all {@link EnergyScheduleHandler}s and stores the + * {@link SimulationResult} in `this.simulationResult`. + * + * @param simulationResult the {@link SimulationResult} */ - public Params getParams() { - return this.params; + protected void applySimulationResult(SimulationResult simulationResult) { + if (simulationResult == EMPTY /* no result */) { + this.traceLog(() -> "Simulation gave no result!"); + } + + final var simulator = this.simulator; + if (simulator != null) { + // Debug Log best Schedule + logSimulationResult(simulator, simulationResult); + + // Calculate metrics + var stats = simulator.cache.stats(); + this.simulationsPerQuarterChannel.setNextValue(stats.loadCount()); + } + + // Store result + this.simulationResult = simulationResult; + + // Send Schedule to Controllers + simulationResult.schedules().forEach((esh, schedule) -> { + esh.applySchedule(schedule); + }); + } + + private void traceLog(Supplier message) { + switch (this.logVerbosity.get()) { + case NONE, DEBUG_LOG -> FunctionUtils.doNothing(); + case TRACE -> this.log.info("OPTIMIZER " + message.get()); + } } /** - * Gets a copy of the Schedule. + * Gets the {@link SimulationResult}. * - * @return {@link ImmutableSortedMap} + * @return {@link SimulationResult} + */ + public SimulationResult getSimulationResult() { + return this.simulationResult; + } + + /** + * Output for Controller.Debug.Log. + * + * @return the debug log output */ - public ImmutableSortedMap getSchedule() { - synchronized (this.schedule) { - return ImmutableSortedMap.copyOf(this.schedule); + public String debugLog() { + var b = new StringBuilder(); + if (this.simulationResult.periods().isEmpty()) { + b.append("No Schedule available"); + } else { + b.append("ScheduledPeriods:" + this.simulationResult.periods().size()); + } + var simulator = this.simulator; + if (simulator != null) { + var stats = simulator.cache.stats(); + b.append("|SimulationCounter:" + stats.loadCount()); } + b.append("|PerQuarter:" + this.simulationsPerQuarterChannel.value()); + return b.toString(); } } diff --git a/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/SimulationResult.java b/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/SimulationResult.java new file mode 100644 index 00000000000..4928906bd03 --- /dev/null +++ b/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/SimulationResult.java @@ -0,0 +1,177 @@ +package io.openems.edge.energy.optimizer; + +import static com.google.common.collect.ImmutableMap.toImmutableMap; + +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Locale; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSortedMap; + +import io.jenetics.Genotype; +import io.openems.edge.energy.api.EnergyScheduleHandler; +import io.openems.edge.energy.api.simulation.EnergyFlow; +import io.openems.edge.energy.api.simulation.GlobalSimulationsContext; +import io.openems.edge.energy.api.simulation.GlobalSimulationsContext.Period.Hour; +import io.openems.edge.energy.api.simulation.GlobalSimulationsContext.Period.Quarter; +import io.openems.edge.energy.optimizer.Simulator.EshToState; + +public record SimulationResult(// + double cost, // + ImmutableSortedMap periods, // + ImmutableMap, // + ImmutableSortedMap> schedules) { + + /** + * A Period in a {@link SimulationResult}. Duration of one period is always one + * quarter. + */ + public record Period(// + GlobalSimulationsContext.Period context, // + EnergyFlow energyFlow, // + int essInitialEnergy // + ) { + + /** + * Constructor for {@link Period}. + * + * @param context the {@link GlobalSimulationsContext} + * @param energyFlow the {@link EnergyFlow} + * @param essInitialEnergy the initial ESS energy in the beginning of the period + * in [Wh] + * @return a {@link Period} + */ + public static Period from(GlobalSimulationsContext.Period context, EnergyFlow energyFlow, + int essInitialEnergy) { + return new Period(context, energyFlow, essInitialEnergy); + } + } + + /** + * An empty {@link SimulationResult}. + */ + public static final SimulationResult EMPTY = new SimulationResult(0., ImmutableSortedMap.of(), ImmutableMap.of()); + + /** + * Re-Simulate a {@link Genotype} to create a {@link SimulationResult}. + * + * @param cache the {@link GenotypeCache} + * @param gsc the {@link GlobalSimulationsContext} + * @param schedule the schedule as defined by {@link EshCodec} + * @return the {@link SimulationResult} + */ + private static SimulationResult from(GlobalSimulationsContext gsc, int[][] schedule) { + var allPeriods = ImmutableSortedMap.naturalOrder(); + var allEshToStates = new ArrayList(); + var cost = Simulator.simulate(gsc, schedule, new Simulator.BestScheduleCollector(// + p -> allPeriods.put(p.context().time(), p), // + allEshToStates::add)); + + var schedules = allEshToStates.stream() // + .collect(toImmutableMap(EshToState::esh, // + eshToState -> ImmutableSortedMap.of(eshToState.period().context.time(), + new EnergyScheduleHandler.WithDifferentStates.Period.Transition( + eshToState.postProcessedStateIndex(), eshToState.period().context.price(), + eshToState.period().energyFlow, eshToState.period().essInitialEnergy)), + (a, b) -> ImmutableSortedMap.naturalOrder() + .putAll(a).putAll(b).build())); + + return new SimulationResult(cost, allPeriods.build(), schedules); + } + + /** + * Re-Simulate a {@link Genotype} to create a {@link SimulationResult}. + * + *

+ * This method re-simulates using the {@link Quarter} periods and not (only) the + * {@link Hour} periods. + * + * @param gsc the {@link GlobalSimulationsContext} + * @param schedule the schedule as defined by {@link EshCodec} + * @return the {@link SimulationResult} + */ + public static SimulationResult fromQuarters(GlobalSimulationsContext gsc, int[][] schedule) { + if (gsc == null || schedule.length == 0) { + return SimulationResult.EMPTY; + } + + // Convert to Quarters + final var quarterPeriods = gsc.periods().stream() // + .flatMap(period -> period instanceof GlobalSimulationsContext.Period.Hour ph // + ? ph.quarterPeriods().stream() + : Stream.of(period)) // + .collect(ImmutableList.toImmutableList()); + final GlobalSimulationsContext quarterGsc = new GlobalSimulationsContext(gsc.clock(), gsc.riskLevel(), + gsc.startTime(), gsc.eshs(), gsc.eshsWithDifferentStates(), gsc.grid(), gsc.ess(), gsc.evcss(), + quarterPeriods); + final var quarterSchedule = IntStream.range(0, gsc.periods().size()) // + .flatMap(periodIndex // + -> gsc.periods().get(periodIndex) instanceof GlobalSimulationsContext.Period.Hour ph // + ? ph.quarterPeriods().stream().mapToInt(ignore -> periodIndex) // repeat + : IntStream.of(periodIndex)) // + .mapToObj(periodIndex // + -> IntStream.range(0, gsc.eshsWithDifferentStates().size()) // + .map(eshIndex -> { + if (periodIndex < schedule.length && eshIndex < schedule[periodIndex].length) { + return schedule[periodIndex][eshIndex]; + } + if (periodIndex < gsc.eshsWithDifferentStates().size()) { + return gsc.eshsWithDifferentStates().get(periodIndex).getDefaultStateIndex(); + } + return 0; + }) // + .toArray()) // + .toArray(int[][]::new); + + return from(quarterGsc, quarterSchedule); + } + + private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm"); + + private static void log(StringBuilder b, String format, Object... args) { + b.append(String.format(Locale.ENGLISH, format, args).toString()); + } + + /** + * Builds a log string of this {@link SimulationResult}. + * + * @return log string + */ + public String toLogString(String prefix) { + var b = new StringBuilder(prefix) // + .append("Time Price Production Consumption ManagedCons Ess Grid ProdToCons ProdToGrid ProdToEss GridToCons GridToEss EssToCons EssInitial\n"); + this.periods.entrySet().forEach(e -> { + final var time = e.getKey(); + final var p = e.getValue(); + final var c = p.context; + final var ef = p.energyFlow; + log(b, "%s", prefix); + log(b, "%s ", time.format(TIME_FORMATTER)); + log(b, "%6.2f ", c.price()); + log(b, "%10d ", ef.getProd()); + log(b, "%11d ", ef.getCons()); + log(b, "%11d ", ef.getManagedCons()); + log(b, "%6d ", ef.getEss()); + log(b, "%6d ", ef.getGrid()); + log(b, "%10d ", ef.getProdToCons()); + log(b, "%10d ", ef.getProdToGrid()); + log(b, "%9d ", ef.getProdToEss()); + log(b, "%10d ", ef.getGridToCons()); + log(b, "%9d ", ef.getGridToEss()); + log(b, "%9d ", ef.getEssToCons()); + log(b, "%10d ", p.essInitialEnergy); + this.schedules.forEach((esh, schedule) -> { + log(b, "%-15s ", esh.toStateString(schedule.get(time).stateIndex())); + }); + b.append("\n"); + }); + b.append(prefix).append("cost=").append(this.cost); + return b.toString(); + } +} diff --git a/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/Simulator.java b/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/Simulator.java index 0b79a5f6cc2..fbdaa6fa30e 100644 --- a/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/Simulator.java +++ b/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/Simulator.java @@ -1,177 +1,268 @@ package io.openems.edge.energy.optimizer; -import static io.jenetics.engine.EvolutionResult.toBestGenotype; -import static io.jenetics.engine.Limits.byExecutionTime; -import static io.openems.edge.energy.optimizer.InitialPopulationUtils.buildInitialPopulation; -import static io.openems.edge.energy.optimizer.Utils.paramsAreValid; -import static io.openems.edge.energy.optimizer.Utils.postprocessSimulatorState; -import static java.lang.Math.max; -import static java.time.Duration.ofSeconds; - -import java.time.ZonedDateTime; -import java.util.concurrent.atomic.AtomicInteger; +import static com.google.common.base.MoreObjects.toStringHelper; +import static io.jenetics.engine.EvolutionResult.toBestResult; +import static io.openems.edge.energy.optimizer.InitialPopulation.generateInitialPopulation; +import static io.openems.edge.energy.optimizer.SimulationResult.EMPTY; +import static java.lang.Thread.currentThread; + +import java.util.concurrent.Executor; +import java.util.concurrent.ForkJoinPool; import java.util.function.Consumer; import java.util.function.Function; -import java.util.stream.IntStream; -import java.util.stream.Stream; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSortedMap; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import io.jenetics.Gene; import io.jenetics.Genotype; -import io.jenetics.IntegerChromosome; import io.jenetics.IntegerGene; +import io.jenetics.Mutator; +import io.jenetics.SinglePointCrossover; import io.jenetics.engine.Engine; -import io.jenetics.engine.EvolutionResult; -import io.openems.edge.controller.ess.timeofusetariff.StateMachine; -import io.openems.edge.energy.optimizer.Params.Length; -import io.openems.edge.energy.optimizer.Params.OptimizePeriod; +import io.jenetics.engine.EvolutionStream; +import io.openems.edge.energy.api.EnergyScheduleHandler; +import io.openems.edge.energy.api.EnergyScheduleHandler.AbstractEnergyScheduleHandler; +import io.openems.edge.energy.api.simulation.EnergyFlow; +import io.openems.edge.energy.api.simulation.GlobalSimulationsContext; +import io.openems.edge.energy.api.simulation.OneSimulationContext; public class Simulator { - /** Used to incorporate charge/discharge efficiency. */ - public static final double EFFICIENCY_FACTOR = 1.17; + private static final Logger LOG = LoggerFactory.getLogger(Simulator.class); + + public final GlobalSimulationsContext gsc; + + protected final LoadingCache cache; + + public Simulator(GlobalSimulationsContext gsc) { + this.gsc = gsc; + this.cache = CacheBuilder.newBuilder() // + .recordStats() // + .build(new CacheLoader() { + + @Override + /** + * Simulates a Schedule and calculates the cost. + * + *

+ * NOTE: do not throw an Exception here, because we use + * {@link LoadingCache#getUnchecked(Object)} below. + * + * @param schedule the schedule as defined by {@link EshCodec} + * @return the cost, lower is better, always positive; {@link Double#NaN} on + * error + */ + public Double load(final int[][] schedule) { + return simulate(Simulator.this.gsc, schedule, null); + } + }); - public record Period(OptimizePeriod op, StateMachine state, int essInitial, EnergyFlow ef) { + // Initialize the EnergyScheduleHandlers. + for (var esh : gsc.eshs()) { + ((AbstractEnergyScheduleHandler) esh /* this is safe */).initialize(gsc); + } } /** * Simulates a Schedule and calculates the cost. * - * @param p the {@link Params} - * @param schedule the {@link StateMachine} states of the Schedule - * @return the cost, lower is better; always positive + *

+ * This method internally uses a Cache for schedule costs. + * + * @param schedule the schedule as defined by {@link EshCodec} + * @return the cost, lower is better, always positive; {@link Double#NaN} on + * error */ - protected static double calculateCost(Params p, StateMachine[] schedule) { - final var nextEssInitial = new AtomicInteger(p.essInitialEnergy()); - var sum = 0.; - for (var i = 0; i < p.optimizePeriods().size(); i++) { - sum += simulatePeriod(p, p.optimizePeriods().get(i), schedule[i], nextEssInitial, null); - } - return sum; + public double calculateCost(int[][] schedule) { + return this.cache.getUnchecked(schedule); } /** - * Simulates a Schedule in quarterly periods. + * Simulates a Schedule and calculates the cost. + * + *

+ * This method does not a Cache for {@link Genotype}s. * - * @param p the {@link Params} - * @param schedule the {@link StateMachine} states of the Schedule - * @return a Map of {@link Period}s + * @param schedule the schedule as defined by {@link EshCodec} + * @param bestScheduleCollector the {@link BestScheduleCollector} + * @return the cost, lower is better, always positive; + * {@link Double#POSITIVE_INFINITY} on error */ - protected static ImmutableSortedMap simulate(Params p, StateMachine[] schedule) { - final var nextEssInitial = new AtomicInteger(p.essInitialEnergy()); - var result = ImmutableSortedMap.naturalOrder(); - for (var i = 0; i < p.optimizePeriods().size(); i++) { - var state = schedule[i]; - var op = p.optimizePeriods().get(i); - var length = op.quarterPeriods().size() == 1 ? Length.QUARTER : Length.HOUR; - // Convert mixed OptimizePeriods to pure quarterly - for (var qp : op.quarterPeriods()) { - var quarterlyOp = new OptimizePeriod(qp.time(), length, qp.essMaxChargeEnergy(), - qp.essMaxDischargeEnergy(), qp.essChargeInChargeGrid(), qp.maxBuyFromGrid(), qp.production(), - qp.consumption(), qp.price(), ImmutableList.of(qp)); - simulatePeriod(p, quarterlyOp, state, nextEssInitial, period -> result.put(period.op().time(), period)); - } + public double simulate(int[][] schedule, BestScheduleCollector bestScheduleCollector) { + return simulate(this.gsc, schedule, bestScheduleCollector); + } + + protected static double simulate(GlobalSimulationsContext gsc, int[][] schedule, + BestScheduleCollector bestScheduleCollector) { + final var osc = OneSimulationContext.from(gsc); + final var noOfPeriods = gsc.periods().size(); + + var sum = 0.; + for (var period = 0; period < noOfPeriods; period++) { + sum += simulatePeriod(osc, schedule, period, bestScheduleCollector); } - return result.build(); + return sum; } /** * Calculates the cost of one Period under the given Schedule. * - * @param p the {@link Params} - * @param op the current {@link OptimizePeriod} - * @param state the {@link StateMachine} of the current period - * @param nextEssInitial the initial SoC-Energy; also used as return value - * @param collect a {@link Consumer} to collect the simulation results if - * required. We are not always collecting results to - * reduce workload during simulation. - * @return the cost, lower is better; always positive + * @param simulation the {@link OneSimulationContext} + * @param schedule the schedule as defined by {@link EshCodec} + * @param periodIndex the index of the simulated period + * @param bestScheduleCollector the {@link BestScheduleCollector}; or null + * @return the cost, lower is better, always positive; + * {@link Double#POSITIVE_INFINITY} on error */ - protected static double simulatePeriod(Params p, OptimizePeriod op, StateMachine state, - final AtomicInteger nextEssInitial, Consumer collect) { - // Constants - final var essInitial = max(0, nextEssInitial.get()); // always at least '0' + public static double simulatePeriod(OneSimulationContext simulation, int[][] schedule, int periodIndex, + BestScheduleCollector bestScheduleCollector) { + final var period = simulation.global.periods().get(periodIndex); + final var eshs = simulation.global.eshs(); + final var model = EnergyFlow.Model.from(simulation, period); + + double cost = 0.; + var eshIndex = 0; + for (var esh : eshs) { + if (esh instanceof EnergyScheduleHandler.WithDifferentStates e) { + // Simulate with state given by Genotype + cost += e.simulatePeriod(simulation, period, model, schedule[periodIndex][eshIndex++]); + } else if (esh instanceof EnergyScheduleHandler.WithOnlyOneState e) { + e.simulatePeriod(simulation, period, model); + } + } - // Calculate Energy-Flow - final var ef = switch (state) { - case BALANCING -> EnergyFlow.withBalancing(p, op, essInitial); - case DELAY_DISCHARGE -> EnergyFlow.withDelayDischarge(p, op, essInitial); - case CHARGE_GRID -> EnergyFlow.withChargeGrid(p, op, essInitial); - }; + final EnergyFlow energyFlow = model.solve(); - nextEssInitial.set(essInitial - ef.ess()); + if (energyFlow == null) { + LOG.error("Error while simulating period [" + periodIndex + "]"); + // TODO add configurable debug logging + // LOG.info(simulation.toString()); + // model.logConstraints(); + // model.logMinMaxValues(); + return Double.POSITIVE_INFINITY; + } // Calculate Cost - double cost; - if (ef.grid() > 0) { + // TODO should be done also by ESH to enable this use-case: + // https://community.openems.io/t/limitierung-bei-negativen-preisen-und-lastgang-einkauf/2713/2 + if (energyFlow.getGrid() > 0) { // Filter negative prices - var price = max(0, op.price()); + var price = Math.max(0, period.price()); - cost = // Cost for direct Consumption - ef.gridToConsumption() * price + cost += // Cost for direct Consumption + energyFlow.getGridToCons() * price // Cost for future Consumption after storage - + ef.gridToEss() * price * EFFICIENCY_FACTOR; + + energyFlow.getGridToEss() * price * simulation.global.riskLevel().efficiencyFactor; } else { - // Sell-to-Grid - cost = 0.; + // Sell-to-Grid -> no cost } - if (collect != null) { - var postprocessedState = postprocessSimulatorState(state, // - EnergyFlow.withBalancing(p, op, essInitial), // - EnergyFlow.withDelayDischarge(p, op, essInitial), // - EnergyFlow.withChargeGrid(p, op, essInitial)); - collect.accept(new Period(op, postprocessedState, essInitial, ef)); + if (bestScheduleCollector != null) { + final var srp = SimulationResult.Period.from(period, energyFlow, simulation.ess.getInitialEnergy()); + bestScheduleCollector.allPeriods.accept(srp); + eshIndex = 0; + for (var esh : eshs) { + if (esh instanceof EnergyScheduleHandler.WithDifferentStates e) { + bestScheduleCollector.eshStates.accept(new EshToState(e, srp, // + e.postProcessPeriod(period, simulation, energyFlow, schedule[periodIndex][eshIndex++]))); + } + } } + + // Prepare for next period + simulation.ess.calculateInitialEnergy(energyFlow.getEss()); + return cost; } /** - * Runs the optimization with default settings. + * Runs the optimization and returns the "best" simulation result. * - * @param p the {@link Params} - * @param executionLimitSeconds limit.byExecutionTime.ofSeconds - * @return the best schedule + * @param previousResult the {@link SimulationResult} of the + * previous optimization run + * @param isCurrentPeriodFixed fixes the {@link Gene} of the current + * period to the previousResult + * @param engineInterceptor an interceptor for the + * {@link Engine.Builder} + * @param evolutionStreamInterceptor an interceptor for the + * {@link EvolutionStream} + * @return the best Schedule */ - protected static StateMachine[] getBestSchedule(Params p, long executionLimitSeconds) { - return getBestSchedule(p, executionLimitSeconds, null, null); - } + public SimulationResult getBestSchedule(SimulationResult previousResult, boolean isCurrentPeriodFixed, + Function, Engine.Builder> engineInterceptor, + Function, EvolutionStream> evolutionStreamInterceptor) { + final var codec = EshCodec.of(this.gsc, previousResult, isCurrentPeriodFixed); + if (codec == null) { + return EMPTY; + } - protected static StateMachine[] getBestSchedule(Params p, long executionLimitSeconds, Integer populationSize, - Integer limit) { - // Return pure BALANCING Schedule if no predictions are available - if (!paramsAreValid(p)) { - return p.optimizePeriods().stream() // - .map(op -> StateMachine.BALANCING) // - .toArray(StateMachine[]::new); + // Decide for single- or multi-threading + final Executor executor; + final var availableCores = Runtime.getRuntime().availableProcessors() - 1; + if (availableCores > 1) { + // Executor is a Thread-Pool with CPU-Cores minus one + executor = new ForkJoinPool(availableCores); + System.out.println("OPTIMIZER Executor runs on " + availableCores + " cores"); + } else { + // Executor is the current thread + executor = Runnable::run; + System.out.println("OPTIMIZER Executor runs on current thread"); } - var gtf = Genotype.of(IntegerChromosome.of(IntegerGene.of(0, p.states().length)), p.optimizePeriods().size()); // - var eval = (Function, Double>) (gt) -> { - var modes = new StateMachine[p.optimizePeriods().size()]; - for (var i = 0; i < modes.length; i++) { - modes[i] = p.states()[gt.get(i).get(0).intValue()]; - } - return calculateCost(p, modes); - }; + // Build the Jenetics Engine + final var initialPopulation = generateInitialPopulation(this.gsc, codec, previousResult, isCurrentPeriodFixed); var engine = Engine // - .builder(eval, gtf) // - .executor(Runnable::run) // current thread + .builder(this.cache::getUnchecked, codec) // + .alterers(// + new SinglePointCrossover(0.2), // + new Mutator(0.15)) // + .populationSize(initialPopulation.size()) // + .executor(executor) // .minimizing(); - if (populationSize != null) { - engine.populationSize(populationSize); // + if (engineInterceptor != null) { + engine = engineInterceptor.apply(engine); } - Stream> stream = engine.build() // - .stream(buildInitialPopulation(p)) // - .limit(byExecutionTime(ofSeconds(executionLimitSeconds))); // - if (limit != null) { - stream = stream.limit(limit); // apply optional limit + + var stream = engine.build() // + .stream(initialPopulation) // + .limit(result -> !currentThread().isInterrupted()); + if (evolutionStreamInterceptor != null) { + stream = evolutionStreamInterceptor.apply(stream); } + + // Start the evaluation var bestGt = stream // - .collect(toBestGenotype()); - return IntStream.range(0, p.optimizePeriods().size()) // - .mapToObj(period -> p.states()[bestGt.get(period).get(0).intValue()]) // - .toArray(StateMachine[]::new); + .collect(toBestResult(codec)); + + return SimulationResult.fromQuarters(this.gsc, bestGt); + } + + protected static record BestScheduleCollector(// + Consumer allPeriods, // + Consumer eshStates) { + } + + protected static record EshToState(// + EnergyScheduleHandler.WithDifferentStates esh, // + SimulationResult.Period period, // + int postProcessedStateIndex) { + } + + /** + * Builds a log string of this {@link Simulator}. + * + * @param prefix a line prefix + * @return log string + */ + public String toLogString(String prefix) { + return prefix + toStringHelper(this) // + .addValue(this.gsc) // + .addValue(this.cache.stats()) // + .toString(); } } diff --git a/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/Utils.java b/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/Utils.java index 4bc3d97858c..b3710c01575 100644 --- a/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/Utils.java +++ b/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/Utils.java @@ -1,88 +1,41 @@ package io.openems.edge.energy.optimizer; -import static com.google.common.collect.Streams.concat; import static io.openems.common.utils.DateUtils.roundDownToQuarter; -import static io.openems.edge.common.type.TypeUtils.multiply; -import static io.openems.edge.common.type.TypeUtils.orElse; -import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.BALANCING; -import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.CHARGE_GRID; -import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.DELAY_DISCHARGE; -import static io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffController.PERIODS_PER_HOUR; import static java.lang.Math.max; -import static java.lang.Math.round; -import static java.util.Arrays.stream; import java.time.Clock; import java.time.Duration; import java.time.ZonedDateTime; -import java.time.temporal.ChronoUnit; import java.util.List; import java.util.Objects; -import java.util.Set; -import java.util.TreeMap; -import java.util.UUID; +import java.util.function.Consumer; +import java.util.function.Supplier; import java.util.random.RandomGeneratorFactory; -import java.util.stream.IntStream; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSortedMap; -import com.google.common.collect.Streams; +import com.google.common.collect.Ordering; import io.jenetics.util.RandomRegistry; -import io.openems.common.exceptions.InvalidValueException; -import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.common.timedata.Resolution; +import io.openems.common.exceptions.OpenemsException; +import io.openems.common.function.ThrowingSupplier; import io.openems.common.types.ChannelAddress; -import io.openems.edge.controller.ess.emergencycapacityreserve.ControllerEssEmergencyCapacityReserve; -import io.openems.edge.controller.ess.limittotaldischarge.ControllerEssLimitTotalDischarge; -import io.openems.edge.controller.ess.timeofusetariff.StateMachine; -import io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffController; -import io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffControllerImpl; -import io.openems.edge.energy.jsonrpc.GetScheduleResponse; -import io.openems.edge.energy.optimizer.ScheduleDatas.ScheduleData; -import io.openems.edge.energy.optimizer.Simulator.Period; -import io.openems.edge.ess.api.SymmetricEss; -import io.openems.edge.timedata.api.Timedata; -import io.openems.edge.timeofusetariff.api.TimeOfUseTariff; +import io.openems.edge.energy.api.EnergySchedulable; +import io.openems.edge.energy.api.simulation.GlobalSimulationsContext; +import io.openems.edge.scheduler.api.Scheduler; -/** - * Utils for {@link TimeOfUseTariffController}. - * - *

- * All energy values are in [Wh] and positive, unless stated differently. - */ public final class Utils { private Utils() { } - /** Keep some buffer to avoid scheduling errors because of bad predictions. */ - public static final float ESS_MAX_SOC = 90F; - /** Limit Charge Power for §14a EnWG. */ public static final int ESS_LIMIT_14A_ENWG = -4200; - /** - * C-Rate (capacity divided by time) during {@link StateMachine#CHARGE_GRID}. - * With a C-Rate of 0.5 the battery gets fully charged within 2 hours. - */ - public static final float ESS_CHARGE_C_RATE = 0.5F; - - public static final ChannelAddress SUM_PRODUCTION = new ChannelAddress("_sum", "ProductionActivePower"); - public static final ChannelAddress SUM_CONSUMPTION = new ChannelAddress("_sum", "ConsumptionActivePower"); public static final ChannelAddress SUM_GRID = new ChannelAddress("_sum", "GridActivePower"); - public static final ChannelAddress SUM_UNMANAGED_CONSUMPTION = new ChannelAddress("_sum", - "UnmanagedConsumptionActivePower"); public static final ChannelAddress SUM_ESS_DISCHARGE_POWER = new ChannelAddress("_sum", "EssDischargePower"); public static final ChannelAddress SUM_ESS_SOC = new ChannelAddress("_sum", "EssSoc"); - protected static final long EXECUTION_LIMIT_SECONDS_BUFFER = 30; - protected static final long EXECUTION_LIMIT_SECONDS_MINIMUM = 60; - - private static final Logger LOG = LoggerFactory.getLogger(Utils.class); + protected static final long EXECUTION_LIMIT_SECONDS_BUFFER = 5; /** * Initializes the Jenetics {@link RandomRegistry} for production. @@ -121,383 +74,89 @@ private static void initializeRandomRegistry(boolean isUnitTest) { } /** - * Create {@link Params} for {@link Simulator}. + * Creates a {@link Simulator}. * - * @param globalContext the {@link GlobalContext} object - * @param existingSchedule the existing schedule, i.e. result of previous - * optimization - * @return {@link Params} - * @throws InvalidValueException on error + *

+ * This will possibly run forever and call the callbacks multiple times before + * returning. + * + * @param simulator a callback for a {@link GlobalSimulationsContext}; + * possibly null + * @param gscSupplier a {@link Supplier} for {@link GlobalSimulationsContext} + * @param error a callback for a error string + * @throws InterruptedException on interrupted sleep */ - public static Params createSimulatorParams(GlobalContext globalContext, - ImmutableSortedMap existingSchedule) throws InvalidValueException { - final var time = roundDownToQuarter(ZonedDateTime.now()); - - // Prediction values - final var predictionConsumption = joinConsumptionPredictions(4, // - globalContext.predictorManager().getPrediction(SUM_CONSUMPTION).asArray(), // - globalContext.predictorManager().getPrediction(SUM_UNMANAGED_CONSUMPTION).asArray()); - final var predictionProduction = generateProductionPrediction(// - globalContext.predictorManager().getPrediction(SUM_PRODUCTION).asArray(), // - predictionConsumption.length); - - // Prices contains the price values and the time it is retrieved. - final var prices = globalContext.timeOfUseTariff().getPrices(); + public static synchronized void createSimulator( + ThrowingSupplier gscSupplier, Consumer simulator, + Consumer> error) throws InterruptedException { + final GlobalSimulationsContext gsc; + try { + // Create GlobalSimulationsContext -> this might fail a few times during + // initialization of OpenEMS + gsc = gscSupplier.get(); - // Ess information. - TimeOfUseTariffControllerImpl.Context context = globalContext.energyScheduleHandler().getContext(); - final var essTotalEnergy = context.ess().getCapacity().getOrError(); - final var essMinSocEnergy = getEssMinSocEnergy(context, essTotalEnergy); - final var essMaxSocEnergy = round(ESS_MAX_SOC / 100F * essTotalEnergy); - final var essSoc = context.ess().getSoc().getOrError(); - final var essSocEnergy = essTotalEnergy /* [Wh] */ / 100 * essSoc; - - // Power Values for scheduling battery for individual periods. - var maxDischargePower = globalContext.sum().getEssMaxDischargePower().orElse(1000 /* at least 1000 */); - var maxChargePower = globalContext.sum().getEssMaxDischargePower().orElse(-1000 /* at least 1000 */); - if (context.limitChargePowerFor14aEnWG()) { - maxChargePower = max(ESS_LIMIT_14A_ENWG, maxChargePower); // Apply §14a EnWG limit + } catch (OpenemsException | IllegalArgumentException e) { + simulator.accept(null); + error.accept(() -> "Unable to create GlobalSimulationsContext. " + e.getClass().getSimpleName() + ": " + + e.getMessage()); + Thread.sleep(10 * 1000); + return; } - return Params.create() // - .setTime(time) // - .setEssTotalEnergy(essTotalEnergy) // - .setEssMinSocEnergy(essMinSocEnergy) // - .setEssMaxSocEnergy(essMaxSocEnergy) // - .setEssInitialEnergy(essSocEnergy) // - .setEssMaxChargeEnergy(toEnergy(Math.abs(maxChargePower))) // - .setEssMaxDischargeEnergy(toEnergy(maxDischargePower)) // - .seMaxBuyFromGrid(toEnergy(context.maxChargePowerFromGrid())) // - .setProductions(stream(interpolateArray(predictionProduction)).map(v -> toEnergy(v)).toArray()) // - .setConsumptions(stream(interpolateArray(predictionConsumption)).map(v -> toEnergy(v)).toArray()) // - .setPrices(interpolateArray(prices.asArray())) // - .setStates(context.controlMode().states) // - .setExistingSchedule(existingSchedule) // - .build(); - } - - /** - * Postprocesses production prediction; makes sure length is at least the same - * as consumption prediction - filling up with zeroes. - * - * @param prediction the production prediction - * @param minLength the min length (= consumption prediction length) - * @return new production prediction - */ - protected static Integer[] generateProductionPrediction(Integer[] prediction, int minLength) { - if (prediction.length >= minLength) { - return prediction; + // Are there any schedulable ESHs? + if (gsc.eshsWithDifferentStates().size() > 0) { + simulator.accept(new Simulator(gsc)); + return; } - return IntStream.range(0, minLength) // - .mapToObj(i -> i > prediction.length - 1 ? 0 : prediction[i]) // - .toArray(Integer[]::new); - } - - protected static Integer[] joinConsumptionPredictions(int splitAfterIndex, Integer[] totalConsumption, - Integer[] unmanagedConsumption) { - return Streams.concat(// - stream(totalConsumption) // - .limit(splitAfterIndex), // - stream(unmanagedConsumption) // - .skip(splitAfterIndex)) // - .toArray(Integer[]::new); - } - protected static boolean paramsAreValid(Params p) { - if (p.optimizePeriods().isEmpty()) { - // No periods are available - LOG.warn("No periods are available"); - return false; + // None. Freeze till interrupt + simulator.accept(null); + error.accept(() -> "List of schedulable EnergyScheduleHandlers is empty -> freeze till interrupt"); + while (true) { + Thread.sleep(5 * 60 * 1000); } - if (p.optimizePeriods().stream() // - .allMatch(pp -> pp.production() == 0 && pp.consumption() == 0)) { - // Production and Consumption predictions are all zero - LOG.warn("Production and Consumption predictions are all zero"); - return false; - } - if (p.optimizePeriods().stream() // - .mapToDouble(Params.OptimizePeriod::price) // - .distinct() // - .count() <= 1) { - // Prices are all the same - LOG.info("Prices are all the same"); - return false; - } - - return true; } /** - * Returns the amount of energy that is not available for scheduling because of - * a configured minimum SoC. + * Calculates the milliseconds to sleep start of next Quarter. * - * @param context the {@link TimeOfUseTariffControllerImpl.Context} - * @param essCapacity net {@link SymmetricEss.ChannelId#CAPACITY} - * @return the value in [Wh] + * @return sleep time in [ms] */ - protected static int getEssMinSocEnergy(TimeOfUseTariffControllerImpl.Context context, int essCapacity) { - return essCapacity /* [Wh] */ / 100 // - * getEssMinSocPercentage(// - context.ctrlLimitTotalDischarges(), // - context.ctrlEmergencyCapacityReserves()); + public static long calculateSleepMillis() { + return calculateSleepMillis(Clock.systemDefaultZone()); } /** - * Returns the configured minimum SoC, or zero. + * Calculates the milliseconds to sleep start of next Quarter. * - * @param ctrlLimitTotalDischarges the list of - * {@link ControllerEssLimitTotalDischarge} - * @param ctrlEmergencyCapacityReserves the list of - * {@link ControllerEssEmergencyCapacityReserve} - * @return the value in [%] + * @param clock a {@link Clock} + * @return sleep time in [ms] */ - public static int getEssMinSocPercentage(List ctrlLimitTotalDischarges, - List ctrlEmergencyCapacityReserves) { - return concat(// - ctrlLimitTotalDischarges.stream() // - .map(ctrl -> ctrl.getMinSoc().get()) // - .filter(Objects::nonNull) // - .mapToInt(v -> max(0, v)), // only positives - ctrlEmergencyCapacityReserves.stream() // - .map(ctrl -> ctrl.getActualReserveSoc().get()) // - .filter(Objects::nonNull) // - .mapToInt(v -> max(0, v))) // only positives - .max().orElse(0); - } - - /** - * Interpolate an Array of {@link Double}s. - * - *

- * Replaces nulls with previous value. If first entry is null, it is set to - * first available value. If all values are null, all are set to 0. - * - * @param values the values - * @return values without nulls - */ - protected static double[] interpolateArray(Double[] values) { - var firstNonNull = stream(values) // - .filter(Objects::nonNull) // - .findFirst(); - var lastNonNullIndex = IntStream.range(0, values.length) // - .filter(i -> values[i] != null) // - .reduce((first, second) -> second); - if (lastNonNullIndex.isEmpty()) { - return new double[0]; - } - var result = new double[lastNonNullIndex.getAsInt() + 1]; - if (firstNonNull.isEmpty()) { - // all null - return result; - } - double last = firstNonNull.get(); - for (var i = 0; i < result.length; i++) { - double value = orElse(values[i], last); - result[i] = last = value; - } - return result; - } - - /** - * Interpolate an Array of {@link Integer}s. - * - *

- * Replaces nulls with previous value. If first entry is null, it is set to - * first available value. If all values are null, all are set to 0. - * - * @param values the values - * @return values without nulls - */ - protected static int[] interpolateArray(Integer[] values) { - var firstNonNull = stream(values) // - .filter(Objects::nonNull) // - .findFirst(); - var lastNonNullIndex = IntStream.range(0, values.length) // - .filter(i -> values[i] != null) // - .reduce((first, second) -> second); // - if (lastNonNullIndex.isEmpty()) { - return new int[0]; - } - var result = new int[lastNonNullIndex.getAsInt() + 1]; - if (firstNonNull.isEmpty()) { - // all null - return result; - } - int last = firstNonNull.get(); - for (var i = 0; i < result.length; i++) { - int value = orElse(values[i], last); - result[i] = last = value; - } - return result; - } - - protected static int findFirstPeakIndex(int fromIndex, double[] values) { - if (values.length <= fromIndex) { - return fromIndex; - } else { - var previous = values[fromIndex]; - for (var i = fromIndex + 1; i < values.length; i++) { - var value = values[i]; - if (value < previous) { - return i - 1; - } - previous = value; - } - } - return values.length - 1; - } - - protected static int findFirstValleyIndex(int fromIndex, double[] values) { - if (values.length <= fromIndex) { - return fromIndex; - } else { - var previous = values[fromIndex]; - for (var i = fromIndex + 1; i < values.length; i++) { - var value = values[i]; - if (value > previous) { - return i - 1; - } - previous = value; - } - } - return values.length - 1; + public static long calculateSleepMillis(Clock clock) { + var now = ZonedDateTime.now(clock); + var nextQuarter = roundDownToQuarter(now).plusMinutes(15); + return Duration.between(now, nextQuarter).toMillis() + 100 /* buffer */; } /** - * Utilizes the previous three hours' data and computes the next 21 hours data - * from the {@link Optimizer} provided, then concatenates them to generate a - * 24-hour {@link GetScheduleResponse}. + * Calculates the ExecutionLimitSeconds for the {@link Optimizer}. * - * @param optimizer the {@link Optimizer} - * @param requestId the JSON-RPC request-id - * @param timedata the{@link Timedata} - * @param timeOfUseTariff the {@link TimeOfUseTariff} - * @param componentId the Component-ID - * @param now the current {@link ZonedDateTime} (will get rounded - * down to 15 minutes) - * @return the {@link GetScheduleResponse} - * @throws OpenemsNamedException on error + * @return execution limit in [s] */ - public static GetScheduleResponse handleGetScheduleRequest(Optimizer optimizer, UUID requestId, Timedata timedata, - TimeOfUseTariff timeOfUseTariff, String componentId, ZonedDateTime now) { - final var b = ImmutableList.builder(); - now = roundDownToQuarter(now); - final var fromTime = now.minusHours(3); - - final var params = optimizer.getParams(); - if (params != null) { - // Process last three hours of historic data - final var channelQuarterlyPrices = new ChannelAddress(componentId, "QuarterlyPrices"); - final var channelStateMachine = new ChannelAddress(componentId, "StateMachine"); - try { - var queryResult = timedata.queryHistoricData(null, fromTime, now, // - Set.of(channelQuarterlyPrices, channelStateMachine, // - SUM_GRID, SUM_PRODUCTION, SUM_CONSUMPTION, SUM_ESS_DISCHARGE_POWER, SUM_ESS_SOC), - new Resolution(15, ChronoUnit.MINUTES)); - ScheduleData.fromHistoricDataQuery(// - params.essTotalEnergy(), channelQuarterlyPrices, channelStateMachine, queryResult) // - .forEach(b::add); - } catch (Exception e) { - LOG.warn("Unable to read historic data: " + e.getMessage()); - } - } - - // Process future schedule - final var schedule = optimizer.getSchedule(); - optimizer.getSchedule().values().stream() // - .flatMap(ScheduleData::fromPeriod) // - .forEach(b::add); - - // Find 'toTime' of result - final ZonedDateTime toTime; - if (!schedule.isEmpty()) { - toTime = schedule.lastKey(); - } else { - var pricesPerQuarter = timeOfUseTariff.getPrices().pricePerQuarter; - if (!pricesPerQuarter.isEmpty()) { - toTime = pricesPerQuarter.lastKey(); - } else { - toTime = fromTime; - } - } - - return new GetScheduleResponse(requestId, fromTime, toTime, - new ScheduleDatas(params.essTotalEnergy(), b.build())); + public static long calculateExecutionLimitSeconds() { + return calculateExecutionLimitSeconds(Clock.systemDefaultZone()); } /** * Calculates the ExecutionLimitSeconds for the {@link Optimizer}. * - * @param clock a clock + * @param clock a {@link Clock} * @return execution limit in [s] */ public static long calculateExecutionLimitSeconds(Clock clock) { var now = ZonedDateTime.now(clock); var nextQuarter = roundDownToQuarter(now).plusMinutes(15).minusSeconds(EXECUTION_LIMIT_SECONDS_BUFFER); - var duration = Duration.between(now, nextQuarter).getSeconds(); - if (duration >= EXECUTION_LIMIT_SECONDS_MINIMUM) { - return duration; - } - // Otherwise add 15 more minutes - return Duration.between(now, nextQuarter.plusMinutes(15)).getSeconds(); - } - - /** - * Post-Process a state of a Period during Simulation, i.e. replace with - * 'better' state with the same behaviour. - * - *

- * NOTE: heavy computation is ok here, because this method is called only at the - * end with the best Schedule. - * - * @param state the initial state - * @param efBalancing the {@link EnergyFlow} as it would be in - * {@link StateMachine#BALANCING} - * @param efDelayDischarge the {@link EnergyFlow} as it would be in - * {@link StateMachine#DELAY_DISCHARGE} - * @param efChargeGrid the {@link EnergyFlow} as it would be in - * {@link StateMachine#CHARGE_GRID} - * @return the new state - */ - public static StateMachine postprocessSimulatorState(StateMachine state, EnergyFlow efBalancing, - EnergyFlow efDelayDischarge, EnergyFlow efChargeGrid) { - if (state == CHARGE_GRID) { - // CHARGE_GRID,... - if (efChargeGrid.ess() >= efDelayDischarge.ess()) { - // but battery charge/discharge is the same as DELAY_DISCHARGE - state = DELAY_DISCHARGE; - } - } - - if (state == DELAY_DISCHARGE) { - // DELAY_DISCHARGE,... - if (efDelayDischarge.ess() >= efBalancing.ess()) { - // but battery charge/discharge is the same as BALANCING - state = BALANCING; - } - } - - return state; - } - - /** - * Converts power [W] to energy [Wh/15 min]. - * - * @param power the power value - * @return the energy value - */ - public static int toEnergy(int power) { - return power / PERIODS_PER_HOUR; - } - - /** - * Converts energy [Wh/15 min] to power [W]. - * - * @param energy the energy value - * @return the power value - */ - public static Integer toPower(Integer energy) { - return multiply(energy, PERIODS_PER_HOUR); + return max(0, Duration.between(now, nextQuarter).getSeconds()); } /** @@ -507,36 +166,45 @@ public static Integer toPower(Integer energy) { * NOTE: The output format is suitable as input for "RunOptimizerFromLogApp". * This is useful to re-run a simulation. * - * @param params the {@link Params} - * @param periods the map of {@link Period}s + * @param simulator the {@link Simulator} + * @param simulationResult the {@link SimulationResult} */ - protected static void logSchedule(Params params, ImmutableSortedMap periods) { - System.out.println("OPTIMIZER " + params.toLogString()); - System.out.println(ScheduleDatas.fromSchedule(params.essTotalEnergy(), periods).toLogString("OPTIMIZER ")); + public static void logSimulationResult(Simulator simulator, SimulationResult simulationResult) { + final var prefix = "OPTIMIZER "; + System.out.println(simulator.toLogString(prefix)); + System.out.println(simulationResult.toLogString(prefix)); } /** - * Updates the active Schedule with a new Schedule. - * - *

- *

    - *
  • Period of the currently active Quarter is never changed - *
  • Old Periods are removed from the Schedule - *
  • Remaining Schedules are updated from new Schedule - *
+ * Sorts the list of {@link EnergySchedulable}s by the order given by + * {@link Scheduler}. * - * @param now the current {@link ZonedDateTime} - * @param schedule the active Schedule - * @param newSchedule the new Schedule + * @param scheduler the {@link Scheduler} + * @param list the list of {@link EnergySchedulable}s + * @return sorted list of {@link EnergySchedulable}s */ - public static void updateSchedule(ZonedDateTime now, TreeMap schedule, - ImmutableSortedMap newSchedule) { - var thisQuarter = roundDownToQuarter(now); - var current = schedule.get(thisQuarter); - schedule.clear(); - schedule.putAll(newSchedule); - if (current != null) { - schedule.put(thisQuarter, current); - } + public static ImmutableList sortByScheduler(Scheduler scheduler, List list) { + var ref = scheduler.getControllers().stream().toList(); + + final Ordering byScheduler = new Ordering() { + public int compare(String left, String right) { + var leftIdx = ref.indexOf(left); + var rightIdx = ref.indexOf(right); + if (leftIdx < 0 && rightIdx < 0) { // both not found + return Objects.compare(left, right, String::compareTo); + } else if (leftIdx < 0) { // only right is in list + return 1; + } else if (rightIdx < 0) { // only left is in list + return -1; + } else { + return leftIdx - rightIdx; + } + } + }; + + return byScheduler // + .onResultOf(EnergySchedulable::id) // + .immutableSortedCopy(list); } + } diff --git a/io.openems.edge.energy/src/io/openems/edge/energy/jsonrpc/GetScheduleResponse.java b/io.openems.edge.energy/src/io/openems/edge/energy/v1/jsonrpc/GetScheduleResponse.java similarity index 90% rename from io.openems.edge.energy/src/io/openems/edge/energy/jsonrpc/GetScheduleResponse.java rename to io.openems.edge.energy/src/io/openems/edge/energy/v1/jsonrpc/GetScheduleResponse.java index e27461abc45..e20510d8fe3 100644 --- a/io.openems.edge.energy/src/io/openems/edge/energy/jsonrpc/GetScheduleResponse.java +++ b/io.openems.edge.energy/src/io/openems/edge/energy/v1/jsonrpc/GetScheduleResponse.java @@ -1,4 +1,4 @@ -package io.openems.edge.energy.jsonrpc; +package io.openems.edge.energy.v1.jsonrpc; import java.time.ZonedDateTime; import java.util.Map.Entry; @@ -9,8 +9,8 @@ import io.openems.common.jsonrpc.base.JsonrpcResponseSuccess; import io.openems.common.utils.JsonUtils; -import io.openems.edge.energy.optimizer.ScheduleDatas; -import io.openems.edge.energy.optimizer.ScheduleDatas.ScheduleData; +import io.openems.edge.energy.v1.optimizer.ScheduleDatas; +import io.openems.edge.energy.v1.optimizer.ScheduleDatas.ScheduleData; /** * Represents a JSON-RPC Response for 'getMeters'. @@ -34,6 +34,7 @@ * } *
*/ +@Deprecated public class GetScheduleResponse extends JsonrpcResponseSuccess { private final ZonedDateTime fromDate; diff --git a/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/EnergyFlow.java b/io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/EnergyFlowV1.java similarity index 76% rename from io.openems.edge.energy/src/io/openems/edge/energy/optimizer/EnergyFlow.java rename to io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/EnergyFlowV1.java index bf108379c74..3de095d0913 100644 --- a/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/EnergyFlow.java +++ b/io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/EnergyFlowV1.java @@ -1,16 +1,17 @@ -package io.openems.edge.energy.optimizer; +package io.openems.edge.energy.v1.optimizer; import static io.openems.edge.common.type.TypeUtils.fitWithin; import static java.lang.Math.max; import static java.lang.Math.min; import io.openems.edge.controller.ess.timeofusetariff.StateMachine; -import io.openems.edge.energy.optimizer.Params.OptimizePeriod; +import io.openems.edge.energy.v1.optimizer.ParamsV1.OptimizePeriod; /** * Simulates a detailed Energy-Flow. */ -public record EnergyFlow(// +@Deprecated +public record EnergyFlowV1(// int production, /* positive */ int consumption, /* positive */ int ess, /* charge negative, discharge positive */ @@ -24,49 +25,49 @@ public record EnergyFlow(// ) { /** - * Simulate {@link EnergyFlow} in {@link StateMachine#BALANCING}. + * Simulate {@link EnergyFlowV1} in {@link StateMachine#BALANCING}. * - * @param p the {@link Params} + * @param p the {@link ParamsV1} * @param op the {@link OptimizePeriod} * @param essInitial ESS Initially Available Energy (SoC in [Wh]) - * @return the {@link EnergyFlow} + * @return the {@link EnergyFlowV1} */ - public static EnergyFlow withBalancing(Params p, OptimizePeriod op, int essInitial) { + public static EnergyFlowV1 withBalancing(ParamsV1 p, OptimizePeriod op, int essInitial) { return create(p, op, essInitial, // p.essTotalEnergy(), // Allow Balancing till full battery op.consumption() - op.production()); } /** - * Simulate {@link EnergyFlow} in {@link StateMachine#DELAY_DISCHARGE}. + * Simulate {@link EnergyFlowV1} in {@link StateMachine#DELAY_DISCHARGE}. * - * @param p the {@link Params} + * @param p the {@link ParamsV1} * @param op the {@link OptimizePeriod} * @param essInitial ESS Initially Available Energy (SoC in [Wh]) - * @return the {@link EnergyFlow} + * @return the {@link EnergyFlowV1} */ - public static EnergyFlow withDelayDischarge(Params p, OptimizePeriod op, int essInitial) { + public static EnergyFlowV1 withDelayDischarge(ParamsV1 p, OptimizePeriod op, int essInitial) { return create(p, op, essInitial, // p.essTotalEnergy(), // Allow Delay-Discharge with full battery min(0, op.consumption() - op.production())); // Allow charge; no discharge } /** - * Simulate {@link EnergyFlow} in {@link StateMachine#CHARGE_GRID}. + * Simulate {@link EnergyFlowV1} in {@link StateMachine#CHARGE_GRID}. * - * @param p the {@link Params} + * @param p the {@link ParamsV1} * @param op the {@link OptimizePeriod} * @param essInitial ESS Initially Available Energy (SoC in [Wh]) - * @return the {@link EnergyFlow} + * @return the {@link EnergyFlowV1} */ - public static EnergyFlow withChargeGrid(Params p, OptimizePeriod op, int essInitial) { + public static EnergyFlowV1 withChargeGrid(ParamsV1 p, OptimizePeriod op, int essInitial) { return create(p, op, essInitial, // p.essMaxSocEnergy(), // Allow Charge-Grid only till Max-SoC // Same as Delay-Discharge + Charge-From-Grid min(0, op.consumption() - op.production()) - op.essChargeInChargeGrid()); } - protected static EnergyFlow create(Params p, OptimizePeriod op, int essInitial, int essMaxSocEnergy, + protected static EnergyFlowV1 create(ParamsV1 p, OptimizePeriod op, int essInitial, int essMaxSocEnergy, int essTarget) { var essMaxDischarge = max(0, essInitial - p.essMinSocEnergy()); var essMaxCharge = max(0, essMaxSocEnergy - essInitial); @@ -86,7 +87,7 @@ protected static EnergyFlow create(Params p, OptimizePeriod op, int essInitial, var essToConsumption = max(0, min(op.consumption() - productionToConsumption, ess - productionToGrid)); var gridToConsumption = max(0, op.consumption() - essToConsumption - productionToConsumption); var gridToEss = grid - gridToConsumption + productionToGrid; - return new EnergyFlow(// + return new EnergyFlowV1(// op.production(), /* production */ op.consumption(), /* consumption */ ess, /* ess */ diff --git a/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/GlobalContext.java b/io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/GlobalContextV1.java similarity index 67% rename from io.openems.edge.energy/src/io/openems/edge/energy/optimizer/GlobalContext.java rename to io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/GlobalContextV1.java index c7b889a6eff..3af5e879047 100644 --- a/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/GlobalContext.java +++ b/io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/GlobalContextV1.java @@ -1,25 +1,25 @@ -package io.openems.edge.energy.optimizer; +package io.openems.edge.energy.v1.optimizer; import java.time.Clock; import io.openems.edge.common.sum.Sum; -import io.openems.edge.controller.ess.timeofusetariff.StateMachine; import io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffController; -import io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffControllerImpl; +import io.openems.edge.controller.ess.timeofusetariff.v1.EnergyScheduleHandlerV1; import io.openems.edge.energy.api.EnergyScheduleHandler; import io.openems.edge.predictor.api.manager.PredictorManager; import io.openems.edge.timeofusetariff.api.TimeOfUseTariff; -public record GlobalContext(// +@Deprecated +public record GlobalContextV1(// Clock clock, // - EnergyScheduleHandler energyScheduleHandler, // + EnergyScheduleHandlerV1 energyScheduleHandler, // Sum sum, // PredictorManager predictorManager, // TimeOfUseTariff timeOfUseTariff) { public static class Builder { private Clock clock; - private EnergyScheduleHandler energyScheduleHandler; + private EnergyScheduleHandlerV1 energyScheduleHandler; private Sum sum; private PredictorManager predictorManager; private TimeOfUseTariff timeOfUseTariff; @@ -41,8 +41,7 @@ public Builder setClock(Clock clock) { * @param energyScheduleHandler the {@link EnergyScheduleHandler} * @return myself */ - public Builder setEnergyScheduleHandler( - EnergyScheduleHandler energyScheduleHandler) { + public Builder setEnergyScheduleHandler(EnergyScheduleHandlerV1 energyScheduleHandler) { this.energyScheduleHandler = energyScheduleHandler; return this; } @@ -81,23 +80,23 @@ public Builder setTimeOfUseTariff(TimeOfUseTariff timeOfUseTariff) { } /** - * Builds the {@link GlobalContext}. + * Builds the {@link GlobalContextV1}. * - * @return the {@link GlobalContext} record + * @return the {@link GlobalContextV1} record */ - public GlobalContext build() { - return new GlobalContext(this.clock, this.energyScheduleHandler, this.sum, this.predictorManager, + public GlobalContextV1 build() { + return new GlobalContextV1(this.clock, this.energyScheduleHandler, this.sum, this.predictorManager, this.timeOfUseTariff); } } /** - * Create a {@link GlobalContext} {@link Builder}. + * Create a {@link GlobalContextV1} {@link Builder}. * * @return a {@link Builder} */ public static Builder create() { - return new GlobalContext.Builder(); + return new GlobalContextV1.Builder(); } } diff --git a/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/InitialPopulationUtils.java b/io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/InitialPopulationV1Utils.java similarity index 91% rename from io.openems.edge.energy/src/io/openems/edge/energy/optimizer/InitialPopulationUtils.java rename to io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/InitialPopulationV1Utils.java index b09e3e3036c..41b99551fcf 100644 --- a/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/InitialPopulationUtils.java +++ b/io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/InitialPopulationV1Utils.java @@ -1,10 +1,10 @@ -package io.openems.edge.energy.optimizer; +package io.openems.edge.energy.v1.optimizer; import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.BALANCING; import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.CHARGE_GRID; import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.DELAY_DISCHARGE; -import static io.openems.edge.energy.optimizer.Utils.findFirstPeakIndex; -import static io.openems.edge.energy.optimizer.Utils.findFirstValleyIndex; +import static io.openems.edge.energy.api.EnergyUtils.findFirstPeakIndex; +import static io.openems.edge.energy.api.EnergyUtils.findFirstValleyIndex; import java.util.Arrays; import java.util.List; @@ -19,11 +19,12 @@ import io.jenetics.IntegerChromosome; import io.jenetics.IntegerGene; import io.openems.edge.controller.ess.timeofusetariff.StateMachine; -import io.openems.edge.energy.optimizer.Params.OptimizePeriod; +import io.openems.edge.energy.v1.optimizer.ParamsV1.OptimizePeriod; -public class InitialPopulationUtils { +@Deprecated +public class InitialPopulationV1Utils { - private InitialPopulationUtils() { + private InitialPopulationV1Utils() { } /** @@ -40,10 +41,10 @@ private InitialPopulationUtils() { * sure, that this one wins in case there are other results with same cost, e.g. * when battery never gets empty anyway. * - * @param p the {@link Params} + * @param p the {@link ParamsV1} * @return the {@link Genotype} */ - public static ImmutableList> buildInitialPopulation(Params p) { + public static ImmutableList> buildInitialPopulation(ParamsV1 p) { var states = List.of(p.states()); if (!states.contains(BALANCING)) { throw new IllegalArgumentException("State option BALANCING is always required!"); diff --git a/io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/OptimizerV1.java b/io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/OptimizerV1.java new file mode 100644 index 00000000000..77fda46f75b --- /dev/null +++ b/io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/OptimizerV1.java @@ -0,0 +1,159 @@ +package io.openems.edge.energy.v1.optimizer; + +import static com.google.common.collect.ImmutableSortedMap.toImmutableSortedMap; +import static io.openems.edge.energy.optimizer.Utils.calculateExecutionLimitSeconds; +import static io.openems.edge.energy.optimizer.Utils.initializeRandomRegistryForProduction; +import static io.openems.edge.energy.v1.optimizer.SimulatorV1.simulate; +import static io.openems.edge.energy.v1.optimizer.UtilsV1.createSimulatorParams; +import static io.openems.edge.energy.v1.optimizer.UtilsV1.logSchedule; +import static io.openems.edge.energy.v1.optimizer.UtilsV1.updateSchedule; +import static java.lang.Thread.sleep; + +import java.time.Duration; +import java.time.Instant; +import java.time.ZonedDateTime; +import java.util.Map.Entry; +import java.util.TreeMap; +import java.util.function.Supplier; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.ImmutableSortedMap; + +import io.openems.common.exceptions.OpenemsException; +import io.openems.common.function.ThrowingSupplier; +import io.openems.common.test.TimeLeapClock; +import io.openems.common.utils.FunctionUtils; +import io.openems.common.worker.AbstractImmediateWorker; +import io.openems.edge.controller.ess.timeofusetariff.v1.EnergyScheduleHandlerV1; +import io.openems.edge.energy.LogVerbosity; +import io.openems.edge.energy.v1.optimizer.SimulatorV1.Period; + +/** + * This task is executed once in the beginning and afterwards every full 15 + * minutes. + */ +@Deprecated +public class OptimizerV1 extends AbstractImmediateWorker { + + private final Logger log = LoggerFactory.getLogger(OptimizerV1.class); + + private final Supplier logVerbosity; + private final ThrowingSupplier globalContext; + private final TreeMap schedule = new TreeMap<>(); + + private ParamsV1 params = null; + + public OptimizerV1(Supplier logVerbosity, // + ThrowingSupplier globalContext) { + this.logVerbosity = logVerbosity; + this.globalContext = globalContext; + initializeRandomRegistryForProduction(); + + // Run Optimizer thread in LOW PRIORITY + this.setPriority(Thread.MIN_PRIORITY); + } + + @Override + public void forever() throws InterruptedException, OpenemsException { + this.traceLog(() -> "Start next run of Optimizer"); + + this.createParams(); // this possibly takes forever + + final var globalContext = this.globalContext.get(); + final var start = Instant.now(globalContext.clock()); + + long executionLimitSeconds; + + // Calculate max execution time till next quarter (with buffer) + executionLimitSeconds = calculateExecutionLimitSeconds(globalContext.clock()); + + // Find best Schedule + var schedule = SimulatorV1.getBestSchedule(this.params, executionLimitSeconds); + + // Re-Simulate and keep best Schedule + var newSchedule = simulate(this.params, schedule); + + // Debug Log best Schedule + logSchedule(this.params, newSchedule); + + // Update Schedule from newly simulated Schedule + synchronized (this.schedule) { + updateSchedule(ZonedDateTime.now(globalContext.clock()), this.schedule, newSchedule); + } + + // Send Schedule to Controller + globalContext.energyScheduleHandler().setSchedule(this.schedule.entrySet().stream()// + .collect(toImmutableSortedMap(// + ZonedDateTime::compareTo, // + Entry::getKey, // + e -> new EnergyScheduleHandlerV1.Period<>(e.getValue().state(), + e.getValue().op().essChargeInChargeGrid())))); + + // Sleep remaining time + if (!(globalContext.clock() instanceof TimeLeapClock)) { + var remainingExecutionLimit = Duration + .between(Instant.now(globalContext.clock()), start.plusSeconds(executionLimitSeconds)).getSeconds(); + if (remainingExecutionLimit > 0) { + this.traceLog(() -> "Sleep [" + remainingExecutionLimit + "s] till next run of Optimizer"); + sleep(remainingExecutionLimit * 1000); + } + } + } + + /** + * Try forever till all data is available (e.g. ESS Capacity) + * + * @throws InterruptedException during sleep + */ + private void createParams() throws InterruptedException { + while (true) { + try { + synchronized (this.schedule) { + this.params = createSimulatorParams(this.globalContext.get(), // + this.schedule.entrySet().stream() // + .collect(toImmutableSortedMap(// + ZonedDateTime::compareTo, // + Entry::getKey, e -> e.getValue().state()))); + return; + } + + } catch (OpenemsException e) { + this.traceLog(() -> "Stuck trying to get Params. " + e.getMessage()); + this.params = null; + synchronized (this.schedule) { + this.schedule.clear(); + } + sleep(30_000); + } + } + } + + /** + * Gets the current {@link ParamsV1} or null. + * + * @return the {@link ParamsV1} or null + */ + public ParamsV1 getParams() { + return this.params; + } + + /** + * Gets a copy of the Schedule. + * + * @return {@link ImmutableSortedMap} + */ + public ImmutableSortedMap getSchedule() { + synchronized (this.schedule) { + return ImmutableSortedMap.copyOf(this.schedule); + } + } + + private void traceLog(Supplier message) { + switch (this.logVerbosity.get()) { + case NONE, DEBUG_LOG -> FunctionUtils.doNothing(); + case TRACE -> this.log.info("OPTIMIZER " + message.get()); + } + } +} diff --git a/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/ParamsUtils.java b/io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/ParamsUtilsV1.java similarity index 88% rename from io.openems.edge.energy/src/io/openems/edge/energy/optimizer/ParamsUtils.java rename to io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/ParamsUtilsV1.java index cd1e73e75ec..8cca4e3bfdd 100644 --- a/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/ParamsUtils.java +++ b/io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/ParamsUtilsV1.java @@ -1,10 +1,10 @@ -package io.openems.edge.energy.optimizer; +package io.openems.edge.energy.v1.optimizer; import static com.google.common.math.Quantiles.percentiles; -import static io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffController.PERIODS_PER_HOUR; -import static io.openems.edge.energy.optimizer.Utils.ESS_CHARGE_C_RATE; -import static io.openems.edge.energy.optimizer.Utils.findFirstPeakIndex; -import static io.openems.edge.energy.optimizer.Utils.findFirstValleyIndex; +import static io.openems.edge.controller.ess.timeofusetariff.Utils.ESS_CHARGE_C_RATE; +import static io.openems.edge.energy.api.EnergyConstants.PERIODS_PER_HOUR; +import static io.openems.edge.energy.api.EnergyUtils.findFirstPeakIndex; +import static io.openems.edge.energy.api.EnergyUtils.findFirstValleyIndex; import static java.lang.Math.max; import static java.lang.Math.min; import static java.lang.Math.round; @@ -16,11 +16,12 @@ import com.google.common.primitives.ImmutableIntArray; import io.openems.edge.controller.ess.timeofusetariff.StateMachine; -import io.openems.edge.energy.optimizer.Simulator.Period; +import io.openems.edge.energy.v1.optimizer.SimulatorV1.Period; -public class ParamsUtils { +@Deprecated +public class ParamsUtilsV1 { - private ParamsUtils() { + private ParamsUtilsV1() { } /** diff --git a/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/Params.java b/io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/ParamsV1.java similarity index 94% rename from io.openems.edge.energy/src/io/openems/edge/energy/optimizer/Params.java rename to io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/ParamsV1.java index f49fe773eb9..92ce404372a 100644 --- a/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/Params.java +++ b/io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/ParamsV1.java @@ -1,8 +1,8 @@ -package io.openems.edge.energy.optimizer; +package io.openems.edge.energy.v1.optimizer; import static com.google.common.collect.ImmutableList.toImmutableList; -import static io.openems.edge.energy.optimizer.ParamsUtils.calculateChargeEnergyInChargeGrid; -import static io.openems.edge.energy.optimizer.ParamsUtils.calculatePeriodLengthHourFromIndex; +import static io.openems.edge.energy.v1.optimizer.ParamsUtilsV1.calculateChargeEnergyInChargeGrid; +import static io.openems.edge.energy.v1.optimizer.ParamsUtilsV1.calculatePeriodLengthHourFromIndex; import static java.lang.Math.min; import java.time.ZonedDateTime; @@ -16,7 +16,8 @@ import io.openems.edge.controller.ess.timeofusetariff.StateMachine; -public record Params(// +@Deprecated +public record ParamsV1(// /** Start-Timestamp of the Schedule */ ZonedDateTime time, /** ESS Total Energy (Capacity) [Wh] */ @@ -206,15 +207,15 @@ private ImmutableList generatePeriods() { return result.build(); } - public Params build() { - return new Params(this.time, this.essTotalEnergy, this.essMinSocEnergy, this.essMaxSocEnergy, + public ParamsV1 build() { + return new ParamsV1(this.time, this.essTotalEnergy, this.essMinSocEnergy, this.essMaxSocEnergy, this.essInitialEnergy, this.states, // this.existingSchedule, this.generatePeriods()); } } protected static Builder create() { - return new Params.Builder(); + return new ParamsV1.Builder(); } @Override diff --git a/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/ScheduleDatas.java b/io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/ScheduleDatas.java similarity index 94% rename from io.openems.edge.energy/src/io/openems/edge/energy/optimizer/ScheduleDatas.java rename to io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/ScheduleDatas.java index 053dac39d13..ed0f95c1600 100644 --- a/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/ScheduleDatas.java +++ b/io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/ScheduleDatas.java @@ -1,4 +1,4 @@ -package io.openems.edge.energy.optimizer; +package io.openems.edge.energy.v1.optimizer; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.ImmutableSortedMap.toImmutableSortedMap; @@ -7,13 +7,13 @@ import static io.openems.common.utils.JsonUtils.getAsDouble; import static io.openems.common.utils.JsonUtils.getAsInt; import static io.openems.common.utils.JsonUtils.toJson; -import static io.openems.edge.energy.optimizer.Utils.SUM_CONSUMPTION; +import static io.openems.edge.controller.ess.timeofusetariff.Utils.SUM_PRODUCTION; import static io.openems.edge.energy.optimizer.Utils.SUM_ESS_DISCHARGE_POWER; import static io.openems.edge.energy.optimizer.Utils.SUM_ESS_SOC; import static io.openems.edge.energy.optimizer.Utils.SUM_GRID; -import static io.openems.edge.energy.optimizer.Utils.SUM_PRODUCTION; -import static io.openems.edge.energy.optimizer.Utils.toEnergy; -import static io.openems.edge.energy.optimizer.Utils.toPower; +import static io.openems.edge.energy.v1.optimizer.UtilsV1.SUM_CONSUMPTION; +import static io.openems.edge.energy.v1.optimizer.UtilsV1.toEnergy; +import static io.openems.edge.energy.v1.optimizer.UtilsV1.toPower; import static java.lang.Double.parseDouble; import static java.lang.Integer.parseInt; import static java.lang.Math.round; @@ -43,24 +43,25 @@ import io.openems.common.exceptions.OpenemsException; import io.openems.common.types.ChannelAddress; import io.openems.edge.controller.ess.timeofusetariff.StateMachine; -import io.openems.edge.energy.optimizer.Params.Length; -import io.openems.edge.energy.optimizer.Simulator.Period; +import io.openems.edge.energy.v1.optimizer.ParamsV1.Length; +import io.openems.edge.energy.v1.optimizer.SimulatorV1.Period; /** * Data for JSONRPC-Response. Values are in [W]. */ +@Deprecated public record ScheduleDatas(int essTotalEnergy, ImmutableList entries) { private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("HH:mm"); /** - * Creates {@link ScheduleDatas} from an {@link Optimizer}. + * Creates {@link ScheduleDatas} from an {@link OptimizerV1}. * - * @param optimizer the {@link Optimizer} + * @param optimizer the {@link OptimizerV1} * @return a {@link ScheduleDatas}a * @throws OpenemsException on error */ - public static ScheduleDatas fromSchedule(Optimizer optimizer) throws OpenemsException { + public static ScheduleDatas fromSchedule(OptimizerV1 optimizer) throws OpenemsException { final var schedule = optimizer.getSchedule(); if (schedule == null) { throw new OpenemsException("Has no Schedule"); diff --git a/io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/SimulatorV1.java b/io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/SimulatorV1.java new file mode 100644 index 00000000000..52145544f2e --- /dev/null +++ b/io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/SimulatorV1.java @@ -0,0 +1,178 @@ +package io.openems.edge.energy.v1.optimizer; + +import static io.jenetics.engine.EvolutionResult.toBestGenotype; +import static io.jenetics.engine.Limits.byExecutionTime; +import static io.openems.edge.energy.v1.optimizer.InitialPopulationV1Utils.buildInitialPopulation; +import static io.openems.edge.energy.v1.optimizer.UtilsV1.paramsAreValid; +import static io.openems.edge.energy.v1.optimizer.UtilsV1.postprocessSimulatorState; +import static java.lang.Math.max; +import static java.time.Duration.ofSeconds; + +import java.time.ZonedDateTime; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSortedMap; + +import io.jenetics.Genotype; +import io.jenetics.IntegerChromosome; +import io.jenetics.IntegerGene; +import io.jenetics.engine.Engine; +import io.jenetics.engine.EvolutionResult; +import io.openems.edge.controller.ess.timeofusetariff.StateMachine; +import io.openems.edge.energy.v1.optimizer.ParamsV1.Length; +import io.openems.edge.energy.v1.optimizer.ParamsV1.OptimizePeriod; + +@Deprecated +public class SimulatorV1 { + + /** Used to incorporate charge/discharge efficiency. */ + public static final double EFFICIENCY_FACTOR = 1.17; + + public record Period(OptimizePeriod op, StateMachine state, int essInitial, EnergyFlowV1 ef) { + } + + /** + * Simulates a Schedule and calculates the cost. + * + * @param p the {@link ParamsV1} + * @param schedule the {@link StateMachine} states of the Schedule + * @return the cost, lower is better; always positive + */ + protected static double calculateCost(ParamsV1 p, StateMachine[] schedule) { + final var nextEssInitial = new AtomicInteger(p.essInitialEnergy()); + var sum = 0.; + for (var i = 0; i < p.optimizePeriods().size(); i++) { + sum += simulatePeriod(p, p.optimizePeriods().get(i), schedule[i], nextEssInitial, null); + } + return sum; + } + + /** + * Simulates a Schedule in quarterly periods. + * + * @param p the {@link ParamsV1} + * @param schedule the {@link StateMachine} states of the Schedule + * @return a Map of {@link Period}s + */ + protected static ImmutableSortedMap simulate(ParamsV1 p, StateMachine[] schedule) { + final var nextEssInitial = new AtomicInteger(p.essInitialEnergy()); + var result = ImmutableSortedMap.naturalOrder(); + for (var i = 0; i < p.optimizePeriods().size(); i++) { + var state = schedule[i]; + var op = p.optimizePeriods().get(i); + var length = op.quarterPeriods().size() == 1 ? Length.QUARTER : Length.HOUR; + // Convert mixed OptimizePeriods to pure quarterly + for (var qp : op.quarterPeriods()) { + var quarterlyOp = new OptimizePeriod(qp.time(), length, qp.essMaxChargeEnergy(), + qp.essMaxDischargeEnergy(), qp.essChargeInChargeGrid(), qp.maxBuyFromGrid(), qp.production(), + qp.consumption(), qp.price(), ImmutableList.of(qp)); + simulatePeriod(p, quarterlyOp, state, nextEssInitial, period -> result.put(period.op().time(), period)); + } + } + return result.build(); + } + + /** + * Calculates the cost of one Period under the given Schedule. + * + * @param p the {@link ParamsV1} + * @param op the current {@link OptimizePeriod} + * @param state the {@link StateMachine} of the current period + * @param nextEssInitial the initial SoC-Energy; also used as return value + * @param collect a {@link Consumer} to collect the simulation results if + * required. We are not always collecting results to + * reduce workload during simulation. + * @return the cost, lower is better; always positive + */ + protected static double simulatePeriod(ParamsV1 p, OptimizePeriod op, StateMachine state, + final AtomicInteger nextEssInitial, Consumer collect) { + // Constants + final var essInitial = max(0, nextEssInitial.get()); // always at least '0' + + // Calculate Energy-Flow + final var ef = switch (state) { + case BALANCING -> EnergyFlowV1.withBalancing(p, op, essInitial); + case DELAY_DISCHARGE -> EnergyFlowV1.withDelayDischarge(p, op, essInitial); + case CHARGE_GRID -> EnergyFlowV1.withChargeGrid(p, op, essInitial); + }; + + nextEssInitial.set(essInitial - ef.ess()); + + // Calculate Cost + double cost; + if (ef.grid() > 0) { + // Filter negative prices + var price = max(0, op.price()); + + cost = // Cost for direct Consumption + ef.gridToConsumption() * price + // Cost for future Consumption after storage + + ef.gridToEss() * price * EFFICIENCY_FACTOR; + + } else { + // Sell-to-Grid + cost = 0.; + } + if (collect != null) { + var postprocessedState = postprocessSimulatorState(state, // + EnergyFlowV1.withBalancing(p, op, essInitial), // + EnergyFlowV1.withDelayDischarge(p, op, essInitial), // + EnergyFlowV1.withChargeGrid(p, op, essInitial)); + collect.accept(new Period(op, postprocessedState, essInitial, ef)); + } + return cost; + } + + /** + * Runs the optimization with default settings. + * + * @param p the {@link ParamsV1} + * @param executionLimitSeconds limit.byExecutionTime.ofSeconds + * @return the best schedule + */ + protected static StateMachine[] getBestSchedule(ParamsV1 p, long executionLimitSeconds) { + return getBestSchedule(p, executionLimitSeconds, null, null); + } + + protected static StateMachine[] getBestSchedule(ParamsV1 p, long executionLimitSeconds, Integer populationSize, + Integer limit) { + // Return pure BALANCING Schedule if no predictions are available + if (!paramsAreValid(p)) { + return p.optimizePeriods().stream() // + .map(op -> StateMachine.BALANCING) // + .toArray(StateMachine[]::new); + } + + var gtf = Genotype.of(IntegerChromosome.of(IntegerGene.of(0, p.states().length)), p.optimizePeriods().size()); // + var eval = (Function, Double>) (gt) -> { + var modes = new StateMachine[p.optimizePeriods().size()]; + for (var i = 0; i < modes.length; i++) { + modes[i] = p.states()[gt.get(i).get(0).intValue()]; + } + return calculateCost(p, modes); + }; + var engine = Engine // + .builder(eval, gtf) // + .executor(Runnable::run) // current thread + .minimizing(); + if (populationSize != null) { + engine.populationSize(populationSize); // + } + Stream> stream = engine.build() // + .stream(buildInitialPopulation(p)) // + .limit(byExecutionTime(ofSeconds(executionLimitSeconds))); // + if (limit != null) { + stream = stream.limit(limit); // apply optional limit + } + var bestGt = stream // + .collect(toBestGenotype()); + return IntStream.range(0, p.optimizePeriods().size()) // + .mapToObj(period -> p.states()[bestGt.get(period).get(0).intValue()]) // + .toArray(StateMachine[]::new); + } +} diff --git a/io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/UtilsV1.java b/io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/UtilsV1.java new file mode 100644 index 00000000000..d141d0938f6 --- /dev/null +++ b/io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/UtilsV1.java @@ -0,0 +1,432 @@ +package io.openems.edge.energy.v1.optimizer; + +import static io.openems.common.utils.DateUtils.roundDownToQuarter; +import static io.openems.edge.common.type.TypeUtils.multiply; +import static io.openems.edge.common.type.TypeUtils.orElse; +import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.BALANCING; +import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.CHARGE_GRID; +import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.DELAY_DISCHARGE; +import static io.openems.edge.controller.ess.timeofusetariff.Utils.ESS_MAX_SOC; +import static io.openems.edge.controller.ess.timeofusetariff.v1.UtilsV1.calculateLimitChargePowerFor14aEnWG; +import static io.openems.edge.controller.ess.timeofusetariff.v1.UtilsV1.getEssMinSocPercentage; +import static io.openems.edge.energy.api.EnergyConstants.PERIODS_PER_HOUR; +import static io.openems.edge.energy.optimizer.Utils.SUM_ESS_DISCHARGE_POWER; +import static io.openems.edge.energy.optimizer.Utils.SUM_ESS_SOC; +import static io.openems.edge.energy.optimizer.Utils.SUM_GRID; +import static java.lang.Math.round; +import static java.util.Arrays.stream; + +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; +import java.util.Objects; +import java.util.Set; +import java.util.TreeMap; +import java.util.UUID; +import java.util.stream.IntStream; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSortedMap; +import com.google.common.collect.Streams; + +import io.openems.common.exceptions.InvalidValueException; +import io.openems.common.timedata.Resolution; +import io.openems.common.types.ChannelAddress; +import io.openems.edge.common.channel.value.Value; +import io.openems.edge.common.sum.Sum; +import io.openems.edge.controller.ess.emergencycapacityreserve.statemachine.Context; +import io.openems.edge.controller.ess.timeofusetariff.StateMachine; +import io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffController; +import io.openems.edge.controller.ess.timeofusetariff.v1.EnergyScheduleHandlerV1.ContextV1; +import io.openems.edge.energy.v1.jsonrpc.GetScheduleResponse; +import io.openems.edge.energy.v1.optimizer.ScheduleDatas.ScheduleData; +import io.openems.edge.energy.v1.optimizer.SimulatorV1.Period; +import io.openems.edge.ess.api.SymmetricEss; +import io.openems.edge.timedata.api.Timedata; +import io.openems.edge.timeofusetariff.api.TimeOfUseTariff; + +/** + * Utils for {@link TimeOfUseTariffController}. + * + *

+ * All energy values are in [Wh] and positive, unless stated differently. + */ +@Deprecated +public final class UtilsV1 { + + private UtilsV1() { + } + + public static final ChannelAddress SUM_PRODUCTION = new ChannelAddress("_sum", "ProductionActivePower"); + public static final ChannelAddress SUM_CONSUMPTION = new ChannelAddress("_sum", "ConsumptionActivePower"); + public static final ChannelAddress SUM_UNMANAGED_CONSUMPTION = new ChannelAddress("_sum", + "UnmanagedConsumptionActivePower"); + + private static final Logger LOG = LoggerFactory.getLogger(UtilsV1.class); + + /** + * Create {@link ParamsV1} for {@link SimulatorV1}. + * + * @param globalContext the {@link GlobalContextV1} object + * @param existingSchedule the existing schedule, i.e. result of previous + * optimization + * @return {@link ParamsV1} + * @throws InvalidValueException on error + */ + public static ParamsV1 createSimulatorParams(GlobalContextV1 globalContext, + ImmutableSortedMap existingSchedule) throws InvalidValueException { + final var time = roundDownToQuarter(ZonedDateTime.now()); + + // Prediction values + final var predictionConsumption = joinConsumptionPredictions(4, // + globalContext.predictorManager().getPrediction(SUM_CONSUMPTION).asArray(), // + globalContext.predictorManager().getPrediction(SUM_UNMANAGED_CONSUMPTION).asArray()); + final var predictionProduction = generateProductionPrediction(// + globalContext.predictorManager().getPrediction(SUM_PRODUCTION).asArray(), // + predictionConsumption.length); + + // Prices contains the price values and the time it is retrieved. + final var prices = globalContext.timeOfUseTariff().getPrices(); + + // Ess information. + var context = globalContext.energyScheduleHandler().getContext(); + final var essTotalEnergy = context.ess().getCapacity().getOrError(); + final var essMinSocEnergy = getEssMinSocEnergy(context, essTotalEnergy); + final var essMaxSocEnergy = round(ESS_MAX_SOC / 100F * essTotalEnergy); + final var essSoc = context.ess().getSoc().getOrError(); + final var essSocEnergy = essTotalEnergy /* [Wh] */ / 100 * essSoc; + + // Power Values for scheduling battery for individual periods. + var maxDischargePower = globalContext.sum().getEssMaxDischargePower().orElse(1000 /* at least 1000 */); + var maxChargePower = calculateMaxChargePower(// + calculateLimitChargePowerFor14aEnWG(context.ctrlLimiter14as()), // Apply §14a EnWG limit + globalContext.sum().getEssMaxDischargePower(), -1000 /* at least 1000 */); + + return ParamsV1.create() // + .setTime(time) // + .setEssTotalEnergy(essTotalEnergy) // + .setEssMinSocEnergy(essMinSocEnergy) // + .setEssMaxSocEnergy(essMaxSocEnergy) // + .setEssInitialEnergy(essSocEnergy) // + .setEssMaxChargeEnergy(toEnergy(maxChargePower)) // + .setEssMaxDischargeEnergy(toEnergy(maxDischargePower)) // + .seMaxBuyFromGrid(toEnergy(context.maxChargePowerFromGrid())) // + .setProductions(stream(interpolateArray(predictionProduction)).map(v -> toEnergy(v)).toArray()) // + .setConsumptions(stream(interpolateArray(predictionConsumption)).map(v -> toEnergy(v)).toArray()) // + .setPrices(interpolateDoubleArray(prices.asArray())) // + .setStates(context.controlMode().states) // + .setExistingSchedule(existingSchedule) // + .build(); + } + + /** + * Postprocesses production prediction; makes sure length is at least the same + * as consumption prediction - filling up with zeroes. + * + * @param prediction the production prediction + * @param minLength the min length (= consumption prediction length) + * @return new production prediction + */ + protected static Integer[] generateProductionPrediction(Integer[] prediction, int minLength) { + if (prediction.length >= minLength) { + return prediction; + } + return IntStream.range(0, minLength) // + .mapToObj(i -> i > prediction.length - 1 ? 0 : prediction[i]) // + .toArray(Integer[]::new); + } + + protected static Integer[] joinConsumptionPredictions(int splitAfterIndex, Integer[] totalConsumption, + Integer[] unmanagedConsumption) { + return Streams.concat(// + stream(totalConsumption) // + .limit(splitAfterIndex), // + stream(unmanagedConsumption) // + .skip(splitAfterIndex)) // + .toArray(Integer[]::new); + } + + protected static boolean paramsAreValid(ParamsV1 p) { + if (p.optimizePeriods().isEmpty()) { + // No periods are available + LOG.warn("No periods are available"); + return false; + } + if (p.optimizePeriods().stream() // + .allMatch(pp -> pp.production() == 0 && pp.consumption() == 0)) { + // Production and Consumption predictions are all zero + LOG.warn("Production and Consumption predictions are all zero"); + return false; + } + if (p.optimizePeriods().stream() // + .mapToDouble(ParamsV1.OptimizePeriod::price) // + .distinct() // + .count() <= 1) { + // Prices are all the same + LOG.info("Prices are all the same"); + return false; + } + + return true; + } + + /** + * Returns the amount of energy that is not available for scheduling because of + * a configured minimum SoC. + * + * @param context the {@link Context} + * @param essCapacity net {@link SymmetricEss.ChannelId#CAPACITY} + * @return the value in [Wh] + */ + protected static int getEssMinSocEnergy(ContextV1 context, int essCapacity) { + return essCapacity /* [Wh] */ / 100 // + * getEssMinSocPercentage(// + context.ctrlLimitTotalDischarges(), // + context.ctrlEmergencyCapacityReserves()); + } + + /** + * Interpolate an Array of {@link Double}s. + * + *

+ * Replaces nulls with previous value. If first entry is null, it is set to + * first available value. If all values are null, all are set to 0. + * + * @param values the values + * @return values without nulls + */ + protected static double[] interpolateDoubleArray(Double[] values) { + var firstNonNull = stream(values) // + .filter(Objects::nonNull) // + .findFirst(); + var lastNonNullIndex = IntStream.range(0, values.length) // + .filter(i -> values[i] != null) // + .reduce((first, second) -> second); + if (lastNonNullIndex.isEmpty()) { + return new double[0]; + } + var result = new double[lastNonNullIndex.getAsInt() + 1]; + if (firstNonNull.isEmpty()) { + // all null + return result; + } + double last = firstNonNull.get(); + for (var i = 0; i < result.length; i++) { + double value = orElse(values[i], last); + result[i] = last = value; + } + return result; + } + + /** + * Utilizes the previous three hours' data and computes the next 21 hours data + * from the {@link OptimizerV1} provided, then concatenates them to generate a + * 24-hour {@link GetScheduleResponse}. + * + * @param optimizer the {@link OptimizerV1} + * @param requestId the JSON-RPC request-id + * @param timedata the{@link Timedata} + * @param timeOfUseTariff the {@link TimeOfUseTariff} + * @param componentId the Component-ID + * @param now the current {@link ZonedDateTime} (will get rounded + * down to 15 minutes) + * @return the {@link GetScheduleResponse} + */ + public static GetScheduleResponse handleGetScheduleRequest(OptimizerV1 optimizer, UUID requestId, Timedata timedata, + TimeOfUseTariff timeOfUseTariff, String componentId, ZonedDateTime now) { + final var b = ImmutableList.builder(); + now = roundDownToQuarter(now); + final var fromTime = now.minusHours(3); + + final var params = optimizer.getParams(); + if (params != null) { + // Process last three hours of historic data + final var channelQuarterlyPrices = new ChannelAddress(componentId, "QuarterlyPrices"); + final var channelStateMachine = new ChannelAddress(componentId, "StateMachine"); + try { + var queryResult = timedata.queryHistoricData(null, fromTime, now, // + Set.of(channelQuarterlyPrices, channelStateMachine, // + SUM_GRID, SUM_PRODUCTION, SUM_CONSUMPTION, SUM_ESS_DISCHARGE_POWER, SUM_ESS_SOC), + new Resolution(15, ChronoUnit.MINUTES)); + ScheduleData.fromHistoricDataQuery(// + params.essTotalEnergy(), channelQuarterlyPrices, channelStateMachine, queryResult) // + .forEach(b::add); + } catch (Exception e) { + LOG.warn("Unable to read historic data: " + e.getMessage()); + } + } + + // Process future schedule + final var schedule = optimizer.getSchedule(); + optimizer.getSchedule().values().stream() // + .flatMap(ScheduleData::fromPeriod) // + .forEach(b::add); + + // Find 'toTime' of result + final ZonedDateTime toTime; + if (!schedule.isEmpty()) { + toTime = schedule.lastKey(); + } else { + var lastTime = timeOfUseTariff.getPrices().getLastTime(); + if (lastTime != null) { + toTime = lastTime; + } else { + toTime = fromTime; + } + } + + return new GetScheduleResponse(requestId, fromTime, toTime, + new ScheduleDatas(params.essTotalEnergy(), b.build())); + } + + /** + * Post-Process a state of a Period during Simulation, i.e. replace with + * 'better' state with the same behaviour. + * + *

+ * NOTE: heavy computation is ok here, because this method is called only at the + * end with the best Schedule. + * + * @param state the initial state + * @param efBalancing the {@link EnergyFlowV1} as it would be in + * {@link StateMachine#BALANCING} + * @param efDelayDischarge the {@link EnergyFlowV1} as it would be in + * {@link StateMachine#DELAY_DISCHARGE} + * @param efChargeGrid the {@link EnergyFlowV1} as it would be in + * {@link StateMachine#CHARGE_GRID} + * @return the new state + */ + public static StateMachine postprocessSimulatorState(StateMachine state, EnergyFlowV1 efBalancing, + EnergyFlowV1 efDelayDischarge, EnergyFlowV1 efChargeGrid) { + if (state == CHARGE_GRID) { + // CHARGE_GRID,... + if (efChargeGrid.ess() >= efDelayDischarge.ess()) { + // but battery charge/discharge is the same as DELAY_DISCHARGE + state = DELAY_DISCHARGE; + } + } + + if (state == DELAY_DISCHARGE) { + // DELAY_DISCHARGE,... + if (efDelayDischarge.ess() >= efBalancing.ess()) { + // but battery charge/discharge is the same as BALANCING + state = BALANCING; + } + } + + return state; + } + + /** + * Converts power [W] to energy [Wh/15 min]. + * + * @param power the power value + * @return the energy value + */ + public static int toEnergy(int power) { + return power / PERIODS_PER_HOUR; + } + + /** + * Converts energy [Wh/15 min] to power [W]. + * + * @param energy the energy value + * @return the power value + */ + public static Integer toPower(Integer energy) { + return multiply(energy, PERIODS_PER_HOUR); + } + + /** + * Prints the Schedule to System.out. + * + *

+ * NOTE: The output format is suitable as input for "RunOptimizerFromLogApp". + * This is useful to re-run a simulation. + * + * @param params the {@link ParamsV1} + * @param periods the map of {@link Period}s + */ + protected static void logSchedule(ParamsV1 params, ImmutableSortedMap periods) { + System.out.println("OPTIMIZER " + params.toLogString()); + System.out.println(ScheduleDatas.fromSchedule(params.essTotalEnergy(), periods).toLogString("OPTIMIZER ")); + } + + /** + * Updates the active Schedule with a new Schedule. + * + *

    + *
  • Period of the currently active Quarter is never changed + *
  • Old Periods are removed from the Schedule + *
  • Remaining Schedules are updated from new Schedule + *
+ * + * @param now the current {@link ZonedDateTime} + * @param schedule the active Schedule + * @param newSchedule the new Schedule + */ + public static void updateSchedule(ZonedDateTime now, TreeMap schedule, + ImmutableSortedMap newSchedule) { + var thisQuarter = roundDownToQuarter(now); + var current = schedule.get(thisQuarter); + schedule.clear(); + schedule.putAll(newSchedule); + if (current != null) { + schedule.put(thisQuarter, current); + } + } + + /** + * Calculates the max-charge-power. + * + * @param calculateLimitChargePowerFor14aEnWG negative value provided by + * calculateLimitChargePowerFor14aEnWG; + * possilby Integer.MIN_VALUE + * @param essMaxDischargePower {@link Sum.ChannelId#ESS_MAX_DISCHARGE_POWER}; + * positive + * @param orElse a default value; negative + * @return max-charge-power as positive value + */ + protected static int calculateMaxChargePower(int calculateLimitChargePowerFor14aEnWG, + Value essMaxDischargePower, int orElse) { + return Math.abs(// + Math.max(// + calculateLimitChargePowerFor14aEnWG, // Apply §14a EnWG limit + -1 * essMaxDischargePower.orElse(orElse))); + } + + /** + * Interpolate an Array of {@link Integer}s. + * + *

+ * Replaces nulls with previous value. If first entry is null, it is set to + * first available value. If all values are null, all are set to 0. + * + * @param values the values + * @return values without nulls + */ + public static int[] interpolateArray(Integer[] values) { + var firstNonNull = stream(values) // + .filter(Objects::nonNull) // + .findFirst(); + var lastNonNullIndex = IntStream.range(0, values.length) // + .filter(i -> values[i] != null) // + .reduce((first, second) -> second); // + if (lastNonNullIndex.isEmpty()) { + return new int[0]; + } + var result = new int[lastNonNullIndex.getAsInt() + 1]; + if (firstNonNull.isEmpty()) { + // all null + return result; + } + int last = firstNonNull.get(); + for (var i = 0; i < result.length; i++) { + int value = orElse(values[i], last); + result[i] = last = value; + } + return result; + } +} diff --git a/io.openems.edge.energy/test/io/openems/edge/energy/EnergySchedulerImplTest.java b/io.openems.edge.energy/test/io/openems/edge/energy/EnergySchedulerImplTest.java index 10331208f63..7e5b0717e03 100644 --- a/io.openems.edge.energy/test/io/openems/edge/energy/EnergySchedulerImplTest.java +++ b/io.openems.edge.energy/test/io/openems/edge/energy/EnergySchedulerImplTest.java @@ -1,46 +1,52 @@ package io.openems.edge.energy; +import static io.openems.common.test.TestUtils.createDummyClock; import static io.openems.common.utils.DateUtils.roundDownToQuarter; -import static io.openems.edge.energy.TestData.CONSUMPTION_PREDICTION_QUARTERLY; -import static io.openems.edge.energy.TestData.HOURLY_PRICES_SUMMER; -import static io.openems.edge.energy.TestData.PRODUCTION_PREDICTION_QUARTERLY; -import static io.openems.edge.energy.optimizer.Utils.SUM_CONSUMPTION; -import static io.openems.edge.energy.optimizer.Utils.SUM_PRODUCTION; +import static io.openems.common.utils.ReflectionUtils.getValueViaReflection; +import static io.openems.edge.energy.LogVerbosity.TRACE; +import static io.openems.edge.energy.api.EnergyConstants.SUM_PRODUCTION; +import static io.openems.edge.energy.api.EnergyConstants.SUM_UNMANAGED_CONSUMPTION; +import static io.openems.edge.energy.api.EnergyUtils.toEnergy; +import static io.openems.edge.energy.api.RiskLevel.MEDIUM; +import static io.openems.edge.energy.api.Version.V2_ENERGY_SCHEDULABLE; +import static io.openems.edge.energy.optimizer.TestData.CONSUMPTION_PREDICTION_QUARTERLY; +import static io.openems.edge.energy.optimizer.TestData.HOURLY_PRICES_SUMMER; +import static io.openems.edge.energy.optimizer.TestData.PRODUCTION_PREDICTION_QUARTERLY; +import static io.openems.edge.ess.power.api.Relationship.GREATER_OR_EQUALS; import static java.time.temporal.ChronoUnit.DAYS; import java.time.Clock; -import java.time.Instant; -import java.time.ZoneOffset; +import java.time.LocalTime; import java.time.ZonedDateTime; -import java.util.List; -import java.util.function.Supplier; import org.junit.Test; -import io.openems.common.test.TimeLeapClock; import io.openems.edge.common.sum.DummySum; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyComponentManager; import io.openems.edge.common.test.DummyConfigurationAdmin; +import io.openems.edge.controller.ess.emergencycapacityreserve.ControllerEssEmergencyCapacityReserveImpl; +import io.openems.edge.controller.ess.fixactivepower.ControllerEssFixActivePowerImpl; +import io.openems.edge.controller.ess.gridoptimizedcharge.ControllerEssGridOptimizedChargeImpl; +import io.openems.edge.controller.ess.limittotaldischarge.ControllerEssLimitTotalDischargeImpl; +import io.openems.edge.controller.ess.timeofusetariff.ControlMode; import io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffControllerImpl; -import io.openems.edge.energy.optimizer.GlobalContext; +import io.openems.edge.energy.api.test.DummyEnergySchedulable; import io.openems.edge.energy.optimizer.Optimizer; +import io.openems.edge.ess.test.DummyManagedSymmetricEss; import io.openems.edge.predictor.api.prediction.Prediction; import io.openems.edge.predictor.api.test.DummyPredictor; import io.openems.edge.predictor.api.test.DummyPredictorManager; +import io.openems.edge.scheduler.api.test.DummyScheduler; import io.openems.edge.timedata.test.DummyTimedata; import io.openems.edge.timeofusetariff.test.DummyTimeOfUseTariffProvider; public class EnergySchedulerImplTest { - public static final Clock CLOCK = new TimeLeapClock(Instant.parse("2020-03-04T14:19:00.00Z"), ZoneOffset.UTC); - - private static final String CTRL_ID = "ctrl0"; - @Test public void test() throws Exception { - create(CLOCK); + create(createDummyClock()); } /** @@ -51,33 +57,60 @@ public void test() throws Exception { * @throws Exception on error */ public static EnergySchedulerImpl create(Clock clock) throws Exception { - var now = roundDownToQuarter(ZonedDateTime.now(clock)); + final var now = roundDownToQuarter(ZonedDateTime.now(clock)); final var midnight = now.truncatedTo(DAYS); - var componentManager = new DummyComponentManager(clock); - var sum = new DummySum(); - var predictor0 = new DummyPredictor("predictor0", componentManager, + final var componentManager = new DummyComponentManager(clock); + final var sum = new DummySum() // + .withEssCapacity(10000) // + .withEssSoc(50); + final var ess = new DummyManagedSymmetricEss("ess0"); + final var predictor0 = new DummyPredictor("predictor0", componentManager, Prediction.from(sum, SUM_PRODUCTION, midnight, PRODUCTION_PREDICTION_QUARTERLY), SUM_PRODUCTION); - var predictor1 = new DummyPredictor("predictor0", componentManager, - Prediction.from(sum, SUM_CONSUMPTION, midnight, CONSUMPTION_PREDICTION_QUARTERLY), SUM_CONSUMPTION); - var timeOfUseTariff = DummyTimeOfUseTariffProvider.fromHourlyPrices(clock, HOURLY_PRICES_SUMMER); - var ctrl = new TimeOfUseTariffControllerImpl(); // this is not fully activated; config is null + final var predictor1 = new DummyPredictor("predictor1", componentManager, + Prediction.from(sum, SUM_UNMANAGED_CONSUMPTION, midnight, CONSUMPTION_PREDICTION_QUARTERLY), + SUM_UNMANAGED_CONSUMPTION); + final var timeOfUseTariff = DummyTimeOfUseTariffProvider.fromHourlyPrices(clock, HOURLY_PRICES_SUMMER); - var sut = new EnergySchedulerImpl(); + final var sut = new EnergySchedulerImpl(); new ComponentTest(sut) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", componentManager) // .addReference("predictorManager", new DummyPredictorManager(predictor0, predictor1)) // .addReference("timedata", new DummyTimedata("timedata0")) // .addReference("timeOfUseTariff", timeOfUseTariff) // - .addReference("schedulables", List.of(ctrl)) // + .addReference("scheduler", new DummyScheduler("scheduler0")) // + .addReference("addSchedulable", + new DummyEnergySchedulable("ctrlEmergencyCapacityReserve0", + ControllerEssEmergencyCapacityReserveImpl.buildEnergyScheduleHandler(// + () -> /* reserveSoc */ 10))) // + .addReference("addSchedulable", + new DummyEnergySchedulable("ctrlLimitTotalDischarge0", + ControllerEssLimitTotalDischargeImpl.buildEnergyScheduleHandler(// + () -> /* minSoc */ 12))) // + .addReference("addSchedulable", + new DummyEnergySchedulable("ctrlFixActivePower0", + ControllerEssFixActivePowerImpl.buildEnergyScheduleHandler(// + () -> new ControllerEssFixActivePowerImpl.EshContext( + io.openems.edge.controller.ess.fixactivepower.Mode.MANUAL_ON, // + toEnergy(-1000), GREATER_OR_EQUALS)))) // + .addReference("addSchedulable", + new DummyEnergySchedulable("ctrlGridOptimizedCharge0", + ControllerEssGridOptimizedChargeImpl.buildEnergyScheduleHandler(// + () -> io.openems.edge.controller.ess.gridoptimizedcharge.Mode.MANUAL, // + () -> LocalTime.of(10, 00)))) // + .addReference("addSchedulable", + new DummyEnergySchedulable("ctrlEssTimeOfUseTariff0", + TimeOfUseTariffControllerImpl.buildEnergyScheduleHandler(// + () -> ess, // + () -> ControlMode.CHARGE_CONSUMPTION, // + () -> /* maxChargePowerFromGrid */ 20_000))) .addReference("sum", sum) // .activate(MyConfig.create() // - .setId(CTRL_ID) // + .setId("_energy") // .setEnabled(false) // - .setEssId("ess0") // - .setEssMaxChargePower(5000) // - .setMaxChargePowerFromGrid(10000) // - .setLimitChargePowerFor14aEnWG(false) // + .setLogVerbosity(TRACE) // + .setVersion(V2_ENERGY_SCHEDULABLE) // + .setRiskLevel(MEDIUM) // .build()) // .next(new TestCase()); return sut; @@ -91,36 +124,7 @@ public static EnergySchedulerImpl create(Clock clock) throws Exception { * @throws Exception on error */ public static Optimizer getOptimizer(EnergySchedulerImpl energyScheduler) throws Exception { - var field = EnergySchedulerImpl.class.getDeclaredField("optimizer"); - field.setAccessible(true); - return (Optimizer) field.get(energyScheduler); - } - - /** - * Calls the 'createParams()' method in the {@link Optimizer} via Java - * Reflection. - * - * @param optimizer the {@link Optimizer} - * @throws Exception on error - */ - public static void callCreateParams(Optimizer optimizer) throws Exception { - var method = Optimizer.class.getDeclaredMethod("createParams"); - method.setAccessible(true); - method.invoke(optimizer); + return getValueViaReflection(energyScheduler, "optimizer"); } - /** - * Gets the {@link GlobalContext} via Java Reflection. - * - * @param energyScheduler the {@link EnergySchedulerImpl} - * @return the object - * @throws Exception on error - */ - @SuppressWarnings("unchecked") - public static GlobalContext getGlobalContext(EnergySchedulerImpl energyScheduler) throws Exception { - var optimizer = getOptimizer(energyScheduler); - var field = Optimizer.class.getDeclaredField("globalContext"); - field.setAccessible(true); - return ((Supplier) field.get(optimizer)).get(); - } } diff --git a/io.openems.edge.energy/test/io/openems/edge/energy/MyConfig.java b/io.openems.edge.energy/test/io/openems/edge/energy/MyConfig.java index 2aeb9dc7bd3..575177d2ea0 100644 --- a/io.openems.edge.energy/test/io/openems/edge/energy/MyConfig.java +++ b/io.openems.edge.energy/test/io/openems/edge/energy/MyConfig.java @@ -1,17 +1,18 @@ package io.openems.edge.energy; import io.openems.common.test.AbstractComponentConfig; +import io.openems.edge.energy.api.RiskLevel; +import io.openems.edge.energy.api.Version; @SuppressWarnings("all") public class MyConfig extends AbstractComponentConfig implements Config { - protected static class Builder { + public static class Builder { private String id; private boolean enabled; - private String essId; - private int essMaxChargePower; - private int maxChargePowerFromGrid; - private boolean limitChargePowerFor14aEnWG; + private LogVerbosity logVerbosity; + private Version version; + private RiskLevel riskLevel; private Builder() { } @@ -26,23 +27,18 @@ public Builder setEnabled(boolean enabled) { return this; } - public Builder setEssId(String essId) { - this.essId = essId; + public Builder setLogVerbosity(LogVerbosity logVerbosity) { + this.logVerbosity = logVerbosity; return this; } - public Builder setEssMaxChargePower(int essMaxChargePower) { - this.essMaxChargePower = essMaxChargePower; + public Builder setVersion(Version version) { + this.version = version; return this; } - public Builder setMaxChargePowerFromGrid(int maxChargePowerFromGrid) { - this.maxChargePowerFromGrid = maxChargePowerFromGrid; - return this; - } - - public Builder setLimitChargePowerFor14aEnWG(boolean limitChargePowerFor14aEnWG) { - this.limitChargePowerFor14aEnWG = limitChargePowerFor14aEnWG; + public Builder setRiskLevel(RiskLevel riskLevel) { + this.riskLevel = riskLevel; return this; } @@ -71,4 +67,19 @@ private MyConfig(Builder builder) { public boolean enabled() { return this.builder.enabled; } + + @Override + public LogVerbosity logVerbosity() { + return this.builder.logVerbosity; + } + + @Override + public Version version() { + return this.builder.version; + } + + @Override + public RiskLevel riskLevel() { + return this.builder.riskLevel; + } } \ No newline at end of file diff --git a/io.openems.edge.energy/test/io/openems/edge/energy/api/simulation/EnergyFlowTest.java b/io.openems.edge.energy/test/io/openems/edge/energy/api/simulation/EnergyFlowTest.java new file mode 100644 index 00000000000..23e64f443ca --- /dev/null +++ b/io.openems.edge.energy/test/io/openems/edge/energy/api/simulation/EnergyFlowTest.java @@ -0,0 +1,499 @@ +package io.openems.edge.energy.api.simulation; + +import static io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffControllerImpl.applyBalancing; +import static io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffControllerImpl.applyChargeGrid; +import static io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffControllerImpl.applyDelayDischarge; +import static io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffControllerImpl.applyDischargeGrid; +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public class EnergyFlowTest { + + /* + * BALANCING + */ + + @Test + public void testBalancingAndCharge() { + var m = new EnergyFlow.Model(// + /* production */ 2500, // + /* consumption */ 200, // + /* essMaxCharge */ 5000, // + /* essMaxDischarge */ 0, // + /* gridMaxBuy */ 4000, // + /* gridMaxSell */ 10000); + m.addConsumption(300); + applyBalancing(m); + var ef = m.solve(); + + assertEquals(500, ef.getCons()); + assertEquals(500, ef.getProdToCons()); + + assertEquals(2500, ef.getProd()); + assertEquals(500, ef.getProdToCons()); + assertEquals(2000, ef.getProdToEss()); + + assertEquals(-2000, ef.getEss()); + assertEquals(2000, ef.getProdToEss()); + + assertEquals(0, ef.getGrid()); + assertEquals(0, ef.getProdToGrid()); + assertEquals(0, ef.getGridToCons()); + assertEquals(0, ef.getEssToCons()); + assertEquals(0, ef.getGridToEss()); + } + + @Test + public void testBalancingAndAddConsumption() { + var m = new EnergyFlow.Model(// + /* production */ 2500, // + /* consumption */ 200, // + /* essMaxCharge */ 5000, // + /* essMaxDischarge */ 0, // + /* gridMaxBuy */ 4000, // + /* gridMaxSell */ 10000); + m.addConsumption(300); + applyBalancing(m); + var ef = m.solve(); + + assertEquals(500, ef.getCons()); + assertEquals(500, ef.getProdToCons()); + + assertEquals(2500, ef.getProd()); + assertEquals(500, ef.getProdToCons()); + assertEquals(2000, ef.getProdToEss()); + + assertEquals(-2000, ef.getEss()); + assertEquals(2000, ef.getProdToEss()); + + assertEquals(0, ef.getGrid()); + assertEquals(0, ef.getProdToGrid()); + assertEquals(0, ef.getGridToCons()); + assertEquals(0, ef.getEssToCons()); + assertEquals(0, ef.getGridToEss()); + } + + @Test + public void testBalancingAndChargeFull() { + var m = new EnergyFlow.Model(// + /* production */ 3000, // + /* consumption */ 100, // + /* essMaxCharge */ 2400, // + /* essMaxDischarge */ 0, // + /* gridMaxBuy */ 4000, // + /* gridMaxSell */ 10000); + applyBalancing(m); + var ef = m.solve(); + + assertEquals(100, ef.getCons()); + assertEquals(100, ef.getProdToCons()); + + assertEquals(3000, ef.getProd()); + assertEquals(100, ef.getProdToCons()); + assertEquals(2400, ef.getProdToEss()); + assertEquals(500, ef.getProdToGrid()); + + assertEquals(-2400, ef.getEss()); + assertEquals(2400, ef.getProdToEss()); + + assertEquals(-500, ef.getGrid()); + assertEquals(500, ef.getProdToGrid()); + + assertEquals(0, ef.getGridToCons()); + assertEquals(0, ef.getEssToCons()); + assertEquals(0, ef.getGridToEss()); + } + + @Test + public void testBalancingAndDischarge() { + var m = new EnergyFlow.Model(// + /* production */ 500, // + /* consumption */ 2500, // + /* essMaxCharge */ 5000, // + /* essMaxDischarge */ 5000, // + /* gridMaxBuy */ 4000, // + /* gridMaxSell */ 10000); + applyBalancing(m); + var ef = m.solve(); + + assertEquals(2500, ef.getCons()); + assertEquals(2000, ef.getEssToCons()); + assertEquals(500, ef.getProdToCons()); + + assertEquals(500, ef.getProd()); + assertEquals(500, ef.getProdToCons()); + + assertEquals(2000, ef.getEss()); + assertEquals(2000, ef.getEssToCons()); + + assertEquals(0, ef.getGrid()); + assertEquals(0, ef.getProdToGrid()); + assertEquals(0, ef.getProdToEss()); + assertEquals(0, ef.getGridToCons()); + assertEquals(0, ef.getGridToEss()); + } + + @Test + public void testBalancingAndDischargeEmpty() { + var m = new EnergyFlow.Model(// + /* production */ 500, // + /* consumption */ 4500, // + /* essMaxCharge */ 5000, // + /* essMaxDischarge */ 1800, // + /* gridMaxBuy */ 4000, // + /* gridMaxSell */ 10000); + applyBalancing(m); + var ef = m.solve(); + + assertEquals(4500, ef.getCons()); + assertEquals(2200, ef.getGridToCons()); + assertEquals(1800, ef.getEssToCons()); + + assertEquals(500, ef.getProd()); + assertEquals(500, ef.getProdToCons()); + + assertEquals(1800, ef.getEss()); + assertEquals(1800, ef.getEssToCons()); + + assertEquals(2200, ef.getGrid()); + assertEquals(2200, ef.getGridToCons()); + + assertEquals(0, ef.getProdToGrid()); + assertEquals(0, ef.getProdToEss()); + assertEquals(0, ef.getGridToEss()); + } + + @Test + public void testBalancingAndChargeMoreThanEssMaxEnergy() { + var m = new EnergyFlow.Model(// + /* production */ 2500, // + /* consumption */ 500, // + /* essMaxCharge */ 900, // + /* essMaxDischarge */ 900, // + /* gridMaxBuy */ 4000, // + /* gridMaxSell */ 10000); + applyBalancing(m); + var ef = m.solve(); + + assertEquals(500, ef.getCons()); + assertEquals(500, ef.getProdToCons()); + + assertEquals(2500, ef.getProd()); + assertEquals(500, ef.getProdToCons()); + assertEquals(900, ef.getProdToEss()); + assertEquals(1100, ef.getProdToGrid()); + + assertEquals(-900, ef.getEss()); + assertEquals(900, ef.getProdToEss()); + + assertEquals(-1100, ef.getGrid()); + assertEquals(1100, ef.getProdToGrid()); + + assertEquals(0, ef.getGridToCons()); + assertEquals(0, ef.getEssToCons()); + assertEquals(0, ef.getGridToEss()); + } + + @Test + public void testBalancingAndDischargeAboveEssMaxEnergy() { + var m = new EnergyFlow.Model(// + /* production */ 500, // + /* consumption */ 2500, // + /* essMaxCharge */ 900, // + /* essMaxDischarge */ 900, // + /* gridMaxBuy */ 4000, // + /* gridMaxSell */ 10000); + applyBalancing(m); + var ef = m.solve(); + + assertEquals(2500, ef.getCons()); + assertEquals(900, ef.getEssToCons()); + assertEquals(1100, ef.getGridToCons()); + assertEquals(500, ef.getProdToCons()); + + assertEquals(500, ef.getProd()); + assertEquals(500, ef.getProdToCons()); + + assertEquals(900, ef.getEss()); + assertEquals(900, ef.getEssToCons()); + + assertEquals(1100, ef.getGrid()); + assertEquals(1100, ef.getGridToCons()); + + assertEquals(0, ef.getProdToGrid()); + assertEquals(0, ef.getProdToEss()); + assertEquals(0, ef.getGridToEss()); + } + + @Test + public void testBalancingAndAboveGridMaxEnergy() { + var m = new EnergyFlow.Model(// + /* production */ 1000, // + /* consumption */ 4900, // + /* essMaxCharge */ 1600, // + /* essMaxDischarge */ 2000, // + /* gridMaxBuy */ 4000, // + /* gridMaxSell */ 10000); + applyBalancing(m); + var ef = m.solve(); + + assertEquals(4900, ef.getCons()); + assertEquals(2000, ef.getEssToCons()); + assertEquals(1900, ef.getGridToCons()); + assertEquals(1000, ef.getProdToCons()); + + assertEquals(1000, ef.getProd()); + assertEquals(1000, ef.getProdToCons()); + + assertEquals(2000, ef.getEss()); + assertEquals(2000, ef.getEssToCons()); + + assertEquals(1900, ef.getGrid()); + assertEquals(1900, ef.getGridToCons()); + + assertEquals(0, ef.getProdToGrid()); + assertEquals(0, ef.getProdToEss()); + assertEquals(0, ef.getGridToEss()); + } + + /* + * DELAY DISCHARGE + */ + + @Test + public void testDelayDischargeAndCharge() { + var m = new EnergyFlow.Model(// + /* production */ 2500, // + /* consumption */ 500, // + /* essMaxCharge */ 5000, // + /* essMaxDischarge */ 0, // + /* gridMaxBuy */ 4000, // + /* gridMaxSell */ 10000); + applyDelayDischarge(m); + var ef = m.solve(); + + assertEquals(500, ef.getCons()); + assertEquals(500, ef.getProdToCons()); + + assertEquals(2500, ef.getProd()); + assertEquals(2000, ef.getProdToEss()); + assertEquals(500, ef.getProdToCons()); + + assertEquals(-2000, ef.getEss()); + assertEquals(2000, ef.getProdToEss()); + + assertEquals(0, ef.getGrid()); + assertEquals(0, ef.getProdToGrid()); + assertEquals(0, ef.getGridToCons()); + assertEquals(0, ef.getEssToCons()); + assertEquals(0, ef.getGridToEss()); + } + + @Test + public void testDelayDischargeAndChargeFull() { + var m = new EnergyFlow.Model(// + /* production */ 3000, // + /* consumption */ 100, // + /* essMaxCharge */ 2400, // + /* essMaxDischarge */ 5000, // + /* gridMaxBuy */ 4000, // + /* gridMaxSell */ 10000); + applyDelayDischarge(m); + var ef = m.solve(); + + assertEquals(100, ef.getCons()); + assertEquals(100, ef.getProdToCons()); + + assertEquals(3000, ef.getProd()); + assertEquals(100, ef.getProdToCons()); + assertEquals(500, ef.getProdToGrid()); + assertEquals(2400, ef.getProdToEss()); + + assertEquals(-2400, ef.getEss()); + assertEquals(2400, ef.getProdToEss()); + + assertEquals(-500, ef.getGrid()); + assertEquals(500, ef.getProdToGrid()); + + assertEquals(0, ef.getGridToCons()); + assertEquals(0, ef.getEssToCons()); + assertEquals(0, ef.getGridToEss()); + } + + @Test + public void testDelayDischargeAndWouldDischarge() { + var m = new EnergyFlow.Model(// + /* production */ 500, // + /* consumption */ 2500, // + /* essMaxCharge */ 5000, // + /* essMaxDischarge */ 5000, // + /* gridMaxBuy */ 4000, // + /* gridMaxSell */ 10000); + applyDelayDischarge(m); + var ef = m.solve(); + + assertEquals(2500, ef.getCons()); + assertEquals(500, ef.getProdToCons()); + assertEquals(2000, ef.getGridToCons()); + + assertEquals(500, ef.getProd()); + assertEquals(500, ef.getProdToCons()); + + assertEquals(2000, ef.getGrid()); + assertEquals(2000, ef.getGridToCons()); + + assertEquals(0, ef.getEss()); + assertEquals(0, ef.getProdToGrid()); + assertEquals(0, ef.getProdToEss()); + assertEquals(0, ef.getEssToCons()); + assertEquals(0, ef.getGridToEss()); + } + + /* + * CHARGE GRID + */ + + @Test + public void testChargeGridAndCharge() { + var m = new EnergyFlow.Model(// + /* production */ 2500, // + /* consumption */ 500, // + /* essMaxCharge */ 5000, // + /* essMaxDischarge */ 0, // + /* gridMaxBuy */ 4000, // + /* gridMaxSell */ 10000); + applyChargeGrid(m, 2500); + var ef = m.solve(); + + assertEquals(500, ef.getCons()); + assertEquals(500, ef.getProdToCons()); + + assertEquals(2500, ef.getProd()); + assertEquals(2000, ef.getProdToEss()); + assertEquals(500, ef.getProdToCons()); + + assertEquals(-4500, ef.getEss()); + assertEquals(2500, ef.getGridToEss()); + assertEquals(2000, ef.getProdToEss()); + + assertEquals(2500, ef.getGrid()); + assertEquals(2500, ef.getGridToEss()); + + assertEquals(0, ef.getProdToGrid()); + assertEquals(0, ef.getGridToCons()); + assertEquals(0, ef.getEssToCons()); + } + + @Test + public void testChargeGridAndChargeFull() { + var m = new EnergyFlow.Model(// + /* production */ 3000, // + /* consumption */ 100, // + /* essMaxCharge */ 3400, // + /* essMaxDischarge */ 5000, // + /* gridMaxBuy */ 4000, // + /* gridMaxSell */ 10000); + applyChargeGrid(m, 2500); + var ef = m.solve(); + + assertEquals(100, ef.getCons()); + assertEquals(100, ef.getProdToCons()); + + assertEquals(3000, ef.getProd()); + assertEquals(100, ef.getProdToCons()); + assertEquals(2900, ef.getProdToEss()); + + assertEquals(-3400, ef.getEss()); + assertEquals(500, ef.getGridToEss()); + assertEquals(2900, ef.getProdToEss()); + + assertEquals(500, ef.getGrid()); + assertEquals(500, ef.getGridToEss()); + + assertEquals(0, ef.getProdToGrid()); + assertEquals(0, ef.getGridToCons()); + assertEquals(0, ef.getEssToCons()); + } + + @Test + public void testChargeGridAndAboveGridMaxEnergy() { + var m = new EnergyFlow.Model(// + /* production */ 1000, // + /* consumption */ 2000, // + /* essMaxCharge */ 5000, // + /* essMaxDischarge */ 5000, // + /* gridMaxBuy */ 1600, // + /* gridMaxSell */ 10000); + applyChargeGrid(m, 2500); + var ef = m.solve(); + + assertEquals(2000, ef.getCons()); + assertEquals(1000, ef.getProdToCons()); + assertEquals(1000, ef.getGridToCons()); + + assertEquals(1000, ef.getProd()); + assertEquals(1000, ef.getGridToCons()); + + assertEquals(-600, ef.getEss()); + assertEquals(600, ef.getGridToEss()); + + assertEquals(1600, ef.getGrid()); + assertEquals(1000, ef.getGridToCons()); + assertEquals(600, ef.getGridToEss()); + + assertEquals(0, ef.getProdToGrid()); + assertEquals(0, ef.getProdToEss()); + assertEquals(0, ef.getEssToCons()); + } + + /* + * DISCHARGE GRID - just for completeness + */ + + @Test + public void testDischargeGridAndCharge() { + var m = new EnergyFlow.Model(// + /* production */ 2500, // + /* consumption */ 500, // + /* essMaxCharge */ 5000, // + /* essMaxDischarge */ 5000, // + /* gridMaxBuy */ 1600, // + /* gridMaxSell */ 10000); + applyDischargeGrid(m, 2500); + var ef = m.solve(); + + assertEquals(500, ef.getCons()); + assertEquals(500, ef.getProdToCons()); + + assertEquals(2500, ef.getProd()); + assertEquals(500, ef.getProdToCons()); + assertEquals(2000, ef.getProdToGrid()); + + assertEquals(2500, ef.getEss()); + assertEquals(-2500, ef.getGridToEss()); + + assertEquals(-4500, ef.getGrid()); + assertEquals(2000, ef.getProdToGrid()); + assertEquals(-2500, ef.getGridToEss()); + + assertEquals(0, ef.getProdToEss()); + assertEquals(0, ef.getEssToCons()); + assertEquals(0, ef.getGridToCons()); + } + + @Test + public void testLog() { + // No actual test. Would have to mock Logger + var m = new EnergyFlow.Model(// + /* production */ 2500, // + /* consumption */ 500, // + /* essMaxCharge */ 5000, // + /* essMaxDischarge */ 0, // + /* gridMaxBuy */ 4000, // + /* gridMaxSell */ 10000); + applyBalancing(m); + m.logConstraints(); + m.logMinMaxValues(); + } +} diff --git a/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/EshCodecTest.java b/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/EshCodecTest.java new file mode 100644 index 00000000000..943c4bac6fb --- /dev/null +++ b/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/EshCodecTest.java @@ -0,0 +1,72 @@ +package io.openems.edge.energy.optimizer; + +import static java.util.stream.Collectors.joining; +import static java.util.stream.IntStream.range; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import org.junit.Test; + +public class EshCodecTest { + + @Test + public void test() { + final var simulator = SimulatorTest.DUMMY_SIMULATOR; + final var gsc = simulator.gsc; + final var codec = EshCodec.of(gsc, SimulationResult.EMPTY, false); + + var gt = codec.encoding().newInstance(); + + var decoded = codec.decode(gt); + var decodedString = "" // + + "[" // + + range(0, decoded[0].length) // + .mapToObj(eshIndex -> "" // + + "[" // + + range(0, decoded.length) // + .mapToObj(periodIndex -> "" // + + "[" // + + decoded[periodIndex][eshIndex] // + + "]") // + .collect(joining(",")) + + "]") // + .collect(joining(",")) // + + "]"; + assertEquals(gt.toString(), decodedString); + + var encoded = codec.encode(decoded); + System.out.println(encoded); + assertEquals(gt, encoded); + } + + @Test + public void testNulls() { + final var simulator = SimulatorTest.DUMMY_SIMULATOR; + final var gsc = simulator.gsc; + final var codec = EshCodec.of(gsc, InitialPopulationTest.PREVIOUS_RESULT, true); + assertNull(codec.encode(new int[0][0])); + assertNull(codec.encode(new int[1][0])); + var oneone = codec.encode(new int[1][1]); + assertEquals("[[[0]]]", oneone.toString()); + var gene = oneone.get(0).get(0); + assertEquals(3, gene.max().intValue()); + assertEquals(0, gene.min().intValue()); + assertEquals(0, gene.allele().intValue()); + } + + @Test + public void testPreviousResult() { + final var simulator = SimulatorTest.DUMMY_SIMULATOR; + final var gsc = simulator.gsc; + final var codec = EshCodec.of(gsc, InitialPopulationTest.PREVIOUS_RESULT, true); + + var gt = codec.encoding().newInstance(); + var decoded = codec.decode(gt); + var encoded = codec.encode(decoded); + System.out.println(gt); + System.out.println(encoded); + System.out.println(encoded.get(0).get(1)); + assertEquals(gt.get(0).get(1).intValue(), encoded.get(0).get(1).intValue()); + assertEquals(2, encoded.get(0).get(0).intValue()); + } +} diff --git a/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/InitialPopulationTest.java b/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/InitialPopulationTest.java new file mode 100644 index 00000000000..015b0fd48c2 --- /dev/null +++ b/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/InitialPopulationTest.java @@ -0,0 +1,98 @@ +package io.openems.edge.energy.optimizer; + +import static io.openems.common.test.TestUtils.createDummyClock; +import static io.openems.edge.energy.optimizer.EshCodec.schedulesToStringArray; +import static io.openems.edge.energy.optimizer.InitialPopulation.variationsFromExistingSimulationResult; +import static io.openems.edge.energy.optimizer.InitialPopulation.variationsOfAllStatesDefault; +import static io.openems.edge.energy.optimizer.SimulatorTest.DUMMY_SIMULATOR; +import static io.openems.edge.energy.optimizer.SimulatorTest.ESH_TIME_OF_USE_TARIFF_CTRL; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.time.ZonedDateTime; + +import org.junit.Test; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSortedMap; + +import io.openems.edge.energy.api.EnergyScheduleHandler; +import io.openems.edge.energy.api.EnergyScheduleHandler.WithDifferentStates.Period.Transition; + +public class InitialPopulationTest { + + private static final ZonedDateTime TIME = ZonedDateTime.now(createDummyClock()); + + public static final SimulationResult PREVIOUS_RESULT = new SimulationResult(0., ImmutableSortedMap.of(), // + ImmutableMap., ImmutableSortedMap>builder() // + .put(ESH_TIME_OF_USE_TARIFF_CTRL, ImmutableSortedMap.naturalOrder() // + .put(TIME.plusHours(0).plusMinutes(00), state(2)) // + .put(TIME.plusHours(0).plusMinutes(15), state(2)) // + .put(TIME.plusHours(0).plusMinutes(30), state(2)) // + .build()) // + .build()); + + @Test + public void testAllStatesDefaultCurrentPeriodFixed() { + final var simulator = DUMMY_SIMULATOR; + var schedules = variationsOfAllStatesDefault(simulator.gsc, PREVIOUS_RESULT, true).toList(); + assertEquals(6, schedules.size()); // variations of 3 x 2 + + var s = schedulesToStringArray(schedules); + assertTrue(s[0].startsWith("[[2,0],[0,0],[0,1],")); + assertTrue(s[1].startsWith("[[2,1],[0,1],[0,1],")); + assertTrue(s[2].startsWith("[[2,0],[1,0],[0,1],")); + assertTrue(s[3].startsWith("[[2,1],[1,1],[0,1],")); + assertTrue(s[4].startsWith("[[2,0],[2,0],[0,1],")); + assertTrue(s[5].startsWith("[[2,1],[2,1],[0,1],")); + } + + @Test + public void testAllStatesDefaultCurrentPeriodNotFixed() { + final var simulator = DUMMY_SIMULATOR; + var schedules = variationsOfAllStatesDefault(simulator.gsc, PREVIOUS_RESULT, false).toList(); + assertEquals(6, schedules.size()); // variations of 3 x 2 + + var s = schedulesToStringArray(schedules); + assertTrue(s[0].startsWith("[[0,0],[0,0],[0,1],")); + assertTrue(s[1].startsWith("[[0,1],[0,1],[0,1],")); + assertTrue(s[2].startsWith("[[1,0],[1,0],[0,1],")); + assertTrue(s[3].startsWith("[[1,1],[1,1],[0,1],")); + assertTrue(s[4].startsWith("[[2,0],[2,0],[0,1],")); + assertTrue(s[5].startsWith("[[2,1],[2,1],[0,1],")); + } + + @Test + public void testVariationsFromExistingSimulationResultCurrentPeriodFixed() { + final var simulator = DUMMY_SIMULATOR; + var schedules = variationsFromExistingSimulationResult(simulator.gsc, PREVIOUS_RESULT, true).toList(); + assertEquals(6, schedules.size()); // variations of 3 x 2 + + var s = schedulesToStringArray(schedules); + assertTrue(s[0].startsWith("[[2,0],[0,0],[2,1],")); + assertTrue(s[1].startsWith("[[2,1],[0,1],[2,1],")); + assertTrue(s[2].startsWith("[[2,0],[1,0],[2,1],")); + assertTrue(s[3].startsWith("[[2,1],[1,1],[2,1],")); + assertTrue(s[4].startsWith("[[2,0],[2,0],[2,1],")); + assertTrue(s[5].startsWith("[[2,1],[2,1],[2,1],")); + } + + @Test + public void testVariationsFromExistingSimulationResultCurrentPeriodNotFixed() { + final var simulator = DUMMY_SIMULATOR; + var schedules = variationsFromExistingSimulationResult(simulator.gsc, PREVIOUS_RESULT, false).toList(); + assertEquals(6, schedules.size()); // variations of 3 x 2 + + var s = schedulesToStringArray(schedules); + assertTrue(s[0].startsWith("[[0,0],[0,0],[2,1],")); + assertTrue(s[1].startsWith("[[0,1],[0,1],[2,1],")); + assertTrue(s[2].startsWith("[[1,0],[1,0],[2,1],")); + assertTrue(s[3].startsWith("[[1,1],[1,1],[2,1],")); + assertTrue(s[4].startsWith("[[2,0],[2,0],[2,1],")); + assertTrue(s[5].startsWith("[[2,1],[2,1],[2,1],")); + } + + protected static Transition state(int state) { + return new Transition(state, 0., null, 0); + } +} diff --git a/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/OptimizerTest.java b/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/OptimizerTest.java index 1ab693e4e03..2486c8f3a36 100644 --- a/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/OptimizerTest.java +++ b/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/OptimizerTest.java @@ -1,21 +1,84 @@ package io.openems.edge.energy.optimizer; -import static io.openems.edge.energy.EnergySchedulerImplTest.CLOCK; +import static io.jenetics.engine.Limits.byFixedGeneration; +import static io.openems.common.test.TestUtils.createDummyClock; +import static io.openems.common.utils.ReflectionUtils.getValueViaReflection; import static io.openems.edge.energy.EnergySchedulerImplTest.getOptimizer; -import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import java.util.concurrent.ExecutionException; + import org.junit.Test; +import io.openems.common.exceptions.OpenemsException; +import io.openems.common.function.ThrowingSupplier; +import io.openems.common.utils.ReflectionUtils.ReflectionException; +import io.openems.edge.common.test.DummyChannel; +import io.openems.edge.controller.ess.timeofusetariff.StateMachine; import io.openems.edge.energy.EnergySchedulerImplTest; +import io.openems.edge.energy.LogVerbosity; +import io.openems.edge.energy.api.simulation.GlobalSimulationsContext; +import io.openems.edge.energy.optimizer.SimulatorTest.Esh2State; public class OptimizerTest { @Test - public void testEmpty() throws Exception { - var sut = getOptimizer(EnergySchedulerImplTest.create(CLOCK)); - assertNull(sut.getParams()); - assertTrue(sut.getSchedule().isEmpty()); + public void testRunQuickOptimization() throws Exception { + var sut = EnergySchedulerImplTest.create(createDummyClock()); + var optimizer = getOptimizer(sut); + assertEquals("No Schedule available|PerQuarter:UNDEFINED", optimizer.debugLog()); + + var simulationResult = optimizer.runQuickOptimization(); + optimizer.applySimulationResult(simulationResult); + + assertTrue(optimizer.debugLog().startsWith("ScheduledPeriods:96|SimulationCounter:")); + + var sr = optimizer.getSimulationResult(); + assertTrue(sr.cost() < 1100000); + assertEquals(96, sr.periods().size()); + } + + @Test + public void test2() throws InterruptedException, ExecutionException { + var simulator = SimulatorTest.DUMMY_SIMULATOR; + var channel = DummyChannel.of("DummyChannel"); + var optimizer = new Optimizer(// + () -> LogVerbosity.NONE, // + () -> simulator.gsc, // + channel); + var simulationResult = optimizer.runSimulation(simulator, // + false, // current period can get adjusted + byFixedGeneration(1) // simulate only two generations + ).get(); + optimizer.applySimulationResult(simulationResult); + + assertEquals(0., simulationResult.cost(), 0.001); + { + var schedule = simulator.gsc.eshsWithDifferentStates().get(0).getSchedule(); + assertEquals(52, schedule.size()); + assertTrue(schedule.values().stream() // + .allMatch(p -> p.state() == StateMachine.BALANCING)); + } + { + var schedule = simulator.gsc.eshsWithDifferentStates().get(1).getSchedule(); + assertEquals(52, schedule.size()); + assertTrue(schedule.values().stream() // + .allMatch(p -> p.state() == Esh2State.FOO || p.state() == Esh2State.BAR)); + } + } + + /** + * Gets the {@link GlobalSimulationsContext} {@link ThrowingSupplier} via Java + * Reflection. + * + * @param optimizer the {@link Optimizer} + * @return the object + * @throws ReflectionException on error + */ + public static ThrowingSupplier getGlobalSimulationContextSupplier( + Optimizer optimizer) throws ReflectionException { + return getValueViaReflection(optimizer, "gscSupplier"); } } diff --git a/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/SimulationResultTest.java b/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/SimulationResultTest.java new file mode 100644 index 00000000000..d671a703532 --- /dev/null +++ b/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/SimulationResultTest.java @@ -0,0 +1,31 @@ +package io.openems.edge.energy.optimizer; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public class SimulationResultTest { + + @Test + public void test() { + final var simulator = SimulatorTest.DUMMY_SIMULATOR; + + // ESH1 (BALANCING, DELAY_DISCHARGE, CHARGE_GRID) + // ESH2 (FOO, BAR) + var result = SimulationResult.fromQuarters(simulator.gsc, new int[][] { // + p(0, 0), p(1, 1), p(2, 1), p(0, 0), p(0, 0), p(0, 0), p(0, 0), p(0, 0), p(0, 0), p(0, 0), p(0, 0), + p(0, 0), p(0, 0), p(0, 0), p(1, 1), p(2, 1), p(0, 0), p(0, 0), p(0, 0), p(0, 0), p(0, 0), p(0, 0), + p(0, 0), p(0, 0), p(0, 0), p(0, 0), p(0, 0), p(1, 1), p(2, 1), p(0, 0), p(0, 0), p(0, 0), p(0, 0), + p(0, 0), p(0, 0), p(0, 0), p(0, 0), p(0, 0), p(0, 0), p(0, 0), p(1, 1), p(2, 1), p(0, 0), p(0, 0), + p(0, 0), p(0, 0), p(0, 0), p(0, 0), p(0, 0), p(0, 0), p(0, 0), p(0, 0), p(0, 0), p(1, 1), p(2, 1), + p(0, 0), p(0, 0), p(0, 0), p(0, 0), p(0, 0), p(0, 0), p(0, 0), p(0, 0), p(0, 0), p(0, 0), p(0, 0), + p(0, 0), p(1, 1), p(2, 0) // + }); + + assertEquals(1166163.462, result.cost(), 0.001); + } + + private static int[] p(int... states) { + return states; + } +} diff --git a/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/SimulatorTest.java b/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/SimulatorTest.java index 73cae4f79cb..16496d95f2a 100644 --- a/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/SimulatorTest.java +++ b/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/SimulatorTest.java @@ -1,40 +1,60 @@ package io.openems.edge.energy.optimizer; -import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.BALANCING; -import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.CHARGE_GRID; -import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.DELAY_DISCHARGE; -import static io.openems.edge.energy.TestData.CONSUMPTION_888_20231106; -import static io.openems.edge.energy.TestData.PRICES_888_20231106; -import static io.openems.edge.energy.TestData.PRODUCTION_888_20231106; -import static io.openems.edge.energy.optimizer.Simulator.getBestSchedule; -import static io.openems.edge.energy.optimizer.Simulator.simulate; -import static io.openems.edge.energy.optimizer.Utils.interpolateArray; -import static io.openems.edge.energy.optimizer.Utils.toEnergy; -import static java.util.Arrays.stream; -import static org.junit.Assert.assertArrayEquals; +import static io.jenetics.engine.Limits.byFixedGeneration; +import static io.openems.edge.energy.api.EnergyUtils.socToEnergy; import static org.junit.Assert.assertEquals; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.util.Arrays; import java.util.Random; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; -import java.util.stream.DoubleStream; import org.junit.Before; import org.junit.Test; -import com.google.common.collect.ImmutableSortedMap; - import io.jenetics.util.RandomRegistry; import io.openems.edge.controller.ess.timeofusetariff.ControlMode; -import io.openems.edge.controller.ess.timeofusetariff.StateMachine; -import io.openems.edge.energy.optimizer.Simulator.Period; +import io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffControllerImpl; +import io.openems.edge.energy.api.EnergyScheduleHandler; +import io.openems.edge.energy.api.test.DummyGlobalSimulationsContext; +import io.openems.edge.ess.api.ManagedSymmetricEss; +import io.openems.edge.ess.test.DummyManagedSymmetricEss; public class SimulatorTest { - public static final ZonedDateTime TIME = ZonedDateTime.of(2000, 1, 1, 0, 0, 0, 0, ZoneId.of("UTC")); + public static final EnergyScheduleHandler.WithOnlyOneState ESH0 = // + EnergyScheduleHandler.WithOnlyOneState.create() // + .setContextFunction(simContext -> simContext.ess().totalEnergy()) // + .setSimulator((simContext, period, energyFlow, ctrlContext) -> { + var minEnergy = socToEnergy(simContext.global.ess().totalEnergy(), 10 /* [%] */); + energyFlow.setEssMaxDischarge(Math.max(0, simContext.ess.getInitialEnergy() - minEnergy)); + }) // + .build(); + + public static final ManagedSymmetricEss ESS = new DummyManagedSymmetricEss("ess0") // + .withMaxApparentPower(10_000) // + .withAllowedChargePower(8_000) // + .withAllowedDischargePower(8_000) // + .withCapacity(22_000); + public static final EnergyScheduleHandler.WithDifferentStates ESH_TIME_OF_USE_TARIFF_CTRL = TimeOfUseTariffControllerImpl + .buildEnergyScheduleHandler(// + () -> ESS, // + () -> ControlMode.CHARGE_CONSUMPTION, // + () -> 20_000 /* maxChargePowerFromGrid */); + + protected static enum Esh2State { + FOO, BAR; + } + + public static final EnergyScheduleHandler.WithDifferentStates ESH2 = // + EnergyScheduleHandler.WithDifferentStates.create() // + .setDefaultState(Esh2State.BAR) // + .setAvailableStates(() -> Esh2State.values()) // + .setContextFunction(simContext -> null) // + .setSimulator((simContext, period, energyFlow, ctrlContext, state) -> { + return 0.; + }) // + .build(); + + public static final Simulator DUMMY_SIMULATOR = new Simulator(// + DummyGlobalSimulationsContext.fromHandlers(ESH0, ESH_TIME_OF_USE_TARIFF_CTRL, ESH2)); @Before public void before() { @@ -43,164 +63,31 @@ public void before() { RandomRegistry.random(new Random(123)); } - private static Period simulatePeriod(StateMachine state, int production, int consumption, double price, - int essInitial) { - var result = new AtomicReference(); - var params = Params.create() // - .setTime(TIME) // - .setEssTotalEnergy(22000) // - .setEssMinSocEnergy(0) // - .setEssMaxSocEnergy(20000) // - .setEssInitialEnergy(essInitial) // - .setEssMaxChargeEnergy(3000 /* [Wh/15 Minutes] */) // - .setEssMaxDischargeEnergy(3000 /* [Wh/15 Minutes] */) // - .seMaxBuyFromGrid(4000 /* [Wh/15 Minutes] */) // - .setProductions(new int[] { production }) // - .setConsumptions(new int[] { consumption }) // - .setPrices(new double[] { price }) // - .setStates(new StateMachine[] { state }) // - .setExistingSchedule(ImmutableSortedMap.of()) // - .build(); - Simulator.simulatePeriod(params, params.optimizePeriods().get(0), state, new AtomicInteger(essInitial), - result::set); - - return result.get(); - } - - private static void assertPeriod(String message, Period period, int essChargeDischarge, int grid, double cost) { - assertEquals(period.state() + "-essChargeDischarge: " + message, essChargeDischarge, period.ef().ess()); - assertEquals(period.state() + "-grid: " + message, grid, period.ef().grid()); - } - - @Test - public void testCalculatePeriodCostBalancing() { - assertPeriod("Consumption > Production; SoC ok", // - simulatePeriod(BALANCING, 200, 300, 0.1, 10000), // - 100, 0, 0); - assertPeriod("Consumption > Production; discharge limited by essMaxEnergyPerPeriod", // - simulatePeriod(BALANCING, 1000, 5000, 0.1, 10000), // - 3000, 1000, 100); - assertPeriod("Consumption > Production; discharge limited by essMinSocEnergy", // - simulatePeriod(BALANCING, 1000, 5000, 0.1, 2500), // - 2500, 1500, 150); - - assertPeriod("Production > Consumption; SoC ok", // - simulatePeriod(BALANCING, 300, 200, 0.1, 10000), // - -100, 0, 0); - assertPeriod("Production > Consumption; charge limited by essMaxEnergyPerPeriod", // - simulatePeriod(BALANCING, 5000, 1000, 0.1, 10000), // - -3000, -1000, 0); - assertPeriod("Production > Consumption; charge limited by essTotalEnergy", // - simulatePeriod(BALANCING, 5000, 1000, 0.1, 19500), // - -2500, -1500, 0); - } - - @Test - public void testCalculatePeriodCostDelayDischarge() { - assertPeriod("Consumption > Production", // - simulatePeriod(DELAY_DISCHARGE, 200, 300, 0.1, 10000), // - 0, 100, 10); - - assertPeriod("Production > Consumption; SoC ok", // - simulatePeriod(DELAY_DISCHARGE, 300, 200, 0.1, 10000), // - -100, 0, 0); - assertPeriod("Production > Consumption; charge limited by essMaxEnergyPerPeriod", // - simulatePeriod(DELAY_DISCHARGE, 5000, 1000, 0.1, 10000), // - -3000, -1000, 0); - assertPeriod("Production > Consumption; charge limited by essTotalEnergy", // - simulatePeriod(DELAY_DISCHARGE, 5000, 1000, 0.1, 19500), // - -2500, -1500, 0); - } - - @Test - public void testCalculatePeriodCostChargeGrid() { - assertPeriod("Consumption > Production", // - simulatePeriod(CHARGE_GRID, 200, 300, 0.1, 10000), // - -842, 942 /* 842 + 100 */, 302.5); - - assertPeriod("Consumption > Production; charge limited by maxBuyFromGrid", // - simulatePeriod(CHARGE_GRID, 0, 4500, 0.1, 10000), // - 500, 4000, 450.12); - - assertPeriod("Production > Consumption", // - simulatePeriod(CHARGE_GRID, 300, 200, 0.1, 10000), // - -2600 /* 2500 + 100 */, 2500, 292.5); - - assertPeriod("Production > Consumption; charge limited by essMaxEnergyPerPeriod", // - simulatePeriod(CHARGE_GRID, 3000, 900, 0.1, 10000), // - -3000, 900, 105.3); - - assertPeriod("Production > Consumption", // - simulatePeriod(CHARGE_GRID, 2000, 1700, 0.1, 10000), // - -2800, 2500, 292.5); - - assertPeriod("Production > Consumption; battery nearly full", // - simulatePeriod(CHARGE_GRID, 3000, 100, 0.1, 19600), // - -400 /* 400 from PV; then full */, -2500 /* sell-to-grid */, 292.5); - } - - @Test - public void testGetFirstSchedule0() { - var existingSchedule = new StateMachine[] { CHARGE_GRID, DELAY_DISCHARGE, CHARGE_GRID, BALANCING }; - - var p = Params.create() // - .setTime(TIME) // - .setEssTotalEnergy(22000) // - .setEssMinSocEnergy(0) // - .setEssMaxSocEnergy(22000) // - .setEssInitialEnergy((int) (22000 * 0.1)) // - .setEssMaxChargeEnergy(toEnergy(10000)) // - .setEssMaxDischargeEnergy(toEnergy(10000)) // - .seMaxBuyFromGrid(toEnergy(24_000)) // - .setProductions(stream(interpolateArray(PRODUCTION_888_20231106)).map(v -> toEnergy(v)).toArray()) // - .setConsumptions(stream(interpolateArray(CONSUMPTION_888_20231106)).map(v -> toEnergy(v)).toArray()) // - .setPrices(hourlyToQuarterly(interpolateArray(PRICES_888_20231106))) // - .setStates(ControlMode.CHARGE_CONSUMPTION.states) // - .setExistingSchedule(UtilsTest.prepareExistingSchedule(TIME, existingSchedule)) // - .build(); - var s = getBestSchedule(p, // - /* executionLimitSeconds */ 30, // - /* populationSize */ 2, // - /* limit */ 1); - - assertArrayEquals(existingSchedule, Arrays.copyOfRange(s, 0, existingSchedule.length)); - } - /** - * Creates dummy {@link Params}. + * Generates a dummy {@link SimulationResult}. * - * @param states the allowed states - * @return {@link Params} + * @return the {@link SimulationResult} */ - public static Params createParams888d20231106(StateMachine... states) { - return Params.create() // - .setTime(TIME) // - .setEssTotalEnergy(22000) // - .setEssMinSocEnergy(0) // - .setEssMaxSocEnergy(22000) // - .setEssMaxChargeEnergy(toEnergy(10000)) // - .setEssMaxDischargeEnergy(toEnergy(10000)) // - .seMaxBuyFromGrid(toEnergy(24_000)) // - .setProductions(stream(interpolateArray(PRODUCTION_888_20231106)).map(v -> toEnergy(v)).toArray()) // - .setConsumptions(stream(interpolateArray(CONSUMPTION_888_20231106)).map(v -> toEnergy(v)).toArray()) // - .setPrices(hourlyToQuarterly(interpolateArray(PRICES_888_20231106))) // - .setStates(states) // - .build(); + public static SimulationResult generateDummySimulationResult() { + final var simulator = DUMMY_SIMULATOR; + + return simulator.getBestSchedule(SimulationResult.EMPTY, true /* isCurrentPeriodFixed */, // + engine -> engine // + .populationSize(1), // + stream -> stream // + .limit(byFixedGeneration(1))); } - protected static void logSchedule(Params p, StateMachine[] schedule) { - Utils.logSchedule(p, simulate(p, schedule)); - } + @Test + public void testGetBestSchedule() { + var simulationResult = generateDummySimulationResult(); - /** - * Convert hourly values to quarterly. - * - * @param values hourly values - * @return quarterly values - */ - protected static double[] hourlyToQuarterly(double[] values) { - return DoubleStream.of(values) // - .flatMap(v -> DoubleStream.of(v, v, v, v)) // - .toArray(); + assertEquals(2, simulationResult.schedules().size()); + + simulationResult.schedules().forEach((esh, schedule) -> { + esh.applySchedule(schedule); + }); + + assertEquals("BALANCING", ESH_TIME_OF_USE_TARIFF_CTRL.getCurrentPeriod().state().toString()); } } \ No newline at end of file diff --git a/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/TestData.java b/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/TestData.java new file mode 100644 index 00000000000..920ff079c13 --- /dev/null +++ b/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/TestData.java @@ -0,0 +1,67 @@ +package io.openems.edge.energy.optimizer; + +public class TestData { + + public static final Integer[] PRODUCTION_PREDICTION_QUARTERLY = { + /* 00:00-03:45 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // + /* 04:00-07:45 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 74, 297, 610, // + /* 08:00-11:45 */ + 913, 1399, 1838, 2261, 2662, 3052, 3405, 3708, 4011, 4270, 4458, 4630, 4794, 4908, 4963, 4960, // + /* 12:00-15:45 */ + 4973, 4940, 4859, 4807, 4698, 4530, 4348, 4147, 1296, 1399, 1838, 1261, 1662, 1052, 1405, 1402, + /* 16:00-19:45 */ + 1662, 1052, 1405, 1630, 1285, 1520, 1250, 910, 0, 0, 0, 0, 0, 0, 0, 0, // + /* 20:00-23:45 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // + /* 00:00-03:45 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // + /* 04:00-07:45 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 130, 402, 667, // + /* 08:00-11:45 */ + 1023, 1631, 2020, 2420, 2834, 3237, 3638, 4006, 4338, 4597, 4825, 4965, 5111, 5213, 5268, 5317, // + /* 12:00-15:45 */ + 5321, 5271, 5232, 5193, 5044, 4915, 4738, 4499, 3702, 3226, 3046, 2857, 2649, 2421, 2184, 1933, // + /* 16:00-19:45 */ + 1674, 1364, 1070, 754, 447, 193, 52, 0, 0, 0, 0, 0, 0, 0, 0, 0, // + /* 20:00-23:45 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // + }; + + public static final Integer[] CONSUMPTION_PREDICTION_QUARTERLY = { + /* 00:00-03:450 */ + 1021, 1208, 713, 931, 2847, 2551, 1558, 1234, 433, 633, 1355, 606, 430, 1432, 1121, 502, // + /* 04:00-07:45 */ + 294, 1048, 1194, 914, 1534, 1226, 1235, 977, 578, 1253, 1983, 1417, 513, 929, 1102, 445, // + /* 08:00-11:45 */ + 1208, 2791, 2729, 2609, 2086, 1454, 848, 816, 2610, 3150, 2036, 1180, 359, 1316, 3447, 2104, // + /* 12:00-15:45 */ + 905, 802, 828, 812, 863, 633, 293, 379, 1250, 2296, 2436, 2140, 2135, 1196, 2230, 1725, + /* 16:00-19:45 */ + 2365, 1758, 2325, 2264, 2181, 2167, 2228, 1082, 777, 417, 798, 1268, 409, 830, 1191, 417, // + /* 20:00-23:45 */ + 1087, 2958, 2946, 2235, 1343, 483, 796, 1201, 567, 395, 989, 1066, 370, 989, 1255, 660, // + /* 00:00-03:45 */ + 349, 880, 1186, 580, 327, 911, 1135, 553, 265, 938, 1165, 567, 278, 863, 1239, 658, // + /* 04:00-07:45 */ + 236, 816, 1173, 1131, 498, 550, 1344, 1226, 874, 504, 1733, 1809, 1576, 369, 771, 2583, // + /* 08:00-11:45 */ + 3202, 2174, 1878, 2132, 2109, 1895, 1565, 1477, 1613, 1716, 1867, 1726, 1700, 1787, 1755, 1734, // + /* 12:00-15:45 */ + 1380, 691, 338, 168, 199, 448, 662, 205, 183, 70, 169, 276, 149, 76, 195, 168, // + /* 16:00-19:45 */ + 159, 266, 135, 120, 224, 979, 2965, 1337, 1116, 795, 334, 390, 433, 369, 762, 2908, // + /* 20:00-23:45 */ + 3226, 2358, 1778, 1002, 455, 654, 534, 1587, 1638, 459, 330, 258, 368, 728, 1096, 878 // + }; + + public static final Double[] HOURLY_PRICES_SUMMER = { // + 70.95, 71.98, 71.95, 74.96, // + 78.93, 80., 84.01, 111.03, // + 105.04, 105., 74.23, 73.28, // + 67.97, 72.53, 89.66, 150.01, // + 173.54, 178.4, 158.91, 140.01, // + 149.99, 157.43, 130.9, 120.14 // + }; +} diff --git a/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/UtilsTest.java b/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/UtilsTest.java index a2b82ad90a9..c4a45a1c673 100644 --- a/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/UtilsTest.java +++ b/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/UtilsTest.java @@ -1,337 +1,64 @@ package io.openems.edge.energy.optimizer; -import static io.openems.common.utils.DateUtils.roundDownToQuarter; -import static io.openems.edge.common.test.TestUtils.withValue; -import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.BALANCING; -import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.DELAY_DISCHARGE; -import static io.openems.edge.energy.EnergySchedulerImplTest.getOptimizer; -import static io.openems.edge.energy.TestData.CONSUMPTION_PREDICTION_QUARTERLY; -import static io.openems.edge.energy.TestData.PAST_HOURLY_PRICES; -import static io.openems.edge.energy.TestData.PAST_SOC; -import static io.openems.edge.energy.TestData.PAST_STATES; -import static io.openems.edge.energy.TestData.PRODUCTION_888_20231106; -import static io.openems.edge.energy.TestData.PRODUCTION_PREDICTION_QUARTERLY; -import static io.openems.edge.energy.optimizer.EnergyFlowTest.NO_FLOW; -import static io.openems.edge.energy.optimizer.SimulatorTest.TIME; -import static io.openems.edge.energy.optimizer.Utils.SUM_CONSUMPTION; -import static io.openems.edge.energy.optimizer.Utils.SUM_ESS_DISCHARGE_POWER; -import static io.openems.edge.energy.optimizer.Utils.SUM_ESS_SOC; -import static io.openems.edge.energy.optimizer.Utils.SUM_GRID; -import static io.openems.edge.energy.optimizer.Utils.SUM_PRODUCTION; +import static io.openems.common.test.TestUtils.createDummyClock; import static io.openems.edge.energy.optimizer.Utils.calculateExecutionLimitSeconds; -import static io.openems.edge.energy.optimizer.Utils.findFirstPeakIndex; -import static io.openems.edge.energy.optimizer.Utils.findFirstValleyIndex; -import static io.openems.edge.energy.optimizer.Utils.generateProductionPrediction; -import static io.openems.edge.energy.optimizer.Utils.getEssMinSocEnergy; -import static io.openems.edge.energy.optimizer.Utils.interpolateArray; -import static io.openems.edge.energy.optimizer.Utils.joinConsumptionPredictions; -import static io.openems.edge.energy.optimizer.Utils.paramsAreValid; -import static io.openems.edge.energy.optimizer.Utils.toEnergy; -import static io.openems.edge.energy.optimizer.Utils.toPower; -import static io.openems.edge.energy.optimizer.Utils.updateSchedule; +import static io.openems.edge.energy.optimizer.Utils.calculateSleepMillis; +import static io.openems.edge.energy.optimizer.Utils.sortByScheduler; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; import java.time.Duration; -import java.time.Instant; -import java.time.ZoneId; -import java.time.ZoneOffset; -import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; -import java.util.List; -import java.util.TreeMap; -import java.util.stream.IntStream; +import java.util.stream.Stream; import org.junit.Test; -import com.google.common.collect.ImmutableSortedMap; - -import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.common.test.TimeLeapClock; -import io.openems.common.types.ChannelAddress; -import io.openems.edge.common.component.OpenemsComponent; -import io.openems.edge.common.test.AbstractDummyOpenemsComponent; -import io.openems.edge.controller.ess.emergencycapacityreserve.ControllerEssEmergencyCapacityReserve; -import io.openems.edge.controller.ess.limittotaldischarge.ControllerEssLimitTotalDischarge; -import io.openems.edge.controller.ess.timeofusetariff.StateMachine; -import io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffControllerImpl; -import io.openems.edge.energy.EnergySchedulerImplTest; -import io.openems.edge.energy.optimizer.Simulator.Period; -import io.openems.edge.timedata.test.DummyTimedata; +import io.openems.edge.energy.api.EnergySchedulable; +import io.openems.edge.energy.api.test.DummyEnergySchedulable; +import io.openems.edge.scheduler.api.test.DummyScheduler; public class UtilsTest { - protected static ImmutableSortedMap prepareExistingSchedule(ZonedDateTime fromDate, - StateMachine... existingSchedule) { - return IntStream.range(0, existingSchedule.length) // - .mapToObj(Integer::valueOf) // - .collect(ImmutableSortedMap.toImmutableSortedMap( - ZonedDateTime::compareTo, // - i -> fromDate.plusMinutes(i * 15), // - i -> existingSchedule[i])); - } - - @Test - public void testInterpolateArrayFloat() { - assertArrayEquals(new double[] { 123, 123, 234, 234, 345 }, // - interpolateArray(new Double[] { null, 123., 234., null, 345., null }), // - 0.0001F); - - assertArrayEquals(new double[] {}, // - interpolateArray(new Double[] { null }), // - 0.0001F); - } - - @Test - public void testInterpolateArrayInteger() { - assertArrayEquals(new int[] { 123, 123, 234, 234, 345 }, // - interpolateArray(new Integer[] { null, 123, 234, null, 345, null })); - - assertArrayEquals(new int[] {}, // - interpolateArray(new Integer[] { null })); - - assertArrayEquals(new int[] { 123, 123 }, // - interpolateArray(new Integer[] { null, 123 })); - - assertArrayEquals(new int[] { 123 }, // - interpolateArray(new Integer[] { 123, null })); - } - - @Test - public void testToPower() { - assertEquals(2000, (int) toPower(500)); - assertNull(toPower(null)); - } - - @Test - public void testGenerateProductionPrediction() { - final var arr = new Integer[] { 1, 2, 3 }; - assertArrayEquals(arr, generateProductionPrediction(arr, 2)); - assertArrayEquals(new Integer[] { 1, 2, 3, 0 }, generateProductionPrediction(arr, 4)); - } - - @Test - public void testJoinConsumptionPredictions() { - assertArrayEquals(// - new Integer[] { 1, 2, 3, 4, 55, 66, 77, 88, 99 }, // - joinConsumptionPredictions(4, // - new Integer[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, // - new Integer[] { 11, 22, 33, 44, 55, 66, 77, 88, 99 })); - } - - @Test - public void testFindFirstPeakIndex() { - assertEquals(0, findFirstPeakIndex(0, new double[0])); - assertEquals(0, findFirstPeakIndex(0, new double[] { 1 })); - assertEquals(0, findFirstPeakIndex(0, new double[] { 1, 0 })); - assertEquals(1, findFirstPeakIndex(0, new double[] { 0, 1, 0 })); - assertEquals(1, findFirstPeakIndex(0, new double[] { 0, 1, 0, 1 })); - assertEquals(5, findFirstPeakIndex(5, new double[0])); - } - - @Test - public void testFindFirstValleyIndex() { - assertEquals(0, findFirstValleyIndex(0, new double[0])); - assertEquals(0, findFirstValleyIndex(0, new double[] { 1 })); - assertEquals(1, findFirstValleyIndex(0, new double[] { 1, 0 })); - assertEquals(0, findFirstValleyIndex(0, new double[] { 0, 1, 0 })); - assertEquals(2, findFirstValleyIndex(1, new double[] { 0, 1, 0, 1 })); - assertEquals(5, findFirstValleyIndex(5, new double[0])); - } - @Test - public void testParamsAreValid() throws Exception { - var builder = Params.create() // - .setTime(TIME) // - .setEssInitialEnergy(0) // - .setEssTotalEnergy(22000) // - .setEssMinSocEnergy(2_000) // - .setEssMaxSocEnergy(20_000) // - .seMaxBuyFromGrid(toEnergy(24_000)) // - .seMaxBuyFromGrid(0) // - .setStates(new StateMachine[0]); + public void testCalculateSleepMillis() { + final var clock = createDummyClock(); + assertEquals(Duration.ofMinutes(15).toMillis() + 100, calculateSleepMillis(clock)); - // No periods are available - assertFalse(paramsAreValid(builder // - .setProductions() // - .setConsumptions() // - .setPrices() // - .build())); - - // Production and Consumption predictions are all zero - assertFalse(paramsAreValid(builder // - .setProductions(0, 0, 0) // - .setConsumptions(0, 0) // - .setPrices(123F) // - .build())); - - // Prices are all the same - assertFalse(paramsAreValid(builder // - .setProductions(0, 1, 3) // - .setConsumptions(0, 2) // - .setPrices(123F, 123F) // - .build())); - - // Finally got it right... - assertTrue(paramsAreValid(builder // - .setProductions(0, 1, 3) // - .setConsumptions(0, 2) // - .setPrices(123F, 124F) // - .build())); - assertEquals(2, builder.build().optimizePeriods().size()); - } - - private static class MyControllerEssLimitTotalDischarge - extends AbstractDummyOpenemsComponent - implements ControllerEssLimitTotalDischarge { - - protected MyControllerEssLimitTotalDischarge(Integer minSoc) { - super("ctrl0", // - OpenemsComponent.ChannelId.values(), // - ControllerEssLimitTotalDischarge.ChannelId.values() // - ); - withValue(this.getMinSocChannel(), minSoc); - } - - @Override - public void run() throws OpenemsNamedException { - } - - @Override - protected MyControllerEssLimitTotalDischarge self() { - return this; - } - } - - private static class MyControllerEssEmergencyCapacityReserve - extends AbstractDummyOpenemsComponent - implements ControllerEssEmergencyCapacityReserve { - - protected MyControllerEssEmergencyCapacityReserve(Integer reserveSoc) { - super("ctrl0", // - OpenemsComponent.ChannelId.values(), // - ControllerEssEmergencyCapacityReserve.ChannelId.values() // - ); - withValue(this.getActualReserveSocChannel(), reserveSoc); - } - - @Override - public void run() throws OpenemsNamedException { - } - - @Override - protected MyControllerEssEmergencyCapacityReserve self() { - return this; - } - } - - @Test - public void testGetEssMinSocEnergy() { - var t1 = new MyControllerEssLimitTotalDischarge(50); - var t2 = new MyControllerEssLimitTotalDischarge(null); - var t3 = new MyControllerEssEmergencyCapacityReserve(30); - assertEquals(5000, getEssMinSocEnergy( - new TimeOfUseTariffControllerImpl.Context(List.of(t3), List.of(t1, t2), null, null, 10000, false), - 10000)); - } - - @Test - public void testHandleScheduleRequest() throws Exception { - final var clock = new TimeLeapClock(Instant.parse("2020-03-04T14:19:00.00Z"), ZoneOffset.UTC); - final var energyScheduler = EnergySchedulerImplTest.create(clock); - - // Simulate historic data - var now = roundDownToQuarter(ZonedDateTime.now(clock)); - final var fromDate = now.minusHours(3); - var timedata = new DummyTimedata("timedata0"); - for (var i = 0; i < 12; i++) { - var quarter = fromDate.plusMinutes(i * 15); - timedata.add(quarter, new ChannelAddress("ctrl0", "QuarterlyPrices"), PAST_HOURLY_PRICES[i]); - timedata.add(quarter, new ChannelAddress("ctrl0", "StateMachine"), PAST_STATES[i]); - timedata.add(quarter, SUM_PRODUCTION, PRODUCTION_PREDICTION_QUARTERLY[i]); - timedata.add(quarter, SUM_CONSUMPTION, CONSUMPTION_PREDICTION_QUARTERLY[i]); - timedata.add(quarter, SUM_ESS_SOC, PAST_SOC[i]); - timedata.add(quarter, SUM_ESS_DISCHARGE_POWER, PRODUCTION_888_20231106[i]); - timedata.add(quarter, SUM_GRID, PRODUCTION_888_20231106[i]); - } - - var optimizer = getOptimizer(energyScheduler); - System.out.println(optimizer); - // TODO this requires a fully configured TimeOfUseTariffControllerImpl - // callCreateParams(optimizer); - // - // // Testing only past data. For full data, optimizer has to be created as - // well. - // var result = handleGetScheduleRequest(optimizer, getNilUuid(), timedata, - // "ctrl0", clock.now()).getResult(); - // - // // JsonUtils.prettyPrint(result); - // - // var schedule = getAsJsonArray(result, "schedule"); - // assertEquals(11, schedule.size()); - // { - // var period = getAsJsonObject(schedule.get(0)); - // assertEquals(PAST_HOURLY_PRICES[0], getAsFloat(period, "price"), 0.00F); - // assertEquals(PRODUCTION_PREDICTION_QUARTERLY[0] / 4, getAsInt(period, - // "production")); - // } + clock.leap(11, ChronoUnit.MINUTES); + assertEquals(Duration.ofMinutes(4).toMillis() + 100, calculateSleepMillis(clock)); } @Test public void testCalculateExecutionLimitSeconds() { - final var clock = new TimeLeapClock(Instant.parse("2022-01-01T00:00:00.00Z"), ZoneOffset.UTC); - assertEquals(Duration.ofMinutes(14).plusSeconds(30).toSeconds(), calculateExecutionLimitSeconds(clock)); + final var clock = createDummyClock(); + assertEquals(Duration.ofMinutes(14).plusSeconds(55).toSeconds(), calculateExecutionLimitSeconds(clock)); clock.leap(11, ChronoUnit.MINUTES); - assertEquals(Duration.ofMinutes(3).plusSeconds(30).toSeconds(), calculateExecutionLimitSeconds(clock)); + assertEquals(Duration.ofMinutes(3).plusSeconds(55).toSeconds(), calculateExecutionLimitSeconds(clock)); clock.leap(150, ChronoUnit.SECONDS); - assertEquals(60, calculateExecutionLimitSeconds(clock)); + assertEquals(85, calculateExecutionLimitSeconds(clock)); clock.leap(1, ChronoUnit.SECONDS); - assertEquals(Duration.ofMinutes(15).plusSeconds(59).toSeconds(), calculateExecutionLimitSeconds(clock)); + assertEquals(84, calculateExecutionLimitSeconds(clock)); } @Test - public void testUpdateSchedule() { - final ZonedDateTime t = ZonedDateTime.of(2000, 1, 1, 0, 0, 0, 0, ZoneId.of("UTC")); - final Period pOld = new Period(null, DELAY_DISCHARGE, 0, NO_FLOW); - final Period pNew = new Period(null, BALANCING, 0, NO_FLOW); + public void testSortByScheduler() { + final var scheduler = new DummyScheduler("scheduler0") // + .setControllers("d", "f", null, "b"); + final var list = Stream.of("a", "b", "c", "d", "e") // + .map(id -> new DummyEnergySchedulable(id, null)) // + .toList(); - var schedule = new TreeMap(); - schedule.put(t.minusMinutes(15), pOld); // old entry is removed - schedule.put(t, pOld); // current entry stays - schedule.put(t.plusMinutes(15), pOld); // is overridden - schedule.put(t.plusMinutes(30), pOld); // is overridden - schedule.put(t.plusMinutes(45), pOld); // timestamp is missing in new Schedule -> remove + var result = sortByScheduler(scheduler, list).stream() // + .map(EnergySchedulable::id) // + .toArray(); - var newSchedule = ImmutableSortedMap.naturalOrder() // - .put(t, pNew) // - .put(t.plusMinutes(15), pNew) // - .put(t.plusMinutes(30), pNew) // - .build(); - - updateSchedule(t, schedule, newSchedule); - - // One old entry - assertEquals(1, schedule.values().stream().filter(v -> v == pOld).count()); - - // Two new entries - assertEquals(2, schedule.values().stream().filter(v -> v == pNew).count()); - - // No old entry - assertEquals(0, schedule.keySet().stream().filter(tz -> tz.isBefore(t)).count()); - - // Details - assertEquals(pOld, schedule.get(t)); - assertEquals(pNew, schedule.get(t.plusMinutes(15))); - assertEquals(pNew, schedule.get(t.plusMinutes(30))); - - // No current entry -> handle null - schedule.remove(t); - updateSchedule(t, schedule, newSchedule); + assertArrayEquals(// + new String[] { // + "d", "b", // by Scheduler + "a", "c", "e" // remaining alphabetically + }, result); } } diff --git a/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/app/AppUtils.java b/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/app/AppUtils.java new file mode 100644 index 00000000000..278b7ca1258 --- /dev/null +++ b/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/app/AppUtils.java @@ -0,0 +1,130 @@ +package io.openems.edge.energy.optimizer.app; + +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.collect.ImmutableMap.toImmutableMap; +import static io.openems.edge.energy.api.EnergyUtils.filterEshsWithDifferentStates; +import static java.lang.Double.parseDouble; +import static java.lang.Integer.parseInt; + +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicReference; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.google.common.collect.ImmutableList; + +import io.openems.common.test.TimeLeapClock; +import io.openems.edge.energy.api.EnergyScheduleHandler; +import io.openems.edge.energy.api.RiskLevel; +import io.openems.edge.energy.api.simulation.GlobalSimulationsContext; +import io.openems.edge.energy.api.simulation.GlobalSimulationsContext.Period; +import io.openems.edge.energy.optimizer.SimulationResult; +import io.openems.edge.evcs.api.Status; + +public class AppUtils { + + private AppUtils() { + } + + /** + * Creates {@link GlobalSimulationsContext} from the log output of + * {@link SimulationResult#toLogString()}. + * + * @param log the log output of {@link SimulationResult#toLogString()} + * @param eshs the {@link EnergyScheduleHandler}s + * @return a {@link GlobalSimulationsContext} + */ + public static GlobalSimulationsContext parseGlobalSimulationsContextFromLogString(String log, + ImmutableList eshs) throws IllegalArgumentException { + var headerMatcher = log.lines() // + .filter(l -> l.contains("OPTIMIZER ") && l.contains("GlobalSimulationsContext")) // + .map(l -> applyPattern(HEADER_PATTERN, l)) // + .findFirst().get(); + final var startDateTime = ZonedDateTime.parse(headerMatcher.group("startTime")); + final var clock = new TimeLeapClock(startDateTime.toInstant(), ZoneId.of("UTC")); + + var gridMatcher = applyPattern(GRID_PATTERN, headerMatcher.group("grid")); + final var grid = new GlobalSimulationsContext.Grid(// + parseInt(gridMatcher.group("maxBuy")), // + parseInt(gridMatcher.group("maxSell"))); + + var essMatcher = applyPattern(ESS_PATTERN, headerMatcher.group("ess")); + final var ess = new GlobalSimulationsContext.Ess(// + parseInt(essMatcher.group("currentEnergy")), // + parseInt(essMatcher.group("totalEnergy")), // + parseInt(essMatcher.group("maxChargeEnergy")), // + parseInt(essMatcher.group("maxDischargeEnergy"))); + + final var evcss = Arrays.stream(headerMatcher.group("evcss").split("], ")) // + .map(evcs -> applyPattern(EVCS_PATTERN, evcs)) // + .collect(toImmutableMap(// + m -> m.group("componentId"), // + m -> new GlobalSimulationsContext.Evcs(// + Status.valueOf(m.group("status")), // + parseInt(m.group("energySession"))))); // + + var nextTime = new AtomicReference<>(startDateTime); + var periods = log.lines() // + .filter(l -> l.contains("OPTIMIZER ") && !l.contains("GlobalSimulationsContext") && !l.contains("Time")) // + .map(l -> applyPattern(PERIOD_PATTERN, l)) // + .map(m -> { + var time = nextTime.get(); + if (!nextTime.get().toLocalTime().equals(LocalTime.parse(m.group("time"), HOURS_MINUTES))) { + throw new IllegalArgumentException("Times do not match: " + time); + } + nextTime.set(time.plusMinutes(15)); + return (Period) new Period.Quarter(time, // + parseInt(m.group("production")), // + parseInt(m.group("consumption")), // + parseDouble(m.group("price"))); + }) // + .collect(toImmutableList()); + + return new GlobalSimulationsContext(clock, RiskLevel.MEDIUM, startDateTime, // + eshs, filterEshsWithDifferentStates(eshs).collect(toImmutableList()), // + grid, ess, evcss, periods); + } + + private static Matcher applyPattern(Pattern pattern, String line) { + var matcher = pattern.matcher(line); + if (!matcher.find()) { + throw new IllegalArgumentException("Pattern [" + pattern + "] does not match line [" + line + "]"); + } + return matcher; + } + + private static final DateTimeFormatter HOURS_MINUTES = DateTimeFormatter.ofPattern("HH:mm"); + + private static final Pattern HEADER_PATTERN = Pattern.compile("" // + + "startTime=(?\\S*), " // + + "Grid\\[(?.*)\\], " // + + "Ess\\[(?.*)\\], " // + + "evcss=\\{(?.*)\\}, " // + + "eshs=\\["); + + private static final Pattern GRID_PATTERN = Pattern.compile("" // + + "maxBuy=(?\\d+), " // + + "maxSell=(?\\d+)"); + + private static final Pattern ESS_PATTERN = Pattern.compile("" // + + "currentEnergy=(?\\d+), " // + + "totalEnergy=(?\\d+), " // + + "maxChargeEnergy=(?\\d+), " // + + "maxDischargeEnergy=(?\\d+)"); + + private static final Pattern EVCS_PATTERN = Pattern.compile("" // + + "(?\\S+)=Evcs\\[" // + + "status=(?\\S+), " // + + "energySession=(?\\d+)"); + + private static final Pattern PERIOD_PATTERN = Pattern.compile("" // + + "(?

+ * To get the log from a system running with systemd, execute the following + * query: + * + *

+ * + * journalctl -lu openems --since="20 minutes ago" | grep OPTIMIZER + * + */ +public class RunOptimizerFromLogApp { + + private static final long EXECUTION_LIMIT_SECONDS = 30; + + private static final ManagedSymmetricEss ESS = new DummyManagedSymmetricEss("ess0"); + + private static final ImmutableList ESHS = ImmutableList.of(// + ControllerEssEmergencyCapacityReserveImpl.buildEnergyScheduleHandler(// + () -> /* reserveSoc */ 0), // + ControllerEssLimitTotalDischargeImpl.buildEnergyScheduleHandler(// + () -> /* minSoc */ 0), // + ControllerEssFixActivePowerImpl.buildEnergyScheduleHandler(// + () -> new ControllerEssFixActivePowerImpl.EshContext( + io.openems.edge.controller.ess.fixactivepower.Mode.MANUAL_ON, // + EnergyUtils.toEnergy(-1000), Relationship.GREATER_OR_EQUALS)), // + io.openems.edge.controller.evcs.Utils.buildEshSmart(// + () -> new EshSmartContext("evcs1", 30000, ImmutableList.of(// + JSCalendar.Task.create() // + .setStart(LocalDateTime.of(2024, 28, 12, 7, 0, 0)) // + .addRecurrenceRule(b -> b // + .setFrequency(WEEKLY) // + .addByDay(TUESDAY, WEDNESDAY, THURSDAY, FRIDAY)) // + .setPayload(new EshSmartContext.Payload(10000)) // + .build(), // + JSCalendar.Task.create() // + .setStart(LocalDateTime.of(2024, 28, 12, 7, 0, 0)) // + .addRecurrenceRule(b -> b // + .setFrequency(WEEKLY) // + .addByDay(MONDAY)) // + .setPayload(new EshSmartContext.Payload(40000)) // + .build()))), // + ControllerEssGridOptimizedChargeImpl.buildEnergyScheduleHandler(// + () -> Mode.MANUAL, // + () -> LocalTime.of(10, 00)), // + // TODO EssLimiter + TimeOfUseTariffControllerImpl.buildEnergyScheduleHandler(// + () -> ESS, // + () -> ControlMode.CHARGE_CONSUMPTION, // + () -> /* maxChargePowerFromGrid */ 20_000) // + ); + + /** Insert the full log lines including GlobalSimulationsContext header. */ + private static final String LOG = """ + """; + + /** + * Run the Application. + * + * @param args the args + * @throws Exception on error + */ + public static void main(String[] args) throws Exception { + var simulator = new Simulator(parseGlobalSimulationsContextFromLogString(LOG, ESHS)); + + var simulationResult = simulator.getBestSchedule(EMPTY, false /* isCurrentPeriodFixed */, null, // + stream -> stream // + .limit(byExecutionTime(ofSeconds(EXECUTION_LIMIT_SECONDS)))); + + Utils.logSimulationResult(simulator, simulationResult); + } +} \ No newline at end of file diff --git a/io.openems.edge.energy/test/io/openems/edge/energy/v1/EnergySchedulerImplTest.java b/io.openems.edge.energy/test/io/openems/edge/energy/v1/EnergySchedulerImplTest.java new file mode 100644 index 00000000000..258c045d323 --- /dev/null +++ b/io.openems.edge.energy/test/io/openems/edge/energy/v1/EnergySchedulerImplTest.java @@ -0,0 +1,118 @@ +package io.openems.edge.energy.v1; + +import static io.openems.common.utils.DateUtils.roundDownToQuarter; +import static io.openems.common.utils.ReflectionUtils.getValueViaReflection; +import static io.openems.edge.energy.LogVerbosity.DEBUG_LOG; +import static io.openems.edge.energy.api.RiskLevel.MEDIUM; +import static io.openems.edge.energy.api.Version.V1_ESS_ONLY; +import static io.openems.edge.energy.optimizer.TestData.CONSUMPTION_PREDICTION_QUARTERLY; +import static io.openems.edge.energy.optimizer.TestData.HOURLY_PRICES_SUMMER; +import static io.openems.edge.energy.optimizer.TestData.PRODUCTION_PREDICTION_QUARTERLY; +import static io.openems.edge.energy.v1.optimizer.UtilsV1.SUM_CONSUMPTION; +import static io.openems.edge.energy.v1.optimizer.UtilsV1.SUM_PRODUCTION; +import static java.time.temporal.ChronoUnit.DAYS; + +import java.time.Clock; +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.List; + +import org.junit.Test; + +import io.openems.common.exceptions.OpenemsException; +import io.openems.common.function.ThrowingSupplier; +import io.openems.common.test.TimeLeapClock; +import io.openems.common.utils.ReflectionUtils; +import io.openems.common.utils.ReflectionUtils.ReflectionException; +import io.openems.edge.common.sum.DummySum; +import io.openems.edge.common.test.AbstractComponentTest.TestCase; +import io.openems.edge.common.test.ComponentTest; +import io.openems.edge.common.test.DummyComponentManager; +import io.openems.edge.common.test.DummyConfigurationAdmin; +import io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffControllerImpl; +import io.openems.edge.energy.EnergySchedulerImpl; +import io.openems.edge.energy.MyConfig; +import io.openems.edge.energy.v1.optimizer.GlobalContextV1; +import io.openems.edge.energy.v1.optimizer.OptimizerV1; +import io.openems.edge.predictor.api.prediction.Prediction; +import io.openems.edge.predictor.api.test.DummyPredictor; +import io.openems.edge.predictor.api.test.DummyPredictorManager; +import io.openems.edge.timedata.test.DummyTimedata; +import io.openems.edge.timeofusetariff.test.DummyTimeOfUseTariffProvider; + +@SuppressWarnings("deprecation") +public class EnergySchedulerImplTest { + + public static final Clock CLOCK = new TimeLeapClock(Instant.parse("2020-03-04T14:19:00.00Z"), ZoneOffset.UTC); + + @Test + public void test() throws Exception { + create(CLOCK); + } + + /** + * Creates a {@link EnergySchedulerImplTest} instance. + * + * @param clock a {@link Clock} + * @return the object + * @throws Exception on error + */ + public static EnergySchedulerImpl create(Clock clock) throws Exception { + var now = roundDownToQuarter(ZonedDateTime.now(clock)); + final var midnight = now.truncatedTo(DAYS); + var componentManager = new DummyComponentManager(clock); + var sum = new DummySum(); + var predictor0 = new DummyPredictor("predictor0", componentManager, + Prediction.from(sum, SUM_PRODUCTION, midnight, PRODUCTION_PREDICTION_QUARTERLY), SUM_PRODUCTION); + var predictor1 = new DummyPredictor("predictor0", componentManager, + Prediction.from(sum, SUM_CONSUMPTION, midnight, CONSUMPTION_PREDICTION_QUARTERLY), SUM_CONSUMPTION); + var timeOfUseTariff = DummyTimeOfUseTariffProvider.fromHourlyPrices(clock, HOURLY_PRICES_SUMMER); + var ctrl = new TimeOfUseTariffControllerImpl(); // this is not fully activated; config is null + + var sut = new EnergySchedulerImpl(); + new ComponentTest(sut) // + .addReference("cm", new DummyConfigurationAdmin()) // + .addReference("componentManager", componentManager) // + .addReference("predictorManager", new DummyPredictorManager(predictor0, predictor1)) // + .addReference("timedata", new DummyTimedata("timedata0")) // + .addReference("timeOfUseTariff", timeOfUseTariff) // + .addReference("timeOfUseTariffController", ctrl) // + .addReference("schedulables", List.of(ctrl)) // + .addReference("sum", sum) // + .activate(MyConfig.create() // + .setId("ctrl0") // + .setEnabled(false) // + .setLogVerbosity(DEBUG_LOG) // + .setVersion(V1_ESS_ONLY) // + .setRiskLevel(MEDIUM) // + .build()) // + .next(new TestCase()); + return sut; + } + + /** + * Gets the {@link OptimizerV1} via Java Reflection. + * + * @param energyScheduler the {@link EnergySchedulerImpl} + * @return the object + * @throws ReflectionException on error + */ + public static OptimizerV1 getOptimizer(EnergySchedulerImpl energyScheduler) throws ReflectionException { + return getValueViaReflection(energyScheduler, "optimizerV1"); + } + + /** + * Gets the {@link GlobalContextV1} via Java Reflection. + * + * @param energyScheduler the {@link EnergySchedulerImpl} + * @return the object + * @throws Exception on error + */ + public static GlobalContextV1 getGlobalContext(EnergySchedulerImpl energyScheduler) throws Exception { + var optimizer = getOptimizer(energyScheduler); + return ReflectionUtils + .>getValueViaReflection(optimizer, "globalContext") + .get(); + } +} diff --git a/io.openems.edge.energy/test/io/openems/edge/energy/jsonrpc/GetScheduleResponseTest.java b/io.openems.edge.energy/test/io/openems/edge/energy/v1/jsonrpc/GetScheduleResponseTest.java similarity index 86% rename from io.openems.edge.energy/test/io/openems/edge/energy/jsonrpc/GetScheduleResponseTest.java rename to io.openems.edge.energy/test/io/openems/edge/energy/v1/jsonrpc/GetScheduleResponseTest.java index 780b77f7b9d..dbb99d1ed18 100644 --- a/io.openems.edge.energy/test/io/openems/edge/energy/jsonrpc/GetScheduleResponseTest.java +++ b/io.openems.edge.energy/test/io/openems/edge/energy/v1/jsonrpc/GetScheduleResponseTest.java @@ -1,15 +1,16 @@ -package io.openems.edge.energy.jsonrpc; +package io.openems.edge.energy.v1.jsonrpc; import static io.openems.common.utils.JsonUtils.prettyToString; import static io.openems.common.utils.UuidUtils.getNilUuid; -import static io.openems.edge.energy.optimizer.ScheduleDatasTest.SCHEDULE_DATAS; -import static io.openems.edge.energy.optimizer.SimulatorTest.TIME; +import static io.openems.edge.energy.v1.optimizer.ScheduleDatasV1Test.SCHEDULE_DATAS; +import static io.openems.edge.energy.v1.optimizer.SimulatorV1Test.TIME; import static org.junit.Assert.assertEquals; import org.junit.Test; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +@SuppressWarnings("deprecation") public class GetScheduleResponseTest { @Test diff --git a/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/EnergyFlowTest.java b/io.openems.edge.energy/test/io/openems/edge/energy/v1/optimizer/EnergyFlowV1Test.java similarity index 74% rename from io.openems.edge.energy/test/io/openems/edge/energy/optimizer/EnergyFlowTest.java rename to io.openems.edge.energy/test/io/openems/edge/energy/v1/optimizer/EnergyFlowV1Test.java index d58ae40fc1d..b0de70323b6 100644 --- a/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/EnergyFlowTest.java +++ b/io.openems.edge.energy/test/io/openems/edge/energy/v1/optimizer/EnergyFlowV1Test.java @@ -1,9 +1,9 @@ -package io.openems.edge.energy.optimizer; +package io.openems.edge.energy.v1.optimizer; import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.BALANCING; import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.CHARGE_GRID; import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.DELAY_DISCHARGE; -import static io.openems.edge.energy.optimizer.Utils.postprocessSimulatorState; +import static io.openems.edge.energy.v1.optimizer.UtilsV1.postprocessSimulatorState; import static java.lang.Math.max; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -15,14 +15,15 @@ import org.junit.Test; import io.openems.common.function.TriFunction; -import io.openems.edge.energy.optimizer.Params.OptimizePeriod; +import io.openems.edge.energy.v1.optimizer.ParamsV1.OptimizePeriod; -public class EnergyFlowTest { +@SuppressWarnings("deprecation") +public class EnergyFlowV1Test { public static final ZonedDateTime TIME = ZonedDateTime.of(2000, 1, 1, 0, 0, 0, 0, ZoneId.of("UTC")); - public static final EnergyFlow NO_FLOW = new EnergyFlow(0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + public static final EnergyFlowV1 NO_FLOW = new EnergyFlowV1(0, 0, 0, 0, 0, 0, 0, 0, 0, 0); - private static void assertEnergyFlow(EnergyFlow ef) { + private static void assertEnergyFlow(EnergyFlowV1 ef) { assertTrue("Production is positive", ef.production() >= 0); assertTrue("Consumption is positive", ef.consumption() >= 0); assertTrue("ProductionToConsumption is positive", ef.productionToConsumption() >= 0); @@ -40,11 +41,11 @@ private static void assertEnergyFlow(EnergyFlow ef) { assertEquals("Overall Sum", 0, ef.grid() + ef.ess() + ef.production() - ef.consumption()); } - private static Params.Builder P; + private static ParamsV1.Builder P; @Before public void prepareParams() { - P = Params.create() // + P = ParamsV1.create() // .setTime(TIME) // .setEssMinSocEnergy(1000) // .setEssTotalEnergy(22000) // @@ -57,38 +58,38 @@ public void prepareParams() { // essChargeInChargeGrid = 2375 } - private static EnergyFlow execute(TriFunction function, int essInitial, - Params.Builder pb) { + private static EnergyFlowV1 execute(TriFunction function, + int essInitial, ParamsV1.Builder pb) { var p = pb.build(); return function.apply(p, p.optimizePeriods().get(0), essInitial); } - private static EnergyFlow charge(TriFunction function) { + private static EnergyFlowV1 charge(TriFunction function) { return execute(function, 10000, P // .setProductions(2500) // .setConsumptions(500)); } - private static EnergyFlow chargeFull(TriFunction function) { + private static EnergyFlowV1 chargeFull(TriFunction function) { return execute(function, 19_600, P // .setProductions(3000) // .setConsumptions(100)); } - private static EnergyFlow discharge(TriFunction function) { + private static EnergyFlowV1 discharge(TriFunction function) { return execute(function, 10000, P // .setProductions(500) // .setConsumptions(2500)); } - private static EnergyFlow dischargeEmpty(TriFunction function) { + private static EnergyFlowV1 dischargeEmpty(TriFunction function) { return execute(function, 2800, P // .setProductions(500) // .setConsumptions(4500)); } - private static EnergyFlow chargeMoreThanEssMaxEnergy( - TriFunction function) { + private static EnergyFlowV1 chargeMoreThanEssMaxEnergy( + TriFunction function) { return execute(function, 10000, P // .setProductions(2500) // .setConsumptions(500) // @@ -96,8 +97,8 @@ private static EnergyFlow chargeMoreThanEssMaxEnergy( .setEssMaxDischargeEnergy(900)); } - private static EnergyFlow dischargeMoreThanEssMaxEnergy( - TriFunction function) { + private static EnergyFlowV1 dischargeMoreThanEssMaxEnergy( + TriFunction function) { return execute(function, 10000, P // .setProductions(500) // .setConsumptions(2500) // @@ -105,7 +106,7 @@ private static EnergyFlow dischargeMoreThanEssMaxEnergy( .setEssMaxDischargeEnergy(900)); } - private static void testBalancingCharge(TriFunction function) { + private static void testBalancingCharge(TriFunction function) { var e = charge(function); assertEnergyFlow(e); assertEquals(-2000, e.ess()); @@ -114,7 +115,7 @@ private static void testBalancingCharge(TriFunction function) { + private static void testBalancingChargeFull(TriFunction function) { var e = chargeFull(function); assertEnergyFlow(e); assertEquals(-2400, e.ess()); // expect 2900, but limited by essTotalEnergy @@ -130,17 +131,17 @@ private static void testBalancingChargeFull(TriFunction toEnergy(v)).toArray()) // .setConsumptions(stream(interpolateArray(CONSUMPTION_888_20231106)).map(v -> toEnergy(v)).toArray()) // - .setPrices(hourlyToQuarterly(interpolateArray(PRICES_888_20231106))) // + .setPrices(hourlyToQuarterly(interpolateDoubleArray(PRICES_888_20231106))) // .setStates(ControlMode.CHARGE_CONSUMPTION.states) // .setExistingSchedule(prepareExistingSchedule(TIME)) // .build()); assertEquals(5, lgt.size()); // No Schedule -> only pure BALANCING + CHARGE_GRID } { - var lgt = buildInitialPopulation(Params.create() // + var lgt = buildInitialPopulation(ParamsV1.create() // .setTime(TIME) // .setProductions(stream(interpolateArray(PRODUCTION_888_20231106)).map(v -> toEnergy(v)).toArray()) // .setConsumptions(stream(interpolateArray(CONSUMPTION_888_20231106)).map(v -> toEnergy(v)).toArray()) // - .setPrices(hourlyToQuarterly(interpolateArray(PRICES_888_20231106))) // + .setPrices(hourlyToQuarterly(interpolateDoubleArray(PRICES_888_20231106))) // .setStates(ControlMode.CHARGE_CONSUMPTION.states) // .setExistingSchedule(prepareExistingSchedule(TIME, BALANCING, BALANCING)) // .build()); assertEquals(5, lgt.size()); // Existing Schedule is only BALANCING -> only pure BALANCING + CHARGE_GRID } { - var gt = buildInitialPopulation(Params.create() // + var gt = buildInitialPopulation(ParamsV1.create() // .setTime(TIME) // .setProductions(stream(interpolateArray(PRODUCTION_888_20231106)).map(v -> toEnergy(v)).toArray()) // .setConsumptions(stream(interpolateArray(CONSUMPTION_888_20231106)).map(v -> toEnergy(v)).toArray()) // - .setPrices(hourlyToQuarterly(interpolateArray(PRICES_888_20231106))) // + .setPrices(hourlyToQuarterly(interpolateDoubleArray(PRICES_888_20231106))) // .setStates(ControlMode.CHARGE_CONSUMPTION.states) // .setExistingSchedule(prepareExistingSchedule(TIME, // CHARGE_GRID, DELAY_DISCHARGE, CHARGE_GRID, DELAY_DISCHARGE, BALANCING)) // @@ -67,11 +69,11 @@ public void testBuildInitialPopulation() { assertEquals(0 /* BALANCING */, gt.get(5).get(0).intValue()); // default } { - var gt = buildInitialPopulation(Params.create() // + var gt = buildInitialPopulation(ParamsV1.create() // .setTime(TIME) // .setProductions(stream(interpolateArray(PRODUCTION_888_20231106)).map(v -> toEnergy(v)).toArray()) // .setConsumptions(stream(interpolateArray(CONSUMPTION_888_20231106)).map(v -> toEnergy(v)).toArray()) // - .setPrices(hourlyToQuarterly(interpolateArray(PRICES_888_20231106))) // + .setPrices(hourlyToQuarterly(interpolateDoubleArray(PRICES_888_20231106))) // .setStates(ControlMode.DELAY_DISCHARGE.states) // .setExistingSchedule(prepareExistingSchedule(TIME, // CHARGE_GRID, DELAY_DISCHARGE, CHARGE_GRID, DELAY_DISCHARGE, BALANCING)) // diff --git a/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/IntegrationTests.java b/io.openems.edge.energy/test/io/openems/edge/energy/v1/optimizer/IntegrationTestsV1.java similarity index 80% rename from io.openems.edge.energy/test/io/openems/edge/energy/optimizer/IntegrationTests.java rename to io.openems.edge.energy/test/io/openems/edge/energy/v1/optimizer/IntegrationTestsV1.java index f5b0ebbc544..3e1d7572262 100644 --- a/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/IntegrationTests.java +++ b/io.openems.edge.energy/test/io/openems/edge/energy/v1/optimizer/IntegrationTestsV1.java @@ -1,11 +1,11 @@ -package io.openems.edge.energy.optimizer; +package io.openems.edge.energy.v1.optimizer; import static io.openems.common.utils.DateUtils.parseZonedDateTimeOrError; -import static io.openems.edge.energy.optimizer.Params.PARAMS_PATTERN; -import static io.openems.edge.energy.optimizer.Simulator.calculateCost; -import static io.openems.edge.energy.optimizer.Simulator.getBestSchedule; -import static io.openems.edge.energy.optimizer.SimulatorTest.logSchedule; import static io.openems.edge.energy.optimizer.Utils.initializeRandomRegistryForUnitTest; +import static io.openems.edge.energy.v1.optimizer.ParamsV1.PARAMS_PATTERN; +import static io.openems.edge.energy.v1.optimizer.SimulatorV1.calculateCost; +import static io.openems.edge.energy.v1.optimizer.SimulatorV1.getBestSchedule; +import static io.openems.edge.energy.v1.optimizer.SimulatorV1Test.logSchedule; import static java.lang.Integer.parseInt; import static org.junit.Assert.assertEquals; @@ -20,9 +20,10 @@ import io.openems.common.exceptions.OpenemsException; import io.openems.edge.controller.ess.timeofusetariff.StateMachine; -import io.openems.edge.energy.optimizer.ScheduleDatas.ScheduleData; +import io.openems.edge.energy.v1.optimizer.ScheduleDatas.ScheduleData; -public class IntegrationTests { +@SuppressWarnings("deprecation") +public class IntegrationTestsV1 { @Before public void before() { @@ -36,11 +37,10 @@ public void before() { */ @Ignore @Test - // TODO to be updated public void test1() throws Exception { var log = """ """; - var p = IntegrationTests.parseParams(log); + var p = IntegrationTestsV1.parseParams(log); var schedule = getBestSchedule(p, 30); logSchedule(p, schedule); @@ -50,7 +50,7 @@ public void test1() throws Exception { public static final Pattern PERIOD_PATTERN = Pattern.compile("^.*(?\\d{2}:\\d{2}\s+.*$)"); - protected static Params parseParams(String log) throws IllegalArgumentException, OpenemsException { + protected static ParamsV1 parseParams(String log) throws IllegalArgumentException, OpenemsException { var paramsMatcher = log.lines() // .findFirst() // .map(PARAMS_PATTERN::matcher) // @@ -72,7 +72,7 @@ protected static Params parseParams(String log) throws IllegalArgumentException, } var sd = sds.stream().findFirst().get(); - return Params.create() // + return ParamsV1.create() // .setTime(time) // .setEssTotalEnergy(essTotalEnergy) // .setEssMinSocEnergy(essMinSocEnergy) // diff --git a/io.openems.edge.energy/test/io/openems/edge/energy/v1/optimizer/OptimizerV1Test.java b/io.openems.edge.energy/test/io/openems/edge/energy/v1/optimizer/OptimizerV1Test.java new file mode 100644 index 00000000000..30f646d156f --- /dev/null +++ b/io.openems.edge.energy/test/io/openems/edge/energy/v1/optimizer/OptimizerV1Test.java @@ -0,0 +1,22 @@ +package io.openems.edge.energy.v1.optimizer; + +import static io.openems.edge.energy.v1.EnergySchedulerImplTest.CLOCK; +import static io.openems.edge.energy.v1.EnergySchedulerImplTest.getOptimizer; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import io.openems.edge.energy.v1.EnergySchedulerImplTest; + +@SuppressWarnings("deprecation") +public class OptimizerV1Test { + + @Test + public void testEmpty() throws Exception { + var sut = getOptimizer(EnergySchedulerImplTest.create(CLOCK)); + assertNull(sut.getParams()); + assertTrue(sut.getSchedule().isEmpty()); + } + +} diff --git a/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/ParamsUtilsTest.java b/io.openems.edge.energy/test/io/openems/edge/energy/v1/optimizer/ParamsUtilsV1Test.java similarity index 83% rename from io.openems.edge.energy/test/io/openems/edge/energy/optimizer/ParamsUtilsTest.java rename to io.openems.edge.energy/test/io/openems/edge/energy/v1/optimizer/ParamsUtilsV1Test.java index 68fb733131c..6be0a5e1e6c 100644 --- a/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/ParamsUtilsTest.java +++ b/io.openems.edge.energy/test/io/openems/edge/energy/v1/optimizer/ParamsUtilsV1Test.java @@ -1,14 +1,15 @@ -package io.openems.edge.energy.optimizer; +package io.openems.edge.energy.v1.optimizer; -import static io.openems.edge.energy.optimizer.ParamsUtils.calculateChargeEnergyInChargeGrid; -import static io.openems.edge.energy.optimizer.ParamsUtils.calculatePeriodLengthHourFromIndex; +import static io.openems.edge.energy.v1.optimizer.ParamsUtilsV1.calculateChargeEnergyInChargeGrid; +import static io.openems.edge.energy.v1.optimizer.ParamsUtilsV1.calculatePeriodLengthHourFromIndex; import static org.junit.Assert.assertEquals; import java.time.ZonedDateTime; import org.junit.Test; -public class ParamsUtilsTest { +@SuppressWarnings("deprecation") +public class ParamsUtilsV1Test { @Test public void testCalculateParamsMaxChargeEnergyInChargeGrid() { diff --git a/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/RunOptimizerFromLogApp.java b/io.openems.edge.energy/test/io/openems/edge/energy/v1/optimizer/RunOptimizerFromLogV1App.java similarity index 66% rename from io.openems.edge.energy/test/io/openems/edge/energy/optimizer/RunOptimizerFromLogApp.java rename to io.openems.edge.energy/test/io/openems/edge/energy/v1/optimizer/RunOptimizerFromLogV1App.java index 1c468f22028..1b26ffa282f 100644 --- a/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/RunOptimizerFromLogApp.java +++ b/io.openems.edge.energy/test/io/openems/edge/energy/v1/optimizer/RunOptimizerFromLogV1App.java @@ -1,13 +1,13 @@ -package io.openems.edge.energy.optimizer; +package io.openems.edge.energy.v1.optimizer; -import static io.openems.edge.energy.optimizer.Simulator.getBestSchedule; -import static io.openems.edge.energy.optimizer.Simulator.simulate; -import static io.openems.edge.energy.optimizer.Utils.logSchedule; +import static io.openems.edge.energy.v1.optimizer.SimulatorV1.getBestSchedule; +import static io.openems.edge.energy.v1.optimizer.SimulatorV1.simulate; +import static io.openems.edge.energy.v1.optimizer.UtilsV1.logSchedule; /** * This little application allows running the Optimizer from an existing log. * Just fill the header data and paste the log lines in - * {@link RunOptimizerFromLogApp#LOG}. + * {@link RunOptimizerFromLogV1App#LOG}. * *

* To get the log from a system running with systemd, execute the following @@ -18,7 +18,8 @@ * journalctl -lu openems --since="20 minutes ago" | grep OPTIMIZER * */ -public class RunOptimizerFromLogApp { +@Deprecated +public class RunOptimizerFromLogV1App { private static final long EXECUTION_LIMIT_SECONDS = 30; @@ -33,7 +34,7 @@ public class RunOptimizerFromLogApp { * @throws Exception on error */ public static void main(String[] args) throws Exception { - var params = IntegrationTests.parseParams(LOG); + var params = IntegrationTestsV1.parseParams(LOG); var schedule = getBestSchedule(params, EXECUTION_LIMIT_SECONDS); var periods = simulate(params, schedule); diff --git a/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/ScheduleDatasTest.java b/io.openems.edge.energy/test/io/openems/edge/energy/v1/optimizer/ScheduleDatasV1Test.java similarity index 85% rename from io.openems.edge.energy/test/io/openems/edge/energy/optimizer/ScheduleDatasTest.java rename to io.openems.edge.energy/test/io/openems/edge/energy/v1/optimizer/ScheduleDatasV1Test.java index 33cc5327106..9c5570d4c69 100644 --- a/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/ScheduleDatasTest.java +++ b/io.openems.edge.energy/test/io/openems/edge/energy/v1/optimizer/ScheduleDatasV1Test.java @@ -1,4 +1,4 @@ -package io.openems.edge.energy.optimizer; +package io.openems.edge.energy.v1.optimizer; import static io.openems.common.utils.JsonUtils.getAsInt; import static io.openems.common.utils.JsonUtils.prettyToString; @@ -6,15 +6,15 @@ import static io.openems.common.utils.JsonUtils.toJsonArray; import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.CHARGE_GRID; import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.DELAY_DISCHARGE; -import static io.openems.edge.energy.EnergySchedulerImplTest.CLOCK; -import static io.openems.edge.energy.EnergySchedulerImplTest.getOptimizer; -import static io.openems.edge.energy.optimizer.ScheduleDatas.fromLogString; -import static io.openems.edge.energy.optimizer.ScheduleDatas.ScheduleData.fromHistoricDataQuery; -import static io.openems.edge.energy.optimizer.Utils.SUM_CONSUMPTION; import static io.openems.edge.energy.optimizer.Utils.SUM_ESS_DISCHARGE_POWER; import static io.openems.edge.energy.optimizer.Utils.SUM_ESS_SOC; import static io.openems.edge.energy.optimizer.Utils.SUM_GRID; -import static io.openems.edge.energy.optimizer.Utils.SUM_PRODUCTION; +import static io.openems.edge.energy.v1.EnergySchedulerImplTest.CLOCK; +import static io.openems.edge.energy.v1.EnergySchedulerImplTest.getOptimizer; +import static io.openems.edge.energy.v1.optimizer.ScheduleDatas.fromLogString; +import static io.openems.edge.energy.v1.optimizer.ScheduleDatas.ScheduleData.fromHistoricDataQuery; +import static io.openems.edge.energy.v1.optimizer.UtilsV1.SUM_CONSUMPTION; +import static io.openems.edge.energy.v1.optimizer.UtilsV1.SUM_PRODUCTION; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; @@ -33,13 +33,14 @@ import io.openems.common.types.ChannelAddress; import io.openems.common.utils.DateUtils; import io.openems.edge.controller.ess.timeofusetariff.StateMachine; -import io.openems.edge.energy.EnergySchedulerImplTest; -import io.openems.edge.energy.optimizer.Params.Length; -import io.openems.edge.energy.optimizer.Params.OptimizePeriod; -import io.openems.edge.energy.optimizer.Params.QuarterPeriod; -import io.openems.edge.energy.optimizer.ScheduleDatas.ScheduleData; +import io.openems.edge.energy.v1.EnergySchedulerImplTest; +import io.openems.edge.energy.v1.optimizer.ParamsV1.Length; +import io.openems.edge.energy.v1.optimizer.ParamsV1.OptimizePeriod; +import io.openems.edge.energy.v1.optimizer.ParamsV1.QuarterPeriod; +import io.openems.edge.energy.v1.optimizer.ScheduleDatas.ScheduleData; -public class ScheduleDatasTest { +@SuppressWarnings("deprecation") +public class ScheduleDatasV1Test { protected static final ZonedDateTime TIME = ZonedDateTime.of(2000, 1, 1, 0, 15, 0, 0, ZoneId.of("UTC")); @@ -116,11 +117,11 @@ public void testFromSchedule1() throws Exception { public void testFromSchedule2() throws Exception { var sds = ScheduleDatas.fromSchedule(22_000, ImmutableSortedMap.of(// TIME, // - new Simulator.Period(// + new SimulatorV1.Period(// new OptimizePeriod(TIME, Length.QUARTER, 1, 2, 3, 4, 5, 6, 7., ImmutableList.of(// new QuarterPeriod(TIME, 1, 2, 3, 4, 5, 6, 7))), StateMachine.BALANCING, 10_000, - new EnergyFlow(0, 0, 1000 /* ess */, 500 /* grid */, 0, 0, 0, 0, 0, 0)) // + new EnergyFlowV1(0, 0, 1000 /* ess */, 500 /* grid */, 0, 0, 0, 0, 0, 0)) // )); assertEquals( """ diff --git a/io.openems.edge.energy/test/io/openems/edge/energy/v1/optimizer/SimulatorV1Test.java b/io.openems.edge.energy/test/io/openems/edge/energy/v1/optimizer/SimulatorV1Test.java new file mode 100644 index 00000000000..cd3bd8e9e0e --- /dev/null +++ b/io.openems.edge.energy/test/io/openems/edge/energy/v1/optimizer/SimulatorV1Test.java @@ -0,0 +1,208 @@ +package io.openems.edge.energy.v1.optimizer; + +import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.BALANCING; +import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.CHARGE_GRID; +import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.DELAY_DISCHARGE; +import static io.openems.edge.energy.api.EnergyUtils.toEnergy; +import static io.openems.edge.energy.v1.optimizer.SimulatorV1.getBestSchedule; +import static io.openems.edge.energy.v1.optimizer.SimulatorV1.simulate; +import static io.openems.edge.energy.v1.optimizer.TestDataV1.CONSUMPTION_888_20231106; +import static io.openems.edge.energy.v1.optimizer.TestDataV1.PRICES_888_20231106; +import static io.openems.edge.energy.v1.optimizer.TestDataV1.PRODUCTION_888_20231106; +import static io.openems.edge.energy.v1.optimizer.UtilsV1.interpolateArray; +import static io.openems.edge.energy.v1.optimizer.UtilsV1.interpolateDoubleArray; +import static java.util.Arrays.stream; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.Arrays; +import java.util.Random; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.DoubleStream; + +import org.junit.Before; +import org.junit.Test; + +import com.google.common.collect.ImmutableSortedMap; + +import io.jenetics.util.RandomRegistry; +import io.openems.edge.controller.ess.timeofusetariff.ControlMode; +import io.openems.edge.controller.ess.timeofusetariff.StateMachine; +import io.openems.edge.energy.v1.optimizer.SimulatorV1.Period; + +@SuppressWarnings("deprecation") +public class SimulatorV1Test { + + public static final ZonedDateTime TIME = ZonedDateTime.of(2000, 1, 1, 0, 0, 0, 0, ZoneId.of("UTC")); + + @Before + public void before() { + // Make reproducible results + System.setProperty("io.jenetics.util.defaultRandomGenerator", "Random"); + RandomRegistry.random(new Random(123)); + } + + private static Period simulatePeriod(StateMachine state, int production, int consumption, double price, + int essInitial) { + var result = new AtomicReference(); + var params = ParamsV1.create() // + .setTime(TIME) // + .setEssTotalEnergy(22000) // + .setEssMinSocEnergy(0) // + .setEssMaxSocEnergy(20000) // + .setEssInitialEnergy(essInitial) // + .setEssMaxChargeEnergy(3000 /* [Wh/15 Minutes] */) // + .setEssMaxDischargeEnergy(3000 /* [Wh/15 Minutes] */) // + .seMaxBuyFromGrid(4000 /* [Wh/15 Minutes] */) // + .setProductions(new int[] { production }) // + .setConsumptions(new int[] { consumption }) // + .setPrices(new double[] { price }) // + .setStates(new StateMachine[] { state }) // + .setExistingSchedule(ImmutableSortedMap.of()) // + .build(); + SimulatorV1.simulatePeriod(params, params.optimizePeriods().get(0), state, new AtomicInteger(essInitial), + result::set); + + return result.get(); + } + + private static void assertPeriod(String message, Period period, int essChargeDischarge, int grid, double cost) { + assertEquals(period.state() + "-essChargeDischarge: " + message, essChargeDischarge, period.ef().ess()); + assertEquals(period.state() + "-grid: " + message, grid, period.ef().grid()); + } + + @Test + public void testCalculatePeriodCostBalancing() { + assertPeriod("Consumption > Production; SoC ok", // + simulatePeriod(BALANCING, 200, 300, 0.1, 10000), // + 100, 0, 0); + assertPeriod("Consumption > Production; discharge limited by essMaxEnergyPerPeriod", // + simulatePeriod(BALANCING, 1000, 5000, 0.1, 10000), // + 3000, 1000, 100); + assertPeriod("Consumption > Production; discharge limited by essMinSocEnergy", // + simulatePeriod(BALANCING, 1000, 5000, 0.1, 2500), // + 2500, 1500, 150); + + assertPeriod("Production > Consumption; SoC ok", // + simulatePeriod(BALANCING, 300, 200, 0.1, 10000), // + -100, 0, 0); + assertPeriod("Production > Consumption; charge limited by essMaxEnergyPerPeriod", // + simulatePeriod(BALANCING, 5000, 1000, 0.1, 10000), // + -3000, -1000, 0); + assertPeriod("Production > Consumption; charge limited by essTotalEnergy", // + simulatePeriod(BALANCING, 5000, 1000, 0.1, 19500), // + -2500, -1500, 0); + } + + @Test + public void testCalculatePeriodCostDelayDischarge() { + assertPeriod("Consumption > Production", // + simulatePeriod(DELAY_DISCHARGE, 200, 300, 0.1, 10000), // + 0, 100, 10); + + assertPeriod("Production > Consumption; SoC ok", // + simulatePeriod(DELAY_DISCHARGE, 300, 200, 0.1, 10000), // + -100, 0, 0); + assertPeriod("Production > Consumption; charge limited by essMaxEnergyPerPeriod", // + simulatePeriod(DELAY_DISCHARGE, 5000, 1000, 0.1, 10000), // + -3000, -1000, 0); + assertPeriod("Production > Consumption; charge limited by essTotalEnergy", // + simulatePeriod(DELAY_DISCHARGE, 5000, 1000, 0.1, 19500), // + -2500, -1500, 0); + } + + @Test + public void testCalculatePeriodCostChargeGrid() { + assertPeriod("Consumption > Production", // + simulatePeriod(CHARGE_GRID, 200, 300, 0.1, 10000), // + -842, 942 /* 842 + 100 */, 302.5); + + assertPeriod("Consumption > Production; charge limited by maxBuyFromGrid", // + simulatePeriod(CHARGE_GRID, 0, 4500, 0.1, 10000), // + 500, 4000, 450.12); + + assertPeriod("Production > Consumption", // + simulatePeriod(CHARGE_GRID, 300, 200, 0.1, 10000), // + -2600 /* 2500 + 100 */, 2500, 292.5); + + assertPeriod("Production > Consumption; charge limited by essMaxEnergyPerPeriod", // + simulatePeriod(CHARGE_GRID, 3000, 900, 0.1, 10000), // + -3000, 900, 105.3); + + assertPeriod("Production > Consumption", // + simulatePeriod(CHARGE_GRID, 2000, 1700, 0.1, 10000), // + -2800, 2500, 292.5); + + assertPeriod("Production > Consumption; battery nearly full", // + simulatePeriod(CHARGE_GRID, 3000, 100, 0.1, 19600), // + -400 /* 400 from PV; then full */, -2500 /* sell-to-grid */, 292.5); + } + + @Test + public void testGetFirstSchedule0() { + var existingSchedule = new StateMachine[] { CHARGE_GRID, DELAY_DISCHARGE, CHARGE_GRID, BALANCING }; + + var p = ParamsV1.create() // + .setTime(TIME) // + .setEssTotalEnergy(22000) // + .setEssMinSocEnergy(0) // + .setEssMaxSocEnergy(22000) // + .setEssInitialEnergy((int) (22000 * 0.1)) // + .setEssMaxChargeEnergy(toEnergy(10000)) // + .setEssMaxDischargeEnergy(toEnergy(10000)) // + .seMaxBuyFromGrid(toEnergy(24_000)) // + .setProductions(stream(interpolateArray(PRODUCTION_888_20231106)).map(v -> toEnergy(v)).toArray()) // + .setConsumptions(stream(interpolateArray(CONSUMPTION_888_20231106)).map(v -> toEnergy(v)).toArray()) // + .setPrices(hourlyToQuarterly(interpolateDoubleArray(PRICES_888_20231106))) // + .setStates(ControlMode.CHARGE_CONSUMPTION.states) // + .setExistingSchedule(UtilsV1Test.prepareExistingSchedule(TIME, existingSchedule)) // + .build(); + var s = getBestSchedule(p, // + /* executionLimitSeconds */ 30, // + /* populationSize */ 2, // + /* limit */ 1); + + assertArrayEquals(existingSchedule, Arrays.copyOfRange(s, 0, existingSchedule.length)); + } + + /** + * Creates dummy {@link ParamsV1}. + * + * @param states the allowed states + * @return {@link ParamsV1} + */ + public static ParamsV1 createParams888d20231106(StateMachine... states) { + return ParamsV1.create() // + .setTime(TIME) // + .setEssTotalEnergy(22000) // + .setEssMinSocEnergy(0) // + .setEssMaxSocEnergy(22000) // + .setEssMaxChargeEnergy(toEnergy(10000)) // + .setEssMaxDischargeEnergy(toEnergy(10000)) // + .seMaxBuyFromGrid(toEnergy(24_000)) // + .setProductions(stream(interpolateArray(PRODUCTION_888_20231106)).map(v -> toEnergy(v)).toArray()) // + .setConsumptions(stream(interpolateArray(CONSUMPTION_888_20231106)).map(v -> toEnergy(v)).toArray()) // + .setPrices(hourlyToQuarterly(interpolateDoubleArray(PRICES_888_20231106))) // + .setStates(states) // + .build(); + } + + protected static void logSchedule(ParamsV1 p, StateMachine[] schedule) { + UtilsV1.logSchedule(p, simulate(p, schedule)); + } + + /** + * Convert hourly values to quarterly. + * + * @param values hourly values + * @return quarterly values + */ + protected static double[] hourlyToQuarterly(double[] values) { + return DoubleStream.of(values) // + .flatMap(v -> DoubleStream.of(v, v, v, v)) // + .toArray(); + } +} \ No newline at end of file diff --git a/io.openems.edge.energy/test/io/openems/edge/energy/v1/optimizer/TestDataV1.java b/io.openems.edge.energy/test/io/openems/edge/energy/v1/optimizer/TestDataV1.java new file mode 100644 index 00000000000..a5d5c7c92c4 --- /dev/null +++ b/io.openems.edge.energy/test/io/openems/edge/energy/v1/optimizer/TestDataV1.java @@ -0,0 +1,40 @@ +package io.openems.edge.energy.v1.optimizer; + +public class TestDataV1 { + + // Edge 888; 06.11.2023 + + protected static final Integer[] PRODUCTION_888_20231106 = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 12, 19, 24, 92, 301, 441, 653, 741, 1921, 1923, 1649, 2045, 2638, 3399, 4071, 4359, + 4516, 5541, 6993, 6292, 3902, 7700, 9098, 9555, 8119, 6868, 6560, 6380, 6193, 5389, 4349, 3743, 5367, 5319, + 4383, 2243, 1122, 1315, 1107, 268, 48, 2, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }; + + protected static final Integer[] CONSUMPTION_888_20231106 = { 308, 285, 384, 471, 480, 385, 464, 448, 288, 333, 346, + 313, 1786, 332, 300, 259, 373, 358, 279, 308, 309, 415, 392, 299, 2913, 3105, 4416, 4442, 497, 5910, 4106, + 2171, 3898, 922, 1601, 1088, 303, 2384, 430, 2428, 2899, 371, 613, 1663, 366, 2072, 456, 1589, 2004, 488, + 199, 1628, 613, 198, 1796, 202, 1180, 4975, 4493, 5511, 7757, 2926, 2640, 4335, 2630, 2799, 5111, 2979, + 3062, 4842, 4194, 4474, 4750, 4876, 1238, 1395, 1425, 1123, 3366, 4088, 418, 436, 3234, 1504, 1092, 1853, + 365, 628, 2095, 552, 1113, 1808, 3223, 1629, 1329, 264 }; + + protected static final Double[] PRICES_888_20231106 = { 155., 152., 152., 152., 157., 172., 238., 266., 266., 241., + 224., 219., 221., 232., 248., 271., 286., 316., 332., 318., 284., 278., 270., 257. }; + + protected static final Integer[] PAST_STATES = { // + 1, 1, 1, 1, // + 1, 3, 3, 1, // + 2, 1, 2, 2, // + }; + + protected static final Integer[] PAST_HOURLY_PRICES = { // + 158, 160, 171, 174, // + 161, 152, 120, 111, // + 105, 105, 74, 73, // + }; + + protected static final Integer[] PAST_SOC = { // + 60, 62, 64, 66, // + 65, 67, 70, 73, // + 76, 79, 83, 87, // + }; +} diff --git a/io.openems.edge.energy/test/io/openems/edge/energy/v1/optimizer/UtilsV1Test.java b/io.openems.edge.energy/test/io/openems/edge/energy/v1/optimizer/UtilsV1Test.java new file mode 100644 index 00000000000..f7dc44a3b89 --- /dev/null +++ b/io.openems.edge.energy/test/io/openems/edge/energy/v1/optimizer/UtilsV1Test.java @@ -0,0 +1,350 @@ +package io.openems.edge.energy.v1.optimizer; + +import static io.openems.common.utils.DateUtils.roundDownToQuarter; +import static io.openems.edge.common.test.TestUtils.withValue; +import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.BALANCING; +import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.DELAY_DISCHARGE; +import static io.openems.edge.controller.ess.timeofusetariff.v1.UtilsV1.calculateLimitChargePowerFor14aEnWG; +import static io.openems.edge.energy.api.EnergyUtils.findFirstPeakIndex; +import static io.openems.edge.energy.api.EnergyUtils.findFirstValleyIndex; +import static io.openems.edge.energy.optimizer.TestData.CONSUMPTION_PREDICTION_QUARTERLY; +import static io.openems.edge.energy.optimizer.TestData.PRODUCTION_PREDICTION_QUARTERLY; +import static io.openems.edge.energy.optimizer.Utils.SUM_ESS_DISCHARGE_POWER; +import static io.openems.edge.energy.optimizer.Utils.SUM_ESS_SOC; +import static io.openems.edge.energy.optimizer.Utils.SUM_GRID; +import static io.openems.edge.energy.v1.EnergySchedulerImplTest.getOptimizer; +import static io.openems.edge.energy.v1.optimizer.EnergyFlowV1Test.NO_FLOW; +import static io.openems.edge.energy.v1.optimizer.SimulatorV1Test.TIME; +import static io.openems.edge.energy.v1.optimizer.TestDataV1.PAST_HOURLY_PRICES; +import static io.openems.edge.energy.v1.optimizer.TestDataV1.PAST_SOC; +import static io.openems.edge.energy.v1.optimizer.TestDataV1.PAST_STATES; +import static io.openems.edge.energy.v1.optimizer.TestDataV1.PRODUCTION_888_20231106; +import static io.openems.edge.energy.v1.optimizer.UtilsV1.SUM_CONSUMPTION; +import static io.openems.edge.energy.v1.optimizer.UtilsV1.SUM_PRODUCTION; +import static io.openems.edge.energy.v1.optimizer.UtilsV1.calculateMaxChargePower; +import static io.openems.edge.energy.v1.optimizer.UtilsV1.generateProductionPrediction; +import static io.openems.edge.energy.v1.optimizer.UtilsV1.getEssMinSocEnergy; +import static io.openems.edge.energy.v1.optimizer.UtilsV1.interpolateArray; +import static io.openems.edge.energy.v1.optimizer.UtilsV1.interpolateDoubleArray; +import static io.openems.edge.energy.v1.optimizer.UtilsV1.joinConsumptionPredictions; +import static io.openems.edge.energy.v1.optimizer.UtilsV1.paramsAreValid; +import static io.openems.edge.energy.v1.optimizer.UtilsV1.toEnergy; +import static io.openems.edge.energy.v1.optimizer.UtilsV1.toPower; +import static io.openems.edge.energy.v1.optimizer.UtilsV1.updateSchedule; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.List; +import java.util.TreeMap; +import java.util.stream.IntStream; + +import org.junit.Test; + +import com.google.common.collect.ImmutableSortedMap; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.test.TimeLeapClock; +import io.openems.common.types.ChannelAddress; +import io.openems.edge.common.channel.value.Value; +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.common.test.AbstractDummyOpenemsComponent; +import io.openems.edge.controller.ess.emergencycapacityreserve.ControllerEssEmergencyCapacityReserve; +import io.openems.edge.controller.ess.limiter14a.ControllerEssLimiter14a; +import io.openems.edge.controller.ess.limittotaldischarge.ControllerEssLimitTotalDischarge; +import io.openems.edge.controller.ess.timeofusetariff.StateMachine; +import io.openems.edge.controller.ess.timeofusetariff.v1.EnergyScheduleHandlerV1.ContextV1; +import io.openems.edge.energy.v1.EnergySchedulerImplTest; +import io.openems.edge.energy.v1.optimizer.SimulatorV1.Period; +import io.openems.edge.timedata.test.DummyTimedata; + +@SuppressWarnings("deprecation") +public class UtilsV1Test { + + protected static ImmutableSortedMap prepareExistingSchedule(ZonedDateTime fromDate, + StateMachine... existingSchedule) { + return IntStream.range(0, existingSchedule.length) // + .mapToObj(Integer::valueOf) // + .collect(ImmutableSortedMap.toImmutableSortedMap( + ZonedDateTime::compareTo, // + i -> fromDate.plusMinutes(i * 15), // + i -> existingSchedule[i])); + } + + @Test + public void testInterpolateDoubleArray() { + assertArrayEquals(new double[] { 123, 123, 234, 234, 345 }, // + interpolateDoubleArray(new Double[] { null, 123., 234., null, 345., null }), // + 0.0001F); + + assertArrayEquals(new double[] {}, // + interpolateDoubleArray(new Double[] { null }), // + 0.0001F); + } + + @Test + public void testToPower() { + assertEquals(2000, (int) toPower(500)); + assertNull(toPower(null)); + } + + @Test + public void testGenerateProductionPrediction() { + final var arr = new Integer[] { 1, 2, 3 }; + assertArrayEquals(arr, generateProductionPrediction(arr, 2)); + assertArrayEquals(new Integer[] { 1, 2, 3, 0 }, generateProductionPrediction(arr, 4)); + } + + @Test + public void testJoinConsumptionPredictions() { + assertArrayEquals(// + new Integer[] { 1, 2, 3, 4, 55, 66, 77, 88, 99 }, // + joinConsumptionPredictions(4, // + new Integer[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, // + new Integer[] { 11, 22, 33, 44, 55, 66, 77, 88, 99 })); + } + + @Test + public void testFindFirstPeakIndex() { + assertEquals(0, findFirstPeakIndex(0, new double[0])); + assertEquals(0, findFirstPeakIndex(0, new double[] { 1 })); + assertEquals(0, findFirstPeakIndex(0, new double[] { 1, 0 })); + assertEquals(1, findFirstPeakIndex(0, new double[] { 0, 1, 0 })); + assertEquals(1, findFirstPeakIndex(0, new double[] { 0, 1, 0, 1 })); + assertEquals(5, findFirstPeakIndex(5, new double[0])); + } + + @Test + public void testFindFirstValleyIndex() { + assertEquals(0, findFirstValleyIndex(0, new double[0])); + assertEquals(0, findFirstValleyIndex(0, new double[] { 1 })); + assertEquals(1, findFirstValleyIndex(0, new double[] { 1, 0 })); + assertEquals(0, findFirstValleyIndex(0, new double[] { 0, 1, 0 })); + assertEquals(2, findFirstValleyIndex(1, new double[] { 0, 1, 0, 1 })); + assertEquals(5, findFirstValleyIndex(5, new double[0])); + } + + @Test + public void testParamsAreValid() throws Exception { + var builder = ParamsV1.create() // + .setTime(TIME) // + .setEssInitialEnergy(0) // + .setEssTotalEnergy(22000) // + .setEssMinSocEnergy(2_000) // + .setEssMaxSocEnergy(20_000) // + .seMaxBuyFromGrid(toEnergy(24_000)) // + .seMaxBuyFromGrid(0) // + .setStates(new StateMachine[0]); + + // No periods are available + assertFalse(paramsAreValid(builder // + .setProductions() // + .setConsumptions() // + .setPrices() // + .build())); + + // Production and Consumption predictions are all zero + assertFalse(paramsAreValid(builder // + .setProductions(0, 0, 0) // + .setConsumptions(0, 0) // + .setPrices(123F) // + .build())); + + // Prices are all the same + assertFalse(paramsAreValid(builder // + .setProductions(0, 1, 3) // + .setConsumptions(0, 2) // + .setPrices(123F, 123F) // + .build())); + + // Finally got it right... + assertTrue(paramsAreValid(builder // + .setProductions(0, 1, 3) // + .setConsumptions(0, 2) // + .setPrices(123F, 124F) // + .build())); + assertEquals(2, builder.build().optimizePeriods().size()); + } + + private static class MyControllerEssLimitTotalDischarge + extends AbstractDummyOpenemsComponent + implements ControllerEssLimitTotalDischarge { + + protected MyControllerEssLimitTotalDischarge(Integer minSoc) { + super("ctrl0", // + OpenemsComponent.ChannelId.values(), // + ControllerEssLimitTotalDischarge.ChannelId.values() // + ); + withValue(this.getMinSocChannel(), minSoc); + } + + @Override + public void run() throws OpenemsNamedException { + } + + @Override + protected MyControllerEssLimitTotalDischarge self() { + return this; + } + } + + private static class MyControllerEssEmergencyCapacityReserve + extends AbstractDummyOpenemsComponent + implements ControllerEssEmergencyCapacityReserve { + + protected MyControllerEssEmergencyCapacityReserve(Integer reserveSoc) { + super("ctrl0", // + OpenemsComponent.ChannelId.values(), // + ControllerEssEmergencyCapacityReserve.ChannelId.values() // + ); + withValue(this.getActualReserveSocChannel(), reserveSoc); + } + + @Override + public void run() throws OpenemsNamedException { + } + + @Override + protected MyControllerEssEmergencyCapacityReserve self() { + return this; + } + } + + private static class MyControllerEssLimiter14a extends AbstractDummyOpenemsComponent + implements ControllerEssLimiter14a { + + protected MyControllerEssLimiter14a(boolean restrictionMode) { + super("ctrl0", // + OpenemsComponent.ChannelId.values(), // + ControllerEssLimiter14a.ChannelId.values() // + ); + withValue(this.getRestrictionModeChannel(), restrictionMode); + } + + @Override + public void run() throws OpenemsNamedException { + } + + @Override + protected MyControllerEssLimiter14a self() { + return this; + } + } + + @Test + public void testGetEssMinSocEnergy() { + var t1 = new MyControllerEssLimitTotalDischarge(50); + var t2 = new MyControllerEssLimitTotalDischarge(null); + var t3 = new MyControllerEssEmergencyCapacityReserve(30); + var limiter1 = new MyControllerEssLimiter14a(false); + var limiter2 = new MyControllerEssLimiter14a(true); + assertEquals(5000, getEssMinSocEnergy(new ContextV1(// + List.of(t3), List.of(t1, t2), List.of(limiter1, limiter2), // + null, null, 10000), 10000)); + } + + @Test + public void testHandleScheduleRequest() throws Exception { + final var clock = new TimeLeapClock(Instant.parse("2020-03-04T14:19:00.00Z"), ZoneOffset.UTC); + final var energyScheduler = EnergySchedulerImplTest.create(clock); + + // Simulate historic data + var now = roundDownToQuarter(ZonedDateTime.now(clock)); + final var fromDate = now.minusHours(3); + var timedata = new DummyTimedata("timedata0"); + for (var i = 0; i < 12; i++) { + var quarter = fromDate.plusMinutes(i * 15); + timedata.add(quarter, new ChannelAddress("ctrl0", "QuarterlyPrices"), PAST_HOURLY_PRICES[i]); + timedata.add(quarter, new ChannelAddress("ctrl0", "StateMachine"), PAST_STATES[i]); + timedata.add(quarter, SUM_PRODUCTION, PRODUCTION_PREDICTION_QUARTERLY[i]); + timedata.add(quarter, SUM_CONSUMPTION, CONSUMPTION_PREDICTION_QUARTERLY[i]); + timedata.add(quarter, SUM_ESS_SOC, PAST_SOC[i]); + timedata.add(quarter, SUM_ESS_DISCHARGE_POWER, PRODUCTION_888_20231106[i]); + timedata.add(quarter, SUM_GRID, PRODUCTION_888_20231106[i]); + } + + var globalContext = EnergySchedulerImplTest.getGlobalContext(energyScheduler); + assertNotNull(globalContext); + var optimizer = getOptimizer(energyScheduler); + assertNotNull(optimizer); + } + + @Test + public void testUpdateSchedule() { + final ZonedDateTime t = ZonedDateTime.of(2000, 1, 1, 0, 0, 0, 0, ZoneId.of("UTC")); + final Period pOld = new Period(null, DELAY_DISCHARGE, 0, NO_FLOW); + final Period pNew = new Period(null, BALANCING, 0, NO_FLOW); + + var schedule = new TreeMap(); + schedule.put(t.minusMinutes(15), pOld); // old entry is removed + schedule.put(t, pOld); // current entry stays + schedule.put(t.plusMinutes(15), pOld); // is overridden + schedule.put(t.plusMinutes(30), pOld); // is overridden + schedule.put(t.plusMinutes(45), pOld); // timestamp is missing in new Schedule -> remove + + var newSchedule = ImmutableSortedMap.naturalOrder() // + .put(t, pNew) // + .put(t.plusMinutes(15), pNew) // + .put(t.plusMinutes(30), pNew) // + .build(); + + updateSchedule(t, schedule, newSchedule); + + // One old entry + assertEquals(1, schedule.values().stream().filter(v -> v == pOld).count()); + + // Two new entries + assertEquals(2, schedule.values().stream().filter(v -> v == pNew).count()); + + // No old entry + assertEquals(0, schedule.keySet().stream().filter(tz -> tz.isBefore(t)).count()); + + // Details + assertEquals(pOld, schedule.get(t)); + assertEquals(pNew, schedule.get(t.plusMinutes(15))); + assertEquals(pNew, schedule.get(t.plusMinutes(30))); + + // No current entry -> handle null + schedule.remove(t); + updateSchedule(t, schedule, newSchedule); + } + + @Test + public void testCalculateLimitChargePowerFor14aEnWG() { + assertEquals(-4200, calculateLimitChargePowerFor14aEnWG( + List.of(new MyControllerEssLimiter14a(false), new MyControllerEssLimiter14a(true)))); + assertEquals(Integer.MIN_VALUE, calculateLimitChargePowerFor14aEnWG( + List.of(new MyControllerEssLimiter14a(false), new MyControllerEssLimiter14a(false)))); + } + + @Test + public void testCalculateMaxChargePower() { + assertEquals(3000, calculateMaxChargePower(-4200, new Value<>(null, 3000), -1000)); + assertEquals(4200, calculateMaxChargePower(-4200, new Value<>(null, 5000), -1000)); + assertEquals(5000, calculateMaxChargePower(Integer.MIN_VALUE, new Value<>(null, 5000), -1000)); + assertEquals(1000, calculateMaxChargePower(Integer.MIN_VALUE, new Value<>(null, null), -1000)); + assertEquals(1000, calculateMaxChargePower(-4200, new Value<>(null, null), -1000)); + } + + @Test + public void testInterpolateArrayInteger() { + assertArrayEquals(new int[] { 123, 123, 234, 234, 345 }, // + interpolateArray(new Integer[] { null, 123, 234, null, 345, null })); + + assertArrayEquals(new int[] {}, // + interpolateArray(new Integer[] { null })); + + assertArrayEquals(new int[] { 123, 123 }, // + interpolateArray(new Integer[] { null, 123 })); + + assertArrayEquals(new int[] { 123 }, // + interpolateArray(new Integer[] { 123, null })); + } +} diff --git a/io.openems.edge.ess.adstec.storaxe/.classpath b/io.openems.edge.ess.adstec.storaxe/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.ess.adstec.storaxe/.classpath +++ b/io.openems.edge.ess.adstec.storaxe/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.ess.api/.classpath b/io.openems.edge.ess.api/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.ess.api/.classpath +++ b/io.openems.edge.ess.api/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.ess.api/src/io/openems/edge/ess/api/HybridEss.java b/io.openems.edge.ess.api/src/io/openems/edge/ess/api/HybridEss.java index 3cf5e8d2e5b..cdeb54a7736 100644 --- a/io.openems.edge.ess.api/src/io/openems/edge/ess/api/HybridEss.java +++ b/io.openems.edge.ess.api/src/io/openems/edge/ess/api/HybridEss.java @@ -209,6 +209,8 @@ public default void _setDcDischargeEnergy(long value) { public static ModbusSlaveNatureTable getModbusSlaveNatureTable(AccessMode accessMode) { return ModbusSlaveNatureTable.of(HybridEss.class, accessMode, 100) // .channel(0, ChannelId.DC_DISCHARGE_POWER, ModbusType.UINT16) // + .channel(1, ChannelId.DC_CHARGE_ENERGY, ModbusType.FLOAT64) // + .channel(5, ChannelId.DC_DISCHARGE_ENERGY, ModbusType.FLOAT64) // .build(); } } diff --git a/io.openems.edge.ess.api/src/io/openems/edge/ess/api/ManagedSymmetricEss.java b/io.openems.edge.ess.api/src/io/openems/edge/ess/api/ManagedSymmetricEss.java index 616c112b519..36565ec140f 100644 --- a/io.openems.edge.ess.api/src/io/openems/edge/ess/api/ManagedSymmetricEss.java +++ b/io.openems.edge.ess.api/src/io/openems/edge/ess/api/ManagedSymmetricEss.java @@ -223,7 +223,7 @@ public void accept(ManagedSymmetricEss ess, Integer value) throws OpenemsNamedEx * failed. * */ - APPLY_POWER_FAILED(Doc.of(Level.FAULT) // + APPLY_POWER_FAILED(Doc.of(Level.WARNING) // .persistencePriority(PersistencePriority.HIGH) // .text("Applying the Active/Reactive Power failed")); diff --git a/io.openems.edge.ess.api/src/io/openems/edge/ess/api/SymmetricEss.java b/io.openems.edge.ess.api/src/io/openems/edge/ess/api/SymmetricEss.java index 4c8c476ec44..4f14108fd5e 100644 --- a/io.openems.edge.ess.api/src/io/openems/edge/ess/api/SymmetricEss.java +++ b/io.openems.edge.ess.api/src/io/openems/edge/ess/api/SymmetricEss.java @@ -215,6 +215,7 @@ public static ModbusSlaveNatureTable getModbusSlaveNatureTable(AccessMode access .channel(8, ChannelId.MAX_CELL_VOLTAGE, ModbusType.FLOAT32) // .channel(10, ChannelId.MIN_CELL_TEMPERATURE, ModbusType.FLOAT32) // .channel(12, ChannelId.MAX_CELL_TEMPERATURE, ModbusType.FLOAT32) // + .channel(14, ChannelId.CAPACITY, ModbusType.FLOAT32) // .build(); } diff --git a/io.openems.edge.ess.api/src/io/openems/edge/ess/dccharger/api/EssDcCharger.java b/io.openems.edge.ess.api/src/io/openems/edge/ess/dccharger/api/EssDcCharger.java index 259bc248764..ab9a81dafcb 100644 --- a/io.openems.edge.ess.api/src/io/openems/edge/ess/dccharger/api/EssDcCharger.java +++ b/io.openems.edge.ess.api/src/io/openems/edge/ess/dccharger/api/EssDcCharger.java @@ -17,7 +17,6 @@ import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.modbusslave.ModbusSlaveNatureTable; import io.openems.edge.common.modbusslave.ModbusType; -import io.openems.edge.ess.api.SymmetricEss; @ProviderType public interface EssDcCharger extends OpenemsComponent { @@ -310,7 +309,7 @@ public default void _setCurrent(int value) { * @return the {@link ModbusSlaveNatureTable} */ public static ModbusSlaveNatureTable getModbusSlaveNatureTable(AccessMode accessMode) { - return ModbusSlaveNatureTable.of(SymmetricEss.class, accessMode, 100) // + return ModbusSlaveNatureTable.of(EssDcCharger.class, accessMode, 100) // .channel(0, ChannelId.ACTUAL_POWER, ModbusType.FLOAT32) // .channel(2, ChannelId.ACTUAL_ENERGY, ModbusType.FLOAT64) // .channel(6, ChannelId.VOLTAGE, ModbusType.FLOAT32) // diff --git a/io.openems.edge.ess.api/src/io/openems/edge/ess/power/api/Constraint.java b/io.openems.edge.ess.api/src/io/openems/edge/ess/power/api/Constraint.java index 0bf63c6a866..f9cce7a96a4 100644 --- a/io.openems.edge.ess.api/src/io/openems/edge/ess/power/api/Constraint.java +++ b/io.openems.edge.ess.api/src/io/openems/edge/ess/power/api/Constraint.java @@ -7,7 +7,6 @@ /** * Creates a constraint with following settings:. * - *

*

    *
  • Relationship (EQ, GEQ, LEQ) as given in constructor *
  • Value as given in constructor diff --git a/io.openems.edge.ess.api/src/io/openems/edge/ess/test/DummyManagedSymmetricEss.java b/io.openems.edge.ess.api/src/io/openems/edge/ess/test/DummyManagedSymmetricEss.java index 9e551325b15..7537651620b 100644 --- a/io.openems.edge.ess.api/src/io/openems/edge/ess/test/DummyManagedSymmetricEss.java +++ b/io.openems.edge.ess.api/src/io/openems/edge/ess/test/DummyManagedSymmetricEss.java @@ -1,5 +1,7 @@ package io.openems.edge.ess.test; +import static com.google.common.base.MoreObjects.toStringHelper; + import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.startstop.StartStoppable; import io.openems.edge.ess.api.ManagedSymmetricEss; @@ -24,4 +26,11 @@ public DummyManagedSymmetricEss(String id) { protected final DummyManagedSymmetricEss self() { return this; } + + @Override + public String toString() { + return toStringHelper(this) // + .add("id", this.id()) // + .toString(); + } } diff --git a/io.openems.edge.ess.byd.container/.classpath b/io.openems.edge.ess.byd.container/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.ess.byd.container/.classpath +++ b/io.openems.edge.ess.byd.container/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.ess.byd.container/test/io/openems/edge/ess/byd/container/EssFeneconBydContainerImplTest.java b/io.openems.edge.ess.byd.container/test/io/openems/edge/ess/byd/container/EssFeneconBydContainerImplTest.java index 841489bb5b7..5d1475d92ab 100644 --- a/io.openems.edge.ess.byd.container/test/io/openems/edge/ess/byd/container/EssFeneconBydContainerImplTest.java +++ b/io.openems.edge.ess.byd.container/test/io/openems/edge/ess/byd/container/EssFeneconBydContainerImplTest.java @@ -9,25 +9,20 @@ public class EssFeneconBydContainerImplTest { - private static final String ESS_ID = "ess0"; - private static final String MODBUS0_ID = "modbus0"; - private static final String MODBUS1_ID = "modbus1"; - private static final String MODBUS2_ID = "modbus2"; - @Test public void test() throws Exception { new ManagedSymmetricEssTest(new EssFeneconBydContainerImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("power", new DummyPower()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS0_ID)) // - .addReference("modbus1", new DummyModbusBridge(MODBUS1_ID)) // - .addReference("modbus2", new DummyModbusBridge(MODBUS2_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // + .addReference("modbus1", new DummyModbusBridge("modbus1")) // + .addReference("modbus2", new DummyModbusBridge("modbus2")) // .activate(MyConfig.create() // - .setId(ESS_ID) // + .setId("ess0") // .setReadonly(true) // - .setModbusId0(MODBUS0_ID) // - .setModbusId1(MODBUS1_ID) // - .setModbusId2(MODBUS2_ID) // + .setModbusId0("modbus0") // + .setModbusId1("modbus1") // + .setModbusId2("modbus2") // .build()) // ; } diff --git a/io.openems.edge.ess.byd.container/test/io/openems/edge/ess/byd/container/watchdog/EssFeneconBydContainerWatchdogControllerImplTest.java b/io.openems.edge.ess.byd.container/test/io/openems/edge/ess/byd/container/watchdog/EssFeneconBydContainerWatchdogControllerImplTest.java deleted file mode 100644 index 19d242a4257..00000000000 --- a/io.openems.edge.ess.byd.container/test/io/openems/edge/ess/byd/container/watchdog/EssFeneconBydContainerWatchdogControllerImplTest.java +++ /dev/null @@ -1,81 +0,0 @@ -package io.openems.edge.ess.byd.container.watchdog; - -import io.openems.edge.bridge.modbus.test.DummyModbusBridge; -import io.openems.edge.common.test.ComponentTest; -import io.openems.edge.common.test.DummyComponentManager; -import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.controller.test.ControllerTest; -import io.openems.edge.ess.byd.container.EssFeneconBydContainerImpl; -import io.openems.edge.ess.test.DummyPower; - -public class EssFeneconBydContainerWatchdogControllerImplTest { - - private static final String CTRL_ID = "ctrl0"; - // private final static ChannelAddress CTRL_WATCHDOG = new - // ChannelAddress(CTRL_ID, "Watchdog"); - - private static final String MODBUS0_ID = "modbus0"; - private static final String MODBUS1_ID = "modbus1"; - private static final String MODBUS2_ID = "modbus2"; - - private static final String ESS_ID = "ess0"; - // private final static ChannelAddress ESS_SET_ACTIVE_POWER_EQUALS = new - // ChannelAddress(ESS_ID, - // "SetActivePowerEquals"); - // private final static ChannelAddress ESS_SET_REACTIVE_POWER_EQUALS = new - // ChannelAddress(ESS_ID, - // "SetReactivePowerEquals"); - - // TODO requires fix by Pooran Chandrashekaraiah - // @Test - protected void test() throws Exception { - var ess = new EssFeneconBydContainerImpl(); - new ComponentTest(ess) // - .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("power", new DummyPower()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS0_ID)) // - .addReference("modbus1", new DummyModbusBridge(MODBUS1_ID)) // - .addReference("modbus2", new DummyModbusBridge(MODBUS2_ID)) // - .activate(MyEssConfig.create() // - .setId(ESS_ID) // - .setModbusId0(MODBUS0_ID) // - .setModbusId1(MODBUS1_ID) // - .setModbusId2(MODBUS2_ID) // - .build()); - - new ControllerTest(new EssFeneconBydContainerWatchdogControllerImpl()) // - .addReference("componentManager", new DummyComponentManager()) // - .addReference("cm", new DummyConfigurationAdmin()) // - .addComponent(ess) // - .activate(MyWatchdogConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // - .build()) - // .next(new TestCase()// - // .output(ESS_SET_ACTIVE_POWER_EQUALS, 0)// - // .output(ESS_SET_REACTIVE_POWER_EQUALS, 0))// - // .next(new TestCase() // - // .input(CTRL_WATCHDOG, 0)// - // .output(ESS_SET_ACTIVE_POWER_EQUALS, 0) // - // .output(ESS_SET_REACTIVE_POWER_EQUALS, 0))// - // .next(new TestCase() // - // .input(CTRL_WATCHDOG, 0).// - // output(CTRL_IS_TIMEOUT, 1))// - // .next(new TestCase() // - // .input(CTRL_WATCHDOG, 0)// - // .output(CTRL_IS_TIMEOUT, 1))// - // .next(new TestCase() // - // .input(CTRL_WATCHDOG, 0)// - // .output(CTRL_IS_TIMEOUT, 1)) - // .next(new TestCase() // - // .input(CTRL_WATCHDOG, 1)// - // .output(CTRL_IS_TIMEOUT, 0))// - // .next(new TestCase() // - // .output(CTRL_IS_TIMEOUT, 0)) - // .next(new TestCase() // - // .input(CTRL_WATCHDOG, 0)// - // .output(CTRL_IS_TIMEOUT, 1))// - ; - } - -} diff --git a/io.openems.edge.ess.byd.container/test/io/openems/edge/ess/byd/container/watchdog/MyEssConfig.java b/io.openems.edge.ess.byd.container/test/io/openems/edge/ess/byd/container/watchdog/MyEssConfig.java deleted file mode 100644 index fea2f40e694..00000000000 --- a/io.openems.edge.ess.byd.container/test/io/openems/edge/ess/byd/container/watchdog/MyEssConfig.java +++ /dev/null @@ -1,101 +0,0 @@ -package io.openems.edge.ess.byd.container.watchdog; - -import io.openems.common.test.AbstractComponentConfig; -import io.openems.common.utils.ConfigUtils; -import io.openems.edge.ess.byd.container.Config; - -@SuppressWarnings("all") -public class MyEssConfig extends AbstractComponentConfig implements Config { - - protected static class Builder { - private String id; - private boolean readonly; - private String modbusId0; - private String modbusId1; - private String modbusId2; - - private Builder() { - } - - public Builder setId(String id) { - this.id = id; - return this; - } - - public Builder setModbusId0(String modbusId0) { - this.modbusId0 = modbusId0; - return this; - } - - public Builder setModbusId1(String modbusId1) { - this.modbusId1 = modbusId1; - return this; - } - - public Builder setModbusId2(String modbusId2) { - this.modbusId2 = modbusId2; - return this; - } - - public Builder setReadonly(boolean readonly) { - this.readonly = readonly; - return this; - } - - public MyEssConfig build() { - return new MyEssConfig(this); - } - } - - /** - * Create a Config builder. - * - * @return a {@link Builder} - */ - public static Builder create() { - return new Builder(); - } - - private final Builder builder; - - private MyEssConfig(Builder builder) { - super(Config.class, builder.id); - this.builder = builder; - } - - @Override - public boolean readonly() { - return this.builder.readonly; - } - - @Override - public String modbus_id0() { - return this.builder.modbusId0; - } - - @Override - public String modbus_id1() { - return this.builder.modbusId1; - } - - @Override - public String modbus_id2() { - return this.builder.modbusId2; - } - - @Override - public String Modbus_target() { - return ConfigUtils.generateReferenceTargetFilter(this.id(), this.modbus_id0()); - } - - @Override - public String modbus1_target() { - return ConfigUtils.generateReferenceTargetFilter(this.id(), this.modbus_id1()); - } - - @Override - public String modbus2_target() { - return ConfigUtils.generateReferenceTargetFilter(this.id(), this.modbus_id2()); - } - -} diff --git a/io.openems.edge.ess.byd.container/test/io/openems/edge/ess/byd/container/watchdog/MyWatchdogConfig.java b/io.openems.edge.ess.byd.container/test/io/openems/edge/ess/byd/container/watchdog/MyWatchdogConfig.java deleted file mode 100644 index 90faa50d2fc..00000000000 --- a/io.openems.edge.ess.byd.container/test/io/openems/edge/ess/byd/container/watchdog/MyWatchdogConfig.java +++ /dev/null @@ -1,51 +0,0 @@ -package io.openems.edge.ess.byd.container.watchdog; - -import io.openems.common.test.AbstractComponentConfig; - -@SuppressWarnings("all") -public class MyWatchdogConfig extends AbstractComponentConfig implements Config { - - protected static class Builder { - private String id; - private String essId; - - private Builder() { - } - - public Builder setId(String id) { - this.id = id; - return this; - } - - public Builder setEssId(String essId) { - this.essId = essId; - return this; - } - - public MyWatchdogConfig build() { - return new MyWatchdogConfig(this); - } - } - - /** - * Create a Config builder. - * - * @return a {@link Builder} - */ - public static Builder create() { - return new Builder(); - } - - private final Builder builder; - - private MyWatchdogConfig(Builder builder) { - super(Config.class, builder.id); - this.builder = builder; - } - - @Override - public String ess_id() { - return this.builder.essId; - } - -} \ No newline at end of file diff --git a/io.openems.edge.ess.cluster/.classpath b/io.openems.edge.ess.cluster/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.ess.cluster/.classpath +++ b/io.openems.edge.ess.cluster/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.ess.cluster/test/io/openems/edge/ess/cluster/EssClusterImplTest.java b/io.openems.edge.ess.cluster/test/io/openems/edge/ess/cluster/EssClusterImplTest.java index bc0f208f1af..5a2e9ddf4ab 100644 --- a/io.openems.edge.ess.cluster/test/io/openems/edge/ess/cluster/EssClusterImplTest.java +++ b/io.openems.edge.ess.cluster/test/io/openems/edge/ess/cluster/EssClusterImplTest.java @@ -1,8 +1,17 @@ package io.openems.edge.ess.cluster; +import static io.openems.edge.common.startstop.StartStoppable.ChannelId.START_STOP; +import static io.openems.edge.ess.api.AsymmetricEss.ChannelId.ACTIVE_POWER_L1; +import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.ALLOWED_CHARGE_POWER; +import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.ALLOWED_DISCHARGE_POWER; +import static io.openems.edge.ess.api.SymmetricEss.ChannelId.ACTIVE_CHARGE_ENERGY; +import static io.openems.edge.ess.api.SymmetricEss.ChannelId.ACTIVE_POWER; +import static io.openems.edge.ess.api.SymmetricEss.ChannelId.GRID_MODE; +import static io.openems.edge.ess.api.SymmetricEss.ChannelId.REACTIVE_POWER; +import static io.openems.edge.ess.api.SymmetricEss.ChannelId.SOC; + import org.junit.Test; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.startstop.StartStop; import io.openems.edge.common.startstop.StartStopConfig; import io.openems.edge.common.sum.GridMode; @@ -15,76 +24,39 @@ public class EssClusterImplTest { - private static final String CLUSTER_ID = "ess0"; - private static final String ESS1_ID = "ess1"; - private static final String ESS2_ID = "ess2"; - private static final String ESS3_ID = "ess3"; - private static final ChannelAddress CLUSTER_GRID_MODE = new ChannelAddress(CLUSTER_ID, "GridMode"); - private static final ChannelAddress ESS1_GRID_MODE = new ChannelAddress(ESS1_ID, "GridMode"); - private static final ChannelAddress ESS2_GRID_MODE = new ChannelAddress(ESS2_ID, "GridMode"); - private static final ChannelAddress ESS3_GRID_MODE = new ChannelAddress(ESS3_ID, "GridMode"); - private static final ChannelAddress CLUSTER_SOC = new ChannelAddress(CLUSTER_ID, "Soc"); - private static final ChannelAddress ESS1_SOC = new ChannelAddress(ESS1_ID, "Soc"); - private static final ChannelAddress ESS2_SOC = new ChannelAddress(ESS2_ID, "Soc"); - private static final ChannelAddress CLUSTER_ACTIVE_POWER = new ChannelAddress(CLUSTER_ID, "ActivePower"); - private static final ChannelAddress ESS1_ACTIVE_POWER = new ChannelAddress(ESS1_ID, "ActivePower"); - private static final ChannelAddress ESS2_ACTIVE_POWER = new ChannelAddress(ESS2_ID, "ActivePower"); - private static final ChannelAddress CLUSTER_REACTIVE_POWER = new ChannelAddress(CLUSTER_ID, "ReactivePower"); - private static final ChannelAddress ESS1_REACTIVE_POWER = new ChannelAddress(ESS1_ID, "ReactivePower"); - private static final ChannelAddress ESS2_REACTIVE_POWER = new ChannelAddress(ESS2_ID, "ReactivePower"); - private static final ChannelAddress CLUSTER_ACTIVE_POWER_L1 = new ChannelAddress(CLUSTER_ID, "ActivePowerL1"); - private static final ChannelAddress ESS2_ACTIVE_POWER_L1 = new ChannelAddress(ESS2_ID, "ActivePowerL1"); - private static final ChannelAddress CLUSTER_ACTIVE_CHARGE_ENERGY = new ChannelAddress(CLUSTER_ID, - "ActiveChargeEnergy"); - private static final ChannelAddress ESS1_ACTIVE_CHARGE_ENERGY = new ChannelAddress(ESS1_ID, "ActiveChargeEnergy"); - private static final ChannelAddress ESS2_ACTIVE_CHARGE_ENERGY = new ChannelAddress(ESS2_ID, "ActiveChargeEnergy"); - private static final ChannelAddress CLUSTER_ALLOWED_CHARGE_POWER = new ChannelAddress(CLUSTER_ID, - "AllowedChargePower"); - private static final ChannelAddress ESS1_ALLOWED_CHARGE_POWER = new ChannelAddress(ESS1_ID, "AllowedChargePower"); - private static final ChannelAddress ESS2_ALLOWED_CHARGE_POWER = new ChannelAddress(ESS2_ID, "AllowedChargePower"); - private static final ChannelAddress CLUSTER_ALLOWED_DISCHARGE_POWER = new ChannelAddress(CLUSTER_ID, - "AllowedDischargePower"); - private static final ChannelAddress ESS1_ALLOWED_DISCHARGE_POWER = new ChannelAddress(ESS1_ID, - "AllowedDischargePower"); - private static final ChannelAddress ESS2_ALLOWED_DISCHARGE_POWER = new ChannelAddress(ESS2_ID, - "AllowedDischargePower"); - private static final ChannelAddress CLUSTER_START_STOP = new ChannelAddress(CLUSTER_ID, "StartStop"); - private static final ChannelAddress ESS1_START_STOP = new ChannelAddress(ESS1_ID, "StartStop"); - private static final ChannelAddress ESS2_START_STOP = new ChannelAddress(ESS2_ID, "StartStop"); - @Test public void testCluster() throws Exception { new ComponentTest(new EssClusterImpl()) // .addReference("power", new DummyPower()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("addEss", new DummyManagedSymmetricEss(ESS1_ID)) // - .addReference("addEss", new DummyManagedAsymmetricEss(ESS2_ID)) // + .addReference("addEss", new DummyManagedSymmetricEss("ess1")) // + .addReference("addEss", new DummyManagedAsymmetricEss("ess2")) // .activate(MyConfig.create() // - .setId(CLUSTER_ID) // - .setEssIds(ESS1_ID, ESS2_ID) // + .setId("ess0") // + .setEssIds("ess1", "ess2") // .setStartStop(StartStopConfig.START) // .build()) .next(new TestCase() // - .input(ESS1_GRID_MODE, GridMode.ON_GRID) // - .input(ESS2_GRID_MODE, GridMode.ON_GRID) // - .output(CLUSTER_GRID_MODE, GridMode.ON_GRID) // - .input(ESS1_ACTIVE_POWER, 1234) // - .input(ESS2_ACTIVE_POWER, 9876) // - .output(CLUSTER_ACTIVE_POWER, 11110) // - .input(ESS1_REACTIVE_POWER, 1111) // - .input(ESS2_REACTIVE_POWER, 2222) // - .output(CLUSTER_REACTIVE_POWER, 3333) // - .input(ESS1_ACTIVE_CHARGE_ENERGY, 1) // - .input(ESS2_ACTIVE_CHARGE_ENERGY, 2) // - .output(CLUSTER_ACTIVE_CHARGE_ENERGY, 3L) // - .input(ESS2_ACTIVE_POWER_L1, 1111) // - .output(CLUSTER_ACTIVE_POWER_L1, 1234 / 3 + 1111) // - .input(ESS1_ALLOWED_CHARGE_POWER, 11) // - .input(ESS2_ALLOWED_CHARGE_POWER, 22) // - .output(CLUSTER_ALLOWED_CHARGE_POWER, 33) // - .input(ESS1_ALLOWED_DISCHARGE_POWER, 10) // - .input(ESS2_ALLOWED_DISCHARGE_POWER, 20) // - .output(CLUSTER_ALLOWED_DISCHARGE_POWER, 30) // + .input("ess1", GRID_MODE, GridMode.ON_GRID) // + .input("ess2", GRID_MODE, GridMode.ON_GRID) // + .output(GRID_MODE, GridMode.ON_GRID) // + .input("ess1", ACTIVE_POWER, 1234) // + .input("ess2", ACTIVE_POWER, 9876) // + .output(ACTIVE_POWER, 11110) // + .input("ess1", REACTIVE_POWER, 1111) // + .input("ess2", REACTIVE_POWER, 2222) // + .output(REACTIVE_POWER, 3333) // + .input("ess1", ACTIVE_CHARGE_ENERGY, 1) // + .input("ess2", ACTIVE_CHARGE_ENERGY, 2) // + .output(ACTIVE_CHARGE_ENERGY, 3L) // + .input("ess2", ACTIVE_POWER_L1, 1111) // + .output(ACTIVE_POWER_L1, 1234 / 3 + 1111) // + .input("ess1", ALLOWED_CHARGE_POWER, 11) // + .input("ess2", ALLOWED_CHARGE_POWER, 22) // + .output(ALLOWED_CHARGE_POWER, 33) // + .input("ess1", ALLOWED_DISCHARGE_POWER, 10) // + .input("ess2", ALLOWED_DISCHARGE_POWER, 20) // + .output(ALLOWED_DISCHARGE_POWER, 30) // ); } @@ -93,31 +65,31 @@ public void testGridMode() throws Exception { new ComponentTest(new EssClusterImpl()) // .addReference("power", new DummyPower()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("addEss", new DummyManagedSymmetricEss(ESS1_ID)) // - .addReference("addEss", new DummyManagedSymmetricEss(ESS2_ID)) // - .addReference("addEss", new DummyManagedSymmetricEss(ESS3_ID)) // + .addReference("addEss", new DummyManagedSymmetricEss("ess1")) // + .addReference("addEss", new DummyManagedSymmetricEss("ess2")) // + .addReference("addEss", new DummyManagedSymmetricEss("ess3")) // .activate(MyConfig.create() // - .setId(CLUSTER_ID) // - .setEssIds(ESS1_ID, ESS2_ID, ESS3_ID) // + .setId("ess0") // + .setEssIds("ess1", "ess2", "ess3") // .setStartStop(StartStopConfig.START) // .build()) .next(new TestCase() // - .input(ESS1_GRID_MODE, GridMode.ON_GRID) // - .input(ESS2_GRID_MODE, GridMode.ON_GRID) // - .input(ESS3_GRID_MODE, GridMode.ON_GRID) // - .output(CLUSTER_GRID_MODE, GridMode.ON_GRID) // + .input("ess1", GRID_MODE, GridMode.ON_GRID) // + .input("ess2", GRID_MODE, GridMode.ON_GRID) // + .input("ess3", GRID_MODE, GridMode.ON_GRID) // + .output(GRID_MODE, GridMode.ON_GRID) // ) // .next(new TestCase() // - .input(ESS1_GRID_MODE, GridMode.OFF_GRID) // - .input(ESS2_GRID_MODE, GridMode.OFF_GRID) // - .input(ESS3_GRID_MODE, GridMode.OFF_GRID) // - .output(CLUSTER_GRID_MODE, GridMode.OFF_GRID) // + .input("ess1", GRID_MODE, GridMode.OFF_GRID) // + .input("ess2", GRID_MODE, GridMode.OFF_GRID) // + .input("ess3", GRID_MODE, GridMode.OFF_GRID) // + .output(GRID_MODE, GridMode.OFF_GRID) // ) // .next(new TestCase() // - .input(ESS1_GRID_MODE, GridMode.OFF_GRID) // - .input(ESS2_GRID_MODE, GridMode.OFF_GRID) // - .input(ESS3_GRID_MODE, GridMode.UNDEFINED) // - .output(CLUSTER_GRID_MODE, GridMode.UNDEFINED) // + .input("ess1", GRID_MODE, GridMode.OFF_GRID) // + .input("ess2", GRID_MODE, GridMode.OFF_GRID) // + .input("ess3", GRID_MODE, GridMode.UNDEFINED) // + .output(GRID_MODE, GridMode.UNDEFINED) // ) // ; } @@ -127,25 +99,25 @@ public void testSoc() throws Exception { new ComponentTest(new EssClusterImpl()) // .addReference("power", new DummyPower()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("addEss", new DummyManagedSymmetricEss(ESS1_ID).withCapacity(50000)) // - .addReference("addEss", new DummyManagedSymmetricEss(ESS2_ID).withCapacity(3000)) // + .addReference("addEss", new DummyManagedSymmetricEss("ess1").withCapacity(50000)) // + .addReference("addEss", new DummyManagedSymmetricEss("ess2").withCapacity(3000)) // .activate(MyConfig.create() // - .setId(CLUSTER_ID) // - .setEssIds(ESS1_ID, ESS2_ID) // + .setId("ess0") // + .setEssIds("ess1", "ess2") // .setStartStop(StartStopConfig.START) // .build()) .next(new TestCase() // - .input(ESS1_SOC, 20) // - .input(ESS2_SOC, 90) // - .output(CLUSTER_SOC, 24) // + .input("ess1", SOC, 20) // + .input("ess2", SOC, 90) // + .output(SOC, 24) // ) // .next(new TestCase() // - .input(ESS1_SOC, 21) // - .output(CLUSTER_SOC, 25) // + .input("ess1", SOC, 21) // + .output(SOC, 25) // ) // .next(new TestCase() // - .input(ESS1_SOC, 100) // - .output(CLUSTER_SOC, 99) // + .input("ess1", SOC, 100) // + .output(SOC, 99) // ) // ; } @@ -155,29 +127,29 @@ public void testStartStop() throws Exception { new ComponentTest(new EssClusterImpl()) // .addReference("power", new DummyPower()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("addEss", new DummyManagedSymmetricEss(ESS1_ID)) // - .addReference("addEss", new DummyManagedSymmetricEss(ESS2_ID)) // + .addReference("addEss", new DummyManagedSymmetricEss("ess1")) // + .addReference("addEss", new DummyManagedSymmetricEss("ess2")) // .activate(MyConfig.create() // - .setId(CLUSTER_ID) // - .setEssIds(ESS1_ID, ESS2_ID) // + .setId("ess0") // + .setEssIds("ess1", "ess2") // .setStartStop(StartStopConfig.START) // .build()) .next(new TestCase() // - .input(ESS1_START_STOP, StartStop.UNDEFINED) // - .input(ESS2_START_STOP, StartStop.STOP) // - .output(CLUSTER_START_STOP, StartStop.UNDEFINED)) // + .input("ess1", START_STOP, StartStop.UNDEFINED) // + .input("ess2", START_STOP, StartStop.STOP) // + .output(START_STOP, StartStop.UNDEFINED)) // .next(new TestCase() // - .input(ESS1_START_STOP, StartStop.STOP) // - .input(ESS2_START_STOP, StartStop.STOP) // - .output(CLUSTER_START_STOP, StartStop.STOP)) // + .input("ess1", START_STOP, StartStop.STOP) // + .input("ess2", START_STOP, StartStop.STOP) // + .output(START_STOP, StartStop.STOP)) // .next(new TestCase() // - .input(ESS1_START_STOP, StartStop.START) // - .input(ESS2_START_STOP, StartStop.STOP) // - .output(CLUSTER_START_STOP, StartStop.UNDEFINED)) // + .input("ess1", START_STOP, StartStop.START) // + .input("ess2", START_STOP, StartStop.STOP) // + .output(START_STOP, StartStop.UNDEFINED)) // .next(new TestCase() // - .input(ESS1_START_STOP, StartStop.START) // - .input(ESS2_START_STOP, StartStop.START) // - .output(CLUSTER_START_STOP, StartStop.START)) // + .input("ess1", START_STOP, StartStop.START) // + .input("ess2", START_STOP, StartStop.START) // + .output(START_STOP, StartStop.START)) // ; } diff --git a/io.openems.edge.ess.core/.classpath b/io.openems.edge.ess.core/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.ess.core/.classpath +++ b/io.openems.edge.ess.core/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.ess.core/src/io/openems/edge/ess/core/power/Data.java b/io.openems.edge.ess.core/src/io/openems/edge/ess/core/power/Data.java index f0d1830e97b..8b99b973c60 100644 --- a/io.openems.edge.ess.core/src/io/openems/edge/ess/core/power/Data.java +++ b/io.openems.edge.ess.core/src/io/openems/edge/ess/core/power/Data.java @@ -30,12 +30,12 @@ public class Data { /** * Holds all Inverters, always roughly sorted by weight. */ - private final List inverters = new ArrayList<>(); + private final List inverters = new CopyOnWriteArrayList<>(); /** * Holds all Ess. */ - private final List esss = new ArrayList<>(); + private final List esss = new CopyOnWriteArrayList<>(); private final List constraints = new CopyOnWriteArrayList<>(); private final Coefficients coefficients = new Coefficients(); diff --git a/io.openems.edge.ess.core/src/io/openems/edge/ess/core/power/EssPowerImpl.java b/io.openems.edge.ess.core/src/io/openems/edge/ess/core/power/EssPowerImpl.java index 3f8a079d10f..3cea0e39de1 100644 --- a/io.openems.edge.ess.core/src/io/openems/edge/ess/core/power/EssPowerImpl.java +++ b/io.openems.edge.ess.core/src/io/openems/edge/ess/core/power/EssPowerImpl.java @@ -216,13 +216,19 @@ private int getActivePowerExtrema(ManagedSymmetricEss ess, Phase phase, Pwr pwr, @Override public void handleEvent(Event event) { - switch (event.getTopic()) { - case EdgeEventConstants.TOPIC_CYCLE_BEFORE_WRITE: - this.solver.solve(this.config.strategy()); - break; - case EdgeEventConstants.TOPIC_CYCLE_AFTER_WRITE: - this.data.initializeCycle(); - break; + try { + switch (event.getTopic()) { + case EdgeEventConstants.TOPIC_CYCLE_BEFORE_WRITE // + -> this.solver.solve(this.config.strategy()); + + case EdgeEventConstants.TOPIC_CYCLE_AFTER_WRITE // + -> this.data.initializeCycle(); + } + + } catch (Exception e) { + this.logError(this.log, + "Error during handleEvent(). " + e.getClass().getSimpleName() + ": " + e.getMessage()); + e.printStackTrace(); } } diff --git a/io.openems.edge.ess.core/src/io/openems/edge/ess/core/power/data/LinearSolverUtil.java b/io.openems.edge.ess.core/src/io/openems/edge/ess/core/power/data/LinearSolverUtil.java index 3bc672205eb..12d5182f1b9 100644 --- a/io.openems.edge.ess.core/src/io/openems/edge/ess/core/power/data/LinearSolverUtil.java +++ b/io.openems.edge.ess.core/src/io/openems/edge/ess/core/power/data/LinearSolverUtil.java @@ -1,5 +1,9 @@ package io.openems.edge.ess.core.power.data; +import static org.apache.commons.math3.optim.linear.Relationship.EQ; +import static org.apache.commons.math3.optim.linear.Relationship.GEQ; +import static org.apache.commons.math3.optim.linear.Relationship.LEQ; + import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -9,7 +13,6 @@ import io.openems.edge.ess.power.api.Coefficients; import io.openems.edge.ess.power.api.Constraint; -import io.openems.edge.ess.power.api.LinearCoefficient; public class LinearSolverUtil { @@ -22,28 +25,29 @@ public class LinearSolverUtil { */ public static List convertToLinearConstraints(Coefficients coefficients, List constraints) { - List result = new ArrayList<>(); + final var result = new ArrayList(); for (Constraint c : constraints) { - if (c.getValue().isPresent()) { - var cos = generateEmptyCoefficientsArray(coefficients.getNoOfCoefficients()); - for (LinearCoefficient co : c.getCoefficients()) { - // TODO verify, that ESS is enabled - cos[co.getCoefficient().getIndex()] = co.getValue(); - } - org.apache.commons.math3.optim.linear.Relationship relationship = null; - switch (c.getRelationship()) { - case EQUALS: - relationship = org.apache.commons.math3.optim.linear.Relationship.EQ; - break; - case GREATER_OR_EQUALS: - relationship = org.apache.commons.math3.optim.linear.Relationship.GEQ; - break; - case LESS_OR_EQUALS: - relationship = org.apache.commons.math3.optim.linear.Relationship.LEQ; - break; + final var value = c.getValue(); + if (value.isEmpty()) { + continue; + } + + final var cos = generateEmptyCoefficientsArray(coefficients.getNoOfCoefficients()); + for (var co : c.getCoefficients()) { + var index = co.getCoefficient().getIndex(); + if (index >= cos.length) { // check for race conditions + continue; } - result.add(new LinearConstraint(cos, relationship, c.getValue().get())); + cos[index] = co.getValue(); } + + final var relationship = switch (c.getRelationship()) { + case EQUALS -> EQ; + case GREATER_OR_EQUALS -> GEQ; + case LESS_OR_EQUALS -> LEQ; + }; + + result.add(new LinearConstraint(cos, relationship, value.get())); } return result; } diff --git a/io.openems.edge.ess.core/test/io/openems/edge/ess/core/power/NearEqualSolverTest.java b/io.openems.edge.ess.core/test/io/openems/edge/ess/core/power/NearEqualSolverTest.java index 9766ada10cf..2e9a0b3446b 100644 --- a/io.openems.edge.ess.core/test/io/openems/edge/ess/core/power/NearEqualSolverTest.java +++ b/io.openems.edge.ess.core/test/io/openems/edge/ess/core/power/NearEqualSolverTest.java @@ -8,7 +8,6 @@ public class NearEqualSolverTest { @Test public void solverTest() { - double[] essUpperLimit = { 10000, 10000, 10000, 1900 }; double[] essLowerLimit = { 0, 0, 0, 1800 }; double setValue = 50000; diff --git a/io.openems.edge.ess.core/test/io/openems/edge/ess/core/power/PowerComponentTest.java b/io.openems.edge.ess.core/test/io/openems/edge/ess/core/power/PowerComponentTest.java index 75baf32bbc0..0e5e0282eaf 100644 --- a/io.openems.edge.ess.core/test/io/openems/edge/ess/core/power/PowerComponentTest.java +++ b/io.openems.edge.ess.core/test/io/openems/edge/ess/core/power/PowerComponentTest.java @@ -1,5 +1,11 @@ package io.openems.edge.ess.core.power; +import static io.openems.edge.ess.power.api.Pwr.ACTIVE; +import static io.openems.edge.ess.power.api.Pwr.REACTIVE; +import static io.openems.edge.ess.power.api.Relationship.EQUALS; +import static io.openems.edge.ess.power.api.Relationship.LESS_OR_EQUALS; +import static io.openems.edge.ess.power.api.SolverStrategy.OPTIMIZE_BY_KEEPING_ALL_EQUAL; +import static io.openems.edge.ess.power.api.SolverStrategy.OPTIMIZE_BY_MOVING_TOWARDS_TARGET; import static org.junit.Assert.assertEquals; import java.util.concurrent.atomic.AtomicInteger; @@ -12,9 +18,6 @@ import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; import io.openems.edge.ess.power.api.Phase; -import io.openems.edge.ess.power.api.Pwr; -import io.openems.edge.ess.power.api.Relationship; -import io.openems.edge.ess.power.api.SolverStrategy; import io.openems.edge.ess.test.DummyManagedAsymmetricEss; import io.openems.edge.ess.test.DummyManagedSymmetricEss; import io.openems.edge.ess.test.DummyMetaEss; @@ -50,15 +53,15 @@ public void testSymmetricEss() throws Exception { .addReference("cm", cm) // .addReference("addEss", ess0) // .activate(MyConfig.create() // - .setStrategy(SolverStrategy.OPTIMIZE_BY_MOVING_TOWARDS_TARGET) // + .setStrategy(OPTIMIZE_BY_MOVING_TOWARDS_TARGET) // .setSymmetricMode(true) // .setDebugMode(false) // .setEnablePid(false) // .build()); // expect("#10", ess0, 5000, 3000); - ess0.addPowerConstraint("", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, 5000); - ess0.addPowerConstraint("", Phase.ALL, Pwr.REACTIVE, Relationship.EQUALS, 3000); + ess0.addPowerConstraint("", Phase.ALL, ACTIVE, EQUALS, 5000); + ess0.addPowerConstraint("", Phase.ALL, REACTIVE, EQUALS, 3000); componentTest.next(new TestCase()); } @@ -79,19 +82,19 @@ public void testAsymmetricEss() throws Exception { .addReference("cm", cm) // .addReference("addEss", ess0) // .activate(MyConfig.create() // - .setStrategy(SolverStrategy.OPTIMIZE_BY_MOVING_TOWARDS_TARGET) // + .setStrategy(OPTIMIZE_BY_MOVING_TOWARDS_TARGET) // .setSymmetricMode(false) // .setDebugMode(false) // .setEnablePid(false) // .build()); // expect("#1", ess0, 5000, 3333, 5000, 3333, 5000, 3334); - ess0.addPowerConstraint("", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, 15000); - ess0.addPowerConstraint("", Phase.ALL, Pwr.REACTIVE, Relationship.EQUALS, 10000); - ess0.addPowerConstraint("", Phase.L1, Pwr.ACTIVE, Relationship.EQUALS, 5000); - ess0.addPowerConstraint("", Phase.L1, Pwr.REACTIVE, Relationship.EQUALS, 3333); - ess0.addPowerConstraint("", Phase.L2, Pwr.ACTIVE, Relationship.EQUALS, 5000); - ess0.addPowerConstraint("", Phase.L2, Pwr.REACTIVE, Relationship.EQUALS, 3333); + ess0.addPowerConstraint("", Phase.ALL, ACTIVE, EQUALS, 15000); + ess0.addPowerConstraint("", Phase.ALL, REACTIVE, EQUALS, 10000); + ess0.addPowerConstraint("", Phase.L1, ACTIVE, EQUALS, 5000); + ess0.addPowerConstraint("", Phase.L1, REACTIVE, EQUALS, 3333); + ess0.addPowerConstraint("", Phase.L2, ACTIVE, EQUALS, 5000); + ess0.addPowerConstraint("", Phase.L2, REACTIVE, EQUALS, 3333); componentTest.next(new TestCase()); } @@ -112,15 +115,15 @@ public void testAsymmetricEssAllEqual() throws Exception { .addReference("cm", cm) // .addReference("addEss", ess0) // .activate(MyConfig.create() // - .setStrategy(SolverStrategy.OPTIMIZE_BY_KEEPING_ALL_EQUAL) // + .setStrategy(OPTIMIZE_BY_KEEPING_ALL_EQUAL) // .setSymmetricMode(false) // .setDebugMode(false) // .setEnablePid(false) // .build()); // expect("#1", ess0, 5000, 3000, 5000, 3000, 5000, 3000); - ess0.addPowerConstraint("", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, 15000); - ess0.addPowerConstraint("", Phase.ALL, Pwr.REACTIVE, Relationship.EQUALS, 9000); + ess0.addPowerConstraint("", Phase.ALL, ACTIVE, EQUALS, 15000); + ess0.addPowerConstraint("", Phase.ALL, REACTIVE, EQUALS, 9000); componentTest.next(new TestCase()); } @@ -151,7 +154,7 @@ public void testCluster() throws Exception { .addReference("addEss", ess1) // .addReference("addEss", ess2) // .activate(MyConfig.create() // - .setStrategy(SolverStrategy.OPTIMIZE_BY_MOVING_TOWARDS_TARGET) // + .setStrategy(OPTIMIZE_BY_MOVING_TOWARDS_TARGET) // .setSymmetricMode(true) // .setDebugMode(false) // .setEnablePid(false) // @@ -160,72 +163,72 @@ public void testCluster() throws Exception { // #1 expect("#1", ess1, -5000, -3000); expect("#1", ess2, -0, 0); - ess0.addPowerConstraint("#1", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, -5000); - ess0.addPowerConstraint("#1", Phase.ALL, Pwr.REACTIVE, Relationship.EQUALS, -3000); + ess0.addPowerConstraint("#1", Phase.ALL, ACTIVE, EQUALS, -5000); + ess0.addPowerConstraint("#1", Phase.ALL, REACTIVE, EQUALS, -3000); ess1.withSoc(80); // this is for test #2 componentTest.next(new TestCase("#1")); // #2 expect("#2", ess1, -4697, -2818); expect("#2", ess2, -302, -181); - ess0.addPowerConstraint("#2", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, -5000); - ess0.addPowerConstraint("#2", Phase.ALL, Pwr.REACTIVE, Relationship.EQUALS, -3000); + ess0.addPowerConstraint("#2", Phase.ALL, ACTIVE, EQUALS, -5000); + ess0.addPowerConstraint("#2", Phase.ALL, REACTIVE, EQUALS, -3000); componentTest.next(new TestCase("#2")); // #3 expect("#3", ess1, -4429, -2657); expect("#3", ess2, -570, -342); - ess0.addPowerConstraint("#3", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, -5000); - ess0.addPowerConstraint("#3", Phase.ALL, Pwr.REACTIVE, Relationship.EQUALS, -3000); + ess0.addPowerConstraint("#3", Phase.ALL, ACTIVE, EQUALS, -5000); + ess0.addPowerConstraint("#3", Phase.ALL, REACTIVE, EQUALS, -3000); componentTest.next(new TestCase("#3")); // #4 expect("#4", ess1, -4190, -2514); expect("#4", ess2, -809, -485); - ess0.addPowerConstraint("#4", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, -5000); - ess0.addPowerConstraint("#4", Phase.ALL, Pwr.REACTIVE, Relationship.EQUALS, -3000); + ess0.addPowerConstraint("#4", Phase.ALL, ACTIVE, EQUALS, -5000); + ess0.addPowerConstraint("#4", Phase.ALL, REACTIVE, EQUALS, -3000); componentTest.next(new TestCase("#4")); // #5 expect("#5", ess1, -3976, -2385); expect("#5", ess2, -1023, -614); - ess0.addPowerConstraint("#5", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, -5000); - ess0.addPowerConstraint("#5", Phase.ALL, Pwr.REACTIVE, Relationship.EQUALS, -3000); + ess0.addPowerConstraint("#5", Phase.ALL, ACTIVE, EQUALS, -5000); + ess0.addPowerConstraint("#5", Phase.ALL, REACTIVE, EQUALS, -3000); componentTest.next(new TestCase("#5")); // #6 expect("#6", ess1, -3782, -2269); expect("#6", ess2, -1217, -730); - ess0.addPowerConstraint("#6", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, -5000); - ess0.addPowerConstraint("#6", Phase.ALL, Pwr.REACTIVE, Relationship.EQUALS, -3000); + ess0.addPowerConstraint("#6", Phase.ALL, ACTIVE, EQUALS, -5000); + ess0.addPowerConstraint("#6", Phase.ALL, REACTIVE, EQUALS, -3000); componentTest.next(new TestCase("#6")); // #7 expect("#7", ess1, -3606, -2164); expect("#7", ess2, -1393, -835); - ess0.addPowerConstraint("#7", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, -5000); - ess0.addPowerConstraint("#7", Phase.ALL, Pwr.REACTIVE, Relationship.EQUALS, -3000); + ess0.addPowerConstraint("#7", Phase.ALL, ACTIVE, EQUALS, -5000); + ess0.addPowerConstraint("#7", Phase.ALL, REACTIVE, EQUALS, -3000); componentTest.next(new TestCase("#7")); // #8 expect("#8", ess1, -3446, -2067); expect("#8", ess2, -1553, -932); - ess0.addPowerConstraint("#8", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, -5000); - ess0.addPowerConstraint("#8", Phase.ALL, Pwr.REACTIVE, Relationship.EQUALS, -3000); + ess0.addPowerConstraint("#8", Phase.ALL, ACTIVE, EQUALS, -5000); + ess0.addPowerConstraint("#8", Phase.ALL, REACTIVE, EQUALS, -3000); componentTest.next(new TestCase("#8")); // #9 expect("#9", ess1, -3300, -1980); expect("#9", ess2, -1699, -1019); - ess0.addPowerConstraint("#9", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, -5000); - ess0.addPowerConstraint("#9", Phase.ALL, Pwr.REACTIVE, Relationship.EQUALS, -3000); + ess0.addPowerConstraint("#9", Phase.ALL, ACTIVE, EQUALS, -5000); + ess0.addPowerConstraint("#9", Phase.ALL, REACTIVE, EQUALS, -3000); componentTest.next(new TestCase("#9")); // #10 expect("#10", ess1, -3165, -1899); expect("#10", ess2, -1834, -1100); - ess0.addPowerConstraint("#10", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, -5000); - ess0.addPowerConstraint("#10", Phase.ALL, Pwr.REACTIVE, Relationship.EQUALS, -3000); + ess0.addPowerConstraint("#10", Phase.ALL, ACTIVE, EQUALS, -5000); + ess0.addPowerConstraint("#10", Phase.ALL, REACTIVE, EQUALS, -3000); componentTest.next(new TestCase("#10")); ess1.withSymmetricApplyPowerCallback(null); @@ -244,8 +247,8 @@ public void testCluster() throws Exception { // #20 expect("#20", ess1, -0, 0); expect("#20", ess2, -5000, -3000); - ess0.addPowerConstraint("#20", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, -5000); - ess0.addPowerConstraint("#20", Phase.ALL, Pwr.REACTIVE, Relationship.EQUALS, -3000); + ess0.addPowerConstraint("#20", Phase.ALL, ACTIVE, EQUALS, -5000); + ess0.addPowerConstraint("#20", Phase.ALL, REACTIVE, EQUALS, -3000); componentTest.next(new TestCase("#20")); } @@ -304,7 +307,7 @@ public void testStrSctr() throws Exception { .addReference("addEss", ess5) // .addReference("addEss", ess6) // .activate(MyConfig.create() // - .setStrategy(SolverStrategy.OPTIMIZE_BY_MOVING_TOWARDS_TARGET) // + .setStrategy(OPTIMIZE_BY_MOVING_TOWARDS_TARGET) // .setSymmetricMode(true) // .setDebugMode(false) // .setEnablePid(false) // @@ -317,8 +320,8 @@ public void testStrSctr() throws Exception { expect("#1", ess4, 0, 0); expect("#1", ess5, 10062, 0); // largest SoC expect("#1", ess6, 9986, 0); // second largest SoC - ess0.addPowerConstraint("#1", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, 30000); - ess0.addPowerConstraint("#1", Phase.ALL, Pwr.REACTIVE, Relationship.EQUALS, 0); + ess0.addPowerConstraint("#1", Phase.ALL, ACTIVE, EQUALS, 30000); + ess0.addPowerConstraint("#1", Phase.ALL, REACTIVE, EQUALS, 0); componentTest.next(new TestCase("#1")); // #2 @@ -328,8 +331,8 @@ public void testStrSctr() throws Exception { expect("#2", ess4, 0, 0); expect("#2", ess5, 8435, 5061); // largest SoC expect("#2", ess6, 8310, 4986); // second largest SoC - ess0.addPowerConstraint("#2", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, 25000); - ess0.addPowerConstraint("#2", Phase.ALL, Pwr.REACTIVE, Relationship.EQUALS, 15000); + ess0.addPowerConstraint("#2", Phase.ALL, ACTIVE, EQUALS, 25000); + ess0.addPowerConstraint("#2", Phase.ALL, REACTIVE, EQUALS, 15000); componentTest.next(new TestCase("#2")); // #3 @@ -339,8 +342,8 @@ public void testStrSctr() throws Exception { expect("#3", ess4, 0, 0); expect("#3", ess5, 1723, 689); // largest SoC expect("#3", ess6, 1644, 658); // second largest SoC - ess0.addPowerConstraint("#3", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, 5000); - ess0.addPowerConstraint("#3", Phase.ALL, Pwr.REACTIVE, Relationship.EQUALS, 2000); + ess0.addPowerConstraint("#3", Phase.ALL, ACTIVE, EQUALS, 5000); + ess0.addPowerConstraint("#3", Phase.ALL, REACTIVE, EQUALS, 2000); componentTest.next(new TestCase("#3")); // #4 not strictly defined force charge @@ -350,18 +353,18 @@ public void testStrSctr() throws Exception { expect("#4", ess4, -2000, -1000); expect("#4", ess5, -2000, -1000); // largest SoC expect("#4", ess6, -2000, -1000); // second largest SoC - ess1.addPowerConstraint("#4", Phase.ALL, Pwr.ACTIVE, Relationship.LESS_OR_EQUALS, -2000); - ess2.addPowerConstraint("#4", Phase.ALL, Pwr.ACTIVE, Relationship.LESS_OR_EQUALS, -2000); - ess3.addPowerConstraint("#4", Phase.ALL, Pwr.ACTIVE, Relationship.LESS_OR_EQUALS, -2000); - ess4.addPowerConstraint("#4", Phase.ALL, Pwr.ACTIVE, Relationship.LESS_OR_EQUALS, -2000); - ess5.addPowerConstraint("#4", Phase.ALL, Pwr.ACTIVE, Relationship.LESS_OR_EQUALS, -2000); - ess6.addPowerConstraint("#4", Phase.ALL, Pwr.ACTIVE, Relationship.LESS_OR_EQUALS, -2000); - ess1.addPowerConstraint("#4", Phase.ALL, Pwr.REACTIVE, Relationship.EQUALS, -1000); - ess2.addPowerConstraint("#4", Phase.ALL, Pwr.REACTIVE, Relationship.EQUALS, -1000); - ess3.addPowerConstraint("#4", Phase.ALL, Pwr.REACTIVE, Relationship.EQUALS, -1000); - ess4.addPowerConstraint("#4", Phase.ALL, Pwr.REACTIVE, Relationship.EQUALS, -1000); - ess5.addPowerConstraint("#4", Phase.ALL, Pwr.REACTIVE, Relationship.EQUALS, -1000); - ess6.addPowerConstraint("#4", Phase.ALL, Pwr.REACTIVE, Relationship.EQUALS, -1000); + ess1.addPowerConstraint("#4", Phase.ALL, ACTIVE, LESS_OR_EQUALS, -2000); + ess2.addPowerConstraint("#4", Phase.ALL, ACTIVE, LESS_OR_EQUALS, -2000); + ess3.addPowerConstraint("#4", Phase.ALL, ACTIVE, LESS_OR_EQUALS, -2000); + ess4.addPowerConstraint("#4", Phase.ALL, ACTIVE, LESS_OR_EQUALS, -2000); + ess5.addPowerConstraint("#4", Phase.ALL, ACTIVE, LESS_OR_EQUALS, -2000); + ess6.addPowerConstraint("#4", Phase.ALL, ACTIVE, LESS_OR_EQUALS, -2000); + ess1.addPowerConstraint("#4", Phase.ALL, REACTIVE, EQUALS, -1000); + ess2.addPowerConstraint("#4", Phase.ALL, REACTIVE, EQUALS, -1000); + ess3.addPowerConstraint("#4", Phase.ALL, REACTIVE, EQUALS, -1000); + ess4.addPowerConstraint("#4", Phase.ALL, REACTIVE, EQUALS, -1000); + ess5.addPowerConstraint("#4", Phase.ALL, REACTIVE, EQUALS, -1000); + ess6.addPowerConstraint("#4", Phase.ALL, REACTIVE, EQUALS, -1000); componentTest.next(new TestCase("#4")); } @@ -390,7 +393,7 @@ public void testCommercial40Cluster() throws Exception { .addReference("addEss", ess1) // .addReference("addEss", ess2) // .activate(MyConfig.create() // - .setStrategy(SolverStrategy.OPTIMIZE_BY_MOVING_TOWARDS_TARGET) // + .setStrategy(OPTIMIZE_BY_MOVING_TOWARDS_TARGET) // .setSymmetricMode(true) // .setDebugMode(false) // .setEnablePid(false) // @@ -399,65 +402,65 @@ public void testCommercial40Cluster() throws Exception { // #1 ess1.withAllowedChargePower(-500).withAllowedDischargePower(500); ess2.withAllowedChargePower(-500).withAllowedDischargePower(500); - assertEquals(1000, ess0.getPower().getMaxPower(ess0, Phase.ALL, Pwr.ACTIVE)); - assertEquals(-1000, ess0.getPower().getMinPower(ess0, Phase.ALL, Pwr.ACTIVE)); + assertEquals(1000, ess0.getPower().getMaxPower(ess0, Phase.ALL, ACTIVE)); + assertEquals(-1000, ess0.getPower().getMinPower(ess0, Phase.ALL, ACTIVE)); expect("#1", ess1, -500, 0); expect("#1", ess2, -500, 0); - ess0.addPowerConstraint("#1", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, -1000); + ess0.addPowerConstraint("#1", Phase.ALL, ACTIVE, EQUALS, -1000); componentTest.next(new TestCase("#1")); // #2 ess1.withAllowedChargePower(-1000); ess2.withAllowedChargePower(-1000); - assertEquals(-2000, ess0.getPower().getMinPower(ess0, Phase.ALL, Pwr.ACTIVE)); + assertEquals(-2000, ess0.getPower().getMinPower(ess0, Phase.ALL, ACTIVE)); expect("#2", ess1, -1000, 0); expect("#2", ess2, -1000, 0); - ess0.addPowerConstraint("#2", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, -2000); + ess0.addPowerConstraint("#2", Phase.ALL, ACTIVE, EQUALS, -2000); componentTest.next(new TestCase("#2")); // #3 ess1.withAllowedChargePower(-2000); ess2.withAllowedChargePower(-2000); - assertEquals(-4000, ess0.getPower().getMinPower(ess0, Phase.ALL, Pwr.ACTIVE)); + assertEquals(-4000, ess0.getPower().getMinPower(ess0, Phase.ALL, ACTIVE)); expect("#3", ess1, -2000, 0); expect("#3", ess2, -2000, 0); - ess0.addPowerConstraint("#3", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, -4000); + ess0.addPowerConstraint("#3", Phase.ALL, ACTIVE, EQUALS, -4000); componentTest.next(new TestCase("#3")); // #4 ess1.withAllowedChargePower(-3000); ess2.withAllowedChargePower(-3000); - assertEquals(-6000, ess0.getPower().getMinPower(ess0, Phase.ALL, Pwr.ACTIVE)); + assertEquals(-6000, ess0.getPower().getMinPower(ess0, Phase.ALL, ACTIVE)); expect("#4", ess1, -2700, 0); // move towards ess1 because it is empty expect("#4", ess2, -2300, 0); - ess0.addPowerConstraint("#4", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, -5000); + ess0.addPowerConstraint("#4", Phase.ALL, ACTIVE, EQUALS, -5000); componentTest.next(new TestCase("#4")); // #5 ess1.withAllowedChargePower(-3500); ess2.withAllowedChargePower(-3500); - assertEquals(-7000, ess0.getPower().getMinPower(ess0, Phase.ALL, Pwr.ACTIVE)); + assertEquals(-7000, ess0.getPower().getMinPower(ess0, Phase.ALL, ACTIVE)); expect("#5", ess1, -2900, 0); expect("#5", ess2, -2100, 0); - ess0.addPowerConstraint("#5", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, -5000); + ess0.addPowerConstraint("#5", Phase.ALL, ACTIVE, EQUALS, -5000); componentTest.next(new TestCase("#5")); // #6 ess1.withAllowedChargePower(-4000); ess2.withAllowedChargePower(-4000); - assertEquals(-8000, ess0.getPower().getMinPower(ess0, Phase.ALL, Pwr.ACTIVE)); + assertEquals(-8000, ess0.getPower().getMinPower(ess0, Phase.ALL, ACTIVE)); expect("#6", ess1, -3100, 0); // move towards ess1 because it is empty expect("#6", ess2, -1900, 0); - ess0.addPowerConstraint("#6", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, -5000); + ess0.addPowerConstraint("#6", Phase.ALL, ACTIVE, EQUALS, -5000); componentTest.next(new TestCase("#6")); // #7 ess1.withAllowedChargePower(-6000); ess2.withAllowedChargePower(-6000); - assertEquals(-12000, ess0.getPower().getMinPower(ess0, Phase.ALL, Pwr.ACTIVE)); + assertEquals(-12000, ess0.getPower().getMinPower(ess0, Phase.ALL, ACTIVE)); expect("#7", ess1, -3300, 0); // move towards ess1 because it is empty expect("#7", ess2, -1700, 0); - ess0.addPowerConstraint("#7", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, -5000); + ess0.addPowerConstraint("#7", Phase.ALL, ACTIVE, EQUALS, -5000); componentTest.next(new TestCase("#7")); } @@ -508,7 +511,7 @@ public void testMultilayerCluster() throws Exception { .addReference("addEss", ess21) // .addReference("addEss", ess22) // .activate(MyConfig.create() // - .setStrategy(SolverStrategy.OPTIMIZE_BY_KEEPING_ALL_EQUAL) // + .setStrategy(OPTIMIZE_BY_KEEPING_ALL_EQUAL) // .setSymmetricMode(true) // .setDebugMode(false) // .setEnablePid(false) // @@ -519,8 +522,8 @@ public void testMultilayerCluster() throws Exception { expect("#1", ess12, 1500, 1500); expect("#1", ess21, 1500, 1500); expect("#1", ess22, 1500, 1500); - ess0.addPowerConstraint("#1", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, 6000); - ess0.addPowerConstraint("#1", Phase.ALL, Pwr.REACTIVE, Relationship.EQUALS, 6000); + ess0.addPowerConstraint("#1", Phase.ALL, ACTIVE, EQUALS, 6000); + ess0.addPowerConstraint("#1", Phase.ALL, REACTIVE, EQUALS, 6000); componentTest.next(new TestCase("#1")); } @@ -571,7 +574,7 @@ public void testNearEqualDistribution() throws Exception { .addReference("addEss", ess3) // .addReference("addEss", ess4) // .activate(MyConfig.create() // - .setStrategy(SolverStrategy.OPTIMIZE_BY_KEEPING_ALL_EQUAL) // + .setStrategy(OPTIMIZE_BY_KEEPING_ALL_EQUAL) // .setSymmetricMode(true) // .setDebugMode(false) // .setEnablePid(false) // @@ -583,7 +586,7 @@ public void testNearEqualDistribution() throws Exception { expect("#1.3", ess3, 2500, 0); expect("#1.4", ess4, 2500, 0); - ess0.addPowerConstraint("SetActivePowerEquals", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, 10000); + ess0.addPowerConstraint("SetActivePowerEquals", Phase.ALL, ACTIVE, EQUALS, 10000); ess0.setActivePowerEquals(10000); componentTest.next(new TestCase("#1")); @@ -593,7 +596,7 @@ public void testNearEqualDistribution() throws Exception { expect("#2.3", ess3, -2500, 0); expect("#2.4", ess4, -2500, 0); - ess0.addPowerConstraint("SetActivePowerEquals", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, -10000); + ess0.addPowerConstraint("SetActivePowerEquals", Phase.ALL, ACTIVE, EQUALS, -10000); ess0.setActivePowerEquals(10000); componentTest.next(new TestCase("#1")); @@ -606,7 +609,7 @@ public void testNearEqualDistribution() throws Exception { expect("#3.3", ess3, 2701, 0); expect("#3.4", ess4, 1897, 0); - ess0.addPowerConstraint("SetActivePowerEquals", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, 10000); + ess0.addPowerConstraint("SetActivePowerEquals", Phase.ALL, ACTIVE, EQUALS, 10000); componentTest.next(new TestCase("#3")); // #4 charging with lower allowed ccharge power @@ -618,7 +621,7 @@ public void testNearEqualDistribution() throws Exception { expect("#4.3", ess3, -9900, 0); expect("#4.4", ess4, -1881, 0); - ess0.addPowerConstraint("SetActivePowerEquals", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, -10000); + ess0.addPowerConstraint("SetActivePowerEquals", Phase.ALL, ACTIVE, EQUALS, -10000); componentTest.next(new TestCase("#4")); // #5 keeping zero @@ -627,7 +630,7 @@ public void testNearEqualDistribution() throws Exception { expect("#5.3", ess3, 0, 0); expect("#5.4", ess4, 0, 0); - ess0.addPowerConstraint("SetActivePowerEquals", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, 0); + ess0.addPowerConstraint("SetActivePowerEquals", Phase.ALL, ACTIVE, EQUALS, 0); componentTest.next(new TestCase("#5")); ess4.withAllowedChargePower(1000); @@ -638,7 +641,7 @@ public void testNearEqualDistribution() throws Exception { expect("#5.3", ess3, 0, 0); expect("#5.4", ess4, 0, 0); - ess0.addPowerConstraint("SetActivePowerEquals", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, 0); + ess0.addPowerConstraint("SetActivePowerEquals", Phase.ALL, ACTIVE, EQUALS, 0); componentTest.next(new TestCase("#5")); } diff --git a/io.openems.edge.ess.core/test/io/openems/edge/ess/core/power/PowerComponentTest2.java b/io.openems.edge.ess.core/test/io/openems/edge/ess/core/power/PowerComponentTest2.java index 7060c6e7393..dd36004444b 100644 --- a/io.openems.edge.ess.core/test/io/openems/edge/ess/core/power/PowerComponentTest2.java +++ b/io.openems.edge.ess.core/test/io/openems/edge/ess/core/power/PowerComponentTest2.java @@ -1,5 +1,8 @@ package io.openems.edge.ess.core.power; +import static io.openems.edge.ess.power.api.Pwr.ACTIVE; +import static io.openems.edge.ess.power.api.Relationship.EQUALS; +import static io.openems.edge.ess.power.api.SolverStrategy.OPTIMIZE_BY_KEEPING_ALL_EQUAL; import static org.junit.Assert.assertEquals; import java.util.concurrent.atomic.AtomicInteger; @@ -12,9 +15,6 @@ import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; import io.openems.edge.ess.power.api.Phase; -import io.openems.edge.ess.power.api.Pwr; -import io.openems.edge.ess.power.api.Relationship; -import io.openems.edge.ess.power.api.SolverStrategy; import io.openems.edge.ess.test.DummyManagedSymmetricEss; import io.openems.edge.ess.test.DummyMetaEss; @@ -55,7 +55,7 @@ public void testOnlyOneEssDistribution() throws Exception { .addReference("addEss", ess1) // .activate(MyConfig.create() // - .setStrategy(SolverStrategy.OPTIMIZE_BY_KEEPING_ALL_EQUAL) // + .setStrategy(OPTIMIZE_BY_KEEPING_ALL_EQUAL) // .setSymmetricMode(true) // .setDebugMode(false) // .setEnablePid(false) // @@ -120,7 +120,7 @@ public void testNearEqualDistribution() throws Exception { .addReference("addEss", ess3) // .addReference("addEss", ess4) // .activate(MyConfig.create() // - .setStrategy(SolverStrategy.OPTIMIZE_BY_KEEPING_ALL_EQUAL) // + .setStrategy(OPTIMIZE_BY_KEEPING_ALL_EQUAL) // .setSymmetricMode(true) // .setDebugMode(false) // .setEnablePid(false) // @@ -132,7 +132,7 @@ public void testNearEqualDistribution() throws Exception { expect("#1.3", ess3, 2500, 0); expect("#1.4", ess4, 2500, 0); - ess0.addPowerConstraint("SetActivePowerEquals1", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, 10000); + ess0.addPowerConstraint("SetActivePowerEquals1", Phase.ALL, ACTIVE, EQUALS, 10000); ess0.setActivePowerEquals(10000); componentTest.next(new TestCase("#1")); @@ -142,7 +142,7 @@ public void testNearEqualDistribution() throws Exception { expect("#2.3", ess3, -2500, 0); expect("#2.4", ess4, -2500, 0); - ess0.addPowerConstraint("SetActivePowerEquals2", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, -10000); + ess0.addPowerConstraint("SetActivePowerEquals2", Phase.ALL, ACTIVE, EQUALS, -10000); ess0.setActivePowerEquals(10000); componentTest.next(new TestCase("#1")); @@ -155,7 +155,7 @@ public void testNearEqualDistribution() throws Exception { expect("#3.3", ess3, 2701, 0); expect("#3.4", ess4, 1897, 0); - ess0.addPowerConstraint("SetActivePowerEquals3", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, 10000); + ess0.addPowerConstraint("SetActivePowerEquals3", Phase.ALL, ACTIVE, EQUALS, 10000); componentTest.next(new TestCase("#3")); // #4 charging with lower allowed charge power @@ -167,7 +167,7 @@ public void testNearEqualDistribution() throws Exception { expect("#4.3", ess3, -2703, 0); expect("#4.4", ess4, -1896, 0); - ess0.addPowerConstraint("SetActivePowerEquals4", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, -10000); + ess0.addPowerConstraint("SetActivePowerEquals4", Phase.ALL, ACTIVE, EQUALS, -10000); componentTest.next(new TestCase("#4")); // #5 keeping zero @@ -176,7 +176,7 @@ public void testNearEqualDistribution() throws Exception { expect("#5.3", ess3, 0, 0); expect("#5.4", ess4, 0, 0); - ess0.addPowerConstraint("SetActivePowerEquals5", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, 0); + ess0.addPowerConstraint("SetActivePowerEquals5", Phase.ALL, ACTIVE, EQUALS, 0); componentTest.next(new TestCase("#5")); ess4.withAllowedChargePower(1000); @@ -187,7 +187,7 @@ public void testNearEqualDistribution() throws Exception { expect("#5.3", ess3, 0, 0); expect("#5.4", ess4, 0, 0); - ess0.addPowerConstraint("ctrl0", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, 0); + ess0.addPowerConstraint("ctrl0", Phase.ALL, ACTIVE, EQUALS, 0); componentTest.next(new TestCase("#5")); } diff --git a/io.openems.edge.ess.core/test/io/openems/edge/ess/core/power/data/LinearSolverUtilTest.java b/io.openems.edge.ess.core/test/io/openems/edge/ess/core/power/data/LinearSolverUtilTest.java new file mode 100644 index 00000000000..4f45a67fb89 --- /dev/null +++ b/io.openems.edge.ess.core/test/io/openems/edge/ess/core/power/data/LinearSolverUtilTest.java @@ -0,0 +1,44 @@ +package io.openems.edge.ess.core.power.data; + +import static io.openems.edge.ess.core.power.data.ConstraintUtil.createSimpleConstraint; +import static io.openems.edge.ess.power.api.Relationship.EQUALS; +import static io.openems.edge.ess.power.api.Relationship.GREATER_OR_EQUALS; +import static io.openems.edge.ess.power.api.Relationship.LESS_OR_EQUALS; + +import java.util.List; +import java.util.Set; + +import org.junit.Test; + +import io.openems.common.exceptions.OpenemsException; +import io.openems.edge.ess.power.api.Coefficients; +import io.openems.edge.ess.power.api.Constraint; +import io.openems.edge.ess.power.api.LinearCoefficient; +import io.openems.edge.ess.power.api.Phase; +import io.openems.edge.ess.power.api.Pwr; +import io.openems.edge.ess.power.api.Relationship; + +public class LinearSolverUtilTest { + + @Test(expected = OpenemsException.class) + public void testCoefficientOfThrowsException() throws OpenemsException { + createSimpleConstraint(new Coefficients(), // + "Dummy#1", "ess0", Phase.ALL, Pwr.ACTIVE, EQUALS, 0); + } + + @Test + public void testConvertToLinearConstraints() throws OpenemsException { + final var coefficients = new Coefficients(); + coefficients.initialize(false, Set.of("ess0")); + var constraints = List.of(// + createSimpleConstraint(coefficients, // + "Dummy EQUALS", "ess0", Phase.ALL, Pwr.ACTIVE, EQUALS, 0), // + createSimpleConstraint(coefficients, // + "Dummy GREATER_OR_EQUALS", "ess0", Phase.ALL, Pwr.ACTIVE, GREATER_OR_EQUALS, 0), // + createSimpleConstraint(coefficients, // + "Dummy LESS_OR_EQUALS", "ess0", Phase.ALL, Pwr.ACTIVE, LESS_OR_EQUALS, 0), // + new Constraint("Dummy empty value", new LinearCoefficient[0], Relationship.EQUALS)); + LinearSolverUtil.convertToLinearConstraints(coefficients, constraints); + } + +} diff --git a/io.openems.edge.ess.fenecon.commercial40/.classpath b/io.openems.edge.ess.fenecon.commercial40/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.ess.fenecon.commercial40/.classpath +++ b/io.openems.edge.ess.fenecon.commercial40/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.ess.fenecon.commercial40/test/io/openems/edge/ess/fenecon/commercial40/EssFeneconCommercial40ImplTest.java b/io.openems.edge.ess.fenecon.commercial40/test/io/openems/edge/ess/fenecon/commercial40/EssFeneconCommercial40ImplTest.java index fdd05793704..f1c7d45a6ff 100644 --- a/io.openems.edge.ess.fenecon.commercial40/test/io/openems/edge/ess/fenecon/commercial40/EssFeneconCommercial40ImplTest.java +++ b/io.openems.edge.ess.fenecon.commercial40/test/io/openems/edge/ess/fenecon/commercial40/EssFeneconCommercial40ImplTest.java @@ -8,17 +8,14 @@ public class EssFeneconCommercial40ImplTest { - private static final String ESS_ID = "ess0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ManagedSymmetricEssTest(new EssFeneconCommercial40Impl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(ESS_ID) // - .setModbusId(MODBUS_ID) // + .setId("ess0") // + .setModbusId("modbus0") // .setSurplusFeedInSocLimit(90) // .setSurplusFeedInAllowedChargePowerLimit(-8000) // .setSurplusFeedInIncreasePowerFactor(1.1) // diff --git a/io.openems.edge.ess.fenecon.commercial40/test/io/openems/edge/ess/fenecon/commercial40/charger/EssFeneconCommercial40Pv1ImplTest.java b/io.openems.edge.ess.fenecon.commercial40/test/io/openems/edge/ess/fenecon/commercial40/charger/EssFeneconCommercial40Pv1ImplTest.java index 317c043debe..91597df6669 100644 --- a/io.openems.edge.ess.fenecon.commercial40/test/io/openems/edge/ess/fenecon/commercial40/charger/EssFeneconCommercial40Pv1ImplTest.java +++ b/io.openems.edge.ess.fenecon.commercial40/test/io/openems/edge/ess/fenecon/commercial40/charger/EssFeneconCommercial40Pv1ImplTest.java @@ -10,19 +10,15 @@ public class EssFeneconCommercial40Pv1ImplTest { - private static final String CHARGER_ID = "charger0"; - private static final String ESS_ID = "ess0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { var ess = new EssFeneconCommercial40Impl(); new ManagedSymmetricEssTest(ess) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(io.openems.edge.ess.fenecon.commercial40.MyConfig.create() // - .setId(ESS_ID) // - .setModbusId(MODBUS_ID) // + .setId("ess0") // + .setModbusId("modbus0") // .setSurplusFeedInSocLimit(90) // .setSurplusFeedInAllowedChargePowerLimit(-8000) // .setSurplusFeedInIncreasePowerFactor(1.1) // @@ -34,11 +30,11 @@ public void test() throws Exception { new ComponentTest(new EssFeneconCommercial40Pv1Impl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("ess", ess) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfigPv1.create() // - .setId(CHARGER_ID) // - .setModbusId(MODBUS_ID) // - .setEssId(ESS_ID) // + .setId("charger0") // + .setModbusId("modbus0") // + .setEssId("ess0") // .setMaxActualPower(0) // .build()) // ; diff --git a/io.openems.edge.ess.fenecon.commercial40/test/io/openems/edge/ess/fenecon/commercial40/charger/EssFeneconCommercial40Pv2ImplTest.java b/io.openems.edge.ess.fenecon.commercial40/test/io/openems/edge/ess/fenecon/commercial40/charger/EssFeneconCommercial40Pv2ImplTest.java index 652b0363b74..80003e5447f 100644 --- a/io.openems.edge.ess.fenecon.commercial40/test/io/openems/edge/ess/fenecon/commercial40/charger/EssFeneconCommercial40Pv2ImplTest.java +++ b/io.openems.edge.ess.fenecon.commercial40/test/io/openems/edge/ess/fenecon/commercial40/charger/EssFeneconCommercial40Pv2ImplTest.java @@ -10,19 +10,15 @@ public class EssFeneconCommercial40Pv2ImplTest { - private static final String CHARGER_ID = "charger1"; - private static final String ESS_ID = "ess0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { var ess = new EssFeneconCommercial40Impl(); new ManagedSymmetricEssTest(ess) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(io.openems.edge.ess.fenecon.commercial40.MyConfig.create() // - .setId(ESS_ID) // - .setModbusId(MODBUS_ID) // + .setId("ess0") // + .setModbusId("modbus0") // .setSurplusFeedInSocLimit(90) // .setSurplusFeedInAllowedChargePowerLimit(-8000) // .setSurplusFeedInIncreasePowerFactor(1.1) // @@ -34,11 +30,11 @@ public void test() throws Exception { new ComponentTest(new EssFeneconCommercial40Pv2Impl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("ess", ess) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfigPv2.create() // - .setId(CHARGER_ID) // - .setModbusId(MODBUS_ID) // - .setEssId(ESS_ID) // + .setId("charger1") // + .setModbusId("modbus0") // + .setEssId("ess0") // .setMaxActualPower(0) // .build()) // ; diff --git a/io.openems.edge.ess.generic/.classpath b/io.openems.edge.ess.generic/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.ess.generic/.classpath +++ b/io.openems.edge.ess.generic/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/common/AbstractGenericManagedEss.java b/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/common/AbstractGenericManagedEss.java index 8f8c44bf659..669ceaa08f0 100644 --- a/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/common/AbstractGenericManagedEss.java +++ b/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/common/AbstractGenericManagedEss.java @@ -211,22 +211,10 @@ public ModbusSlaveTable getModbusSlaveTable(AccessMode accessMode) { @Override public StartStop getStartStopTarget() { - switch (this.startStopConfig) { - case AUTO: - // read StartStop-Channel - return this.startStopTarget.get(); - - case START: - // force START - return StartStop.START; - - case STOP: - // force STOP - return StartStop.STOP; - } - - assert false; - return StartStop.UNDEFINED; // can never happen + return switch (this.startStopConfig) { + case AUTO -> this.startStopTarget.get(); + case START -> StartStop.START; + case STOP -> StartStop.STOP; + }; } - } diff --git a/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/common/GenericManagedEss.java b/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/common/GenericManagedEss.java index 83035585f53..62ba61395fe 100644 --- a/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/common/GenericManagedEss.java +++ b/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/common/GenericManagedEss.java @@ -29,14 +29,28 @@ public interface GenericManagedEss extends ManagedSymmetricEss, StartStoppable, */ public static int RETRY_COMMAND_MAX_ATTEMPTS = 30; + /** + * Retry set-command after x Seconds, e.g. for starting battery or + * battery-inverter. + */ + public static int TIMEOUT = 300; + public enum ChannelId implements io.openems.edge.common.channel.ChannelId { - MAX_BATTERY_START_ATTEMPTS_FAULT(Doc.of(Level.FAULT) // + TIMEOUT_START_BATTERY(Doc.of(Level.FAULT) // + .text("Start battery timeout passed!")), // + TIMEOUT_START_BATTERY_INVERTER(Doc.of(Level.FAULT) // + .text("Start battery inverter timeout passed!")), // + TIMEOUT_STOP_BATTERY(Doc.of(Level.FAULT) // + .text("Stop battery timeout passed!")), // + TIMEOUT_STOP_BATTERY_INVERTER(Doc.of(Level.FAULT) // + .text("Stop battery inverter timeout passed!")), // + MAX_BATTERY_START_ATTEMPTS_FAULT(Doc.of(Level.WARNING) // .text("The maximum number of Battery start attempts failed")), // - MAX_BATTERY_STOP_ATTEMPTS_FAULT(Doc.of(Level.FAULT) // + MAX_BATTERY_STOP_ATTEMPTS_FAULT(Doc.of(Level.WARNING) // .text("The maximum number of Battery stop attempts failed")), // - MAX_BATTERY_INVERTER_START_ATTEMPTS_FAULT(Doc.of(Level.FAULT) // + MAX_BATTERY_INVERTER_START_ATTEMPTS_FAULT(Doc.of(Level.WARNING) // .text("The maximum number of Battery-Inverter start attempts failed")), // - MAX_BATTERY_INVERTER_STOP_ATTEMPTS_FAULT(Doc.of(Level.FAULT) // + MAX_BATTERY_INVERTER_STOP_ATTEMPTS_FAULT(Doc.of(Level.WARNING) // .text("The maximum number of Battery-Inverter stop attempts failed")); // private final Doc doc; @@ -68,7 +82,7 @@ public default StateChannel getMaxBatteryStartAttemptsFaultChannel() { } /** - * Gets the {@link StateChannel} for + * Gets the StateChannel value for * {@link ChannelId#MAX_BATTERY_START_ATTEMPTS_FAULT}. * * @return the Channel {@link Value} @@ -97,7 +111,7 @@ public default StateChannel getMaxBatteryStopAttemptsFaultChannel() { } /** - * Gets the {@link StateChannel} for + * Gets the StateChannel value for * {@link ChannelId#MAX_BATTERY_STOP_ATTEMPTS_FAULT}. * * @return the Channel {@link Value} @@ -127,7 +141,7 @@ public default StateChannel getMaxBatteryInverterStartAttemptsFaultChannel() { } /** - * Gets the {@link StateChannel} for + * Gets the StateChannel value for * {@link ChannelId#MAX_BATTERY_INVERTER_START_ATTEMPTS_FAULT}. * * @return the Channel {@link Value} @@ -157,7 +171,7 @@ public default StateChannel getMaxBatteryInverterStopAttemptsFaultChannel() { } /** - * Gets the {@link StateChannel} for + * Gets the StateChannel value for * {@link ChannelId#MAX_BATTERY_INVERTER_STOP_ATTEMPTS_FAULT}. * * @return the Channel {@link Value} @@ -176,4 +190,118 @@ public default void _setMaxBatteryInverterStopAttemptsFault(boolean value) { this.getMaxBatteryInverterStopAttemptsFaultChannel().setNextValue(value); } + /** + * Gets the Channel for {@link ChannelId#TIMEOUT_START_BATTERY}. + * + * @return the Channel + */ + public default StateChannel getTimeoutStartBatteryChannel() { + return this.channel(ChannelId.TIMEOUT_START_BATTERY); + } + + /** + * Gets the StateChannel value for {@link ChannelId#TIMEOUT_START_BATTERY}. + * + * @return the Channel {@link Value} + */ + public default Value getTimeoutStartBattery() { + return this.getTimeoutStartBatteryChannel().value(); + } + + /** + * Internal method to set the 'nextValue' on + * {@link ChannelId#TIMEOUT_START_BATTERY} Channel. + * + * @param value the next value + */ + public default void _setTimeoutStartBattery(boolean value) { + this.getTimeoutStartBatteryChannel().setNextValue(value); + } + + /** + * Gets the Channel for {@link ChannelId#TIMEOUT_START_BATTERY_INVERTER}. + * + * @return the Channel + */ + public default StateChannel getTimeoutStartBatteryInverterhannel() { + return this.channel(ChannelId.TIMEOUT_START_BATTERY_INVERTER); + } + + /** + * Gets the StateChannel value for + * {@link ChannelId#TIMEOUT_START_BATTERY_INVERTER}. + * + * @return the Channel {@link Value} + */ + public default Value getTimeoutStartBatteryInverter() { + return this.getTimeoutStartBatteryInverterhannel().value(); + } + + /** + * Internal method to set the 'nextValue' on + * {@link ChannelId#TIMEOUT_START_BATTERY_INVERTER} Channel. + * + * @param value the next value + */ + public default void _setTimeoutStartBatteryInverter(boolean value) { + this.getTimeoutStartBatteryInverterhannel().setNextValue(value); + } + + /** + * Gets the Channel for {@link ChannelId#TIMEOUT_STOP_BATTERY_INVERTER}. + * + * @return the Channel + */ + public default StateChannel getTimeoutStopBatteryInverterChannel() { + return this.channel(ChannelId.TIMEOUT_STOP_BATTERY_INVERTER); + } + + /** + * Gets the StateChannel value for + * {@link ChannelId#TIMEOUT_STOP_BATTERY_INVERTER}. + * + * @return the Channel {@link Value} + */ + public default Value getTimeoutStopBatteryInverter() { + return this.getTimeoutStopBatteryInverterChannel().value(); + } + + /** + * Internal method to set the 'nextValue' on + * {@link ChannelId#TIMEOUT_STOP_BATTERY_INVERTER} Channel. + * + * @param value the next value + */ + public default void _setTimeoutStopBatteryInverter(boolean value) { + this.getTimeoutStopBatteryInverterChannel().setNextValue(value); + } + + /** + * Gets the Channel for {@link ChannelId#TIMEOUT_STOP_BATTERY}. + * + * @return the Channel + */ + public default StateChannel getTimeoutStopBatteryChannel() { + return this.channel(ChannelId.TIMEOUT_STOP_BATTERY); + } + + /** + * Gets the StateChannel value for {@link ChannelId#TIMEOUT_STOP_BATTERY}. + * + * @return the Channel {@link Value} + */ + public default Value getTimeoutStopBattery() { + return this.getTimeoutStopBatteryChannel().value(); + } + + /** + * Internal method to set the 'nextValue' on + * {@link ChannelId#TIMEOUT_STOP_BATTERY} Channel. + * + * @param value the next value + */ + public default void _setTimeoutStopBattery(boolean value) { + this.getTimeoutStopBatteryChannel().setNextValue(value); + } + } diff --git a/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/offgrid/EssGenericOffGrid.java b/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/offgrid/EssGenericOffGrid.java index a3492209db6..06a66872089 100644 --- a/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/offgrid/EssGenericOffGrid.java +++ b/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/offgrid/EssGenericOffGrid.java @@ -24,7 +24,7 @@ public interface EssGenericOffGrid extends GenericManagedEss, OffGridEss, Manage public enum ChannelId implements io.openems.edge.common.channel.ChannelId { STATE_MACHINE(Doc.of(StateMachine.OffGridState.values()) // .text("Current State of State-Machine")), // - RUN_FAILED(Doc.of(Level.FAULT) // + RUN_FAILED(Doc.of(Level.WARNING) // .text("Running the Logic failed")), // ; diff --git a/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/EssGenericManagedSymmetric.java b/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/EssGenericManagedSymmetric.java index 7fe3be0fe45..7cef132c384 100644 --- a/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/EssGenericManagedSymmetric.java +++ b/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/EssGenericManagedSymmetric.java @@ -4,7 +4,9 @@ import io.openems.common.channel.AccessMode; import io.openems.common.channel.Level; +import io.openems.edge.common.channel.Channel; import io.openems.edge.common.channel.Doc; +import io.openems.edge.common.channel.value.Value; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.modbusslave.ModbusSlave; import io.openems.edge.common.modbusslave.ModbusSlaveNatureTable; @@ -40,6 +42,53 @@ public Doc doc() { } } + /** + * Gets the Channel for {@link ChannelId#STATE_MACHINE}. + * + * @return the Channel + */ + public default Channel getStateMachineChannel() { + return this.channel(ChannelId.STATE_MACHINE); + } + + /** + * Gets the StateMachine channel value for {@link ChannelId#STATE_MACHINE}. + * + * @return the Channel {@link Value} + */ + public default Value getStateMachine() { + return this.getStateMachineChannel().value(); + } + + /** + * Internal method to set the 'nextValue' on {@link ChannelId#STATE_MACHINE} + * Channel. + * + * @param value the next value + */ + public default void _setStateMachine(State value) { + this.getStateMachineChannel().setNextValue(value); + } + + /** + * Gets the Channel for {@link ChannelId#RUN_FAILED}. + * + * @return the Channel + */ + public default Channel getRunFailedChannel() { + return this.channel(ChannelId.RUN_FAILED); + } + + /** + * Internal method to set the 'nextValue' on {@link ChannelId#RUN_FAILED} + * Channel. + * + * @param value the next value + */ + public default void _setRunFailed(boolean value) { + this.getRunFailedChannel().setNextValue(value); + } + @Override public default ModbusSlaveTable getModbusSlaveTable(AccessMode accessMode) { return new ModbusSlaveTable(// diff --git a/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/EssGenericManagedSymmetricImpl.java b/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/EssGenericManagedSymmetricImpl.java index 40a3a6371af..c13369e663c 100644 --- a/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/EssGenericManagedSymmetricImpl.java +++ b/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/EssGenericManagedSymmetricImpl.java @@ -1,5 +1,6 @@ package io.openems.edge.ess.generic.symmetric; +import static com.google.common.base.MoreObjects.toStringHelper; import static io.openems.edge.common.cycle.Cycle.DEFAULT_CYCLE_TIME; import static io.openems.edge.ess.generic.symmetric.statemachine.StateMachine.State.UNDEFINED; @@ -105,23 +106,20 @@ protected void deactivate() { @Override protected void handleStateMachine() { // Store the current State - this.channel(EssGenericManagedSymmetric.ChannelId.STATE_MACHINE) - .setNextValue(this.stateMachine.getCurrentState()); + this._setStateMachine(this.stateMachine.getCurrentState()); // Initialize 'Start-Stop' Channel this._setStartStop(StartStop.UNDEFINED); // Prepare Context - var context = new Context(this, this.getBattery(), this.getBatteryInverter()); + var context = new Context(this, this.getBattery(), this.getBatteryInverter(), this.componentManager.getClock()); // Call the StateMachine try { this.stateMachine.run(context); - - this.channel(EssGenericManagedSymmetric.ChannelId.RUN_FAILED).setNextValue(false); - + this._setRunFailed(false); } catch (OpenemsNamedException e) { - this.channel(EssGenericManagedSymmetric.ChannelId.RUN_FAILED).setNextValue(true); + this._setRunFailed(true); this.logError(this.log, "StateMachine failed: " + e.getMessage()); } } @@ -179,7 +177,6 @@ public boolean isManaged() { @Override public void setStartStop(StartStop value) { if (this.startStopTarget.getAndSet(value) != value) { - // Set only if value changed this.stateMachine.forceNextState(UNDEFINED); } } @@ -188,4 +185,11 @@ public void setStartStop(StartStop value) { public int getCycleTime() { return this.cycle != null ? this.cycle.getCycleTime() : DEFAULT_CYCLE_TIME; } + + @Override + public String toString() { + return toStringHelper(this) // + .addValue(this.id()) // + .toString(); + } } diff --git a/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/Context.java b/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/Context.java index eed94199380..5a25ded906e 100644 --- a/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/Context.java +++ b/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/Context.java @@ -1,5 +1,7 @@ package io.openems.edge.ess.generic.symmetric.statemachine; +import java.time.Clock; + import io.openems.edge.battery.api.Battery; import io.openems.edge.batteryinverter.api.ManagedSymmetricBatteryInverter; import io.openems.edge.common.statemachine.AbstractContext; @@ -9,10 +11,51 @@ public class Context extends AbstractContext { protected final Battery battery; protected final ManagedSymmetricBatteryInverter batteryInverter; + protected final Clock clock; - public Context(GenericManagedEss parent, Battery battery, ManagedSymmetricBatteryInverter batteryInverter) { + public Context(GenericManagedEss parent, Battery battery, ManagedSymmetricBatteryInverter batteryInverter, + Clock clock) { super(parent); this.battery = battery; this.batteryInverter = batteryInverter; + this.clock = clock; + } + + /** + * Generic ess has faults. + * + *

    + * Check for any faults in the generic ess and its dependent battery or battery + * inverter. + * + * @return true on any failure + */ + public boolean hasEssFaults() { + return this.getParent().hasFaults() || this.battery.hasFaults() || this.batteryInverter.hasFaults(); + } + + /** + * Is generic ess started. + * + *

    + * Generic ess is started when battery and battery-inverter started. + * + * @return true if battery and battery-inverter started + */ + public boolean isEssStarted() { + return this.battery.isStarted() && this.batteryInverter.isStarted(); + } + + /** + * Is generic ess stopped. + * + *

    + * Generic ess is stopped when at least the battery stopped. In many cases the + * BatteryInverter is not able to not stop. + * + * @return true if the system stopped. + */ + public boolean isEssStopped() { + return this.battery.isStopped(); } -} \ No newline at end of file +} diff --git a/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/ErrorHandler.java b/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/ErrorHandler.java index 6a2dda3271f..c64884874d3 100644 --- a/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/ErrorHandler.java +++ b/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/ErrorHandler.java @@ -1,48 +1,44 @@ package io.openems.edge.ess.generic.symmetric.statemachine; -import java.time.Duration; -import java.time.Instant; - import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.edge.common.startstop.StartStop; import io.openems.edge.common.statemachine.StateHandler; import io.openems.edge.ess.generic.symmetric.statemachine.StateMachine.State; public class ErrorHandler extends StateHandler { - private static final int WAIT_TIME_IN_SECONDS = 120; - - private Instant entryAt = Instant.MIN; - private int startAttemptCounter = 0; - @Override protected void onEntry(Context context) throws OpenemsNamedException { - this.entryAt = Instant.now(); - this.startAttemptCounter++; - // Try to stop systems - context.battery.setStartStop(StartStop.STOP); - context.batteryInverter.setStartStop(StartStop.STOP); - } - - @Override - protected void onExit(Context context) throws OpenemsNamedException { - var ess = context.getParent(); - - ess._setMaxBatteryStartAttemptsFault(false); - ess._setMaxBatteryStopAttemptsFault(false); - ess._setMaxBatteryInverterStartAttemptsFault(false); - ess._setMaxBatteryInverterStopAttemptsFault(false); + context.batteryInverter.stop(); + context.battery.stop(); } @Override public State runAndGetNextState(Context context) { - if (Duration.between(this.entryAt, Instant.now()).getSeconds() > WAIT_TIME_IN_SECONDS - * Math.pow(16, this.startAttemptCounter)) { - // Try again + final var ess = context.getParent(); + final var battery = context.battery; + final var batteryInverter = context.batteryInverter; + // TODO error handling + + /* + * Wait at least for stopping the battery and check for ess, battery, + * battery-inverter faults + * + * If ModbusCommunicationFault would be a FaultState, check it explicitly. The + * battery could still have a communication fault while starting the battery. + */ + if (!ess.hasFaults() && !batteryInverter.hasFaults() && !battery.hasFaults() && context.battery.isStopped()) { return State.UNDEFINED; } return State.ERROR; } + @Override + protected void onExit(Context context) throws OpenemsNamedException { + final var ess = context.getParent(); + ess._setTimeoutStartBattery(false); + ess._setTimeoutStopBattery(false); + ess._setTimeoutStartBatteryInverter(false); + ess._setTimeoutStopBatteryInverter(false); + } } diff --git a/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/StartBatteryHandler.java b/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/StartBatteryHandler.java index f1f881f1163..3b12dcea87e 100644 --- a/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/StartBatteryHandler.java +++ b/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/StartBatteryHandler.java @@ -1,54 +1,36 @@ package io.openems.edge.ess.generic.symmetric.statemachine; -import java.time.Duration; -import java.time.Instant; - import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.timedata.Timeout; import io.openems.edge.common.statemachine.StateHandler; import io.openems.edge.ess.generic.common.GenericManagedEss; import io.openems.edge.ess.generic.symmetric.statemachine.StateMachine.State; public class StartBatteryHandler extends StateHandler { - private Instant lastAttempt = Instant.MIN; - private int attemptCounter = 0; + private final Timeout timeout = Timeout.ofSeconds(GenericManagedEss.TIMEOUT); @Override protected void onEntry(Context context) throws OpenemsNamedException { - this.lastAttempt = Instant.MIN; - this.attemptCounter = 0; - var ess = context.getParent(); - ess._setMaxBatteryStartAttemptsFault(false); + this.timeout.start(context.clock); } @Override public State runAndGetNextState(Context context) throws OpenemsNamedException { - var ess = context.getParent(); + final var ess = context.getParent(); + final var battery = context.battery; - if (context.battery.isStarted()) { + if (battery.isStarted()) { return State.START_BATTERY_INVERTER; } - var isMaxStartTimePassed = Duration.between(this.lastAttempt, Instant.now()) - .getSeconds() > GenericManagedEss.RETRY_COMMAND_SECONDS; - if (!isMaxStartTimePassed) { - // Still waiting... - return State.START_BATTERY; + // Is max allowed start time passed ? + if (this.timeout.elapsed(context.clock)) { + ess._setTimeoutStartBattery(true); + return State.ERROR; } - if (this.attemptCounter > GenericManagedEss.RETRY_COMMAND_MAX_ATTEMPTS) { - // Too many tries - ess._setMaxBatteryStartAttemptsFault(true); - return State.UNDEFINED; - - } else { - // Trying to start Battery - context.battery.start(); - - this.lastAttempt = Instant.now(); - this.attemptCounter++; - return State.START_BATTERY; - } + battery.start(); + return State.START_BATTERY; } - } diff --git a/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/StartBatteryInverterHandler.java b/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/StartBatteryInverterHandler.java index 5b394a9f5de..128ab668d21 100644 --- a/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/StartBatteryInverterHandler.java +++ b/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/StartBatteryInverterHandler.java @@ -1,54 +1,40 @@ package io.openems.edge.ess.generic.symmetric.statemachine; -import java.time.Duration; -import java.time.Instant; - import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.timedata.Timeout; import io.openems.edge.common.statemachine.StateHandler; import io.openems.edge.ess.generic.common.GenericManagedEss; import io.openems.edge.ess.generic.symmetric.statemachine.StateMachine.State; public class StartBatteryInverterHandler extends StateHandler { - private Instant lastAttempt = Instant.MIN; - private int attemptCounter = 0; + private final Timeout timeout = Timeout.ofSeconds(GenericManagedEss.TIMEOUT); @Override protected void onEntry(Context context) throws OpenemsNamedException { - this.lastAttempt = Instant.MIN; - this.attemptCounter = 0; - var ess = context.getParent(); - ess._setMaxBatteryInverterStartAttemptsFault(false); + this.timeout.start(context.clock); } @Override public State runAndGetNextState(Context context) throws OpenemsNamedException { - var ess = context.getParent(); + final var ess = context.getParent(); + final var inverter = context.batteryInverter; - if (context.batteryInverter.isStarted()) { - return State.STARTED; + if (context.hasEssFaults()) { + return State.ERROR; } - var isMaxStartTimePassed = Duration.between(this.lastAttempt, Instant.now()) - .getSeconds() > GenericManagedEss.RETRY_COMMAND_SECONDS; - if (!isMaxStartTimePassed) { - // Still waiting... - return State.START_BATTERY_INVERTER; + if (inverter.isStarted()) { + return State.STARTED; } - if (this.attemptCounter > GenericManagedEss.RETRY_COMMAND_MAX_ATTEMPTS) { - // Too many tries - ess._setMaxBatteryInverterStartAttemptsFault(true); - return State.UNDEFINED; - - } else { - // Trying to start Battery - context.batteryInverter.start(); - - this.lastAttempt = Instant.now(); - this.attemptCounter++; - return State.START_BATTERY_INVERTER; + // Is max allowed start time passed ? + if (this.timeout.elapsed(context.clock)) { + ess._setTimeoutStartBatteryInverter(true); + return State.ERROR; } - } + inverter.start(); + return State.START_BATTERY_INVERTER; + } } diff --git a/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/StartedHandler.java b/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/StartedHandler.java index 57ee53a0f41..e356b41c160 100644 --- a/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/StartedHandler.java +++ b/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/StartedHandler.java @@ -8,24 +8,18 @@ public class StartedHandler extends StateHandler { @Override public State runAndGetNextState(Context context) { - var ess = context.getParent(); + final var ess = context.getParent(); - if (ess.hasFaults()) { - return State.UNDEFINED; + if (context.hasEssFaults()) { + return State.ERROR; } - if (!context.battery.isStarted()) { - return State.UNDEFINED; - } - - if (!context.batteryInverter.isStarted()) { - return State.UNDEFINED; + if (!context.isEssStarted()) { + return State.ERROR; } // Mark as started ess._setStartStop(StartStop.START); - return State.STARTED; } - } diff --git a/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/StopBatteryHandler.java b/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/StopBatteryHandler.java index 4dc84ac8898..d1e40a2ca3f 100644 --- a/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/StopBatteryHandler.java +++ b/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/StopBatteryHandler.java @@ -1,54 +1,40 @@ package io.openems.edge.ess.generic.symmetric.statemachine; -import java.time.Duration; -import java.time.Instant; - import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.timedata.Timeout; import io.openems.edge.common.statemachine.StateHandler; import io.openems.edge.ess.generic.common.GenericManagedEss; import io.openems.edge.ess.generic.symmetric.statemachine.StateMachine.State; public class StopBatteryHandler extends StateHandler { - private Instant lastAttempt = Instant.MIN; - private int attemptCounter = 0; + private final Timeout timeout = Timeout.ofSeconds(GenericManagedEss.TIMEOUT); @Override protected void onEntry(Context context) throws OpenemsNamedException { - this.lastAttempt = Instant.MIN; - this.attemptCounter = 0; - var ess = context.getParent(); - ess._setMaxBatteryStopAttemptsFault(false); + this.timeout.start(context.clock); } @Override public State runAndGetNextState(Context context) throws OpenemsNamedException { - var ess = context.getParent(); + final var ess = context.getParent(); + final var battery = context.battery; - if (context.battery.isStopped()) { - return State.STOPPED; + if (context.hasEssFaults()) { + return State.ERROR; } - var isMaxStartTimePassed = Duration.between(this.lastAttempt, Instant.now()) - .getSeconds() > GenericManagedEss.RETRY_COMMAND_SECONDS; - if (!isMaxStartTimePassed) { - // Still waiting... - return State.STOP_BATTERY; + if (battery.isStopped()) { + return State.STOPPED; } - if (this.attemptCounter > GenericManagedEss.RETRY_COMMAND_MAX_ATTEMPTS) { - // Too many tries - ess._setMaxBatteryStopAttemptsFault(true); - return State.UNDEFINED; - - } else { - // Trying to stop Battery - context.battery.stop(); - - this.lastAttempt = Instant.now(); - this.attemptCounter++; - return State.STOP_BATTERY; + // Is max allowed start time passed ? + if (this.timeout.elapsed(context.clock)) { + ess._setTimeoutStopBattery(true); + return State.ERROR; } - } + battery.stop(); + return State.STOP_BATTERY; + } } diff --git a/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/StopBatteryInverterHandler.java b/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/StopBatteryInverterHandler.java index b6747fc14b6..e426221f92b 100644 --- a/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/StopBatteryInverterHandler.java +++ b/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/StopBatteryInverterHandler.java @@ -1,54 +1,39 @@ package io.openems.edge.ess.generic.symmetric.statemachine; -import java.time.Duration; -import java.time.Instant; - import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.timedata.Timeout; import io.openems.edge.common.statemachine.StateHandler; import io.openems.edge.ess.generic.common.GenericManagedEss; import io.openems.edge.ess.generic.symmetric.statemachine.StateMachine.State; public class StopBatteryInverterHandler extends StateHandler { - private Instant lastAttempt = Instant.MIN; - private int attemptCounter = 0; + private final Timeout timeout = Timeout.ofSeconds(GenericManagedEss.TIMEOUT); @Override protected void onEntry(Context context) throws OpenemsNamedException { - this.lastAttempt = Instant.MIN; - this.attemptCounter = 0; - var ess = context.getParent(); - ess._setMaxBatteryInverterStopAttemptsFault(false); + this.timeout.start(context.clock); } @Override public State runAndGetNextState(Context context) throws OpenemsNamedException { - var ess = context.getParent(); + final var ess = context.getParent(); + + if (context.hasEssFaults()) { + return State.ERROR; + } if (context.batteryInverter.isStopped()) { return State.STOP_BATTERY; } - var isMaxStartTimePassed = Duration.between(this.lastAttempt, Instant.now()) - .getSeconds() > GenericManagedEss.RETRY_COMMAND_SECONDS; - if (!isMaxStartTimePassed) { - // Still waiting... - return State.STOP_BATTERY_INVERTER; + // Is max allowed start time passed ? + if (this.timeout.elapsed(context.clock)) { + ess._setTimeoutStopBatteryInverter(true); + return State.ERROR; } - if (this.attemptCounter > GenericManagedEss.RETRY_COMMAND_MAX_ATTEMPTS) { - // Too many tries - ess._setMaxBatteryInverterStopAttemptsFault(true); - return State.UNDEFINED; - - } else { - // Trying to stop Battery Inverter - context.batteryInverter.stop(); - this.lastAttempt = Instant.now(); - this.attemptCounter++; - return State.STOP_BATTERY_INVERTER; - - } + context.batteryInverter.stop(); + return State.STOP_BATTERY_INVERTER; } - } diff --git a/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/StoppedHandler.java b/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/StoppedHandler.java index f098749f006..867a9bbe7c5 100644 --- a/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/StoppedHandler.java +++ b/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/StoppedHandler.java @@ -8,23 +8,17 @@ public class StoppedHandler extends StateHandler { @Override public State runAndGetNextState(Context context) { - var ess = context.getParent(); + final var ess = context.getParent(); - if (ess.hasFaults()) { - return State.UNDEFINED; + if (context.hasEssFaults()) { + return State.ERROR; } - if (!context.battery.isStopped()) { - return State.UNDEFINED; + if (!context.isEssStopped()) { + return State.ERROR; } - if (!context.batteryInverter.isStopped()) { - return State.UNDEFINED; - } - - // Mark as stopped ess._setStartStop(StartStop.STOP); - return State.STOPPED; } diff --git a/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/UndefinedHandler.java b/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/UndefinedHandler.java index 2075544f3ed..44d8c454b91 100644 --- a/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/UndefinedHandler.java +++ b/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/UndefinedHandler.java @@ -7,33 +7,17 @@ public class UndefinedHandler extends StateHandler { @Override public State runAndGetNextState(Context context) { - var ess = context.getParent(); - switch (ess.getStartStopTarget()) { - case UNDEFINED: - // Stuck in UNDEFINED State - return State.UNDEFINED; - - case START: - // force START - if (ess.hasFaults()) { - // TODO should we consider also Battery-Inverter and Battery Faults? - // TODO should the Modbus-Device also be on error, when then Modbus-Bridge is on - // error? - - // Has Faults -> error handling - return State.ERROR; - } else { - // No Faults -> start - return State.START_BATTERY; + final var ess = context.getParent(); + return switch (ess.getStartStopTarget()) { + case UNDEFINED -> State.UNDEFINED; + case START -> { + if (ess.hasFaults() || context.batteryInverter.hasFaults()) { + yield State.ERROR; } - - case STOP: - // force STOP - return State.STOP_BATTERY_INVERTER; + yield State.START_BATTERY; } - - assert false; - return State.UNDEFINED; // can never happen + case STOP -> State.STOP_BATTERY_INVERTER; + }; } } diff --git a/io.openems.edge.ess.generic/test/io/openems/edge/ess/generic/common/AllowedChargeDischargeHandlerTest.java b/io.openems.edge.ess.generic/test/io/openems/edge/ess/generic/common/AllowedChargeDischargeHandlerTest.java index 8d8c1e1a08d..890f328d8ba 100644 --- a/io.openems.edge.ess.generic/test/io/openems/edge/ess/generic/common/AllowedChargeDischargeHandlerTest.java +++ b/io.openems.edge.ess.generic/test/io/openems/edge/ess/generic/common/AllowedChargeDischargeHandlerTest.java @@ -1,10 +1,11 @@ package io.openems.edge.ess.generic.common; +import static java.time.temporal.ChronoUnit.MILLIS; +import static java.time.temporal.ChronoUnit.SECONDS; import static org.junit.Assert.assertEquals; import java.time.Instant; import java.time.ZoneOffset; -import java.time.temporal.ChronoUnit; import org.junit.Test; @@ -30,60 +31,60 @@ public void testStart() throws Exception { sut.calculateAllowedChargeDischargePower(clockProvider, false, null, null, null); assertEquals(0, sut.lastBatteryAllowedChargePower, 0.001); assertEquals(0, sut.lastBatteryAllowedDischargePower, 0.001); - clock.leap(1, ChronoUnit.SECONDS); + clock.leap(1, SECONDS); sut.calculateAllowedChargeDischargePower(clockProvider, true, null, null, null); assertEquals(0, sut.lastBatteryAllowedChargePower, 0.001); assertEquals(0, sut.lastBatteryAllowedDischargePower, 0.001); - clock.leap(1, ChronoUnit.SECONDS); + clock.leap(1, SECONDS); sut.calculateAllowedChargeDischargePower(clockProvider, true, 9, -1, 500); assertEquals(225, sut.lastBatteryAllowedChargePower, 0.001); assertEquals(-475, sut.lastBatteryAllowedDischargePower, 0.001); - clock.leap(250, ChronoUnit.MILLIS); + clock.leap(250, MILLIS); sut.calculateAllowedChargeDischargePower(clockProvider, true, 9, -1, 500); - clock.leap(250, ChronoUnit.MILLIS); + clock.leap(250, MILLIS); sut.calculateAllowedChargeDischargePower(clockProvider, true, 9, -1, 500); assertEquals(-475, sut.lastBatteryAllowedDischargePower, 0.001); - clock.leap(250, ChronoUnit.MILLIS); + clock.leap(250, MILLIS); sut.calculateAllowedChargeDischargePower(clockProvider, true, 9, 0, 500); - clock.leap(250, ChronoUnit.MILLIS); + clock.leap(250, MILLIS); sut.calculateAllowedChargeDischargePower(clockProvider, true, 9, 0, 500); assertEquals(450, sut.lastBatteryAllowedChargePower, 0.001); assertEquals(0, sut.lastBatteryAllowedDischargePower, 0.001); - clock.leap(1, ChronoUnit.SECONDS); + clock.leap(1, SECONDS); sut.calculateAllowedChargeDischargePower(clockProvider, true, 9, 0, 500); assertEquals(675, sut.lastBatteryAllowedChargePower, 0.001); assertEquals(0, sut.lastBatteryAllowedDischargePower, 0.001); for (var i = 0; i < 15; i++) { - clock.leap(1, ChronoUnit.SECONDS); + clock.leap(1, SECONDS); sut.calculateAllowedChargeDischargePower(clockProvider, true, 9, 1, 500); } - clock.leap(1, ChronoUnit.SECONDS); + clock.leap(1, SECONDS); sut.calculateAllowedChargeDischargePower(clockProvider, true, 9, 1, 500); assertEquals(4275, sut.lastBatteryAllowedChargePower, 0.001); assertEquals(380, sut.lastBatteryAllowedDischargePower, 0.001); - clock.leap(1, ChronoUnit.SECONDS); + clock.leap(1, SECONDS); sut.calculateAllowedChargeDischargePower(clockProvider, true, 9, 1, 500); assertEquals(4500, sut.lastBatteryAllowedChargePower, 0.001); assertEquals(403.75, sut.lastBatteryAllowedDischargePower, 0.001); - clock.leap(1, ChronoUnit.SECONDS); + clock.leap(1, SECONDS); sut.calculateAllowedChargeDischargePower(clockProvider, true, 9, 2, 500); assertEquals(4500, sut.lastBatteryAllowedChargePower, 0.001); assertEquals(451.25, sut.lastBatteryAllowedDischargePower, 0.001); - clock.leap(1, ChronoUnit.SECONDS); + clock.leap(1, SECONDS); sut.calculateAllowedChargeDischargePower(clockProvider, true, 2, 0, 500); assertEquals(1000, sut.lastBatteryAllowedChargePower, 0.001); assertEquals(0, sut.lastBatteryAllowedDischargePower, 0.001); - clock.leap(1, ChronoUnit.SECONDS); + clock.leap(1, SECONDS); sut.calculateAllowedChargeDischargePower(clockProvider, true, 9, 9, 500); assertEquals(1225, sut.lastBatteryAllowedChargePower, 0.001); assertEquals(213.75, sut.lastBatteryAllowedDischargePower, 0.001); diff --git a/io.openems.edge.ess.generic/test/io/openems/edge/ess/generic/offgrid/EssGenericOffGridImplTest.java b/io.openems.edge.ess.generic/test/io/openems/edge/ess/generic/offgrid/EssGenericOffGridImplTest.java index 5fa9e0840b5..8761b50b6ed 100644 --- a/io.openems.edge.ess.generic/test/io/openems/edge/ess/generic/offgrid/EssGenericOffGridImplTest.java +++ b/io.openems.edge.ess.generic/test/io/openems/edge/ess/generic/offgrid/EssGenericOffGridImplTest.java @@ -1,11 +1,7 @@ package io.openems.edge.ess.generic.offgrid; -import java.time.Instant; -import java.time.ZoneOffset; - import org.junit.Test; -import io.openems.common.test.TimeLeapClock; import io.openems.edge.battery.test.DummyBattery; import io.openems.edge.batteryinverter.test.DummyOffGridBatteryInverter; import io.openems.edge.common.startstop.StartStopConfig; @@ -16,26 +12,20 @@ public class EssGenericOffGridImplTest { - private static final String ESS_ID = "ess0"; - private static final String BATTERY_ID = "battery0"; - private static final String BATTERY_INVERTER_ID = "batteryInverter0"; - private static final String OFF_GRID_SWITCH_ID = "offGridSwitch0"; - @Test public void testStart() throws Exception { - final var clock = new TimeLeapClock(Instant.parse("2020-01-01T01:00:00.00Z"), ZoneOffset.UTC); new ComponentTest(new EssGenericOffGridImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("componentManager", new DummyComponentManager(clock)) // - .addReference("batteryInverter", new DummyOffGridBatteryInverter(BATTERY_INVERTER_ID)) // - .addReference("battery", new DummyBattery(BATTERY_ID)) // - .addReference("offGridSwitch", new DummyOffGridSwitch(OFF_GRID_SWITCH_ID)) // + .addReference("componentManager", new DummyComponentManager()) // + .addReference("batteryInverter", new DummyOffGridBatteryInverter("batteryInverter0")) // + .addReference("battery", new DummyBattery("battery0")) // + .addReference("offGridSwitch", new DummyOffGridSwitch("offGridSwitch0")) // .activate(MyConfig.create() // - .setId(ESS_ID) // + .setId("ess0") // .setStartStopConfig(StartStopConfig.START) // - .setBatteryInverterId(BATTERY_INVERTER_ID) // - .setBatteryId(BATTERY_ID) // - .setOffGridSwitchId(OFF_GRID_SWITCH_ID) // + .setBatteryInverterId("batteryInverter0") // + .setBatteryId("battery0") // + .setOffGridSwitchId("offGridSwitch0") // .build()) // ; } diff --git a/io.openems.edge.ess.generic/test/io/openems/edge/ess/generic/symmetric/EssGenericManagedSymmetricImplTest.java b/io.openems.edge.ess.generic/test/io/openems/edge/ess/generic/symmetric/EssGenericManagedSymmetricImplTest.java index ac2258c43ba..431c8e1d3c9 100644 --- a/io.openems.edge.ess.generic/test/io/openems/edge/ess/generic/symmetric/EssGenericManagedSymmetricImplTest.java +++ b/io.openems.edge.ess.generic/test/io/openems/edge/ess/generic/symmetric/EssGenericManagedSymmetricImplTest.java @@ -1,5 +1,12 @@ package io.openems.edge.ess.generic.symmetric; +import static io.openems.edge.battery.api.Battery.ChannelId.CHARGE_MAX_CURRENT; +import static io.openems.edge.battery.api.Battery.ChannelId.DISCHARGE_MAX_CURRENT; +import static io.openems.edge.batteryinverter.api.SymmetricBatteryInverter.ChannelId.ACTIVE_POWER; +import static io.openems.edge.common.startstop.StartStoppable.ChannelId.START_STOP; +import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.ALLOWED_DISCHARGE_POWER; +import static io.openems.edge.ess.generic.common.GenericManagedEss.EFFICIENCY_FACTOR; +import static io.openems.edge.ess.generic.symmetric.EssGenericManagedSymmetric.ChannelId.STATE_MACHINE; import static org.junit.Assert.assertEquals; import java.time.Instant; @@ -9,7 +16,6 @@ import org.junit.Test; import io.openems.common.test.TimeLeapClock; -import io.openems.common.types.ChannelAddress; import io.openems.edge.battery.test.DummyBattery; import io.openems.edge.batteryinverter.test.DummyManagedSymmetricBatteryInverter; import io.openems.edge.common.startstop.StartStop; @@ -18,57 +24,38 @@ import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyComponentManager; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.ess.generic.common.GenericManagedEss; import io.openems.edge.ess.generic.symmetric.statemachine.StateMachine.State; import io.openems.edge.ess.test.DummyPower; import io.openems.edge.ess.test.ManagedSymmetricEssTest; public class EssGenericManagedSymmetricImplTest { - private static final String ESS_ID = "ess0"; - private static final String BATTERY_ID = "battery0"; - private static final String BATTERY_INVERTER_ID = "batteryInverter0"; - - private static final ChannelAddress ESS_STATE_MACHINE = new ChannelAddress(ESS_ID, "StateMachine"); - private static final ChannelAddress ESS_ALLOWED_DISCHARGE_POWER = new ChannelAddress(ESS_ID, - "AllowedDischargePower"); - - private static final ChannelAddress BATTERY_START_STOP = new ChannelAddress(BATTERY_ID, "StartStop"); - private static final ChannelAddress BATTERY_CHARGE_MAX_CURRENT = new ChannelAddress(BATTERY_ID, "ChargeMaxCurrent"); - private static final ChannelAddress BATTERY_DISCHARGE_MAX_CURRENT = new ChannelAddress(BATTERY_ID, - "DischargeMaxCurrent"); - - private static final ChannelAddress BATTERY_INVERTER_START_STOP = new ChannelAddress(BATTERY_INVERTER_ID, - "StartStop"); - private static final ChannelAddress BATTERY_INVERTER_ACTIVE_POWER = new ChannelAddress(BATTERY_INVERTER_ID, - "ActivePower"); - @Test public void testStart() throws Exception { final var clock = new TimeLeapClock(Instant.parse("2020-01-01T01:00:00.00Z"), ZoneOffset.UTC); new ComponentTest(new EssGenericManagedSymmetricImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager(clock)) // - .addReference("batteryInverter", new DummyManagedSymmetricBatteryInverter(BATTERY_INVERTER_ID)) // - .addReference("battery", new DummyBattery(BATTERY_ID)) // + .addReference("batteryInverter", new DummyManagedSymmetricBatteryInverter("batteryInverter0")) // + .addReference("battery", new DummyBattery("battery0")) // .activate(MyConfig.create() // - .setId(ESS_ID) // + .setId("ess0") // .setStartStopConfig(StartStopConfig.START) // - .setBatteryInverterId(BATTERY_INVERTER_ID) // - .setBatteryId(BATTERY_ID) // + .setBatteryInverterId("batteryInverter0") // + .setBatteryId("battery0") // .build()) // .next(new TestCase() // - .output(ESS_STATE_MACHINE, State.UNDEFINED)) // + .output(STATE_MACHINE, State.UNDEFINED)) // .next(new TestCase() // - .output(ESS_STATE_MACHINE, State.START_BATTERY)) // + .output(STATE_MACHINE, State.START_BATTERY)) // .next(new TestCase("Start the Battery") // - .input(BATTERY_START_STOP, StartStop.START)) // + .input("battery0", START_STOP, StartStop.START)) // .next(new TestCase() // - .output(ESS_STATE_MACHINE, State.START_BATTERY_INVERTER)) // + .output(STATE_MACHINE, State.START_BATTERY_INVERTER)) // .next(new TestCase("Start the Battery-Inverter") // - .input(BATTERY_INVERTER_START_STOP, StartStop.START)) // + .input("batteryInverter0", START_STOP, StartStop.START)) // .next(new TestCase() // - .output(ESS_STATE_MACHINE, State.STARTED)) // + .output(STATE_MACHINE, State.STARTED)) // ; } @@ -78,30 +65,30 @@ public void testForceCharge() throws Exception { .addReference("power", new DummyPower()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // - .addReference("batteryInverter", new DummyManagedSymmetricBatteryInverter(BATTERY_INVERTER_ID)) // - .addReference("battery", new DummyBattery(BATTERY_ID) // + .addReference("batteryInverter", new DummyManagedSymmetricBatteryInverter("batteryInverter0")) // + .addReference("battery", new DummyBattery("battery0") // .withVoltage(500) // .withChargeMaxCurrent(50) // .withDischargeMaxCurrent(-5) // ) // .activate(MyConfig.create() // - .setId(ESS_ID) // + .setId("ess0") // .setStartStopConfig(StartStopConfig.START) // - .setBatteryInverterId(BATTERY_INVERTER_ID) // - .setBatteryId(BATTERY_ID) // + .setBatteryInverterId("batteryInverter0") // + .setBatteryId("battery0") // .build()) // .next(new TestCase("Start the Battery") // - .input(BATTERY_START_STOP, StartStop.START) // - .output(ESS_ALLOWED_DISCHARGE_POWER, 0) // - .output(BATTERY_INVERTER_ACTIVE_POWER, 0)) // + .input("battery0", START_STOP, StartStop.START) // + .output(ALLOWED_DISCHARGE_POWER, 0) // + .output(ACTIVE_POWER, 0)) // .next(new TestCase()) // .next(new TestCase("Start the Battery-Inverter") // - .input(BATTERY_INVERTER_START_STOP, StartStop.START)) // + .input("batteryInverter0", START_STOP, StartStop.START)) // .next(new TestCase()) // .next(new TestCase() // - .input(BATTERY_CHARGE_MAX_CURRENT, 50) // - .input(BATTERY_DISCHARGE_MAX_CURRENT, -5) // - .output(ESS_ALLOWED_DISCHARGE_POWER, (int) (-2500 * GenericManagedEss.EFFICIENCY_FACTOR))) // + .input("battery0", CHARGE_MAX_CURRENT, 50) // + .input("battery0", DISCHARGE_MAX_CURRENT, -5) // + .output(ALLOWED_DISCHARGE_POWER, (int) (-2500 * EFFICIENCY_FACTOR))) // ; } @@ -113,24 +100,55 @@ public void testDebugLog() throws Exception { .addReference("power", new DummyPower()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager(clock)) // - .addReference("batteryInverter", new DummyManagedSymmetricBatteryInverter(BATTERY_INVERTER_ID) // + .addReference("batteryInverter", new DummyManagedSymmetricBatteryInverter("batteryInverter0") // .withStartStop(StartStop.START) // .withMaxApparentPower(92_000)) // - .addReference("battery", new DummyBattery(BATTERY_ID) // + .addReference("battery", new DummyBattery("battery0") // .withStartStop(StartStop.START) // .withSoc(60) // .withVoltage(700) // .withChargeMaxCurrent(80) // .withDischargeMaxCurrent(70)) // .activate(MyConfig.create() // - .setId(ESS_ID) // + .setId("ess0") // .setStartStopConfig(StartStopConfig.START) // - .setBatteryInverterId(BATTERY_INVERTER_ID) // - .setBatteryId(BATTERY_ID) // + .setBatteryInverterId("batteryInverter0") // + .setBatteryId("battery0") // .build()) // .next(new TestCase() // .onBeforeProcessImage(() -> clock.leap(10, ChronoUnit.SECONDS)), 10); assertEquals("Started|SoC:60 %|L:0 W|Allowed:-56000;46550", sut.debugLog()); } + @Test + public void testTimeout() throws Exception { + final var clock = new TimeLeapClock(Instant.parse("2020-01-01T01:00:00.00Z"), ZoneOffset.UTC); + new ComponentTest(new EssGenericManagedSymmetricImpl()) // + .addReference("cm", new DummyConfigurationAdmin()) // + .addReference("componentManager", new DummyComponentManager(clock)) // + .addReference("batteryInverter", new DummyManagedSymmetricBatteryInverter("batteryInverter0")) // + .addReference("battery", new DummyBattery("battery0")) // + .activate(MyConfig.create() // + .setId("ess0") // + .setStartStopConfig(StartStopConfig.START) // + .setBatteryInverterId("batteryInverter0") // + .setBatteryId("battery0") // + .build()) // + .next(new TestCase() // + .output(STATE_MACHINE, State.UNDEFINED)) // + .next(new TestCase() // + .output(STATE_MACHINE, State.START_BATTERY)) // + .next(new TestCase("Start the Battery") // + .input("battery0", START_STOP, StartStop.START)) // + .next(new TestCase() // + .output(STATE_MACHINE, State.START_BATTERY_INVERTER)) // + .next(new TestCase()// + .input("batteryInverter0", START_STOP, StartStop.STOP)// + .timeleap(clock, 350, ChronoUnit.SECONDS)) // + .next(new TestCase() // + .output(STATE_MACHINE, State.ERROR)) // + .next(new TestCase() // + .output(STATE_MACHINE, State.ERROR)) // + ; + } } diff --git a/io.openems.edge.ess.generic/test/io/openems/edge/ess/generic/symmetric/EssProtectionTest.java b/io.openems.edge.ess.generic/test/io/openems/edge/ess/generic/symmetric/EssProtectionTest.java index 62959e6da20..d355eeee95c 100644 --- a/io.openems.edge.ess.generic/test/io/openems/edge/ess/generic/symmetric/EssProtectionTest.java +++ b/io.openems.edge.ess.generic/test/io/openems/edge/ess/generic/symmetric/EssProtectionTest.java @@ -1,15 +1,23 @@ package io.openems.edge.ess.generic.symmetric; +import static io.openems.edge.battery.api.Battery.ChannelId.CHARGE_MAX_CURRENT; +import static io.openems.edge.battery.api.Battery.ChannelId.CHARGE_MAX_VOLTAGE; +import static io.openems.edge.battery.api.Battery.ChannelId.DISCHARGE_MAX_CURRENT; +import static io.openems.edge.battery.api.Battery.ChannelId.DISCHARGE_MIN_VOLTAGE; +import static io.openems.edge.battery.api.Battery.ChannelId.SOC; +import static io.openems.edge.battery.api.Battery.ChannelId.VOLTAGE; +import static io.openems.edge.ess.generic.symmetric.EssProtection.ChannelId.EP_CHARGE_MAX_CURRENT; +import static io.openems.edge.ess.generic.symmetric.EssProtection.ChannelId.EP_DISCHARGE_MAX_CURRENT; +import static java.time.temporal.ChronoUnit.MINUTES; +import static java.time.temporal.ChronoUnit.SECONDS; import static org.junit.Assert.assertEquals; import java.time.Instant; import java.time.ZoneOffset; -import java.time.temporal.ChronoUnit; import org.junit.Test; import io.openems.common.test.TimeLeapClock; -import io.openems.common.types.ChannelAddress; import io.openems.edge.battery.test.DummyBattery; import io.openems.edge.batteryinverter.test.DummyManagedSymmetricBatteryInverter; import io.openems.edge.common.startstop.StartStop; @@ -22,30 +30,16 @@ public class EssProtectionTest { - private static final String ESS_ID = "ess0"; - private static final String BATTERY_ID = "battery0"; - private static final String BATTERY_INVERTER_ID = "batteryInverter0"; - private static final ChannelAddress BATTERY_SOC = new ChannelAddress(BATTERY_ID, "Soc"); - private static final ChannelAddress BATTERY_VOLTAGE = new ChannelAddress(BATTERY_ID, "Voltage"); - private static final ChannelAddress BATTERY_CHARGE_MAX_VOLTAGE = new ChannelAddress(BATTERY_ID, "ChargeMaxVoltage"); - private static final ChannelAddress BATTERY_DISCHARGE_MIN_VOLTAGE = new ChannelAddress(BATTERY_ID, - "DischargeMinVoltage"); - private static final ChannelAddress BATTERY_CHARGE_MAX_CURRENT = new ChannelAddress(BATTERY_ID, "ChargeMaxCurrent"); - private static final ChannelAddress BATTERY_DISCHARGE_MAX_CURRENT = new ChannelAddress(BATTERY_ID, - "DischargeMaxCurrent"); - private static final ChannelAddress ESS_CHARGE_MAX_CURRENT = new ChannelAddress(ESS_ID, "EpChargeMaxCurrent"); - private static final ChannelAddress ESS_DISCHARGE_MAX_CURRENT = new ChannelAddress(ESS_ID, "EpDischargeMaxCurrent"); - @Test public void testEssProtection() throws Exception { final var ess = new EssGenericManagedSymmetricImpl(); final var clock = new TimeLeapClock(Instant.parse("2020-01-01T01:00:00.00Z"), ZoneOffset.UTC); - final var batteryInverter = new DummyManagedSymmetricBatteryInverter(BATTERY_INVERTER_ID)// + final var batteryInverter = new DummyManagedSymmetricBatteryInverter("batteryInverter0")// .withStartStop(StartStop.START) // .withMaxApparentPower(92000)// .withDcMaxVoltage(1315)// .withDcMinVoltage(650); - final var battery = new DummyBattery(BATTERY_ID)// + final var battery = new DummyBattery("battery0")// .withStartStop(StartStop.START) // .withSoc(80)// .withChargeMaxCurrent(169)// @@ -62,64 +56,64 @@ public void testEssProtection() throws Exception { .addReference("batteryInverter", batteryInverter) // .addReference("battery", battery) // .activate(MyConfig.create() // - .setId(ESS_ID) // + .setId("ess0") // .setStartStopConfig(StartStopConfig.START) // - .setBatteryInverterId(BATTERY_INVERTER_ID) // - .setBatteryId(BATTERY_ID) // + .setBatteryInverterId("batteryInverter0") // + .setBatteryId("battery0") // .build()) // .next(new TestCase() // - .onBeforeProcessImage(() -> clock.leap(1, ChronoUnit.MINUTES)), 10)// - .next(new TestCase()// - .input(BATTERY_VOLTAGE, 700)// - .input(BATTERY_SOC, 80)// - .input(BATTERY_CHARGE_MAX_CURRENT, 169)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 169)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594))// + .onBeforeProcessImage(() -> clock.leap(1, MINUTES)), 10)// + .next(new TestCase()// + .input("battery0", VOLTAGE, 700)// + .input("battery0", SOC, 80)// + .input("battery0", CHARGE_MAX_CURRENT, 169)// + .input("battery0", DISCHARGE_MAX_CURRENT, 169)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594))// .next(new TestCase() // - .onBeforeProcessImage(() -> clock.leap(1, ChronoUnit.MINUTES)), 10)// - .next(new TestCase()// - .input(BATTERY_VOLTAGE, 700)// - .input(BATTERY_SOC, 80)// - .input(BATTERY_CHARGE_MAX_CURRENT, 169)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 169)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594))// + .onBeforeProcessImage(() -> clock.leap(1, MINUTES)), 10)// + .next(new TestCase()// + .input("battery0", VOLTAGE, 700)// + .input("battery0", SOC, 80)// + .input("battery0", CHARGE_MAX_CURRENT, 169)// + .input("battery0", DISCHARGE_MAX_CURRENT, 169)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594))// .next(new TestCase() // - .onBeforeProcessImage(() -> clock.leap(1, ChronoUnit.MINUTES)), 10)// - .next(new TestCase()// - .input(BATTERY_VOLTAGE, 700)// - .input(BATTERY_SOC, 80)// - .input(BATTERY_CHARGE_MAX_CURRENT, 169)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 169)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594))// + .onBeforeProcessImage(() -> clock.leap(1, MINUTES)), 10)// + .next(new TestCase()// + .input("battery0", VOLTAGE, 700)// + .input("battery0", SOC, 80)// + .input("battery0", CHARGE_MAX_CURRENT, 169)// + .input("battery0", DISCHARGE_MAX_CURRENT, 169)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594))// ;// assertEquals("Started|SoC:80 %|L:0 W|Allowed:-92000;92000", ess.debugLog()); sutManaged// .next(new TestCase()// - .timeleap(clock, 1, ChronoUnit.MINUTES))// + .timeleap(clock, 1, MINUTES))// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 700)// - .input(BATTERY_SOC, 60)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 595)// + .input("battery0", VOLTAGE, 700)// + .input("battery0", SOC, 60)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 595)// )// .next(new TestCase()// - .timeleap(clock, 1, ChronoUnit.SECONDS))// + .timeleap(clock, 1, SECONDS))// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 700)// - .input(BATTERY_SOC, 60)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 595)// + .input("battery0", VOLTAGE, 700)// + .input("battery0", SOC, 60)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 595)// )// .next(new TestCase()// - .timeleap(clock, 1, ChronoUnit.SECONDS))// + .timeleap(clock, 1, SECONDS))// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 700)// - .input(BATTERY_SOC, 60)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 595)// + .input("battery0", VOLTAGE, 700)// + .input("battery0", SOC, 60)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 595)// )// ;// assertEquals("Started|SoC:60 %|L:0 W|Allowed:-92000;92000", ess.debugLog()); @@ -127,143 +121,143 @@ public void testEssProtection() throws Exception { // Force charge sutManaged// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 645)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 0)// - .output(ESS_DISCHARGE_MAX_CURRENT, 167)// + .input("battery0", VOLTAGE, 645)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, 0)// + .output(EP_DISCHARGE_MAX_CURRENT, 167)// )// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 645)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_CHARGE_MAX_CURRENT, 169)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 0)// - .output(ESS_DISCHARGE_MAX_CURRENT, 112)// + .input("battery0", VOLTAGE, 645)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", CHARGE_MAX_CURRENT, 169)// + .input("battery0", DISCHARGE_MAX_CURRENT, 0)// + .output(EP_DISCHARGE_MAX_CURRENT, 112)// )// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 645)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_CHARGE_MAX_CURRENT, 169)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 0)// - .output(ESS_DISCHARGE_MAX_CURRENT, 75)// + .input("battery0", VOLTAGE, 645)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", CHARGE_MAX_CURRENT, 169)// + .input("battery0", DISCHARGE_MAX_CURRENT, 0)// + .output(EP_DISCHARGE_MAX_CURRENT, 75)// )// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 645)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_CHARGE_MAX_CURRENT, 169)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 0)// - .output(ESS_DISCHARGE_MAX_CURRENT, 49)// + .input("battery0", VOLTAGE, 645)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", CHARGE_MAX_CURRENT, 169)// + .input("battery0", DISCHARGE_MAX_CURRENT, 0)// + .output(EP_DISCHARGE_MAX_CURRENT, 49)// )// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 645)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_CHARGE_MAX_CURRENT, 169)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 0)// - .output(ESS_DISCHARGE_MAX_CURRENT, 32)// + .input("battery0", VOLTAGE, 645)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", CHARGE_MAX_CURRENT, 169)// + .input("battery0", DISCHARGE_MAX_CURRENT, 0)// + .output(EP_DISCHARGE_MAX_CURRENT, 32)// )// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 645)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_CHARGE_MAX_CURRENT, 169)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 0)// - .output(ESS_DISCHARGE_MAX_CURRENT, 20)// + .input("battery0", VOLTAGE, 645)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", CHARGE_MAX_CURRENT, 169)// + .input("battery0", DISCHARGE_MAX_CURRENT, 0)// + .output(EP_DISCHARGE_MAX_CURRENT, 20)// )// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 645)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_CHARGE_MAX_CURRENT, 169)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 0)// - .output(ESS_DISCHARGE_MAX_CURRENT, 12)// + .input("battery0", VOLTAGE, 645)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", CHARGE_MAX_CURRENT, 169)// + .input("battery0", DISCHARGE_MAX_CURRENT, 0)// + .output(EP_DISCHARGE_MAX_CURRENT, 12)// )// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 645)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_CHARGE_MAX_CURRENT, 169)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 0)// - .output(ESS_DISCHARGE_MAX_CURRENT, 6)// + .input("battery0", VOLTAGE, 645)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", CHARGE_MAX_CURRENT, 169)// + .input("battery0", DISCHARGE_MAX_CURRENT, 0)// + .output(EP_DISCHARGE_MAX_CURRENT, 6)// )// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 645)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_CHARGE_MAX_CURRENT, 169)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 0)// - .output(ESS_DISCHARGE_MAX_CURRENT, 3)// + .input("battery0", VOLTAGE, 645)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", CHARGE_MAX_CURRENT, 169)// + .input("battery0", DISCHARGE_MAX_CURRENT, 0)// + .output(EP_DISCHARGE_MAX_CURRENT, 3)// )// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 645)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_CHARGE_MAX_CURRENT, 169)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 0)// - .output(ESS_DISCHARGE_MAX_CURRENT, 0)// + .input("battery0", VOLTAGE, 645)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", CHARGE_MAX_CURRENT, 169)// + .input("battery0", DISCHARGE_MAX_CURRENT, 0)// + .output(EP_DISCHARGE_MAX_CURRENT, 0)// )// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 645)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 0)// - .output(ESS_DISCHARGE_MAX_CURRENT, 0)// + .input("battery0", VOLTAGE, 645)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, 0)// + .output(EP_DISCHARGE_MAX_CURRENT, 0)// )// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 645)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 0)// - .output(ESS_DISCHARGE_MAX_CURRENT, -1)// + .input("battery0", VOLTAGE, 645)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, 0)// + .output(EP_DISCHARGE_MAX_CURRENT, -1)// )// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 645)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 0)// - .output(ESS_DISCHARGE_MAX_CURRENT, -2)// + .input("battery0", VOLTAGE, 645)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, 0)// + .output(EP_DISCHARGE_MAX_CURRENT, -2)// )// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 650)// - .input(BATTERY_SOC, 0)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .output(ESS_DISCHARGE_MAX_CURRENT, -2)// + .input("battery0", VOLTAGE, 650)// + .input("battery0", SOC, 0)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .output(EP_DISCHARGE_MAX_CURRENT, -2)// )// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 650)// - .input(BATTERY_SOC, 0)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, -2)// - .output(ESS_DISCHARGE_MAX_CURRENT, -1)// + .input("battery0", VOLTAGE, 650)// + .input("battery0", SOC, 0)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, -2)// + .output(EP_DISCHARGE_MAX_CURRENT, -1)// )// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 650)// - .input(BATTERY_SOC, 0)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, -2)// - .output(ESS_DISCHARGE_MAX_CURRENT, -1)// + .input("battery0", VOLTAGE, 650)// + .input("battery0", SOC, 0)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, -2)// + .output(EP_DISCHARGE_MAX_CURRENT, -1)// )// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 650)// - .input(BATTERY_SOC, 0)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, -2)// - .output(ESS_DISCHARGE_MAX_CURRENT, 0)// + .input("battery0", VOLTAGE, 650)// + .input("battery0", SOC, 0)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, -2)// + .output(EP_DISCHARGE_MAX_CURRENT, 0)// )// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 650)// - .input(BATTERY_SOC, 0)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, -2)// - .output(ESS_DISCHARGE_MAX_CURRENT, 0)// + .input("battery0", VOLTAGE, 650)// + .input("battery0", SOC, 0)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, -2)// + .output(EP_DISCHARGE_MAX_CURRENT, 0)// )// ;// assertEquals("Started|SoC:0 %|L:-1235 W|Allowed:-92000;-1235", ess.debugLog()); @@ -271,197 +265,197 @@ public void testEssProtection() throws Exception { // normal condition sutManaged// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 700)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 169)// - .input(BATTERY_CHARGE_MAX_CURRENT, 169)// - .output(ESS_CHARGE_MAX_CURRENT, 654)// + .input("battery0", VOLTAGE, 700)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, 169)// + .input("battery0", CHARGE_MAX_CURRENT, 169)// + .output(EP_CHARGE_MAX_CURRENT, 654)// )// .next(new TestCase()// - .timeleap(clock, 1, ChronoUnit.MINUTES))// + .timeleap(clock, 1, MINUTES))// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 700)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 169)// - .input(BATTERY_CHARGE_MAX_CURRENT, 169)// - .output(ESS_CHARGE_MAX_CURRENT, 599)// + .input("battery0", VOLTAGE, 700)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, 169)// + .input("battery0", CHARGE_MAX_CURRENT, 169)// + .output(EP_CHARGE_MAX_CURRENT, 599)// )// .next(new TestCase()// - .timeleap(clock, 1, ChronoUnit.MINUTES))// + .timeleap(clock, 1, MINUTES))// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 700)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 169)// - .input(BATTERY_CHARGE_MAX_CURRENT, 169)// - .output(ESS_CHARGE_MAX_CURRENT, 561)// + .input("battery0", VOLTAGE, 700)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, 169)// + .input("battery0", CHARGE_MAX_CURRENT, 169)// + .output(EP_CHARGE_MAX_CURRENT, 561)// )// .next(new TestCase()// - .timeleap(clock, 1, ChronoUnit.MINUTES))// + .timeleap(clock, 1, MINUTES))// ;// // Force discharge sutManaged// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 796)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 169)// - .input(BATTERY_CHARGE_MAX_CURRENT, 0)// - .output(ESS_CHARGE_MAX_CURRENT, 383)// + .input("battery0", VOLTAGE, 796)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, 169)// + .input("battery0", CHARGE_MAX_CURRENT, 0)// + .output(EP_CHARGE_MAX_CURRENT, 383)// )// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 796)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 169)// - .input(BATTERY_CHARGE_MAX_CURRENT, 0)// - .output(ESS_CHARGE_MAX_CURRENT, 261)// + .input("battery0", VOLTAGE, 796)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, 169)// + .input("battery0", CHARGE_MAX_CURRENT, 0)// + .output(EP_CHARGE_MAX_CURRENT, 261)// )// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 796)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 169)// - .input(BATTERY_CHARGE_MAX_CURRENT, 0)// - .output(ESS_CHARGE_MAX_CURRENT, 178)// + .input("battery0", VOLTAGE, 796)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, 169)// + .input("battery0", CHARGE_MAX_CURRENT, 0)// + .output(EP_CHARGE_MAX_CURRENT, 178)// )// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 796)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 169)// - .input(BATTERY_CHARGE_MAX_CURRENT, 0)// - .output(ESS_CHARGE_MAX_CURRENT, 122)// + .input("battery0", VOLTAGE, 796)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, 169)// + .input("battery0", CHARGE_MAX_CURRENT, 0)// + .output(EP_CHARGE_MAX_CURRENT, 122)// )// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 796)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 169)// - .input(BATTERY_CHARGE_MAX_CURRENT, 0)// - .output(ESS_CHARGE_MAX_CURRENT, 83)// - )// + .input("battery0", VOLTAGE, 796)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, 169)// + .input("battery0", CHARGE_MAX_CURRENT, 0)// + .output(EP_CHARGE_MAX_CURRENT, 83)// + )// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 796)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 169)// - .input(BATTERY_CHARGE_MAX_CURRENT, 0)// - .output(ESS_CHARGE_MAX_CURRENT, 57)// - )// - .next(new TestCase()// - .input(BATTERY_VOLTAGE, 796)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 169)// - .input(BATTERY_CHARGE_MAX_CURRENT, 0)// - .output(ESS_CHARGE_MAX_CURRENT, 38)// - )// - .next(new TestCase()// - .input(BATTERY_VOLTAGE, 796)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 169)// - .input(BATTERY_CHARGE_MAX_CURRENT, 0)// - .output(ESS_CHARGE_MAX_CURRENT, 26)// - )// - .next(new TestCase()// - .input(BATTERY_VOLTAGE, 796)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 169)// - .input(BATTERY_CHARGE_MAX_CURRENT, 0)// - .output(ESS_CHARGE_MAX_CURRENT, 18)// - )// - .next(new TestCase()// - .input(BATTERY_VOLTAGE, 796)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 169)// - .input(BATTERY_CHARGE_MAX_CURRENT, 0)// - .output(ESS_CHARGE_MAX_CURRENT, 12)// - )// - .next(new TestCase()// - .input(BATTERY_VOLTAGE, 796)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 169)// - .input(BATTERY_CHARGE_MAX_CURRENT, 0)// - .output(ESS_CHARGE_MAX_CURRENT, 8)// - )// - .next(new TestCase()// - .input(BATTERY_VOLTAGE, 796)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 169)// - .input(BATTERY_CHARGE_MAX_CURRENT, 0)// - .output(ESS_CHARGE_MAX_CURRENT, 5)// - )// - .next(new TestCase()// - .input(BATTERY_VOLTAGE, 796)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 169)// - .input(BATTERY_CHARGE_MAX_CURRENT, 0)// - .output(ESS_CHARGE_MAX_CURRENT, 3)// - )// - .next(new TestCase()// - .input(BATTERY_VOLTAGE, 798)// - .input(BATTERY_SOC, 100)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 169)// - .input(BATTERY_CHARGE_MAX_CURRENT, 0)// - .output(ESS_CHARGE_MAX_CURRENT, 1)// - )// - .next(new TestCase()// - .input(BATTERY_VOLTAGE, 798)// - .input(BATTERY_SOC, 100)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 169)// - .input(BATTERY_CHARGE_MAX_CURRENT, 0)// - .output(ESS_CHARGE_MAX_CURRENT, 0)// - )// - .next(new TestCase()// - .input(BATTERY_VOLTAGE, 798)// - .input(BATTERY_SOC, 100)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 169)// - .input(BATTERY_CHARGE_MAX_CURRENT, 0)// - .output(ESS_CHARGE_MAX_CURRENT, -2)// - )// - .next(new TestCase()// - .input(BATTERY_VOLTAGE, 796)// - .input(BATTERY_SOC, 100)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 169)// - .input(BATTERY_CHARGE_MAX_CURRENT, 0)// - .output(ESS_CHARGE_MAX_CURRENT, -1)// - )// - .next(new TestCase()// - .input(BATTERY_VOLTAGE, 796)// - .input(BATTERY_SOC, 100)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 169)// - .input(BATTERY_CHARGE_MAX_CURRENT, 0)// - .output(ESS_CHARGE_MAX_CURRENT, -1)// - )// - .next(new TestCase()// - .input(BATTERY_VOLTAGE, 796)// - .input(BATTERY_SOC, 100)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 169)// - .input(BATTERY_CHARGE_MAX_CURRENT, 0)// - .output(ESS_CHARGE_MAX_CURRENT, 0)// + .input("battery0", VOLTAGE, 796)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, 169)// + .input("battery0", CHARGE_MAX_CURRENT, 0)// + .output(EP_CHARGE_MAX_CURRENT, 57)// + )// + .next(new TestCase()// + .input("battery0", VOLTAGE, 796)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, 169)// + .input("battery0", CHARGE_MAX_CURRENT, 0)// + .output(EP_CHARGE_MAX_CURRENT, 38)// + )// + .next(new TestCase()// + .input("battery0", VOLTAGE, 796)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, 169)// + .input("battery0", CHARGE_MAX_CURRENT, 0)// + .output(EP_CHARGE_MAX_CURRENT, 26)// + )// + .next(new TestCase()// + .input("battery0", VOLTAGE, 796)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, 169)// + .input("battery0", CHARGE_MAX_CURRENT, 0)// + .output(EP_CHARGE_MAX_CURRENT, 18)// + )// + .next(new TestCase()// + .input("battery0", VOLTAGE, 796)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, 169)// + .input("battery0", CHARGE_MAX_CURRENT, 0)// + .output(EP_CHARGE_MAX_CURRENT, 12)// + )// + .next(new TestCase()// + .input("battery0", VOLTAGE, 796)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, 169)// + .input("battery0", CHARGE_MAX_CURRENT, 0)// + .output(EP_CHARGE_MAX_CURRENT, 8)// + )// + .next(new TestCase()// + .input("battery0", VOLTAGE, 796)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, 169)// + .input("battery0", CHARGE_MAX_CURRENT, 0)// + .output(EP_CHARGE_MAX_CURRENT, 5)// + )// + .next(new TestCase()// + .input("battery0", VOLTAGE, 796)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, 169)// + .input("battery0", CHARGE_MAX_CURRENT, 0)// + .output(EP_CHARGE_MAX_CURRENT, 3)// + )// + .next(new TestCase()// + .input("battery0", VOLTAGE, 798)// + .input("battery0", SOC, 100)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, 169)// + .input("battery0", CHARGE_MAX_CURRENT, 0)// + .output(EP_CHARGE_MAX_CURRENT, 1)// + )// + .next(new TestCase()// + .input("battery0", VOLTAGE, 798)// + .input("battery0", SOC, 100)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, 169)// + .input("battery0", CHARGE_MAX_CURRENT, 0)// + .output(EP_CHARGE_MAX_CURRENT, 0)// + )// + .next(new TestCase()// + .input("battery0", VOLTAGE, 798)// + .input("battery0", SOC, 100)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, 169)// + .input("battery0", CHARGE_MAX_CURRENT, 0)// + .output(EP_CHARGE_MAX_CURRENT, -2)// + )// + .next(new TestCase()// + .input("battery0", VOLTAGE, 796)// + .input("battery0", SOC, 100)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, 169)// + .input("battery0", CHARGE_MAX_CURRENT, 0)// + .output(EP_CHARGE_MAX_CURRENT, -1)// + )// + .next(new TestCase()// + .input("battery0", VOLTAGE, 796)// + .input("battery0", SOC, 100)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, 169)// + .input("battery0", CHARGE_MAX_CURRENT, 0)// + .output(EP_CHARGE_MAX_CURRENT, -1)// + )// + .next(new TestCase()// + .input("battery0", VOLTAGE, 796)// + .input("battery0", SOC, 100)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, 169)// + .input("battery0", CHARGE_MAX_CURRENT, 0)// + .output(EP_CHARGE_MAX_CURRENT, 0)// )// ;// assertEquals("Started|SoC:100 %|L:796 W|Allowed:796;92000", ess.debugLog()); diff --git a/io.openems.edge.ess.samsung/.classpath b/io.openems.edge.ess.samsung/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.ess.samsung/.classpath +++ b/io.openems.edge.ess.samsung/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.ess.samsung/src/io/openems/edge/ess/samsung/charger/Config.java b/io.openems.edge.ess.samsung/src/io/openems/edge/ess/samsung/charger/Config.java index 60b745cf475..68f1c23dee1 100644 --- a/io.openems.edge.ess.samsung/src/io/openems/edge/ess/samsung/charger/Config.java +++ b/io.openems.edge.ess.samsung/src/io/openems/edge/ess/samsung/charger/Config.java @@ -3,7 +3,7 @@ import org.osgi.service.metatype.annotations.AttributeDefinition; import org.osgi.service.metatype.annotations.ObjectClassDefinition; -import io.openems.edge.meter.api.MeterType; +import io.openems.common.types.MeterType; @ObjectClassDefinition(// name = "Samsung ESS Charger", // diff --git a/io.openems.edge.ess.samsung/src/io/openems/edge/ess/samsung/charger/SamsungEssChargerImpl.java b/io.openems.edge.ess.samsung/src/io/openems/edge/ess/samsung/charger/SamsungEssChargerImpl.java index 8b6af1cabf7..d9afe418c2c 100644 --- a/io.openems.edge.ess.samsung/src/io/openems/edge/ess/samsung/charger/SamsungEssChargerImpl.java +++ b/io.openems.edge.ess.samsung/src/io/openems/edge/ess/samsung/charger/SamsungEssChargerImpl.java @@ -24,6 +24,7 @@ import com.google.gson.JsonElement; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.types.MeterType; import io.openems.edge.bridge.http.api.BridgeHttp; import io.openems.edge.bridge.http.api.BridgeHttpFactory; import io.openems.edge.bridge.http.api.HttpError; @@ -32,7 +33,6 @@ import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.event.EdgeEventConstants; import io.openems.edge.meter.api.ElectricityMeter; -import io.openems.edge.meter.api.MeterType; import io.openems.edge.pvinverter.api.ManagedSymmetricPvInverter; import io.openems.edge.timedata.api.Timedata; import io.openems.edge.timedata.api.TimedataProvider; diff --git a/io.openems.edge.ess.samsung/src/io/openems/edge/ess/samsung/gridmeter/Config.java b/io.openems.edge.ess.samsung/src/io/openems/edge/ess/samsung/gridmeter/Config.java index 23a064d4734..c7faee5077f 100644 --- a/io.openems.edge.ess.samsung/src/io/openems/edge/ess/samsung/gridmeter/Config.java +++ b/io.openems.edge.ess.samsung/src/io/openems/edge/ess/samsung/gridmeter/Config.java @@ -3,7 +3,7 @@ import org.osgi.service.metatype.annotations.AttributeDefinition; import org.osgi.service.metatype.annotations.ObjectClassDefinition; -import io.openems.edge.meter.api.MeterType; +import io.openems.common.types.MeterType; @ObjectClassDefinition(// name = "Samsung ESS Grid-Meter", // diff --git a/io.openems.edge.ess.samsung/src/io/openems/edge/ess/samsung/gridmeter/SamsungEssGridMeterImpl.java b/io.openems.edge.ess.samsung/src/io/openems/edge/ess/samsung/gridmeter/SamsungEssGridMeterImpl.java index aafac47ce2f..0e76402728e 100644 --- a/io.openems.edge.ess.samsung/src/io/openems/edge/ess/samsung/gridmeter/SamsungEssGridMeterImpl.java +++ b/io.openems.edge.ess.samsung/src/io/openems/edge/ess/samsung/gridmeter/SamsungEssGridMeterImpl.java @@ -25,6 +25,7 @@ import com.google.gson.JsonElement; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.types.MeterType; import io.openems.edge.bridge.http.api.BridgeHttp; import io.openems.edge.bridge.http.api.BridgeHttpFactory; import io.openems.edge.bridge.http.api.HttpError; @@ -33,7 +34,6 @@ import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.event.EdgeEventConstants; import io.openems.edge.meter.api.ElectricityMeter; -import io.openems.edge.meter.api.MeterType; import io.openems.edge.timedata.api.Timedata; import io.openems.edge.timedata.api.TimedataProvider; import io.openems.edge.timedata.api.utils.CalculateEnergyFromPower; diff --git a/io.openems.edge.ess.samsung/test/io/openems/edge/ess/samsung/charger/MyConfig.java b/io.openems.edge.ess.samsung/test/io/openems/edge/ess/samsung/charger/MyConfig.java index 643021116ee..9ae694b3801 100644 --- a/io.openems.edge.ess.samsung/test/io/openems/edge/ess/samsung/charger/MyConfig.java +++ b/io.openems.edge.ess.samsung/test/io/openems/edge/ess/samsung/charger/MyConfig.java @@ -1,7 +1,7 @@ package io.openems.edge.ess.samsung.charger; import io.openems.common.test.AbstractComponentConfig; -import io.openems.edge.meter.api.MeterType; +import io.openems.common.types.MeterType; @SuppressWarnings("all") public class MyConfig extends AbstractComponentConfig implements Config { diff --git a/io.openems.edge.ess.samsung/test/io/openems/edge/ess/samsung/charger/SamsungEssChargerImplTest.java b/io.openems.edge.ess.samsung/test/io/openems/edge/ess/samsung/charger/SamsungEssChargerImplTest.java index ba3891b1616..181b230fb4a 100644 --- a/io.openems.edge.ess.samsung/test/io/openems/edge/ess/samsung/charger/SamsungEssChargerImplTest.java +++ b/io.openems.edge.ess.samsung/test/io/openems/edge/ess/samsung/charger/SamsungEssChargerImplTest.java @@ -2,24 +2,24 @@ import org.junit.Test; +import io.openems.common.types.MeterType; import io.openems.edge.bridge.http.dummy.DummyBridgeHttpFactory; +import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; -import io.openems.edge.meter.api.MeterType; public class SamsungEssChargerImplTest { - private static final String COMPONENT_ID = "charger0"; - @Test public void test() throws Exception { new ComponentTest(new SamsungEssChargerImpl()) // .addReference("httpBridgeFactory", DummyBridgeHttpFactory.ofDummyBridge()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("charger0") // .setIp("127.0.0.1") // .setType(MeterType.PRODUCTION) // .build()) // - ; + .next(new TestCase()) // + .deactivate(); } } \ No newline at end of file diff --git a/io.openems.edge.ess.samsung/test/io/openems/edge/ess/samsung/ess/SamsungEssImplTest.java b/io.openems.edge.ess.samsung/test/io/openems/edge/ess/samsung/ess/SamsungEssImplTest.java index c67ca193fad..a1bce0a1ae1 100644 --- a/io.openems.edge.ess.samsung/test/io/openems/edge/ess/samsung/ess/SamsungEssImplTest.java +++ b/io.openems.edge.ess.samsung/test/io/openems/edge/ess/samsung/ess/SamsungEssImplTest.java @@ -3,24 +3,24 @@ import org.junit.Test; import io.openems.edge.bridge.http.dummy.DummyBridgeHttpFactory; +import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.ess.power.api.Phase; public class SamsungEssImplTest { - private static final String COMPONENT_ID = "charger0"; - @Test public void test() throws Exception { new ComponentTest(new SamsungEssImpl()) // .addReference("httpBridgeFactory", DummyBridgeHttpFactory.ofDummyBridge()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("charger0") // .setIp("127.0.0.1") // .setPhase(Phase.L1) // .setCapacity(3600) // .build()) // - ; + .next(new TestCase()) // + .deactivate(); } } \ No newline at end of file diff --git a/io.openems.edge.ess.samsung/test/io/openems/edge/ess/samsung/gridmeter/MyConfig.java b/io.openems.edge.ess.samsung/test/io/openems/edge/ess/samsung/gridmeter/MyConfig.java index fc924bc6e93..87838237998 100644 --- a/io.openems.edge.ess.samsung/test/io/openems/edge/ess/samsung/gridmeter/MyConfig.java +++ b/io.openems.edge.ess.samsung/test/io/openems/edge/ess/samsung/gridmeter/MyConfig.java @@ -1,7 +1,7 @@ package io.openems.edge.ess.samsung.gridmeter; import io.openems.common.test.AbstractComponentConfig; -import io.openems.edge.meter.api.MeterType; +import io.openems.common.types.MeterType; @SuppressWarnings("all") public class MyConfig extends AbstractComponentConfig implements Config { diff --git a/io.openems.edge.ess.samsung/test/io/openems/edge/ess/samsung/gridmeter/SamsungEssGridMeterImplTest.java b/io.openems.edge.ess.samsung/test/io/openems/edge/ess/samsung/gridmeter/SamsungEssGridMeterImplTest.java index 50d708ab648..e9933f4e60c 100644 --- a/io.openems.edge.ess.samsung/test/io/openems/edge/ess/samsung/gridmeter/SamsungEssGridMeterImplTest.java +++ b/io.openems.edge.ess.samsung/test/io/openems/edge/ess/samsung/gridmeter/SamsungEssGridMeterImplTest.java @@ -2,24 +2,24 @@ import org.junit.Test; +import io.openems.common.types.MeterType; import io.openems.edge.bridge.http.dummy.DummyBridgeHttpFactory; +import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; -import io.openems.edge.meter.api.MeterType; public class SamsungEssGridMeterImplTest { - private static final String COMPONENT_ID = "charger0"; - @Test public void test() throws Exception { new ComponentTest(new SamsungEssGridMeterImpl()) // .addReference("httpBridgeFactory", DummyBridgeHttpFactory.ofDummyBridge()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("charger0") // .setIp("127.0.0.1") // .setType(MeterType.GRID) // .build()) // - ; + .next(new TestCase()) // + .deactivate(); } } \ No newline at end of file diff --git a/io.openems.edge.ess.sma/.classpath b/io.openems.edge.ess.sma/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.ess.sma/.classpath +++ b/io.openems.edge.ess.sma/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.ess.sma/test/io/openems/edge/sma/sunnyisland/EssSmaSunnyIslandImplTest.java b/io.openems.edge.ess.sma/test/io/openems/edge/sma/sunnyisland/EssSmaSunnyIslandImplTest.java index fcee18e6c8e..b420b8a95c5 100644 --- a/io.openems.edge.ess.sma/test/io/openems/edge/sma/sunnyisland/EssSmaSunnyIslandImplTest.java +++ b/io.openems.edge.ess.sma/test/io/openems/edge/sma/sunnyisland/EssSmaSunnyIslandImplTest.java @@ -9,17 +9,14 @@ public class EssSmaSunnyIslandImplTest { - private static final String ESS_ID = "ess0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ManagedSymmetricEssTest(new EssSmaSunnyIslandImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(ESS_ID) // - .setModbusId(MODBUS_ID) // + .setId("ess0") // + .setModbusId("modbus0") // .setPhase(Phase.L1) // .build()) // ; diff --git a/io.openems.edge.evcs.alpitronic.hypercharger/.classpath b/io.openems.edge.evcs.alpitronic.hypercharger/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.evcs.alpitronic.hypercharger/.classpath +++ b/io.openems.edge.evcs.alpitronic.hypercharger/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.evcs.alpitronic.hypercharger/bnd.bnd b/io.openems.edge.evcs.alpitronic.hypercharger/bnd.bnd index 865db1aa41d..d1cfcf3a7ad 100644 --- a/io.openems.edge.evcs.alpitronic.hypercharger/bnd.bnd +++ b/io.openems.edge.evcs.alpitronic.hypercharger/bnd.bnd @@ -10,6 +10,7 @@ Bundle-Version: 1.0.0.${tstamp} io.openems.edge.bridge.modbus,\ io.openems.edge.common,\ io.openems.edge.evcs.api,\ + io.openems.edge.meter.api,\ io.openems.edge.timedata.api,\ -testpath: \ diff --git a/io.openems.edge.evcs.alpitronic.hypercharger/src/io/openems/edge/evcs/hypercharger/EvcsAlpitronicHyperchargerImpl.java b/io.openems.edge.evcs.alpitronic.hypercharger/src/io/openems/edge/evcs/hypercharger/EvcsAlpitronicHyperchargerImpl.java index 0dec8b68dad..5bd53f33e6b 100644 --- a/io.openems.edge.evcs.alpitronic.hypercharger/src/io/openems/edge/evcs/hypercharger/EvcsAlpitronicHyperchargerImpl.java +++ b/io.openems.edge.evcs.alpitronic.hypercharger/src/io/openems/edge/evcs/hypercharger/EvcsAlpitronicHyperchargerImpl.java @@ -26,6 +26,7 @@ import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.types.MeterType; import io.openems.common.types.OpenemsType; import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent; import io.openems.edge.bridge.modbus.api.BridgeModbus; @@ -42,11 +43,13 @@ import io.openems.edge.common.taskmanager.Priority; import io.openems.edge.common.type.TypeUtils; import io.openems.edge.evcs.api.ChargeStateHandler; +import io.openems.edge.evcs.api.DeprecatedEvcs; import io.openems.edge.evcs.api.Evcs; import io.openems.edge.evcs.api.EvcsPower; import io.openems.edge.evcs.api.ManagedEvcs; import io.openems.edge.evcs.api.Status; import io.openems.edge.evcs.api.WriteHandler; +import io.openems.edge.meter.api.ElectricityMeter; import io.openems.edge.timedata.api.Timedata; import io.openems.edge.timedata.api.TimedataProvider; import io.openems.edge.timedata.api.utils.CalculateEnergyFromPower; @@ -61,8 +64,9 @@ EdgeEventConstants.TOPIC_CYCLE_BEFORE_PROCESS_IMAGE, // EdgeEventConstants.TOPIC_CYCLE_EXECUTE_WRITE // }) -public class EvcsAlpitronicHyperchargerImpl extends AbstractOpenemsModbusComponent implements Evcs, ManagedEvcs, - OpenemsComponent, ModbusComponent, EventHandler, EvcsAlpitronicHypercharger, TimedataProvider { +public class EvcsAlpitronicHyperchargerImpl extends AbstractOpenemsModbusComponent + implements Evcs, ManagedEvcs, DeprecatedEvcs, ElectricityMeter, OpenemsComponent, ModbusComponent, EventHandler, + EvcsAlpitronicHypercharger, TimedataProvider { private final Logger log = LoggerFactory.getLogger(EvcsAlpitronicHyperchargerImpl.class); /** Modbus offset for multiple connectors. */ @@ -91,7 +95,8 @@ protected void setModbus(BridgeModbus modbus) { *

    * Accumulates the energy by calling this.calculateTotalEnergy.update(power); */ - private CalculateEnergyFromPower calculateTotalEnergy; + private final CalculateEnergyFromPower calculateTotalEnergy = new CalculateEnergyFromPower(this, + ElectricityMeter.ChannelId.ACTIVE_PRODUCTION_ENERGY); /** Handles charge states. */ private final ChargeStateHandler chargeStateHandler = new ChargeStateHandler(this); @@ -103,9 +108,16 @@ public EvcsAlpitronicHyperchargerImpl() { super(// OpenemsComponent.ChannelId.values(), // ModbusComponent.ChannelId.values(), // + ElectricityMeter.ChannelId.values(), // Evcs.ChannelId.values(), // ManagedEvcs.ChannelId.values(), // + DeprecatedEvcs.ChannelId.values(), // EvcsAlpitronicHypercharger.ChannelId.values()); + DeprecatedEvcs.copyToDeprecatedEvcsChannels(this); + + // Automatically calculate L1/l2/L3 values from sum + ElectricityMeter.calculatePhasesFromActivePower(this); + // TODO consider CURRENT and VOLTAGE also } @Activate @@ -136,7 +148,6 @@ private void modified(ComponentContext context, Config config) throws OpenemsNam private void applyConfig(ComponentContext context, Config config) { this.config = config; - this.calculateTotalEnergy = new CalculateEnergyFromPower(this, Evcs.ChannelId.ACTIVE_CONSUMPTION_ENERGY); this._setFixedMinimumHardwarePower(config.minHwPower()); this._setFixedMaximumHardwarePower(config.maxHwPower()); this._setPowerPrecision(1); @@ -149,6 +160,11 @@ protected void deactivate() { super.deactivate(); } + @Override + public MeterType getMeterType() { + return MeterType.MANAGED_CONSUMPTION_METERED; + } + @Override public EvcsPower getEvcsPower() { return this.evcsPower; @@ -160,12 +176,10 @@ public void handleEvent(Event event) { return; } switch (event.getTopic()) { - case EdgeEventConstants.TOPIC_CYCLE_BEFORE_PROCESS_IMAGE: - this.calculateTotalEnergy.update(this.getChargePower().get()); - break; - case EdgeEventConstants.TOPIC_CYCLE_EXECUTE_WRITE: - this.writeHandler.run(); - break; + case EdgeEventConstants.TOPIC_CYCLE_BEFORE_PROCESS_IMAGE // + -> this.calculateTotalEnergy.update(this.getActivePower().get()); + case EdgeEventConstants.TOPIC_CYCLE_EXECUTE_WRITE // + -> this.writeHandler.run(); } } @@ -186,8 +200,10 @@ protected ModbusProtocol defineModbusProtocol() { new FC4ReadInputRegistersTask(this.offset.apply(0), Priority.LOW, m(EvcsAlpitronicHypercharger.ChannelId.RAW_STATUS, new UnsignedWordElement(this.offset.apply(0))), + // TODO consider ElectricityMeter VOLTAGE m(EvcsAlpitronicHypercharger.ChannelId.CHARGING_VOLTAGE, new UnsignedDoublewordElement(this.offset.apply(1)), SCALE_FACTOR_MINUS_2), + // TODO consider ElectricityMeter CURRENT m(EvcsAlpitronicHypercharger.ChannelId.CHARGING_CURRENT, new UnsignedWordElement(this.offset.apply(3)), SCALE_FACTOR_MINUS_2), /* @@ -218,7 +234,6 @@ protected ModbusProtocol defineModbusProtocol() { this._setEnergySession(0); return; case CHARGING: - case CHARGING_FINISHED: case CHARGING_REJECTED: case ENERGY_LIMIT_REACHED: case ERROR: @@ -268,7 +283,7 @@ private void addCalculatePowerListeners() { // Calculate power from voltage and current final Consumer> calculatePower = ignore -> { - this._setChargePower(TypeUtils.getAsType(OpenemsType.INTEGER, TypeUtils.multiply(// + this._setActivePower(TypeUtils.getAsType(OpenemsType.INTEGER, TypeUtils.multiply(// this.getChargingVoltageChannel().getNextValue().get(), // this.getChargingCurrentChannel().getNextValue().get() // ))); @@ -283,35 +298,20 @@ private void addStatusListener() { /** * Maps the raw state into a {@link Status}. */ - switch (rawState) { - case AVAILABLE: - this._setStatus(Status.NOT_READY_FOR_CHARGING); - break; - case PREPARING_TAG_ID_READY: - this._setStatus(Status.READY_FOR_CHARGING); - break; - case CHARGING: - case PREPARING_EV_READY: - this._setStatus(Status.CHARGING); - break; - case RESERVED: - case SUSPENDED_EV: - case SUSPENDED_EV_SE: - this._setStatus(Status.CHARGING_REJECTED); - break; - case FINISHING: - this._setStatus(Status.CHARGING_FINISHED); - break; - case FAULTED: - case UNAVAILABLE: - case UNAVAILABLE_CONNECTION_OBJECT: - this._setStatus(Status.ERROR); - break; - case UNAVAILABLE_FW_UPDATE: - case UNDEFINED: - default: - this._setStatus(Status.UNDEFINED); - } + this._setStatus(switch (rawState) { + case AVAILABLE // + -> Status.NOT_READY_FOR_CHARGING; + case PREPARING_TAG_ID_READY // + -> Status.READY_FOR_CHARGING; + case CHARGING, PREPARING_EV_READY // + -> Status.CHARGING; + case RESERVED, SUSPENDED_EV, SUSPENDED_EV_SE, FINISHING // + -> Status.CHARGING_REJECTED; + case FAULTED, UNAVAILABLE, UNAVAILABLE_CONNECTION_OBJECT // + -> Status.ERROR; + case UNAVAILABLE_FW_UPDATE, UNDEFINED // + -> Status.UNDEFINED; + }); }); } diff --git a/io.openems.edge.evcs.alpitronic.hypercharger/test/io/openems/edge/evcs/hypercharger/EvcsAlpitronicHyperchargerImplTest.java b/io.openems.edge.evcs.alpitronic.hypercharger/test/io/openems/edge/evcs/hypercharger/EvcsAlpitronicHyperchargerImplTest.java index a2fb6ebfdc2..9e83d535c84 100644 --- a/io.openems.edge.evcs.alpitronic.hypercharger/test/io/openems/edge/evcs/hypercharger/EvcsAlpitronicHyperchargerImplTest.java +++ b/io.openems.edge.evcs.alpitronic.hypercharger/test/io/openems/edge/evcs/hypercharger/EvcsAlpitronicHyperchargerImplTest.java @@ -9,17 +9,14 @@ public class EvcsAlpitronicHyperchargerImplTest { - private static final String EVCS_ID = "evcs0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new EvcsAlpitronicHyperchargerImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setModbusId(MODBUS_ID) // - .setId(EVCS_ID) // + .setModbusId("modbus0") // + .setId("evcs0") // .setModbusUnitId(1) // .setConnector(Connector.SLOT_0) // .setMaxHwPower(70_000) // diff --git a/io.openems.edge.evcs.api/.classpath b/io.openems.edge.evcs.api/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.evcs.api/.classpath +++ b/io.openems.edge.evcs.api/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.evcs.api/bnd.bnd b/io.openems.edge.evcs.api/bnd.bnd index bd42bcb6773..eb22c030664 100644 --- a/io.openems.edge.evcs.api/bnd.bnd +++ b/io.openems.edge.evcs.api/bnd.bnd @@ -7,7 +7,7 @@ Bundle-Version: 1.0.0.${tstamp} ${buildpath},\ io.openems.common,\ io.openems.edge.common,\ - io.openems.edge.meter.api + io.openems.edge.meter.api,\ -testpath: \ ${testpath} diff --git a/io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/AbstractManagedEvcsComponent.java b/io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/AbstractManagedEvcsComponent.java index 56afd9dae55..ab88e63294c 100644 --- a/io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/AbstractManagedEvcsComponent.java +++ b/io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/AbstractManagedEvcsComponent.java @@ -1,6 +1,7 @@ package io.openems.edge.evcs.api; import org.osgi.service.component.ComponentContext; +import org.osgi.service.component.annotations.Deactivate; import org.osgi.service.event.Event; import org.osgi.service.event.EventHandler; import org.slf4j.Logger; @@ -8,6 +9,7 @@ import io.openems.edge.common.component.AbstractOpenemsComponent; import io.openems.edge.common.event.EdgeEventConstants; +import io.openems.edge.meter.api.ElectricityMeter; /** * Abstract Managed EVCS Component. @@ -38,16 +40,17 @@ *

*/ public abstract class AbstractManagedEvcsComponent extends AbstractOpenemsComponent - implements Evcs, ManagedEvcs, EventHandler { + implements Evcs, ManagedEvcs, ElectricityMeter, EventHandler { private final Logger log = LoggerFactory.getLogger(AbstractManagedEvcsComponent.class); - private final WriteHandler writeHandler = new WriteHandler(this); + protected final WriteHandler writeHandler; private final ChargeStateHandler chargeStateHandler = new ChargeStateHandler(this); protected AbstractManagedEvcsComponent(io.openems.edge.common.channel.ChannelId[] firstInitialChannelIds, io.openems.edge.common.channel.ChannelId[]... furtherInitialChannelIds) { super(firstInitialChannelIds, furtherInitialChannelIds); + this.writeHandler = this.createWriteHandler(); } @Override @@ -57,6 +60,13 @@ protected void activate(ComponentContext context, String id, String alias, boole Evcs.addCalculatePowerLimitListeners(this); } + @Override + @Deactivate + protected void deactivate() { + super.deactivate(); + this.writeHandler.cancelChargePower(); + } + @Override public void handleEvent(Event event) { if (!this.isEnabled()) { @@ -84,6 +94,10 @@ protected void logWarn(Logger log, String message) { super.logWarn(log, message); } + protected WriteHandler createWriteHandler() { + return new WriteHandler(this); + } + @Override protected void logDebug(Logger log, String message) { if (this.getConfiguredDebugMode()) { diff --git a/io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/DeprecatedEvcs.java b/io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/DeprecatedEvcs.java new file mode 100644 index 00000000000..e5e98c02c74 --- /dev/null +++ b/io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/DeprecatedEvcs.java @@ -0,0 +1,58 @@ +package io.openems.edge.evcs.api; + +import static io.openems.common.channel.PersistencePriority.HIGH; +import static io.openems.common.channel.Unit.WATT; +import static io.openems.common.types.OpenemsType.INTEGER; + +import io.openems.edge.common.channel.Doc; +import io.openems.edge.common.channel.IntegerReadChannel; +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.meter.api.ElectricityMeter; + +/** + * This interface marks old implementations of {@link Evcs} that did not yet + * inherit {@link ElectricityMeter}. + * + *

+ * It should not be used for new implementations, but serves as a migration path + * for old implementations. + */ +public interface DeprecatedEvcs extends ElectricityMeter, OpenemsComponent { + + /** + * Copies values to Deprecated Channels during migration to + * {@link ElectricityMeter}. + * + *

    + *
  • ACTIVE_POWER -> CHARGE_POWER + *
  • ACTIVE_PRODUCTION_ENERGY -> ACTIVE_CONSUMPTION_ENERGY + *
+ * + * @param meter instance of myself + */ + public static void copyToDeprecatedEvcsChannels(DeprecatedEvcs meter) { + var chargePowerChannel = meter.channel(DeprecatedEvcs.ChannelId.CHARGE_POWER); + meter.getActivePowerChannel().onSetNextValue(v -> chargePowerChannel.setNextValue(v.get())); + meter.getActiveProductionEnergyChannel().onSetNextValue(v -> meter._setActiveConsumptionEnergy(v.get())); + } + + public enum ChannelId implements io.openems.edge.common.channel.ChannelId { + /** + * Copy of {@link ElectricityMeter.ChannelId#ACTIVE_POWER}. + */ + CHARGE_POWER(Doc.of(INTEGER) // + .unit(WATT) // + .persistencePriority(HIGH)); + + private final Doc doc; + + private ChannelId(Doc doc) { + this.doc = doc; + } + + @Override + public Doc doc() { + return this.doc; + } + } +} diff --git a/io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/Evcs.java b/io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/Evcs.java index a1405624b2f..3902b41ced3 100644 --- a/io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/Evcs.java +++ b/io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/Evcs.java @@ -11,14 +11,14 @@ import io.openems.edge.common.channel.Doc; import io.openems.edge.common.channel.EnumReadChannel; import io.openems.edge.common.channel.IntegerReadChannel; -import io.openems.edge.common.channel.LongReadChannel; import io.openems.edge.common.channel.StateChannel; import io.openems.edge.common.channel.value.Value; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.modbusslave.ModbusSlaveNatureTable; import io.openems.edge.common.modbusslave.ModbusType; +import io.openems.edge.meter.api.ElectricityMeter; -public interface Evcs extends OpenemsComponent { +public interface Evcs extends ElectricityMeter, OpenemsComponent { public static final Integer DEFAULT_MAXIMUM_HARDWARE_POWER = 22_080; // W public static final Integer DEFAULT_MINIMUM_HARDWARE_POWER = 4_140; // W @@ -42,21 +42,6 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { * */ STATUS(Doc.of(Status.values()) // - .accessMode(AccessMode.READ_ONLY) // - .persistencePriority(PersistencePriority.HIGH)), // - - /** - * Charge Power. - * - *
    - *
  • Interface: Evcs - *
  • Readable - *
  • Type: Integer - *
  • Unit: W - *
- */ - CHARGE_POWER(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.WATT).accessMode(AccessMode.READ_ONLY) // .persistencePriority(PersistencePriority.HIGH)), // /** @@ -72,7 +57,6 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { * */ CHARGING_TYPE(Doc.of(ChargingType.values()) // - .accessMode(AccessMode.READ_ONLY) // .persistencePriority(PersistencePriority.HIGH)), // /** @@ -91,7 +75,6 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { */ PHASES(Doc.of(Phases.values()) // .debounce(5) // - .accessMode(AccessMode.READ_ONLY) // .persistencePriority(PersistencePriority.HIGH)), // /** @@ -116,7 +99,6 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { */ FIXED_MINIMUM_HARDWARE_POWER(Doc.of(OpenemsType.INTEGER) // .unit(Unit.WATT) // - .accessMode(AccessMode.READ_ONLY) // .persistencePriority(PersistencePriority.HIGH)), // /** @@ -140,7 +122,6 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { */ FIXED_MAXIMUM_HARDWARE_POWER(Doc.of(OpenemsType.INTEGER) // .unit(Unit.WATT) // - .accessMode(AccessMode.READ_ONLY) // .persistencePriority(PersistencePriority.HIGH)), // /** @@ -164,7 +145,6 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { */ MINIMUM_HARDWARE_POWER(Doc.of(OpenemsType.INTEGER) // .unit(Unit.WATT) // - .accessMode(AccessMode.READ_ONLY) // .persistencePriority(PersistencePriority.HIGH)), // /** @@ -188,7 +168,6 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { */ MAXIMUM_HARDWARE_POWER(Doc.of(OpenemsType.INTEGER) // .unit(Unit.WATT) // - .accessMode(AccessMode.READ_ONLY) // .persistencePriority(PersistencePriority.HIGH)), // /** @@ -203,7 +182,6 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { */ MAXIMUM_POWER(Doc.of(OpenemsType.INTEGER) // .unit(Unit.WATT) // - .accessMode(AccessMode.READ_ONLY) // .persistencePriority(PersistencePriority.HIGH)), // /** @@ -218,7 +196,6 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { */ MINIMUM_POWER(Doc.of(OpenemsType.INTEGER) // .unit(Unit.WATT) // - .accessMode(AccessMode.READ_ONLY) // .persistencePriority(PersistencePriority.HIGH)), // /** @@ -233,21 +210,6 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { */ ENERGY_SESSION(Doc.of(OpenemsType.INTEGER) // .unit(Unit.WATT_HOURS) // - .accessMode(AccessMode.READ_ONLY) // - .persistencePriority(PersistencePriority.HIGH)), // - - /** - * Active Consumption Energy. - * - *
    - *
  • Interface: Evcs - *
  • Type: Integer - *
  • Unit: Wh - *
- */ - ACTIVE_CONSUMPTION_ENERGY(Doc.of(OpenemsType.LONG) // - .unit(Unit.CUMULATED_WATT_HOURS) // - .accessMode(AccessMode.READ_ONLY) // .persistencePriority(PersistencePriority.HIGH)), // /** @@ -260,7 +222,6 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { * */ CHARGINGSTATION_COMMUNICATION_FAILED(Doc.of(Level.FAULT) // - .accessMode(AccessMode.READ_ONLY) // .persistencePriority(PersistencePriority.HIGH) // .text("Chargingstation Communication Failed " // + "| Keine Verbindung zur Ladestation " // @@ -305,44 +266,6 @@ public default void _setStatus(Status value) { this.getStatusChannel().setNextValue(value); } - /** - * Gets the Channel for {@link ChannelId#CHARGE_POWER}. - * - * @return the Channel - */ - public default IntegerReadChannel getChargePowerChannel() { - return this.channel(ChannelId.CHARGE_POWER); - } - - /** - * Gets the Charge Power in [W]. See {@link ChannelId#CHARGE_POWER}. - * - * @return the Channel {@link Value} - */ - public default Value getChargePower() { - return this.getChargePowerChannel().value(); - } - - /** - * Internal method to set the 'nextValue' on {@link ChannelId#CHARGE_POWER} - * Channel. - * - * @param value the next value - */ - public default void _setChargePower(Integer value) { - this.getChargePowerChannel().setNextValue(value); - } - - /** - * Internal method to set the 'nextValue' on {@link ChannelId#CHARGE_POWER} - * Channel. - * - * @param value the next value - */ - public default void _setChargePower(int value) { - this.getChargePowerChannel().setNextValue(value); - } - /** * Gets the Channel for {@link ChannelId#CHARGING_TYPE}. * @@ -666,45 +589,6 @@ public default void _setEnergySession(int value) { this.getEnergySessionChannel().setNextValue(value); } - /** - * Gets the Channel for {@link ChannelId#ACTIVE_CONSUMPTION_ENERGY}. - * - * @return the Channel - */ - public default LongReadChannel getActiveConsumptionEnergyChannel() { - return this.channel(ChannelId.ACTIVE_CONSUMPTION_ENERGY); - } - - /** - * Gets the Active Consumption Energy in [Wh_Σ]. This relates to negative - * ACTIVE_POWER. See {@link ChannelId#ACTIVE_CONSUMPTION_ENERGY}. - * - * @return the Channel {@link Value} - */ - public default Value getActiveConsumptionEnergy() { - return this.getActiveConsumptionEnergyChannel().value(); - } - - /** - * Internal method to set the 'nextValue' on - * {@link ChannelId#ACTIVE_CONSUMPTION_ENERGY} Channel. - * - * @param value the next value - */ - public default void _setActiveConsumptionEnergy(Long value) { - this.getActiveConsumptionEnergyChannel().setNextValue(value); - } - - /** - * Internal method to set the 'nextValue' on - * {@link ChannelId#ACTIVE_CONSUMPTION_ENERGY} Channel. - * - * @param value the next value - */ - public default void _setActiveConsumptionEnergy(long value) { - this.getActiveConsumptionEnergyChannel().setNextValue(value); - } - /** * Gets the Channel for {@link ChannelId#CHARGINGSTATION_COMMUNICATION_FAILED}. * @@ -765,6 +649,37 @@ public static void addCalculatePowerLimitListeners(Evcs evcs) { evcs.getPhasesChannel().onSetNextValue(calculateHardwarePowerLimits); } + /** + * Evaluates the number of Phases from the individual powers per phase. + * + *

+ * The EVCS will pull power from the grid for its own consumption and report + * that on one of the phases. This value is different from EVCS to EVCS but can + * be high. Because of this, this will only register a phase starting with 100W + * because then we definitively know that this load is caused by a car. + * + * @param activePowerL1 active power on L1 + * @param activePowerL2 active power on L2 + * @param activePowerL3 active power on L3 + * @return integer value indicating the number of phases; null if undefined + */ + public static Integer evaluatePhaseCount(Integer activePowerL1, Integer activePowerL2, Integer activePowerL3) { + int phases = 0; + if (activePowerL1 != null && activePowerL1 > 100) { + phases++; + } + if (activePowerL2 != null && activePowerL2 > 100) { + phases++; + } + if (activePowerL3 != null && activePowerL3 > 100) { + phases++; + } + return switch (phases) { + case 1, 2, 3 -> phases; + default -> null; + }; + } + /** * Used for Modbus/TCP Api Controller. Provides a Modbus table for the Channels * of this Component. @@ -775,7 +690,7 @@ public static void addCalculatePowerLimitListeners(Evcs evcs) { public static ModbusSlaveNatureTable getModbusSlaveNatureTable(AccessMode accessMode) { return ModbusSlaveNatureTable.of(Evcs.class, accessMode, 100) // .channel(0, ChannelId.STATUS, ModbusType.UINT16) // - .channel(1, ChannelId.CHARGE_POWER, ModbusType.UINT16) // + .uint16Reserved(1) // .channel(2, ChannelId.CHARGING_TYPE, ModbusType.UINT16) // .channel(3, ChannelId.PHASES, ModbusType.UINT16) // .channel(4, ChannelId.MAXIMUM_HARDWARE_POWER, ModbusType.UINT16) // @@ -786,7 +701,15 @@ public static ModbusSlaveNatureTable getModbusSlaveNatureTable(AccessMode access .channel(9, ChannelId.FIXED_MINIMUM_HARDWARE_POWER, ModbusType.UINT16) // .channel(10, ChannelId.FIXED_MAXIMUM_HARDWARE_POWER, ModbusType.UINT16) // .channel(11, ChannelId.MINIMUM_POWER, ModbusType.UINT16) // - .channel(12, ChannelId.ACTIVE_CONSUMPTION_ENERGY, ModbusType.UINT16) // .build(); } + + /** + * Defines if the evcs is read only. + * + * @return true if the evcs is read-only + */ + public default boolean isReadOnly() { + return false; + } } diff --git a/io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/ManagedEvcs.java b/io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/ManagedEvcs.java index 1d4ddb30399..160f71c0591 100644 --- a/io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/ManagedEvcs.java +++ b/io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/ManagedEvcs.java @@ -176,7 +176,6 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { *

  • Hardy Barth allows setting in Ampere. It should return 230 W (1A * 230V). * * - *

    *

      *
    • Interface: ManagedEvcs *
    • Writable diff --git a/io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/MeasuringEvcs.java b/io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/MeasuringEvcs.java index 18f7154f46a..50e680aeb0e 100644 --- a/io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/MeasuringEvcs.java +++ b/io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/MeasuringEvcs.java @@ -6,6 +6,7 @@ import io.openems.common.types.OpenemsType; import io.openems.edge.common.channel.Doc; +// TODO consider replacing this by ElectricityMeter public interface MeasuringEvcs extends Evcs { public enum ChannelId implements io.openems.edge.common.channel.ChannelId { @@ -240,27 +241,6 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { .persistencePriority(PersistencePriority.HIGH) // .text("Energy.Reactive.Import.Interval")), - /** - * Frequency. - * - *

      - * Instantaneous reading of powerline frequency. NOTE: OCPP 1.6 does not have a - * UnitOfMeasure for frequency, the UnitOfMeasure for any SampledValue with - * measurand: Frequency is Hertz. - * - *

        - *
      • Interface: MeasuringEvcs - *
      • Readable - *
      • Type: String - *
      • Unit: Hz - *
      - */ - FREQUENCY(Doc.of(OpenemsType.STRING) // - .unit(Unit.HERTZ) // - .accessMode(AccessMode.READ_ONLY) // - .persistencePriority(PersistencePriority.HIGH) // - .text("Frequency")), - /** * Active power to grid (export) * @@ -371,23 +351,6 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { .persistencePriority(PersistencePriority.HIGH) // .text("Fan speed")), - /** - * Voltage. - * - *

      - * Instantaneous AC RMS supply voltage. - * - *

        - *
      • Interface: MeasuringEvcs - *
      • Readable - *
      • Type: String - *
      - */ - VOLTAGE(Doc.of(OpenemsType.STRING) // - .accessMode(AccessMode.READ_ONLY) // - .persistencePriority(PersistencePriority.HIGH) // - .text("Voltage")), - /** * Temperature. * diff --git a/io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/PhaseRotation.java b/io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/PhaseRotation.java new file mode 100644 index 00000000000..e89c312e781 --- /dev/null +++ b/io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/PhaseRotation.java @@ -0,0 +1,77 @@ +package io.openems.edge.evcs.api; + +public enum PhaseRotation { + /** + * EVCS uses standard wiring. + * + *
        + *
      • EVCS L1 is connected to Grid L1 + *
      • EVCS L2 is connected to Grid L2 + *
      • EVCS L3 is connected to Grid L3 + *
      + */ + L1_L2_L3, + + /** + * EVCS uses rotated wiring. + * + *
        + *
      • EVCS L1 is connected to Grid L2 + *
      • EVCS L2 is connected to Grid L3 + *
      • EVCS L3 is connected to Grid L1 + *
      + */ + L2_L3_L1, + + /** + * EVCS uses rotated wiring. + * + *
        + *
      • EVCS L1 is connected to Grid L3 + *
      • EVCS L2 is connected to Grid L1 + *
      • EVCS L3 is connected to Grid L2 + *
      + */ + L3_L1_L2; + + public record RotatedPhases(// + Integer voltageL1, int currentL1, Integer activePowerL1, // + Integer voltageL2, int currentL2, Integer activePowerL2, // + Integer voltageL3, int currentL3, Integer activePowerL3) { + + /** + * Rotate phases for voltage, current and active power. + * + * @param phaseRotation the {@link PhaseRotation} + * @param voltageL1 the voltage on L1 + * @param currentL1 the current on L1 + * @param activePowerL1 the active power on L1 + * @param voltageL2 the voltage on L2 + * @param currentL2 the current on L2 + * @param activePowerL2 the active power on L2 + * @param voltageL3 the voltage on L3 + * @param currentL3 the current on L3 + * @param activePowerL3 the active power on L3 + * @return {@link RotatedPhases} + */ + public static RotatedPhases from(PhaseRotation phaseRotation, // + Integer voltageL1, int currentL1, Integer activePowerL1, // + Integer voltageL2, int currentL2, Integer activePowerL2, // + Integer voltageL3, int currentL3, Integer activePowerL3) { + return switch (phaseRotation) { + case L1_L2_L3 -> new RotatedPhases(// + voltageL1, currentL1, activePowerL1, // + voltageL2, currentL2, activePowerL2, // + voltageL3, currentL3, activePowerL3); + case L2_L3_L1 -> new RotatedPhases(// + voltageL3, currentL3, activePowerL3, // + voltageL1, currentL1, activePowerL1, // + voltageL2, currentL2, activePowerL2); + case L3_L1_L2 -> new RotatedPhases(// + voltageL2, currentL2, activePowerL2, // + voltageL3, currentL3, activePowerL3, // + voltageL1, currentL1, activePowerL1); + }; + } + } +} diff --git a/io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/Status.java b/io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/Status.java index 25b03312e5e..53dca61d8d4 100644 --- a/io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/Status.java +++ b/io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/Status.java @@ -16,8 +16,7 @@ public enum Status implements OptionsEnum { CHARGING(3, "Charging"), // ERROR(4, "Error"), // CHARGING_REJECTED(5, "Charging rejected"), // - ENERGY_LIMIT_REACHED(6, "The charging limit reached"), // - CHARGING_FINISHED(7, "Charging has finished"); + ENERGY_LIMIT_REACHED(6, "The charging limit reached"); // private final int value; private final String name; diff --git a/io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/WriteHandler.java b/io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/WriteHandler.java index 64fcb073b24..5f59c81a709 100644 --- a/io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/WriteHandler.java +++ b/io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/WriteHandler.java @@ -75,7 +75,7 @@ private void setPower() { this.parent._setStatus(Status.ENERGY_LIMIT_REACHED); // Apply Charge Power - if (this.lastTarget != 0 || this.parent.getChargePower().orElse(0) != 0) { + if (this.lastTarget != 0 || this.parent.getActivePower().orElse(0) != 0) { this.parent.getChargeStateHandler().applyNewChargeState(ChargeState.DECREASING); this.applyChargePower(0); } @@ -128,7 +128,7 @@ private void setPower() { * * @param power Power that should be applied */ - private void applyChargePower(int power) { + protected void applyChargePower(int power) { try { boolean sent = false; @@ -224,23 +224,16 @@ private void setChargeStatus() { ChargeState chargeStatus = ChargeState.UNDEFINED; Status status = this.parent.getStatusChannel().getNextValue().asEnum(); - switch (status) { - case CHARGING: - chargeStatus = ChargeState.CHARGING; - break; - case CHARGING_FINISHED: - case CHARGING_REJECTED: - case ENERGY_LIMIT_REACHED: - case ERROR: - case STARTING: - case READY_FOR_CHARGING: - case NOT_READY_FOR_CHARGING: - chargeStatus = ChargeState.NOT_CHARGING; - break; - case UNDEFINED: - chargeStatus = ChargeState.UNDEFINED; - break; - } + chargeStatus = switch (status) { + case CHARGING -> ChargeState.CHARGING; + case CHARGING_REJECTED, // + ENERGY_LIMIT_REACHED, // + ERROR, STARTING, // + READY_FOR_CHARGING, // + NOT_READY_FOR_CHARGING -> + ChargeState.NOT_CHARGING; + case UNDEFINED -> ChargeState.UNDEFINED; + }; this.parent.getChargeStateHandler().applyNewChargeState(chargeStatus); } @@ -249,4 +242,11 @@ private void logDebug(String message) { OpenemsComponent.logInfo(this.parent, this.log, message); } } + + /** + * Used for async applyChargePower calls. + * + */ + public void cancelChargePower() { + } } diff --git a/io.openems.edge.evcs.api/src/io/openems/edge/evcs/test/DummyManagedEvcs.java b/io.openems.edge.evcs.api/src/io/openems/edge/evcs/test/DummyManagedEvcs.java index fd28514e222..75d55e4b902 100644 --- a/io.openems.edge.evcs.api/src/io/openems/edge/evcs/test/DummyManagedEvcs.java +++ b/io.openems.edge.evcs.api/src/io/openems/edge/evcs/test/DummyManagedEvcs.java @@ -1,28 +1,51 @@ package io.openems.edge.evcs.test; +import static io.openems.common.types.MeterType.MANAGED_CONSUMPTION_METERED; + import org.osgi.service.event.Event; import org.osgi.service.event.EventHandler; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.types.MeterType; import io.openems.edge.common.channel.Channel; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.event.EdgeEventConstants; +import io.openems.edge.common.filter.DisabledRampFilter; +import io.openems.edge.common.test.TestUtils; import io.openems.edge.evcs.api.AbstractManagedEvcsComponent; import io.openems.edge.evcs.api.Evcs; import io.openems.edge.evcs.api.EvcsPower; import io.openems.edge.evcs.api.ManagedEvcs; import io.openems.edge.evcs.api.Status; +import io.openems.edge.meter.api.ElectricityMeter; +// TODO should extend AbstractDummyElectricityMeter public class DummyManagedEvcs extends AbstractManagedEvcsComponent - implements Evcs, ManagedEvcs, OpenemsComponent, EventHandler { + implements Evcs, ManagedEvcs, ElectricityMeter, OpenemsComponent, EventHandler { private final EvcsPower evcsPower; private int minimumHardwarePower = Evcs.DEFAULT_MINIMUM_HARDWARE_POWER; private int maximumHardwarePower = Evcs.DEFAULT_MAXIMUM_HARDWARE_POWER; + private MeterType meterType = MANAGED_CONSUMPTION_METERED; + + /** + * Instantiates a disabled {@link DummyManagedEvcs}. + * + * @param id the Component-ID + * @return a new {@link DummyManagedEvcs} + */ + public static DummyManagedEvcs ofDisabled(String id) { + return new DummyManagedEvcs(id, new DummyEvcsPower(new DisabledRampFilter()), false); + } public DummyManagedEvcs(String id, EvcsPower evcsPower) { + this(id, evcsPower, true); + } + + private DummyManagedEvcs(String id, EvcsPower evcsPower, boolean isEnabled) { super(// OpenemsComponent.ChannelId.values(), // + ElectricityMeter.ChannelId.values(), // ManagedEvcs.ChannelId.values(), // Evcs.ChannelId.values() // ); @@ -30,11 +53,36 @@ public DummyManagedEvcs(String id, EvcsPower evcsPower) { for (Channel channel : this.channels()) { channel.nextProcessImage(); } - super.activate(null, id, "", true); + super.activate(null, id, "", isEnabled); + } + + /** + * Set the {@link MeterType}. + * + * @param meterType the meterType + * @return myself + */ + public DummyManagedEvcs withMeterType(MeterType meterType) { + this.meterType = meterType; + return this; + } + + /** + * Set {@link ElectricityMeter.ChannelId#ACTIVE_POWER}. + * + * @param value the value + * @return myself + */ + public DummyManagedEvcs withActivePower(Integer value) { + TestUtils.withValue(this, ElectricityMeter.ChannelId.ACTIVE_POWER, value); + return this; } @Override public void handleEvent(Event event) { + if (!this.isEnabled()) { + return; + } super.handleEvent(event); switch (event.getTopic()) { // Results of the written limits are checked after write in the Dummy Component @@ -73,6 +121,11 @@ private void updateCurrentState() { this._setStatus(nextStatus); } + @Override + public MeterType getMeterType() { + return this.meterType; + } + @Override public EvcsPower getEvcsPower() { return this.evcsPower; @@ -85,14 +138,14 @@ public boolean getConfiguredDebugMode() { @Override public boolean applyChargePowerLimit(int power) throws OpenemsException { - this._setChargePower(power); + this._setActivePower(power); this._setStatus(Status.CHARGING); return true; } @Override public boolean pauseChargeProcess() throws OpenemsException { - this._setChargePower(0); + this._setActivePower(0); return true; } diff --git a/io.openems.edge.evcs.api/test/io/openems/edge/evcs/api/AbstractManagedEvcsTest.java b/io.openems.edge.evcs.api/test/io/openems/edge/evcs/api/AbstractManagedEvcsTest.java index 908cdeb7bfd..573ef70cd38 100644 --- a/io.openems.edge.evcs.api/test/io/openems/edge/evcs/api/AbstractManagedEvcsTest.java +++ b/io.openems.edge.evcs.api/test/io/openems/edge/evcs/api/AbstractManagedEvcsTest.java @@ -1,11 +1,17 @@ package io.openems.edge.evcs.api; +import static io.openems.edge.evcs.api.Evcs.ChannelId.ENERGY_SESSION; +import static io.openems.edge.evcs.api.Evcs.ChannelId.STATUS; +import static io.openems.edge.evcs.api.ManagedEvcs.ChannelId.CHARGE_STATE; +import static io.openems.edge.evcs.api.ManagedEvcs.ChannelId.SET_CHARGE_POWER_LIMIT; +import static io.openems.edge.evcs.api.ManagedEvcs.ChannelId.SET_CHARGE_POWER_LIMIT_WITH_FILTER; +import static io.openems.edge.evcs.api.ManagedEvcs.ChannelId.SET_ENERGY_LIMIT; +import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.ACTIVE_POWER; import static org.junit.Assert.assertEquals; import org.junit.Test; import io.openems.common.function.ThrowingRunnable; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.channel.IntegerReadChannel; import io.openems.edge.common.filter.DisabledRampFilter; import io.openems.edge.common.filter.RampFilter; @@ -16,329 +22,270 @@ public class AbstractManagedEvcsTest { + /** + * Sleep between every TestCase to make sure that the Channel Values are added + * to the pastValues Map. This is required because the Channel Value timestamp + * does not consider the mocked Clock. + * + *

      + * Timeleap is not used, to avoid using a clock in the ChargeSatusHandler and + * therefore a ClockProvider function in every EVCS (.timeleap(clock, 31, + * ChronoUnit.SECONDS)) + */ + private static final ThrowingRunnable SLEEP = () -> Thread.sleep(1510); + private static final DummyEvcsPower EVCS_POWER = new DummyEvcsPower(new DisabledRampFilter()); private static final DummyEvcsPower EVCS_POWER_WITH_FILTER = new DummyEvcsPower(new RampFilter()); private static final DummyManagedEvcs EVCS0 = new DummyManagedEvcs("evcs0", EVCS_POWER); private static final DummyManagedEvcs EVCS1 = new DummyManagedEvcs("evcs1", EVCS_POWER); - private static final DummyManagedEvcs evcs2 = new DummyManagedEvcs("evcs2", EVCS_POWER_WITH_FILTER); + private static final DummyManagedEvcs EVCS2 = new DummyManagedEvcs("evcs2", EVCS_POWER_WITH_FILTER); private static final int MINIMUM = Evcs.DEFAULT_MINIMUM_HARDWARE_POWER; private static final int MAXIMUM = Evcs.DEFAULT_MAXIMUM_HARDWARE_POWER; - // Channel Addresses EVCS0 - private static ChannelAddress evcs0Status = new ChannelAddress("evcs0", "Status"); - private static ChannelAddress evcs0ChargeState = new ChannelAddress("evcs0", "ChargeState"); - private static ChannelAddress evcs0ChargePower = new ChannelAddress("evcs0", "ChargePower"); - private static ChannelAddress evcs0SetChargePowerLimit = new ChannelAddress("evcs0", "SetChargePowerLimit"); - - // Channel Addresses EVCS1 - private static ChannelAddress evcs1Status = new ChannelAddress("evcs1", "Status"); - private static ChannelAddress evcs1ChargeState = new ChannelAddress("evcs1", "ChargeState"); - private static ChannelAddress evcs1ChargePower = new ChannelAddress("evcs1", "ChargePower"); - private static ChannelAddress evcs1SetChargePowerLimit = new ChannelAddress("evcs1", "SetChargePowerLimit"); - private static ChannelAddress evcs1SetEnergyLimit = new ChannelAddress("evcs1", "SetEnergyLimit"); - private static ChannelAddress evcs1EnergySession = new ChannelAddress("evcs1", "EnergySession"); - - // Channel Addresses EVCS 2 - private static ChannelAddress evcs2Status = new ChannelAddress("evcs2", "Status"); - private static ChannelAddress evcs2ChargeState = new ChannelAddress("evcs2", "ChargeState"); - private static ChannelAddress evcs2ChargePower = new ChannelAddress("evcs2", "ChargePower"); - private static ChannelAddress evcs2SetChargePowerLimitWithFilter = new ChannelAddress("evcs2", - "SetChargePowerLimitWithFilter"); - /* * ATTENTION: The test could fail if you run it in Debug mode and e.g. the test * is expecting an output within the "getMinimumTimeTillCharingLimitTaken" time. */ - @Test public void abstractManagedEvcsTest() throws Exception { - // Sleep between every TestCase to make sure that the Channel Values are added - // to the pastValues Map. This is required because the Channel Value timestamp - // does not consider the mocked Clock. - final ThrowingRunnable sleep = () -> Thread.sleep(1010); - - ComponentTest test = new ComponentTest(EVCS0).addComponent(EVCS0); - - // Initial charge - test.next(new TestCase("Initial charge") // - - .input(evcs0SetChargePowerLimit, 15000) // - .input(evcs0ChargePower, 0) // - .input(evcs0Status, Status.READY_FOR_CHARGING) // - - .output(evcs0ChargePower, 15000) // - .output(evcs0ChargeState, ChargeState.INCREASING)); // - - /* - * Cannot check the nextValue of SetChargePowerLimit as output, because the test - * validator checks the write value (.output(evcs0SetChargePowerLimit, 15000)) - */ - // Check set charge limit + var test = new ComponentTest(EVCS0) // + .addComponent(EVCS0) // + + .next(new TestCase("Initial charge") // + .input(SET_CHARGE_POWER_LIMIT, 15000) // + .input(ACTIVE_POWER, 0) // + .input(STATUS, Status.READY_FOR_CHARGING) // + .output(ACTIVE_POWER, 15000) // + .output(CHARGE_STATE, ChargeState.INCREASING)); // + + // Cannot check the nextValue of SetChargePowerLimit as output, because the test + // validator checks the write value + // (.output(ManagedEvcs.ChannelId.SET_CHARGE_POWER_LIMIT, 15000)) assertEquals("Check next value of setChargePowerLimit", 15000, // - ((IntegerReadChannel) EVCS0.channel(ManagedEvcs.ChannelId.SET_CHARGE_POWER_LIMIT)).getNextValue() - .orElse(0).intValue()); - - // Wait till charge limit is taken - test.next(new TestCase("Check ChargeState after 'getMinimumTimeTillCharingLimitTaken'") // - /* - * Timeleap is not used, to avoid using a clock in the ChargeSatusHandler and - * therefore a ClockProvider function in every EVCS (.timeleap(clock, 31, - * ChronoUnit.SECONDS)) - */ - .onAfterProcessImage(sleep) // - .input(evcs0ChargePower, 15000) // - .output(evcs0Status, Status.CHARGING) // - .output(evcs0ChargeState, ChargeState.CHARGING)); // - - // Decrease power - test.next(new TestCase("Decrease Power") // - .input(evcs0ChargePower, 15000) // - .input(evcs0SetChargePowerLimit, 8000) // - .output(evcs0ChargePower, 8000) // - .output(evcs0Status, Status.CHARGING) // - .output(evcs0ChargeState, ChargeState.DECREASING)); // - - // Enough power to increase, but 'MinimumTimeTillCharingLimitTaken' is not - // expired - test.next(new TestCase("Stay in decreasing charge state") // - .input(evcs0ChargePower, 8000) // - .input(evcs0SetChargePowerLimit, 20000) // - .output(evcs0ChargePower, 8000) // - .output(evcs0Status, Status.CHARGING) // - .output(evcs0ChargeState, ChargeState.DECREASING)); // - - // MinimumTimeTillCharingLimitTaken passed - test.next(new TestCase("MinimumTimeTillCharginglimitTaken passed") // - .onAfterProcessImage(sleep) // - .input(evcs0ChargePower, 8000) // - .input(evcs0SetChargePowerLimit, 20000) // - .output(evcs0ChargePower, 20000) // - .output(evcs0Status, Status.CHARGING) // - .output(evcs0ChargeState, ChargeState.INCREASING)); // - - // Charge power is increasing but decrease has higher priority than pause - test.next(new TestCase("Decrease has highest priority") // - .input(evcs0ChargePower, 20000) // - .input(evcs0SetChargePowerLimit, 0) // - .output(evcs0ChargePower, 0) // - .output(evcs0Status, Status.CHARGING_REJECTED) // - .output(evcs0ChargeState, ChargeState.DECREASING)); // + (EVCS0.channel(SET_CHARGE_POWER_LIMIT)).getNextValue().orElse(0).intValue()); + + test // + .next(new TestCase("Check ChargeState after 'getMinimumTimeTillCharingLimitTaken'") // + .onAfterProcessImage(SLEEP) // + .input(ACTIVE_POWER, 15000) // + .output(STATUS, Status.CHARGING) // + .output(CHARGE_STATE, ChargeState.CHARGING)) + + .next(new TestCase("Decrease Power") // + .input(ACTIVE_POWER, 15000) // + .input(SET_CHARGE_POWER_LIMIT, 8000) // + .output(ACTIVE_POWER, 8000) // + .output(STATUS, Status.CHARGING) // + .output(CHARGE_STATE, ChargeState.DECREASING)) + + // Enough power to increase, but 'MinimumTimeTillCharingLimitTaken' is not + // expired + .next(new TestCase("Stay in decreasing charge state") // + .input(ACTIVE_POWER, 8000) // + .input(SET_CHARGE_POWER_LIMIT, 20000) // + .output(ACTIVE_POWER, 8000) // + .output(STATUS, Status.CHARGING) // + .output(CHARGE_STATE, ChargeState.DECREASING)) + + .next(new TestCase("MinimumTimeTillCharginglimitTaken passed") // + .onAfterProcessImage(SLEEP) // + .input(ACTIVE_POWER, 8000) // + .input(SET_CHARGE_POWER_LIMIT, 20000) // + .output(ACTIVE_POWER, 20000) // + .output(STATUS, Status.CHARGING) // + .output(CHARGE_STATE, ChargeState.INCREASING)) + + // Charge power is increasing but decrease has higher priority than pause + .next(new TestCase("Decrease has highest priority") // + .input(ACTIVE_POWER, 20000) // + .input(SET_CHARGE_POWER_LIMIT, 0) // + .output(ACTIVE_POWER, 0) // + .output(STATUS, Status.CHARGING_REJECTED) // + .output(CHARGE_STATE, ChargeState.DECREASING)); // } @Test public void abstractManagedEvcsStateChangesTest() throws Exception { - // Sleep between every TestCase to make sure that the Channel Values are added - // to the pastValues Map. This is required because the Channel Value timestamp - // does not consider the mocked Clock. - final ThrowingRunnable sleep = () -> Thread.sleep(1010); - - ComponentTest test = new ComponentTest(EVCS1).addComponent(EVCS1); - - // Initial charge - test.next(new TestCase("Initial charge") // - - .input(evcs1SetChargePowerLimit, 15000) // - .input(evcs1ChargePower, 0) // - .input(evcs1Status, Status.READY_FOR_CHARGING) // - .input(evcs1EnergySession, 9999) // - .input(evcs1SetEnergyLimit, 10000) // - .output(evcs1ChargePower, 15000) // - .output(evcs1ChargeState, ChargeState.INCREASING)); // - - // EnergyLimit Reached - test.next(new TestCase("Energy limit reached") // - .input(evcs1ChargePower, 15000) // - .input(evcs1EnergySession, 10000) // - .input(evcs1Status, Status.CHARGING) // - .output(evcs1ChargeState, ChargeState.DECREASING) // - .output(evcs1Status, Status.ENERGY_LIMIT_REACHED) // - .output(evcs1ChargePower, 0)); // - - // EnergyLimit increased - still in pause - test.next(new TestCase("Energy limit increased - still in pause") // - .input(evcs1ChargePower, 0) // - .input(evcs1EnergySession, 10000) // - .input(evcs1SetEnergyLimit, 20000) // - .input(evcs1SetChargePowerLimit, 15000) // - .output(evcs1ChargePower, 0) // - .output(evcs1ChargeState, ChargeState.DECREASING)); // - - // EnergyLimit increased - after pause - test.next(new TestCase("Energy limit increased - after pause") // - .onAfterProcessImage(sleep) // - .input(evcs1ChargePower, 0) // - .input(evcs1EnergySession, 10000) // - .input(evcs1SetEnergyLimit, 20000) // - .input(evcs1SetChargePowerLimit, 15000) // - .output(evcs1ChargePower, 15000) // - .output(evcs1ChargeState, ChargeState.INCREASING)); // - - // Decrease power - test.next(new TestCase("Decrease Power") // - .input(evcs1ChargePower, 15000) // - .input(evcs1SetChargePowerLimit, 8000) // - .output(evcs1ChargePower, 8000) // - .output(evcs1Status, Status.CHARGING) // - .output(evcs1ChargeState, ChargeState.DECREASING)); // - - // Enough power to increase, but 'MinimumTimeTillCharingLimitTaken' is not - // expired - test.next(new TestCase("Stay in decreasing charge state") // - .input(evcs1ChargePower, 8000) // - .input(evcs1SetChargePowerLimit, 20000) // - .output(evcs1ChargePower, 8000) // - .output(evcs1Status, Status.CHARGING) // - .output(evcs1ChargeState, ChargeState.DECREASING)); // - - // MinimumTimeTillCharingLimitTaken passed - test.next(new TestCase("MinimumTimeTillCharginglimitTaken passed") // - .onAfterProcessImage(sleep) // - .input(evcs1ChargePower, 8000) // - .input(evcs1SetChargePowerLimit, 20000) // - .output(evcs1ChargePower, 20000) // - .output(evcs1Status, Status.CHARGING) // - .output(evcs1ChargeState, ChargeState.INCREASING)); // - - // Charge power is increasing but decrease has higher priority than pause - test.next(new TestCase("Decrease has highest priority") // - .input(evcs1ChargePower, 20000) // - .input(evcs1SetChargePowerLimit, 0) // - .output(evcs1ChargePower, 0) // - .output(evcs1Status, Status.CHARGING_REJECTED) // - .output(evcs1ChargeState, ChargeState.DECREASING)); // + new ComponentTest(EVCS1) // + .addComponent(EVCS1) // + + .next(new TestCase("Initial charge") // + .input(SET_CHARGE_POWER_LIMIT, 15000) // + .input(ACTIVE_POWER, 0) // + .input(STATUS, Status.READY_FOR_CHARGING) // + .input(ENERGY_SESSION, 9999) // + .input(SET_ENERGY_LIMIT, 10000) // + .output(ACTIVE_POWER, 15000) // + .output(CHARGE_STATE, ChargeState.INCREASING)) + + .next(new TestCase("Energy limit reached") // + .input(ACTIVE_POWER, 15000) // + .input(ENERGY_SESSION, 10000) // + .input(STATUS, Status.CHARGING) // + .output(CHARGE_STATE, ChargeState.DECREASING) // + .output(STATUS, Status.ENERGY_LIMIT_REACHED) // + .output(ACTIVE_POWER, 0)) + + .next(new TestCase("Energy limit increased - still in pause") // + .input(ACTIVE_POWER, 0) // + .input(ENERGY_SESSION, 10000) // + .input(SET_ENERGY_LIMIT, 20000) // + .input(SET_CHARGE_POWER_LIMIT, 15000) // + .output(ACTIVE_POWER, 0) // + .output(CHARGE_STATE, ChargeState.DECREASING)) + + .next(new TestCase("Energy limit increased - after pause") // + .onAfterProcessImage(SLEEP) // + .input(ACTIVE_POWER, 0) // + .input(ENERGY_SESSION, 10000) // + .input(SET_ENERGY_LIMIT, 20000) // + .input(SET_CHARGE_POWER_LIMIT, 15000) // + .output(ACTIVE_POWER, 15000) // + .output(CHARGE_STATE, ChargeState.INCREASING)) + + .next(new TestCase("Decrease Power") // + .input(ACTIVE_POWER, 15000) // + .input(SET_CHARGE_POWER_LIMIT, 8000) // + .output(ACTIVE_POWER, 8000) // + .output(STATUS, Status.CHARGING) // + .output(CHARGE_STATE, ChargeState.DECREASING)) + + .next(new TestCase("Stay in decreasing charge state; 'MinimumTimeTillCharingLimitTaken' is not expired") // + .input(ACTIVE_POWER, 8000) // + .input(SET_CHARGE_POWER_LIMIT, 20000) // + .output(ACTIVE_POWER, 8000) // + .output(STATUS, Status.CHARGING) // + .output(CHARGE_STATE, ChargeState.DECREASING)) + + .next(new TestCase("MinimumTimeTillCharginglimitTaken passed") // + .onAfterProcessImage(SLEEP) // + .input(ACTIVE_POWER, 8000) // + .input(SET_CHARGE_POWER_LIMIT, 20000) // + .output(ACTIVE_POWER, 20000) // + .output(STATUS, Status.CHARGING) // + .output(CHARGE_STATE, ChargeState.INCREASING)) + + .next(new TestCase("Charge power is increasing, but decrease has highest priority") // + .input(ACTIVE_POWER, 20000) // + .input(SET_CHARGE_POWER_LIMIT, 0) // + .output(ACTIVE_POWER, 0) // + .output(STATUS, Status.CHARGING_REJECTED) // + .output(CHARGE_STATE, ChargeState.DECREASING)); // } @Test public void abstractManagedEvcsWithFilterTest() throws Exception { - // Sleep between every TestCase to make sure that the Channel Values are added - // to the pastValues Map. This is required because the Channel Value timestamp - // does not consider the mocked Clock. - final ThrowingRunnable sleep = () -> Thread.sleep(1010); - - ComponentTest test = new ComponentTest(evcs2).addComponent(evcs2); + ComponentTest test = new ComponentTest(EVCS2) // + .addComponent(EVCS2); // Initial charge - int initialResult = (int) (MINIMUM + MAXIMUM * evcs2.getEvcsPower().getIncreaseRate()); // 5244 - test.next(new TestCase("Initial charge") // - - .input(evcs2SetChargePowerLimitWithFilter, 15000) // - .input(evcs2ChargePower, 0) // - .input(evcs2Status, Status.READY_FOR_CHARGING) // - .output(evcs2ChargePower, initialResult) // - .output(evcs2ChargeState, ChargeState.INCREASING)); // - - /* - * Cannot check the nextValue of SetChargePowerLimit as output, because the test - * validator checks the write value (.output(evcs0SetChargePowerLimit, - * initialResult)) - */ - // Check set charge limit + int initialResult = (int) (MINIMUM + MAXIMUM * EVCS2.getEvcsPower().getIncreaseRate()); // 5244 + + test // + .next(new TestCase("Initial charge") // + .input(SET_CHARGE_POWER_LIMIT_WITH_FILTER, 15000) // + .input(ACTIVE_POWER, 0) // + .input(STATUS, Status.READY_FOR_CHARGING) // + .output(ACTIVE_POWER, initialResult) // + .output(CHARGE_STATE, ChargeState.INCREASING)); // + + // Cannot check the nextValue of SetChargePowerLimit as output, because the test + // validator checks the write value + // (.output(ManagedEvcs.ChannelId.SET_CHARGE_POWER_LIMIT, + // initialResult)) assertEquals("Check next value of setChargePowerLimit", initialResult, // - ((IntegerReadChannel) evcs2.channel(ManagedEvcs.ChannelId.SET_CHARGE_POWER_LIMIT)).getNextValue() - .orElse(0).intValue()); - - // Further charge - int increasingValue = (int) (MAXIMUM * evcs2.getEvcsPower().getIncreaseRate()); - test.next(new TestCase("Further charge") // - - .input(evcs2SetChargePowerLimitWithFilter, 15000) // - .input(evcs2ChargePower, 5244) // - .input(evcs2Status, Status.CHARGING) // - .output(evcs2ChargePower, (int) (initialResult + increasingValue)) // 6348 W - .output(evcs2ChargeState, ChargeState.INCREASING)); // - - // Further charge - test.next(new TestCase("Further charge") // - - .input(evcs2SetChargePowerLimitWithFilter, 15000) // - .input(evcs2ChargePower, 6348) // - .input(evcs2Status, Status.CHARGING) // - .output(evcs2ChargePower, initialResult + increasingValue * 2) // 7452 W - .output(evcs2ChargeState, ChargeState.INCREASING)); // - - // Further charge - test.next(new TestCase("Further charge") // - - .input(evcs2SetChargePowerLimitWithFilter, 15000) // - .input(evcs2ChargePower, 6348) // - .input(evcs2Status, Status.CHARGING) // - .output(evcs2ChargePower, initialResult + increasingValue * 3) // 8556 W - .output(evcs2ChargeState, ChargeState.INCREASING)); // - - // Further charge - test.next(new TestCase("Further charge") // - - .input(evcs2SetChargePowerLimitWithFilter, 15000) // - .input(evcs2ChargePower, 6348) // - .input(evcs2Status, Status.CHARGING) // - .output(evcs2ChargePower, initialResult + increasingValue * 4) // 9660 W - .output(evcs2ChargeState, ChargeState.INCREASING)); // - - // Further charge - reached target - test.next(new TestCase("Further charge") // - - .input(evcs2SetChargePowerLimitWithFilter, 10000) // - .input(evcs2ChargePower, 6348) // - .input(evcs2Status, Status.CHARGING) // - .output(evcs2ChargePower, 10_000) // 10000 W - .output(evcs2ChargeState, ChargeState.INCREASING)); // - - // Wait till charge limit is taken - test.next(new TestCase("Check ChargeState after 'getMinimumTimeTillCharingLimitTaken'") // - .onAfterProcessImage(sleep) // - .input(evcs2ChargePower, 10000) // - .output(evcs2Status, Status.CHARGING) // - .output(evcs2ChargeState, ChargeState.CHARGING)); // - - // Decrease power - test.next(new TestCase("Decrease Power") // - .input(evcs2ChargePower, 10000) // - .input(evcs2SetChargePowerLimitWithFilter, 8000) // - .output(evcs2ChargePower, 8000) // - .output(evcs2Status, Status.CHARGING) // - .output(evcs2ChargeState, ChargeState.DECREASING)); // - - // Enough power to increase, but 'MinimumTimeTillCharingLimitTaken' is not - // expired - test.next(new TestCase("Stay in decreasing charge state") // - .input(evcs2ChargePower, 8000) // - .input(evcs2SetChargePowerLimitWithFilter, 20000) // - .output(evcs2ChargePower, 8000) // - .output(evcs2Status, Status.CHARGING) // - .output(evcs2ChargeState, ChargeState.DECREASING)); // - - // MinimumTimeTillCharingLimitTaken passed - test.next(new TestCase("MinimumTimeTillCharginglimitTaken passed") // - .onAfterProcessImage(sleep) // - .input(evcs2ChargePower, 8000) // - .input(evcs2SetChargePowerLimitWithFilter, 20000) // - .output(evcs2ChargePower, 8000 + increasingValue) // 9104 - .output(evcs2Status, Status.CHARGING) // - .output(evcs2ChargeState, ChargeState.INCREASING)); // - - // Charge power is increasing but decrease has higher priority than pause - test.next(new TestCase("Decrease has highest priority") // - .input(evcs2ChargePower, 20000) // - .input(evcs2SetChargePowerLimitWithFilter, 0) // - .output(evcs2ChargePower, 0) // - .output(evcs2Status, Status.CHARGING_REJECTED) // - .output(evcs2ChargeState, ChargeState.DECREASING)); // - - // Charging stopped - still in pause state - test.next(new TestCase("Charging stopped - still in pause state") // - .input(evcs2ChargePower, 0) // - .input(evcs2SetChargePowerLimitWithFilter, 0) // - .output(evcs2ChargePower, 0) // - .output(evcs2Status, Status.CHARGING_REJECTED) // - .output(evcs2ChargeState, ChargeState.DECREASING)); // - - // Charging stopped - test.next(new TestCase("Charging stopped") // - .onAfterProcessImage(sleep) // - .input(evcs2ChargePower, 0) // - .input(evcs2SetChargePowerLimitWithFilter, 0) // - .output(evcs2ChargePower, 0) // - .output(evcs2Status, Status.CHARGING_REJECTED) // - .output(evcs2ChargeState, ChargeState.NOT_CHARGING)); // + (EVCS2.channel(SET_CHARGE_POWER_LIMIT)).getNextValue().orElse(0).intValue()); + + int increasingValue = (int) (MAXIMUM * EVCS2.getEvcsPower().getIncreaseRate()); + test // + .next(new TestCase("Further charge") // + .input(SET_CHARGE_POWER_LIMIT_WITH_FILTER, 15000) // + .input(ACTIVE_POWER, 5244) // + .input(STATUS, Status.CHARGING) // + // 6348 W + .output(ACTIVE_POWER, (int) (initialResult + increasingValue)) + .output(CHARGE_STATE, ChargeState.INCREASING)) + + .next(new TestCase("Further charge") // + .input(SET_CHARGE_POWER_LIMIT_WITH_FILTER, 15000) // + .input(ACTIVE_POWER, 6348) // + .input(STATUS, Status.CHARGING) // + .output(ACTIVE_POWER, initialResult + increasingValue * 2) // 7452 W + .output(CHARGE_STATE, ChargeState.INCREASING)) + + .next(new TestCase("Further charge") // + .input(SET_CHARGE_POWER_LIMIT_WITH_FILTER, 15000) // + .input(ACTIVE_POWER, 6348) // + .input(STATUS, Status.CHARGING) // + .output(ACTIVE_POWER, initialResult + increasingValue * 3) // 8556 W + .output(CHARGE_STATE, ChargeState.INCREASING)) + + .next(new TestCase("Further charge") // + .input(SET_CHARGE_POWER_LIMIT_WITH_FILTER, 15000) // + .input(ACTIVE_POWER, 6348) // + .input(STATUS, Status.CHARGING) // + .output(ACTIVE_POWER, initialResult + increasingValue * 4) // 9660 W + .output(CHARGE_STATE, ChargeState.INCREASING)) + + .next(new TestCase("Further charge - reached target") // + .input(SET_CHARGE_POWER_LIMIT_WITH_FILTER, 10000) // + .input(ACTIVE_POWER, 6348) // + .input(STATUS, Status.CHARGING) // + .output(ACTIVE_POWER, 10_000) // 10000 W + .output(CHARGE_STATE, ChargeState.INCREASING)) + + .next(new TestCase( + "Wait till charge limit is taken. Check ChargeState after 'getMinimumTimeTillCharingLimitTaken'") // + .onAfterProcessImage(SLEEP) // + .input(ACTIVE_POWER, 10000) // + .output(STATUS, Status.CHARGING) // + .output(CHARGE_STATE, ChargeState.CHARGING)) + + .next(new TestCase("Decrease Power") // + .input(ACTIVE_POWER, 10000) // + .input(SET_CHARGE_POWER_LIMIT_WITH_FILTER, 8000) // + .output(ACTIVE_POWER, 8000) // + .output(STATUS, Status.CHARGING) // + .output(CHARGE_STATE, ChargeState.DECREASING)) + + // Enough power to increase, but 'MinimumTimeTillCharingLimitTaken' is not + // expired + .next(new TestCase("Stay in decreasing charge state") // + .input(ACTIVE_POWER, 8000) // + .input(SET_CHARGE_POWER_LIMIT_WITH_FILTER, 20000) // + .output(ACTIVE_POWER, 8000) // + .output(STATUS, Status.CHARGING) // + .output(CHARGE_STATE, ChargeState.DECREASING)) + + .next(new TestCase("MinimumTimeTillCharginglimitTaken passed") // + .onAfterProcessImage(SLEEP) // + .input(ACTIVE_POWER, 8000) // + .input(SET_CHARGE_POWER_LIMIT_WITH_FILTER, 20000) // + .output(ACTIVE_POWER, 8000 + increasingValue) // 9104 + .output(STATUS, Status.CHARGING) // + .output(CHARGE_STATE, ChargeState.INCREASING)) + + // Charge power is increasing but decrease has higher priority than pause + .next(new TestCase("Decrease has highest priority") // + .input(ACTIVE_POWER, 20000) // + .input(SET_CHARGE_POWER_LIMIT_WITH_FILTER, 0) // + .output(ACTIVE_POWER, 0) // + .output(STATUS, Status.CHARGING_REJECTED) // + .output(CHARGE_STATE, ChargeState.DECREASING)) + + .next(new TestCase("Charging stopped - still in pause state") // + .input(ACTIVE_POWER, 0) // + .input(SET_CHARGE_POWER_LIMIT_WITH_FILTER, 0) // + .output(ACTIVE_POWER, 0) // + .output(STATUS, Status.CHARGING_REJECTED) // + .output(CHARGE_STATE, ChargeState.DECREASING)) + + .next(new TestCase("Charging stopped") // + .onAfterProcessImage(SLEEP) // + .input(ACTIVE_POWER, 0) // + .input(SET_CHARGE_POWER_LIMIT_WITH_FILTER, 0) // + .output(ACTIVE_POWER, 0) // + .output(STATUS, Status.CHARGING_REJECTED) // + .output(CHARGE_STATE, ChargeState.NOT_CHARGING)); // } } diff --git a/io.openems.edge.evcs.cluster/.classpath b/io.openems.edge.evcs.cluster/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.evcs.cluster/.classpath +++ b/io.openems.edge.evcs.cluster/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.evcs.cluster/src/io/openems/edge/evcs/cluster/EvcsClusterPeakShavingImpl.java b/io.openems.edge.evcs.cluster/src/io/openems/edge/evcs/cluster/EvcsClusterPeakShavingImpl.java index 348062f8ecd..ef62076353d 100644 --- a/io.openems.edge.evcs.cluster/src/io/openems/edge/evcs/cluster/EvcsClusterPeakShavingImpl.java +++ b/io.openems.edge.evcs.cluster/src/io/openems/edge/evcs/cluster/EvcsClusterPeakShavingImpl.java @@ -1,5 +1,9 @@ package io.openems.edge.evcs.cluster; +import static io.openems.edge.evcs.api.Phases.THREE_PHASE; +import static io.openems.edge.evcs.api.Phases.TWO_PHASE; +import static java.lang.Math.round; + import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -23,6 +27,7 @@ import org.slf4j.LoggerFactory; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.types.MeterType; import io.openems.edge.common.channel.calculate.CalculateIntegerSum; import io.openems.edge.common.component.AbstractOpenemsComponent; import io.openems.edge.common.component.ComponentManager; @@ -39,7 +44,6 @@ import io.openems.edge.evcs.api.Evcs; import io.openems.edge.evcs.api.ManagedEvcs; import io.openems.edge.evcs.api.MetaEvcs; -import io.openems.edge.evcs.api.Phases; import io.openems.edge.meter.api.ElectricityMeter; @Designate(ocd = Config.class, factory = true) @@ -52,7 +56,7 @@ EdgeEventConstants.TOPIC_CYCLE_AFTER_CONTROLLERS, // }) public class EvcsClusterPeakShavingImpl extends AbstractOpenemsComponent - implements MetaEvcs, OpenemsComponent, Evcs, EventHandler, EvcsClusterPeakShaving, + implements MetaEvcs, OpenemsComponent, Evcs, ElectricityMeter, EventHandler, EvcsClusterPeakShaving, /* * Cluster is not a Controller, but we need to be placed at the correct position * in the Cycle by the Scheduler to be able to read the actually available ESS @@ -107,6 +111,7 @@ public class EvcsClusterPeakShavingImpl extends AbstractOpenemsComponent public EvcsClusterPeakShavingImpl() { super(// OpenemsComponent.ChannelId.values(), // + ElectricityMeter.ChannelId.values(), // Evcs.ChannelId.values(), // EvcsClusterPeakShaving.ChannelId.values(), // Controller.ChannelId.values() // @@ -162,6 +167,11 @@ protected void deactivate() { super.deactivate(); } + @Override + public MeterType getMeterType() { + return MeterType.MANAGED_CONSUMPTION_METERED; + } + /** * Fills sortedEvcss using the order of evcs_ids property in the configuration. */ @@ -222,8 +232,8 @@ public void handleEvent(Event event) { */ private void calculateChannelValues() { this.currentEvcsClusterState = EvcsClusterStatus.REGULAR; - final var chargePower = new CalculateIntegerSum(); - final var blockedChargePower = new CalculateIntegerSum(); + final var activePower = new CalculateIntegerSum(); + final var blockedActivePower = new CalculateIntegerSum(); final var minHardwarePower = new CalculateIntegerSum(); final var maxHardwarePowerOfAll = new CalculateIntegerSum(); final var minFixedHardwarePower = new CalculateIntegerSum(); @@ -231,20 +241,16 @@ private void calculateChannelValues() { final var minPower = new CalculateIntegerSum(); final var evcsClusterStatus = new CalculateEvcsClusterStatus(); - for (Evcs evcs : this.getSortedEvcss()) { - chargePower.addValue(evcs.getChargePowerChannel()); - blockedChargePower.addValue(evcs.getChargePowerChannel(), value -> { - + for (var evcs : this.getSortedEvcss()) { + activePower.addValue(evcs.getActivePowerChannel()); + blockedActivePower.addValue(evcs.getActivePowerChannel(), value -> { // Calculate the blocked power using all 3 phases for now if (value != null) { - switch (evcs.getPhases()) { - case ONE_PHASE: - return value * Phases.THREE_PHASE.getValue(); - case TWO_PHASE: - return Math.round(value / Phases.TWO_PHASE.getValue() * Phases.THREE_PHASE.getValue()); - case THREE_PHASE: - return value; - } + return switch (evcs.getPhases()) { + case ONE_PHASE -> value * THREE_PHASE.getValue(); + case TWO_PHASE -> round(value / TWO_PHASE.getValue() * THREE_PHASE.getValue()); + case THREE_PHASE -> value; + }; } return null; }); @@ -258,8 +264,8 @@ private void calculateChannelValues() { } } - this._setChargePower(chargePower.calculate()); - this._setEvcsBlockedChargePower(blockedChargePower.calculate()); + this._setActivePower(activePower.calculate()); + this._setEvcsBlockedChargePower(blockedActivePower.calculate()); this._setFixedMinimumHardwarePower(minFixedHardwarePower.calculate()); this._setFixedMaximumHardwarePower(maxFixedHardwarePower.calculate()); this.channel(Evcs.ChannelId.MINIMUM_HARDWARE_POWER).setNextValue(minHardwarePower.calculate()); @@ -329,7 +335,7 @@ protected void limitEvcss() { * Defines the active charging stations that are charging. */ List activeEvcss = new ArrayList<>(); - for (Evcs evcs : this.getSortedEvcss()) { + for (var evcs : this.getSortedEvcss()) { if (evcs instanceof ManagedEvcs) { var managedEvcs = (ManagedEvcs) evcs; int requestedPower = managedEvcs.getSetChargePowerRequestChannel().getNextWriteValue().orElse(0); @@ -343,26 +349,11 @@ protected void limitEvcss() { var guaranteedPower = this.getGuaranteedPower(managedEvcs); var status = managedEvcs.getStatus(); switch (status) { - case CHARGING_FINISHED: - managedEvcs.setChargePowerLimitWithFilter(requestedPower); - /* - * TODO: Change state from CHARGING_FINISHED to CHARGING should increase the - * minimumPower. e.g. ZOE that is nearly on 100 percent, does not charge with a - * set point of 13kW, but 22kW by charging with less power. - */ - break; - case ERROR: - case STARTING: - case UNDEFINED: - case NOT_READY_FOR_CHARGING: - case ENERGY_LIMIT_REACHED: + case ERROR, STARTING, UNDEFINED, NOT_READY_FOR_CHARGING, ENERGY_LIMIT_REACHED -> managedEvcs.setChargePowerLimit(0); - break; - case READY_FOR_CHARGING: - + case READY_FOR_CHARGING -> { // Check if there is enough power for an initial charge - // if (totalPowerLimit - this.getChargePower().orElse(0) >= guaranteedPower) { - if (totalPowerLimit - initialChargePower - this.getChargePower().orElse(0) >= guaranteedPower) { + if (totalPowerLimit - initialChargePower - this.getActivePower().orElse(0) >= guaranteedPower) { this.logInfoInDebugmode("Set initial power " + guaranteedPower + " to " + evcs.id()); managedEvcs.setChargePowerLimit(guaranteedPower); @@ -380,11 +371,10 @@ protected void limitEvcss() { // Reduce the total power by the initial power to be able to send an initial // charge request in the next cycles totalPowerLeftMinusGuarantee -= guaranteedPower; - break; - + } // EVCS is active. - case CHARGING_REJECTED: - case CHARGING: + case CHARGING_REJECTED, CHARGING -> { + /* * Reduces the available power by the guaranteed power of each charging station. * Sets the minimum power depending on the guaranteed and the maximum Power. @@ -396,7 +386,7 @@ protected void limitEvcss() { } else { managedEvcs.setChargePowerLimit(0); } - break; + } } } } @@ -404,7 +394,7 @@ protected void limitEvcss() { /* * Distributes the available Power to the active EVCSs */ - for (ManagedEvcs evcs : activeEvcss) { + for (var evcs : activeEvcss) { // int guaranteedPower = evcs.getMinimumPowerChannel().getNextValue().orElse(0); int guaranteedPower = evcs.getMinimumPowerChannel().getNextValue().orElse(0); @@ -483,8 +473,7 @@ public int getMaximumPowerToDistribute() { // Calculate maximum grid power var gridPower = this.getGridPower(); - var maxAvailableGridPower = (this.config.hardwarePowerLimitPerPhase() * Phases.THREE_PHASE.getValue()) - - gridPower; + var maxAvailableGridPower = (this.config.hardwarePowerLimitPerPhase() * THREE_PHASE.getValue()) - gridPower; this.channel(EvcsClusterPeakShaving.ChannelId.MAXIMUM_AVAILABLE_GRID_POWER).setNextValue(maxAvailableGridPower); // Current charge power blocked by all EVCS's @@ -496,7 +485,7 @@ public int getMaximumPowerToDistribute() { "Calculation of the maximum charge Power: EVCS Charge [" + evcsCharge + "] + Max. available storage power [" + maxAvailableStoragePower + "] + ( Configured Hardware Limit * 3 [" - + this.config.hardwarePowerLimitPerPhase() * Phases.THREE_PHASE.getValue() + + this.config.hardwarePowerLimitPerPhase() * THREE_PHASE.getValue() + "] - Maximum of all three phases * 3 [" + gridPower + "]"); return allowedChargePower > 0 ? allowedChargePower : 0; @@ -531,8 +520,7 @@ private int getGridPower() { public int getAvailableGridPower() { // Calculate maximum grid power int gridPower = this.getGridPower(); - int maxAvailableGridPower = (this.config.hardwarePowerLimitPerPhase() * Phases.THREE_PHASE.getValue()) - - gridPower; + int maxAvailableGridPower = (this.config.hardwarePowerLimitPerPhase() * THREE_PHASE.getValue()) - gridPower; return maxAvailableGridPower; } diff --git a/io.openems.edge.evcs.cluster/test/io/openems/edge/evcs/cluster/EvcsClusterPeakShavingImplTest.java b/io.openems.edge.evcs.cluster/test/io/openems/edge/evcs/cluster/EvcsClusterPeakShavingImplTest.java index 2f6aee35dbf..ba820f14e0b 100644 --- a/io.openems.edge.evcs.cluster/test/io/openems/edge/evcs/cluster/EvcsClusterPeakShavingImplTest.java +++ b/io.openems.edge.evcs.cluster/test/io/openems/edge/evcs/cluster/EvcsClusterPeakShavingImplTest.java @@ -1,10 +1,23 @@ package io.openems.edge.evcs.cluster; +import static io.openems.edge.common.sum.Sum.ChannelId.ESS_ACTIVE_POWER; +import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.ALLOWED_DISCHARGE_POWER; +import static io.openems.edge.evcs.api.Evcs.ChannelId.MAXIMUM_HARDWARE_POWER; +import static io.openems.edge.evcs.api.Evcs.ChannelId.MAXIMUM_POWER; +import static io.openems.edge.evcs.api.Evcs.ChannelId.MINIMUM_HARDWARE_POWER; +import static io.openems.edge.evcs.api.Evcs.ChannelId.STATUS; +import static io.openems.edge.evcs.api.ManagedEvcs.ChannelId.CHARGE_STATE; +import static io.openems.edge.evcs.api.ManagedEvcs.ChannelId.SET_CHARGE_POWER_LIMIT; +import static io.openems.edge.evcs.api.ManagedEvcs.ChannelId.SET_CHARGE_POWER_REQUEST; +import static io.openems.edge.evcs.cluster.EvcsClusterPeakShaving.ChannelId.EVCS_CLUSTER_STATUS; +import static io.openems.edge.evcs.cluster.EvcsClusterPeakShaving.ChannelId.MAXIMUM_POWER_TO_DISTRIBUTE; +import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.ACTIVE_POWER; +import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.ACTIVE_POWER_L1; +import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.ACTIVE_POWER_L2; +import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.ACTIVE_POWER_L3; + import org.junit.Test; -import io.openems.common.types.ChannelAddress; -import io.openems.edge.common.filter.DisabledRampFilter; -import io.openems.edge.common.filter.RampFilter; import io.openems.edge.common.sum.DummySum; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; @@ -13,7 +26,6 @@ import io.openems.edge.ess.test.DummyManagedSymmetricEss; import io.openems.edge.evcs.api.ChargeState; import io.openems.edge.evcs.api.Status; -import io.openems.edge.evcs.test.DummyEvcsPower; import io.openems.edge.evcs.test.DummyManagedEvcs; import io.openems.edge.meter.test.DummyElectricityMeter; @@ -23,347 +35,261 @@ public class EvcsClusterPeakShavingImplTest { private static final DummyManagedSymmetricEss ESS = new DummyManagedSymmetricEss("ess0") // .withMaxApparentPower(30000); private static final DummyElectricityMeter METER = new DummyElectricityMeter("meter0"); - private static final DummyEvcsPower EVCS_POWER = new DummyEvcsPower(new DisabledRampFilter()); - private static final DummyManagedEvcs EVCS0 = new DummyManagedEvcs("evcs0", EVCS_POWER); - private static final DummyManagedEvcs EVCS1 = new DummyManagedEvcs("evcs1", EVCS_POWER); - private static final DummyManagedEvcs EVCS2 = new DummyManagedEvcs("evcs2", EVCS_POWER); - private static final DummyManagedEvcs EVCS3 = new DummyManagedEvcs("evcs3", EVCS_POWER); - private static final DummyManagedEvcs EVCS4 = new DummyManagedEvcs("evcs4", EVCS_POWER); - - private static final DummyEvcsPower EVCS_POWER_WITH_FILTER = new DummyEvcsPower(new RampFilter()); - private static final DummyManagedEvcs EVCS5 = new DummyManagedEvcs("evcs5", EVCS_POWER_WITH_FILTER); + private static final DummyManagedEvcs EVCS0 = DummyManagedEvcs.ofDisabled("evcs0"); + private static final DummyManagedEvcs EVCS1 = DummyManagedEvcs.ofDisabled("evcs1"); + private static final DummyManagedEvcs EVCS2 = DummyManagedEvcs.ofDisabled("evcs2"); + private static final DummyManagedEvcs EVCS3 = DummyManagedEvcs.ofDisabled("evcs3"); + private static final DummyManagedEvcs EVCS4 = DummyManagedEvcs.ofDisabled("evcs4"); + private static final DummyManagedEvcs EVCS5 = DummyManagedEvcs.ofDisabled("evcs5"); private static final int HARDWARE_POWER_LIMIT_PER_PHASE = 7000; - private static final ChannelAddress SUM_ESS_ACTIVE_POWER = new ChannelAddress("_sum", "EssActivePower"); - private static final ChannelAddress METER_GRID_ACTIVE_POWER = new ChannelAddress("meter0", "ActivePower"); - private static final ChannelAddress METER_GRID_ACTIVE_POWER_L1 = new ChannelAddress("meter0", "ActivePowerL1"); - private static final ChannelAddress METER_GRID_ACTIVE_POWER_L2 = new ChannelAddress("meter0", "ActivePowerL2"); - private static final ChannelAddress METER_GRID_ACTIVE_POWER_L3 = new ChannelAddress("meter0", "ActivePowerL3"); - private static final ChannelAddress ESS_ALLOWED_DISCHARGE_POWER = new ChannelAddress("ess0", - "AllowedDischargePower"); - - private static final ChannelAddress EVCS_CLUSTER_MAXIMUM_POWER_TO_DISTRIBUTE = new ChannelAddress("evcsCluster0", - "MaximumPowerToDistribute"); - private static final ChannelAddress EVCS_CLUSTER_STATUS = new ChannelAddress("evcsCluster0", "EvcsClusterStatus"); - - private static final ChannelAddress EVCS0_STATUS = new ChannelAddress("evcs0", "Status"); - private static final ChannelAddress EVCS0_CHARGE_POWER = new ChannelAddress("evcs0", "ChargePower"); - private static final ChannelAddress EVCS0_MAXIMUM_POWER = new ChannelAddress("evcs0", "MaximumPower"); - private static final ChannelAddress EVCS0_SET_POWER_REQUEST = new ChannelAddress("evcs0", "SetChargePowerRequest"); - private static final ChannelAddress EVCS0_SET_CHARGE_POWER_LIMIT = new ChannelAddress("evcs0", - "SetChargePowerLimit"); - private static final ChannelAddress EVCS0_MAXIMUM_HARDWARE_POWER = new ChannelAddress("evcs0", - "MaximumHardwarePower"); - private static final ChannelAddress EVCS0_MINIMUM_HARDWARE_POWER = new ChannelAddress("evcs0", - "MinimumHardwarePower"); - private static final ChannelAddress EVCS0_CHARE_STATE = new ChannelAddress("evcs0", "ChargeState"); - - private static final ChannelAddress EVCS1_STATUS = new ChannelAddress("evcs1", "Status"); - private static final ChannelAddress EVCS1_CHARGE_POWER = new ChannelAddress("evcs1", "ChargePower"); - private static final ChannelAddress EVCS1_MAXIMUM_POWER = new ChannelAddress("evcs1", "MaximumPower"); - private static final ChannelAddress EVCS1_SET_POWER_REQUEST = new ChannelAddress("evcs1", "SetChargePowerRequest"); - private static final ChannelAddress EVCS1_SET_CHARGE_POWER_LIMIT = new ChannelAddress("evcs1", - "SetChargePowerLimit"); - private static final ChannelAddress EVCS1_MAXIMUM_HARDWARE_POWER = new ChannelAddress("evcs1", - "MaximumHardwarePower"); - private static final ChannelAddress EVCS1_MINIMUM_HARDWARE_POWER = new ChannelAddress("evcs1", - "MinimumHardwarePower"); - private static final ChannelAddress EVCS1_CHARGE_STATE = new ChannelAddress("evcs1", "ChargeState"); - - private static final ChannelAddress EVCS2_STATUS = new ChannelAddress("evcs2", "Status"); - private static final ChannelAddress EVCS2_SET_POWER_REQUEST = new ChannelAddress("evcs2", "SetChargePowerRequest"); - private static final ChannelAddress EVCS2_CHARGE_STATE = new ChannelAddress("evcs2", "ChargeState"); - - private static final ChannelAddress EVCS3_STATUS = new ChannelAddress("evcs3", "Status"); - private static final ChannelAddress EVCS3_SET_POWER_REQUEST = new ChannelAddress("evcs3", "SetChargePowerRequest"); - private static final ChannelAddress EVCS3_CHARGE_STATE = new ChannelAddress("evcs3", "ChargeState"); - - private static final ChannelAddress EVCS4_STATUS = new ChannelAddress("evcs4", "Status"); - private static final ChannelAddress EVCS4_SET_POWER_REQUEST = new ChannelAddress("evcs4", "SetChargePowerRequest"); - - private static final ChannelAddress EVCS5_STATUS = new ChannelAddress("evcs5", "Status"); - private static final ChannelAddress EVCS5_CHARGE_POWER = new ChannelAddress("evcs5", "ChargePower"); - private static final ChannelAddress EVCS5_SET_POWER_REQUEST = new ChannelAddress("evcs5", "SetChargePowerRequest"); - private static final ChannelAddress EVCS5_SET_CHARGE_POWER_LIMIT = new ChannelAddress("evcs5", - "SetChargePowerLimit"); - private static final ChannelAddress EVCS5_MAXIMUM_HARDWARE_POWER = new ChannelAddress("evcs5", - "MaximumHardwarePower"); - private static final ChannelAddress EVCS5_CHARGE_STATE = new ChannelAddress("evcs5", "ChargeState"); - @Test public void clusterMaximum_essActivePowerTest() throws Exception { - String[] evcsIds = { "evcs0", "evcs1" }; - new ComponentTest(new EvcsClusterPeakShavingImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // .addReference("sum", new DummySum()) // .addReference("addEvcs", EVCS0) // .addReference("addEvcs", EVCS1) // - .addReference("addEvcs", EVCS2) // - .addReference("addEvcs", EVCS3) // - .addReference("addEvcs", EVCS4) // .addReference("meter", METER) // .addReference("ess", ESS) // .activate(MyConfig.create() // - .setEssId(ESS.id()) // - .setMeterId(METER.id()) // + .setId("evcsCluster0") // + .setEssId("ess0") // + .setMeterId("meter0") // .setHardwarePowerLimit(HARDWARE_POWER_LIMIT_PER_PHASE) // - .setEvcsIds(evcsIds) // + .setEvcsIds("evcs0", "evcs1") // .build()) // .next(new TestCase() // - .input(EVCS0_CHARE_STATE, ChargeState.CHARGING) // - .input(SUM_ESS_ACTIVE_POWER, 0) // - .input(METER_GRID_ACTIVE_POWER, 0) // - .input(METER_GRID_ACTIVE_POWER_L1, 0) // - .input(METER_GRID_ACTIVE_POWER_L2, 0) // - .input(METER_GRID_ACTIVE_POWER_L3, 0) // - .input(ESS_ALLOWED_DISCHARGE_POWER, 0) // - .output(EVCS_CLUSTER_MAXIMUM_POWER_TO_DISTRIBUTE, 21000)) // + .input("evcs0", CHARGE_STATE, ChargeState.CHARGING) // + .input(ESS_ACTIVE_POWER, 0) // + .input("meter0", ACTIVE_POWER, 0) // + .input("meter0", ACTIVE_POWER_L1, 0) // + .input("meter0", ACTIVE_POWER_L2, 0) // + .input("meter0", ACTIVE_POWER_L3, 0) // + .input("ess0", ALLOWED_DISCHARGE_POWER, 0) // + .output("evcsCluster0", MAXIMUM_POWER_TO_DISTRIBUTE, 21000)) // .next(new TestCase() // - .input(SUM_ESS_ACTIVE_POWER, -5000) // - .output(EVCS_CLUSTER_MAXIMUM_POWER_TO_DISTRIBUTE, 26000)) // + .input(ESS_ACTIVE_POWER, -5000) // + .output("evcsCluster0", MAXIMUM_POWER_TO_DISTRIBUTE, 26000)) // .next(new TestCase() // - .input(SUM_ESS_ACTIVE_POWER, 6000) // - .output(EVCS_CLUSTER_MAXIMUM_POWER_TO_DISTRIBUTE, 15000)) // + .input(ESS_ACTIVE_POWER, 6000) // + .output("evcsCluster0", MAXIMUM_POWER_TO_DISTRIBUTE, 15000)) // ; } @Test public void clusterMaximum_symmetricGridPowerTest() throws Exception { - String[] evcsIds = { "evcs0", "evcs1" }; - new ComponentTest(new EvcsClusterPeakShavingImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // .addReference("sum", new DummySum()) // .addReference("addEvcs", EVCS0) // .addReference("addEvcs", EVCS1) // - .addReference("addEvcs", EVCS2) // - .addReference("addEvcs", EVCS3) // - .addReference("addEvcs", EVCS4) // .addReference("meter", METER) // .addReference("ess", ESS) // .activate(MyConfig.create() // - .setEssId(ESS.id()) // - .setMeterId(METER.id()) // + .setId("evcsCluster0") // + .setEssId("ess0") // + .setMeterId("meter0") // .setHardwarePowerLimit(HARDWARE_POWER_LIMIT_PER_PHASE) // - .setEvcsIds(evcsIds) // + .setEvcsIds("evcs0", "evcs1") // .build()) // .next(new TestCase() // - .input(EVCS0_CHARE_STATE, ChargeState.CHARGING) // - .input(METER_GRID_ACTIVE_POWER, -6000) // - .input(METER_GRID_ACTIVE_POWER_L1, -2000) // - .input(METER_GRID_ACTIVE_POWER_L2, -2000) // - .input(METER_GRID_ACTIVE_POWER_L3, -2000) // - .input(ESS_ALLOWED_DISCHARGE_POWER, 0)) // - .next(new TestCase() // - .output(EVCS_CLUSTER_MAXIMUM_POWER_TO_DISTRIBUTE, 27000)) // - .next(new TestCase() // - .input(METER_GRID_ACTIVE_POWER, 4500) // - .input(METER_GRID_ACTIVE_POWER_L1, 1500) // - .input(METER_GRID_ACTIVE_POWER_L2, 1500) // - .input(METER_GRID_ACTIVE_POWER_L3, 1500) // - .input(ESS_ALLOWED_DISCHARGE_POWER, 0) // - .output(EVCS_CLUSTER_MAXIMUM_POWER_TO_DISTRIBUTE, 16500)) // + .input("evcs0", CHARGE_STATE, ChargeState.CHARGING) // + .input("meter0", ACTIVE_POWER, -6000) // + .input("meter0", ACTIVE_POWER_L1, -2000) // + .input("meter0", ACTIVE_POWER_L2, -2000) // + .input("meter0", ACTIVE_POWER_L3, -2000) // + .input("ess0", ALLOWED_DISCHARGE_POWER, 0)) // + .next(new TestCase() // + .output("evcsCluster0", MAXIMUM_POWER_TO_DISTRIBUTE, 27000)) // + .next(new TestCase() // + .input("meter0", ACTIVE_POWER, 4500) // + .input("meter0", ACTIVE_POWER_L1, 1500) // + .input("meter0", ACTIVE_POWER_L2, 1500) // + .input("meter0", ACTIVE_POWER_L3, 1500) // + .input("ess0", ALLOWED_DISCHARGE_POWER, 0) // + .output("evcsCluster0", MAXIMUM_POWER_TO_DISTRIBUTE, 16500)) // ; } @Test public void clusterMaximum_assymmetricGridPowerTest() throws Exception { - String[] evcsIds = { "evcs0", "evcs1" }; - new ComponentTest(new EvcsClusterPeakShavingImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // .addReference("sum", new DummySum()) // .addReference("addEvcs", EVCS0) // .addReference("addEvcs", EVCS1) // - .addReference("addEvcs", EVCS2) // - .addReference("addEvcs", EVCS3) // - .addReference("addEvcs", EVCS4) // .addReference("meter", METER) // .addReference("ess", ESS) // .activate(MyConfig.create() // - .setEssId(ESS.id()) // - .setMeterId(METER.id()) // + .setId("evcsCluster0") // + .setEssId("ess0") // + .setMeterId("meter0") // .setHardwarePowerLimit(HARDWARE_POWER_LIMIT_PER_PHASE) // - .setEvcsIds(evcsIds) // + .setEvcsIds("evcs0", "evcs1") // .build()) // .next(new TestCase() // - .input(EVCS0_CHARE_STATE, ChargeState.CHARGING) // - .input(METER_GRID_ACTIVE_POWER, -4000) // - .input(METER_GRID_ACTIVE_POWER_L1, -2000) // - .input(METER_GRID_ACTIVE_POWER_L2, -1000) // - .input(METER_GRID_ACTIVE_POWER_L3, -1000) // - .input(ESS_ALLOWED_DISCHARGE_POWER, 0) // - .output(EVCS_CLUSTER_MAXIMUM_POWER_TO_DISTRIBUTE, 24000)) // - .next(new TestCase() // - .input(METER_GRID_ACTIVE_POWER, 4500) // - .input(METER_GRID_ACTIVE_POWER_L1, 3000) // - .input(METER_GRID_ACTIVE_POWER_L2, 1500) // - .input(METER_GRID_ACTIVE_POWER_L3, 500) // - .input(ESS_ALLOWED_DISCHARGE_POWER, 0) // - .output(EVCS_CLUSTER_MAXIMUM_POWER_TO_DISTRIBUTE, 12000)) // + .input("evcs0", CHARGE_STATE, ChargeState.CHARGING) // + .input("meter0", ACTIVE_POWER, -4000) // + .input("meter0", ACTIVE_POWER_L1, -2000) // + .input("meter0", ACTIVE_POWER_L2, -1000) // + .input("meter0", ACTIVE_POWER_L3, -1000) // + .input("ess0", ALLOWED_DISCHARGE_POWER, 0) // + .output("evcsCluster0", MAXIMUM_POWER_TO_DISTRIBUTE, 24000)) // + .next(new TestCase() // + .input("meter0", ACTIVE_POWER, 4500) // + .input("meter0", ACTIVE_POWER_L1, 3000) // + .input("meter0", ACTIVE_POWER_L2, 1500) // + .input("meter0", ACTIVE_POWER_L3, 500) // + .input("ess0", ALLOWED_DISCHARGE_POWER, 0) // + .output("evcsCluster0", MAXIMUM_POWER_TO_DISTRIBUTE, 12000)) // ; } @Test public void clusterMaximum_symmetricGridPower_essActivePowerTest() throws Exception { - String[] evcsIds = { "evcs0", "evcs1" }; - new ComponentTest(new EvcsClusterPeakShavingImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // .addReference("sum", new DummySum()) // .addReference("addEvcs", EVCS0) // .addReference("addEvcs", EVCS1) // - .addReference("addEvcs", EVCS2) // - .addReference("addEvcs", EVCS3) // - .addReference("addEvcs", EVCS4) // .addReference("meter", METER) // .addReference("ess", ESS) // .activate(MyConfig.create() // - .setEssId(ESS.id()) // - .setMeterId(METER.id()) // + .setId("evcsCluster0") // + .setEssId("ess0") // + .setMeterId("meter0") // .setHardwarePowerLimit(HARDWARE_POWER_LIMIT_PER_PHASE) // - .setEvcsIds(evcsIds) // + .setEvcsIds("evcs0", "evcs1") // .build()) // .next(new TestCase() // - .input(EVCS0_CHARE_STATE, ChargeState.CHARGING) // - .input(METER_GRID_ACTIVE_POWER, -6000) // - .input(METER_GRID_ACTIVE_POWER_L1, -2000) // - .input(METER_GRID_ACTIVE_POWER_L2, -2000) // - .input(METER_GRID_ACTIVE_POWER_L3, -2000) // - .input(SUM_ESS_ACTIVE_POWER, -6000) // - .input(ESS_ALLOWED_DISCHARGE_POWER, 0) // - .output(EVCS_CLUSTER_MAXIMUM_POWER_TO_DISTRIBUTE, 33000)) // - .next(new TestCase() // - .input(METER_GRID_ACTIVE_POWER, 4500) // - .input(METER_GRID_ACTIVE_POWER_L1, 1500) // - .input(METER_GRID_ACTIVE_POWER_L2, 1500) // - .input(METER_GRID_ACTIVE_POWER_L3, 1500) // - .input(SUM_ESS_ACTIVE_POWER, 3000) // - .input(ESS_ALLOWED_DISCHARGE_POWER, 0) // - .output(EVCS_CLUSTER_MAXIMUM_POWER_TO_DISTRIBUTE, 13500)) // + .input("evcs0", CHARGE_STATE, ChargeState.CHARGING) // + .input("meter0", ACTIVE_POWER, -6000) // + .input("meter0", ACTIVE_POWER_L1, -2000) // + .input("meter0", ACTIVE_POWER_L2, -2000) // + .input("meter0", ACTIVE_POWER_L3, -2000) // + .input(ESS_ACTIVE_POWER, -6000) // + .input("ess0", ALLOWED_DISCHARGE_POWER, 0) // + .output("evcsCluster0", MAXIMUM_POWER_TO_DISTRIBUTE, 33000)) // + .next(new TestCase() // + .input("meter0", ACTIVE_POWER, 4500) // + .input("meter0", ACTIVE_POWER_L1, 1500) // + .input("meter0", ACTIVE_POWER_L2, 1500) // + .input("meter0", ACTIVE_POWER_L3, 1500) // + .input(ESS_ACTIVE_POWER, 3000) // + .input("ess0", ALLOWED_DISCHARGE_POWER, 0) // + .output("evcsCluster0", MAXIMUM_POWER_TO_DISTRIBUTE, 13500)) // ; } @Test public void clusterMaximum_essAllowedDischargePowerTest() throws Exception { - String[] evcsIds = { "evcs0", "evcs1" }; - var sut = new EvcsClusterPeakShavingImpl(); - var test = new ComponentTest(sut) // + new ComponentTest(sut) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // .addReference("sum", new DummySum()) // .addReference("addEvcs", EVCS0) // .addReference("addEvcs", EVCS1) // - .addReference("addEvcs", EVCS2) // - .addReference("addEvcs", EVCS3) // - .addReference("addEvcs", EVCS4) // .addReference("meter", METER) // .addReference("ess", ESS) // .activate(MyConfig.create() // - .setEssId(ESS.id()) // - .setMeterId(METER.id()) // + .setId("evcsCluster0") // + .setEssId("ess0") // + .setMeterId("meter0") // .setHardwarePowerLimit(HARDWARE_POWER_LIMIT_PER_PHASE) // - .setEvcsIds(evcsIds) // - .build()); // - test// - .next(new TestCase() // - .input(EVCS0_CHARE_STATE, ChargeState.CHARGING) // - .input(METER_GRID_ACTIVE_POWER, -6000) // - .input(METER_GRID_ACTIVE_POWER_L1, -2000) // - .input(METER_GRID_ACTIVE_POWER_L2, -2000) // - .input(METER_GRID_ACTIVE_POWER_L3, -2000) // - .input(SUM_ESS_ACTIVE_POWER, -6000) // - .input(ESS_ALLOWED_DISCHARGE_POWER, 10000) // + .setEvcsIds("evcs0", "evcs1") // + .build()) // + .next(new TestCase() // + .input("evcs0", CHARGE_STATE, ChargeState.CHARGING) // + .input("meter0", ACTIVE_POWER, -6000) // + .input("meter0", ACTIVE_POWER_L1, -2000) // + .input("meter0", ACTIVE_POWER_L2, -2000) // + .input("meter0", ACTIVE_POWER_L3, -2000) // + .input(ESS_ACTIVE_POWER, -6000) // + .input("ess0", ALLOWED_DISCHARGE_POWER, 10000) // .onBeforeControllersCallbacks(() -> sut.run()) // - .output(EVCS_CLUSTER_MAXIMUM_POWER_TO_DISTRIBUTE, 63000)) // - .next(new TestCase() // - .input(METER_GRID_ACTIVE_POWER, 4500) // - .input(METER_GRID_ACTIVE_POWER_L1, 1500) // - .input(METER_GRID_ACTIVE_POWER_L2, 1500) // - .input(METER_GRID_ACTIVE_POWER_L3, 1500) // - .input(SUM_ESS_ACTIVE_POWER, 3000) // - .input(ESS_ALLOWED_DISCHARGE_POWER, 20000) // + .output("evcsCluster0", MAXIMUM_POWER_TO_DISTRIBUTE, 63000)) // + .next(new TestCase() // + .input("meter0", ACTIVE_POWER, 4500) // + .input("meter0", ACTIVE_POWER_L1, 1500) // + .input("meter0", ACTIVE_POWER_L2, 1500) // + .input("meter0", ACTIVE_POWER_L3, 1500) // + .input(ESS_ACTIVE_POWER, 3000) // + .input("ess0", ALLOWED_DISCHARGE_POWER, 20000) // .onBeforeControllersCallbacks(() -> sut.run()) // - .output(EVCS_CLUSTER_MAXIMUM_POWER_TO_DISTRIBUTE, 43500)) // + .output("evcsCluster0", MAXIMUM_POWER_TO_DISTRIBUTE, 43500)) // ; } @Test public void clusterDistribution_nothingToChargeTest() throws Exception { - String[] evcsIds = { "evcs0", "evcs1" }; - new ComponentTest(new EvcsClusterPeakShavingImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // .addReference("sum", new DummySum()) // .addReference("addEvcs", EVCS0) // .addReference("addEvcs", EVCS1) // - .addReference("addEvcs", EVCS2) // - .addReference("addEvcs", EVCS3) // - .addReference("addEvcs", EVCS4) // .addReference("meter", METER) // .addReference("ess", ESS) // .activate(MyConfig.create() // - .setEssId(ESS.id()) // - .setMeterId(METER.id()) // + .setId("evcsCluster0") // + .setEssId("ess0") // + .setMeterId("meter0") // .setHardwarePowerLimit(HARDWARE_POWER_LIMIT_PER_PHASE) // - .setEvcsIds(evcsIds) // + .setEvcsIds("evcs0", "evcs1") // .build()) // .next(new TestCase() // - .input(EVCS0_CHARE_STATE, ChargeState.CHARGING)) // - .next(new TestCase() // - .input(SUM_ESS_ACTIVE_POWER, 0) // - .input(METER_GRID_ACTIVE_POWER, 0) // - .input(METER_GRID_ACTIVE_POWER_L1, 0) // - .input(METER_GRID_ACTIVE_POWER_L2, 0) // - .input(METER_GRID_ACTIVE_POWER_L3, 0) // - .input(ESS_ALLOWED_DISCHARGE_POWER, 0) // - .input(EVCS0_SET_POWER_REQUEST, 0) // - .output(EVCS0_SET_CHARGE_POWER_LIMIT, 0)) // - .next(new TestCase() // - .input(SUM_ESS_ACTIVE_POWER, 0) // - .input(METER_GRID_ACTIVE_POWER, 21000) // - .input(METER_GRID_ACTIVE_POWER_L1, 7000) // - .input(METER_GRID_ACTIVE_POWER_L2, 7000) // - .input(METER_GRID_ACTIVE_POWER_L3, 7000) // - .input(EVCS0_SET_POWER_REQUEST, 15000) // - .input(ESS_ALLOWED_DISCHARGE_POWER, 0) // - .output(EVCS_CLUSTER_MAXIMUM_POWER_TO_DISTRIBUTE, 0) // - .output(EVCS0_SET_CHARGE_POWER_LIMIT, 0)) // - .next(new TestCase() // - .input(SUM_ESS_ACTIVE_POWER, 0) // - .input(METER_GRID_ACTIVE_POWER, 15000) // - .input(METER_GRID_ACTIVE_POWER_L1, 5000) // - .input(METER_GRID_ACTIVE_POWER_L2, 5000) // - .input(METER_GRID_ACTIVE_POWER_L3, 5000) // - .input(EVCS0_SET_POWER_REQUEST, 15000) // - .input(EVCS1_SET_POWER_REQUEST, 15000) // - .input(EVCS0_MAXIMUM_POWER, 22000) // - .input(EVCS1_MAXIMUM_POWER, 22000) // - .input(EVCS0_MINIMUM_HARDWARE_POWER, 4500) // - .input(EVCS1_MINIMUM_HARDWARE_POWER, 4500) // - .input(EVCS0_MAXIMUM_HARDWARE_POWER, 22000) // - .input(EVCS1_MAXIMUM_HARDWARE_POWER, 22000) // - .input(EVCS0_STATUS, Status.CHARGING) // - .input(EVCS1_STATUS, Status.CHARGING) // - .input(ESS_ALLOWED_DISCHARGE_POWER, 0) // - .output(EVCS_CLUSTER_MAXIMUM_POWER_TO_DISTRIBUTE, 6000) // - .output(EVCS0_SET_CHARGE_POWER_LIMIT, 6000) // - .output(EVCS1_SET_CHARGE_POWER_LIMIT, 0)) // + .input("evcs0", CHARGE_STATE, ChargeState.CHARGING)) // + .next(new TestCase() // + .input(ESS_ACTIVE_POWER, 0) // + .input("meter0", ACTIVE_POWER, 0) // + .input("meter0", ACTIVE_POWER_L1, 0) // + .input("meter0", ACTIVE_POWER_L2, 0) // + .input("meter0", ACTIVE_POWER_L3, 0) // + .input("ess0", ALLOWED_DISCHARGE_POWER, 0) // + .input("evcs0", SET_CHARGE_POWER_REQUEST, 0) // + .output("evcs0", SET_CHARGE_POWER_LIMIT, 0)) // + .next(new TestCase() // + .input(ESS_ACTIVE_POWER, 0) // + .input("meter0", ACTIVE_POWER, 21000) // + .input("meter0", ACTIVE_POWER_L1, 7000) // + .input("meter0", ACTIVE_POWER_L2, 7000) // + .input("meter0", ACTIVE_POWER_L3, 7000) // + .input("evcs0", SET_CHARGE_POWER_REQUEST, 15000) // + .input("ess0", ALLOWED_DISCHARGE_POWER, 0) // + .output("evcsCluster0", MAXIMUM_POWER_TO_DISTRIBUTE, 0) // + .output("evcs0", SET_CHARGE_POWER_LIMIT, 0)) // + .next(new TestCase() // + .input(ESS_ACTIVE_POWER, 0) // + .input("meter0", ACTIVE_POWER, 15000) // + .input("meter0", ACTIVE_POWER_L1, 5000) // + .input("meter0", ACTIVE_POWER_L2, 5000) // + .input("meter0", ACTIVE_POWER_L3, 5000) // + .input("evcs0", SET_CHARGE_POWER_REQUEST, 15000) // + .input("evcs1", SET_CHARGE_POWER_REQUEST, 15000) // + .input("evcs0", MAXIMUM_POWER, 22000) // + .input("evcs1", MAXIMUM_POWER, 22000) // + .input("evcs0", MINIMUM_HARDWARE_POWER, 4500) // + .input("evcs1", MINIMUM_HARDWARE_POWER, 4500) // + .input("evcs0", MAXIMUM_HARDWARE_POWER, 22000) // + .input("evcs1", MAXIMUM_HARDWARE_POWER, 22000) // + .input("evcs0", STATUS, Status.CHARGING) // + .input("evcs1", STATUS, Status.CHARGING) // + .input("ess0", ALLOWED_DISCHARGE_POWER, 0) // + .output("evcsCluster0", MAXIMUM_POWER_TO_DISTRIBUTE, 6000) // + .output("evcs0", SET_CHARGE_POWER_LIMIT, 6000) // + .output("evcs1", SET_CHARGE_POWER_LIMIT, 0)) // ; } @Test public void clusterDistribution_chargeTest() throws Exception { - String[] evcsIds = { "evcs0", "evcs1", "evcs2", "evcs3", "evcs4" }; - new ComponentTest(new EvcsClusterPeakShavingImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // @@ -376,237 +302,218 @@ public void clusterDistribution_chargeTest() throws Exception { .addReference("meter", METER) // .addReference("ess", ESS) // .activate(MyConfig.create() // - .setEssId(ESS.id()) // - .setMeterId(METER.id()) // + .setId("evcsCluster0") // + .setEssId("ess0") // + .setMeterId("meter0") // .setHardwarePowerLimit(HARDWARE_POWER_LIMIT_PER_PHASE) // - .setEvcsIds(evcsIds) // + .setEvcsIds("evcs0", "evcs1", "evcs2", "evcs3", "evcs4") // .build()) // .next(new TestCase() // - .input(EVCS0_CHARE_STATE, ChargeState.CHARGING) // - .input(SUM_ESS_ACTIVE_POWER, 0) // - .input(METER_GRID_ACTIVE_POWER, 0) // - .input(METER_GRID_ACTIVE_POWER_L1, 0) // - .input(METER_GRID_ACTIVE_POWER_L2, 0) // - .input(METER_GRID_ACTIVE_POWER_L3, 0) // - .input(ESS_ALLOWED_DISCHARGE_POWER, 30000) // - .input(EVCS0_SET_POWER_REQUEST, 15000) // - .input(EVCS1_SET_POWER_REQUEST, 15000) // - .input(EVCS2_SET_POWER_REQUEST, 15000) // - .input(EVCS3_SET_POWER_REQUEST, 15000) // - .input(EVCS4_SET_POWER_REQUEST, 15000) // - .input(EVCS0_STATUS, Status.CHARGING) // - .input(EVCS1_STATUS, Status.CHARGING) // - .input(EVCS2_STATUS, Status.CHARGING) // - .input(EVCS3_STATUS, Status.CHARGING) // - .input(EVCS4_STATUS, Status.CHARGING) // - .input(EVCS0_MAXIMUM_POWER, null) // - .input(EVCS1_MAXIMUM_POWER, null) // - .input(EVCS0_MAXIMUM_HARDWARE_POWER, 22000) // - .input(EVCS1_MAXIMUM_HARDWARE_POWER, 22000) // - .input(EVCS0_CHARGE_POWER, 0) // - .input(EVCS1_CHARGE_POWER, 0)) // + .input("evcs0", CHARGE_STATE, ChargeState.CHARGING) // + .input(ESS_ACTIVE_POWER, 0) // + .input("meter0", ACTIVE_POWER, 0) // + .input("meter0", ACTIVE_POWER_L1, 0) // + .input("meter0", ACTIVE_POWER_L2, 0) // + .input("meter0", ACTIVE_POWER_L3, 0) // + .input("ess0", ALLOWED_DISCHARGE_POWER, 30000) // + .input("evcs0", SET_CHARGE_POWER_REQUEST, 15000) // + .input("evcs1", SET_CHARGE_POWER_REQUEST, 15000) // + .input("evcs2", SET_CHARGE_POWER_REQUEST, 15000) // + .input("evcs3", SET_CHARGE_POWER_REQUEST, 15000) // + .input("evcs4", SET_CHARGE_POWER_REQUEST, 15000) // + .input("evcs0", STATUS, Status.CHARGING) // + .input("evcs1", STATUS, Status.CHARGING) // + .input("evcs2", STATUS, Status.CHARGING) // + .input("evcs3", STATUS, Status.CHARGING) // + .input("evcs4", STATUS, Status.CHARGING) // + .input("evcs0", MAXIMUM_POWER, null) // + .input("evcs1", MAXIMUM_POWER, null) // + .input("evcs0", MAXIMUM_HARDWARE_POWER, 22000) // + .input("evcs1", MAXIMUM_HARDWARE_POWER, 22000) // + .input("evcs0", ACTIVE_POWER, 0) // + .input("evcs1", ACTIVE_POWER, 0)) // ; } @Test public void clusterDistribution_chargeTest2() throws Exception { - String[] evcsIds = { "evcs0", "evcs1" }; - new ComponentTest(new EvcsClusterPeakShavingImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // .addReference("sum", new DummySum()) // .addReference("addEvcs", EVCS0) // .addReference("addEvcs", EVCS1) // - .addReference("addEvcs", EVCS2) // - .addReference("addEvcs", EVCS3) // - .addReference("addEvcs", EVCS4) // .addReference("meter", METER) // .addReference("ess", ESS) // .activate(MyConfig.create() // - .setEssId(ESS.id()) // - .setMeterId(METER.id()) // + .setId("evcsCluster0") // + .setEssId("ess0") // + .setMeterId("meter0") // .setHardwarePowerLimit(HARDWARE_POWER_LIMIT_PER_PHASE) // - .setEvcsIds(evcsIds) // + .setEvcsIds("evcs0", "evcs1") // .build()) // .next(new TestCase() // - .input(EVCS0_CHARE_STATE, ChargeState.CHARGING) // - .input(SUM_ESS_ACTIVE_POWER, 0) // - .input(METER_GRID_ACTIVE_POWER, 0) // - .input(METER_GRID_ACTIVE_POWER_L1, 0) // - .input(METER_GRID_ACTIVE_POWER_L2, 0) // - .input(METER_GRID_ACTIVE_POWER_L3, 0) // - .input(ESS_ALLOWED_DISCHARGE_POWER, 0) // - .input(EVCS0_SET_POWER_REQUEST, 15000) // - .input(EVCS1_SET_POWER_REQUEST, 15000) // + .input("evcs0", CHARGE_STATE, ChargeState.CHARGING) // + .input(ESS_ACTIVE_POWER, 0) // + .input("meter0", ACTIVE_POWER, 0) // + .input("meter0", ACTIVE_POWER_L1, 0) // + .input("meter0", ACTIVE_POWER_L2, 0) // + .input("meter0", ACTIVE_POWER_L3, 0) // + .input("ess0", ALLOWED_DISCHARGE_POWER, 0) // + .input("evcs0", SET_CHARGE_POWER_REQUEST, 15000) // + .input("evcs1", SET_CHARGE_POWER_REQUEST, 15000) // // TODO: The charge power of an EVCS has to be checked if it really charges // this amount) - .input(EVCS0_CHARGE_POWER, 11000) // - .input(EVCS1_CHARGE_POWER, 22000) // - .input(EVCS0_MAXIMUM_POWER, 22000) // - .input(EVCS1_MAXIMUM_POWER, 22000) // - .input(EVCS0_MAXIMUM_HARDWARE_POWER, 22000) // - .input(EVCS1_MAXIMUM_HARDWARE_POWER, 22000) // - .input(EVCS0_STATUS, Status.CHARGING) // - .input(EVCS1_STATUS, Status.CHARGING)) // - .next(new TestCase() // - .output(EVCS_CLUSTER_MAXIMUM_POWER_TO_DISTRIBUTE, 54000) // - .output(EVCS0_SET_CHARGE_POWER_LIMIT, 15000) // - .output(EVCS1_SET_CHARGE_POWER_LIMIT, 15000)) // + .input("evcs0", ACTIVE_POWER, 11000) // + .input("evcs1", ACTIVE_POWER, 22000) // + .input("evcs0", MAXIMUM_POWER, 22000) // + .input("evcs1", MAXIMUM_POWER, 22000) // + .input("evcs0", MAXIMUM_HARDWARE_POWER, 22000) // + .input("evcs1", MAXIMUM_HARDWARE_POWER, 22000) // + .input("evcs0", STATUS, Status.CHARGING) // + .input("evcs1", STATUS, Status.CHARGING)) // + .next(new TestCase() // + .output("evcsCluster0", MAXIMUM_POWER_TO_DISTRIBUTE, 54000) // + .output("evcs0", SET_CHARGE_POWER_LIMIT, 15000) // + .output("evcs1", SET_CHARGE_POWER_LIMIT, 15000)) // ; } @Test public void clusterDistribution_chargeTest_maximumHardwarePowerTest() throws Exception { - String[] evcsIds = { "evcs0", "evcs1" }; - new ComponentTest(new EvcsClusterPeakShavingImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // .addReference("sum", new DummySum()) // .addReference("addEvcs", EVCS0) // .addReference("addEvcs", EVCS1) // - .addReference("addEvcs", EVCS2) // - .addReference("addEvcs", EVCS3) // - .addReference("addEvcs", EVCS4) // .addReference("meter", METER) // .addReference("ess", ESS) // .activate(MyConfig.create() // - .setEssId(ESS.id()) // - .setMeterId(METER.id()) // + .setId("evcsCluster0") // + .setEssId("ess0") // + .setMeterId("meter0") // .setHardwarePowerLimit(HARDWARE_POWER_LIMIT_PER_PHASE) // - .setEvcsIds(evcsIds) // + .setEvcsIds("evcs0", "evcs1") // .build()) // .next(new TestCase() // - .input(EVCS0_CHARE_STATE, ChargeState.CHARGING) // - .input(SUM_ESS_ACTIVE_POWER, 0) // - .input(METER_GRID_ACTIVE_POWER, 0) // - .input(METER_GRID_ACTIVE_POWER_L1, 0) // - .input(METER_GRID_ACTIVE_POWER_L2, 0) // - .input(METER_GRID_ACTIVE_POWER_L3, 0) // - .input(ESS_ALLOWED_DISCHARGE_POWER, 0) // - .input(EVCS0_SET_POWER_REQUEST, 15000) // - .input(EVCS1_SET_POWER_REQUEST, 15000) // - .input(EVCS0_CHARGE_POWER, 11000) // - .input(EVCS1_CHARGE_POWER, 0) // - .input(EVCS0_MAXIMUM_POWER, null) // - .input(EVCS1_MAXIMUM_POWER, null) // - .input(EVCS0_MAXIMUM_HARDWARE_POWER, 11000) // - .input(EVCS1_MAXIMUM_HARDWARE_POWER, 11000) // - .input(EVCS0_STATUS, Status.CHARGING) // - .input(EVCS1_STATUS, Status.CHARGING)) // - .next(new TestCase() // - .output(EVCS_CLUSTER_MAXIMUM_POWER_TO_DISTRIBUTE, 32000) // - .output(EVCS0_SET_CHARGE_POWER_LIMIT, 11000) // - .output(EVCS1_SET_CHARGE_POWER_LIMIT, 11000)) // + .input("evcs0", CHARGE_STATE, ChargeState.CHARGING) // + .input(ESS_ACTIVE_POWER, 0) // + .input("meter0", ACTIVE_POWER, 0) // + .input("meter0", ACTIVE_POWER_L1, 0) // + .input("meter0", ACTIVE_POWER_L2, 0) // + .input("meter0", ACTIVE_POWER_L3, 0) // + .input("ess0", ALLOWED_DISCHARGE_POWER, 0) // + .input("evcs0", SET_CHARGE_POWER_REQUEST, 15000) // + .input("evcs1", SET_CHARGE_POWER_REQUEST, 15000) // + .input("evcs0", ACTIVE_POWER, 11000) // + .input("evcs1", ACTIVE_POWER, 0) // + .input("evcs0", MAXIMUM_POWER, null) // + .input("evcs1", MAXIMUM_POWER, null) // + .input("evcs0", MAXIMUM_HARDWARE_POWER, 11000) // + .input("evcs1", MAXIMUM_HARDWARE_POWER, 11000) // + .input("evcs0", STATUS, Status.CHARGING) // + .input("evcs1", STATUS, Status.CHARGING)) // + .next(new TestCase() // + .output("evcsCluster0", MAXIMUM_POWER_TO_DISTRIBUTE, 32000) // + .output("evcs0", SET_CHARGE_POWER_LIMIT, 11000) // + .output("evcs1", SET_CHARGE_POWER_LIMIT, 11000)) // ; } @Test public void clusterDistribution_chargeTest_maximumPowerTest() throws Exception { - String[] evcsIds = { "evcs0", "evcs1" }; - new ComponentTest(new EvcsClusterPeakShavingImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // .addReference("sum", new DummySum()) // .addReference("addEvcs", EVCS0) // .addReference("addEvcs", EVCS1) // - .addReference("addEvcs", EVCS2) // - .addReference("addEvcs", EVCS3) // - .addReference("addEvcs", EVCS4) // .addReference("meter", METER) // .addReference("ess", ESS) // .activate(MyConfig.create() // - .setEssId(ESS.id()) // - .setMeterId(METER.id()) // + .setId("evcsCluster0") // + .setEssId("ess0") // + .setMeterId("meter0") // .setHardwarePowerLimit(HARDWARE_POWER_LIMIT_PER_PHASE) // - .setEvcsIds(evcsIds) // + .setEvcsIds("evcs0", "evcs1") // .build()) // .next(new TestCase() // - .input(EVCS0_CHARE_STATE, ChargeState.CHARGING) // - .input(SUM_ESS_ACTIVE_POWER, 0) // - .input(METER_GRID_ACTIVE_POWER, 0) // - .input(METER_GRID_ACTIVE_POWER_L1, 0) // - .input(METER_GRID_ACTIVE_POWER_L2, 0) // - .input(METER_GRID_ACTIVE_POWER_L3, 0) // - .input(ESS_ALLOWED_DISCHARGE_POWER, 0) // - .input(EVCS0_SET_POWER_REQUEST, 15000) // - .input(EVCS1_SET_POWER_REQUEST, 15000) // - .input(EVCS0_CHARGE_POWER, 0) // - .input(EVCS1_CHARGE_POWER, 0) // - .input(EVCS0_MAXIMUM_POWER, 5000) // - .input(EVCS1_MAXIMUM_POWER, 9000) // - .input(EVCS0_MAXIMUM_HARDWARE_POWER, 22000) // - .input(EVCS1_MAXIMUM_HARDWARE_POWER, 22000) // - .input(EVCS0_STATUS, Status.CHARGING) // - .input(EVCS1_STATUS, Status.CHARGING)) // - .next(new TestCase() // - .output(EVCS_CLUSTER_MAXIMUM_POWER_TO_DISTRIBUTE, 21000) // - .output(EVCS0_SET_CHARGE_POWER_LIMIT, 15000) // - .output(EVCS1_SET_CHARGE_POWER_LIMIT, 15000)) // + .input("evcs0", CHARGE_STATE, ChargeState.CHARGING) // + .input(ESS_ACTIVE_POWER, 0) // + .input("meter0", ACTIVE_POWER, 0) // + .input("meter0", ACTIVE_POWER_L1, 0) // + .input("meter0", ACTIVE_POWER_L2, 0) // + .input("meter0", ACTIVE_POWER_L3, 0) // + .input("ess0", ALLOWED_DISCHARGE_POWER, 0) // + .input("evcs0", SET_CHARGE_POWER_REQUEST, 15000) // + .input("evcs1", SET_CHARGE_POWER_REQUEST, 15000) // + .input("evcs0", ACTIVE_POWER, 0) // + .input("evcs1", ACTIVE_POWER, 0) // + .input("evcs0", MAXIMUM_POWER, 5000) // + .input("evcs1", MAXIMUM_POWER, 9000) // + .input("evcs0", MAXIMUM_HARDWARE_POWER, 22000) // + .input("evcs1", MAXIMUM_HARDWARE_POWER, 22000) // + .input("evcs0", STATUS, Status.CHARGING) // + .input("evcs1", STATUS, Status.CHARGING)) // + .next(new TestCase() // + .output("evcsCluster0", MAXIMUM_POWER_TO_DISTRIBUTE, 21000) // + .output("evcs0", SET_CHARGE_POWER_LIMIT, 15000) // + .output("evcs1", SET_CHARGE_POWER_LIMIT, 15000)) // ; } @Test public void clusterDistribution_chargeTest_minimumPowerTest() throws Exception { - String[] evcsIds = { "evcs0", "evcs1" }; - new ComponentTest(new EvcsClusterPeakShavingImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // .addReference("sum", new DummySum()) // .addReference("addEvcs", EVCS0) // .addReference("addEvcs", EVCS1) // - .addReference("addEvcs", EVCS2) // - .addReference("addEvcs", EVCS3) // - .addReference("addEvcs", EVCS4) // .addReference("meter", METER) // .addReference("ess", ESS) // .activate(MyConfig.create() // - .setEssId(ESS.id()) // - .setMeterId(METER.id()) // + .setId("evcsCluster0") // + .setEssId("ess0") // + .setMeterId("meter0") // .setHardwarePowerLimit(HARDWARE_POWER_LIMIT_PER_PHASE) // - .setEvcsIds(evcsIds) // + .setEvcsIds("evcs0", "evcs1") // .build()) // .next(new TestCase() // - .input(EVCS0_CHARE_STATE, ChargeState.CHARGING) // - .input(SUM_ESS_ACTIVE_POWER, 0) // - .input(METER_GRID_ACTIVE_POWER, 0) // - .input(METER_GRID_ACTIVE_POWER_L1, 0) // - .input(METER_GRID_ACTIVE_POWER_L2, 0) // - .input(METER_GRID_ACTIVE_POWER_L3, 0) // - .input(ESS_ALLOWED_DISCHARGE_POWER, 0) // - .input(EVCS0_SET_POWER_REQUEST, 15000) // - .input(EVCS1_SET_POWER_REQUEST, 15000) // - .input(EVCS0_CHARGE_POWER, 0) // - .input(EVCS1_CHARGE_POWER, 0) // - .input(EVCS0_MAXIMUM_POWER, 5000) // - .input(EVCS1_MAXIMUM_POWER, 9000) // - .input(EVCS0_MAXIMUM_HARDWARE_POWER, 22000) // - .input(EVCS1_MAXIMUM_HARDWARE_POWER, 22000) // - .input(EVCS0_MINIMUM_HARDWARE_POWER, 4500) // - .input(EVCS1_MINIMUM_HARDWARE_POWER, 4500) // - .input(EVCS0_STATUS, Status.READY_FOR_CHARGING) // - .input(EVCS1_STATUS, Status.READY_FOR_CHARGING)) // - .next(new TestCase() // - .output(EVCS_CLUSTER_MAXIMUM_POWER_TO_DISTRIBUTE, 21000) // - .output(EVCS0_SET_CHARGE_POWER_LIMIT, 4500) // - .output(EVCS1_SET_CHARGE_POWER_LIMIT, 4500)) // - .next(new TestCase() // - .input(EVCS0_MINIMUM_HARDWARE_POWER, 9000) // - .input(EVCS1_MINIMUM_HARDWARE_POWER, 6900) // - .output(EVCS0_SET_CHARGE_POWER_LIMIT, 9000) // - .output(EVCS1_SET_CHARGE_POWER_LIMIT, 6900)) // + .input("evcs0", CHARGE_STATE, ChargeState.CHARGING) // + .input(ESS_ACTIVE_POWER, 0) // + .input("meter0", ACTIVE_POWER, 0) // + .input("meter0", ACTIVE_POWER_L1, 0) // + .input("meter0", ACTIVE_POWER_L2, 0) // + .input("meter0", ACTIVE_POWER_L3, 0) // + .input("ess0", ALLOWED_DISCHARGE_POWER, 0) // + .input("evcs0", SET_CHARGE_POWER_REQUEST, 15000) // + .input("evcs1", SET_CHARGE_POWER_REQUEST, 15000) // + .input("evcs0", ACTIVE_POWER, 0) // + .input("evcs1", ACTIVE_POWER, 0) // + .input("evcs0", MAXIMUM_POWER, 5000) // + .input("evcs1", MAXIMUM_POWER, 9000) // + .input("evcs0", MAXIMUM_HARDWARE_POWER, 22000) // + .input("evcs1", MAXIMUM_HARDWARE_POWER, 22000) // + .input("evcs0", MINIMUM_HARDWARE_POWER, 4500) // + .input("evcs1", MINIMUM_HARDWARE_POWER, 4500) // + .input("evcs0", STATUS, Status.READY_FOR_CHARGING) // + .input("evcs1", STATUS, Status.READY_FOR_CHARGING)) // + .next(new TestCase() // + .output("evcsCluster0", MAXIMUM_POWER_TO_DISTRIBUTE, 21000) // + .output("evcs0", SET_CHARGE_POWER_LIMIT, 4500) // + .output("evcs1", SET_CHARGE_POWER_LIMIT, 4500)) // + .next(new TestCase() // + .input("evcs0", MINIMUM_HARDWARE_POWER, 9000) // + .input("evcs1", MINIMUM_HARDWARE_POWER, 6900) // + .output("evcs0", SET_CHARGE_POWER_LIMIT, 9000) // + .output("evcs1", SET_CHARGE_POWER_LIMIT, 6900)) // ; } @Test public void clusterDistribution_filterTest() throws Exception { - String[] evcsIds = { "evcs5" }; - - int initialPowerFromCluster = 4500; - new ComponentTest(new EvcsClusterPeakShavingImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // @@ -615,24 +522,25 @@ public void clusterDistribution_filterTest() throws Exception { .addReference("meter", METER) // .addReference("ess", ESS) // .activate(MyConfig.create() // - .setEssId(ESS.id()) // - .setMeterId(METER.id()) // + .setId("evcsCluster0") // + .setEssId("ess0") // + .setMeterId("meter0") // .setHardwarePowerLimit(HARDWARE_POWER_LIMIT_PER_PHASE) // - .setEvcsIds(evcsIds) // + .setEvcsIds("evcs5") // .build()) // .next(new TestCase() // - .input(SUM_ESS_ACTIVE_POWER, 0) // - .input(METER_GRID_ACTIVE_POWER, 0) // - .input(METER_GRID_ACTIVE_POWER_L1, 0) // - .input(METER_GRID_ACTIVE_POWER_L2, 0) // - .input(METER_GRID_ACTIVE_POWER_L3, 0) // - .input(ESS_ALLOWED_DISCHARGE_POWER, 0) // - .input(EVCS5_SET_POWER_REQUEST, 22000) // - .input(EVCS5_CHARGE_POWER, 0) // - .input(EVCS5_CHARGE_STATE, ChargeState.NOT_CHARGING) // - .input(EVCS5_MAXIMUM_HARDWARE_POWER, 22080) // - .input(EVCS5_STATUS, Status.READY_FOR_CHARGING) // - .input(EVCS_CLUSTER_STATUS, EvcsClusterStatus.REGULAR)) // + .input(ESS_ACTIVE_POWER, 0) // + .input("meter0", ACTIVE_POWER, 0) // + .input("meter0", ACTIVE_POWER_L1, 0) // + .input("meter0", ACTIVE_POWER_L2, 0) // + .input("meter0", ACTIVE_POWER_L3, 0) // + .input("ess0", ALLOWED_DISCHARGE_POWER, 0) // + .input("evcs5", SET_CHARGE_POWER_REQUEST, 22000) // + .input("evcs5", ACTIVE_POWER, 0) // + .input("evcs5", CHARGE_STATE, ChargeState.NOT_CHARGING) // + .input("evcs5", MAXIMUM_HARDWARE_POWER, 22080) // + .input("evcs5", STATUS, Status.READY_FOR_CHARGING) // + .input("evcsCluster0", EVCS_CLUSTER_STATUS, EvcsClusterStatus.REGULAR)) // .next(new TestCase() // // Cannot test charge states of evcs because the WriteHandler is not triggered // in a Cluster test. @@ -640,15 +548,13 @@ public void clusterDistribution_filterTest() throws Exception { // .output(evcs5ChargeState, ChargeState.INCREASING)) // // .output(evcs5ChargeState, ChargeState.INCREASING) // // .output(evcsClusterStatus, EvcsClusterStatus.INCREASING)) // - .output(EVCS_CLUSTER_MAXIMUM_POWER_TO_DISTRIBUTE, 21000) // - .output(EVCS5_SET_CHARGE_POWER_LIMIT, initialPowerFromCluster) // + .output("evcsCluster0", MAXIMUM_POWER_TO_DISTRIBUTE, 21000) // + .output("evcs5", SET_CHARGE_POWER_LIMIT, 4500) // ); } @Test public void clusterStatusTest() throws Exception { - String[] evcsIds = { "evcs0", "evcs1", "evcs2", "evcs3" }; - new ComponentTest(new EvcsClusterPeakShavingImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // @@ -657,50 +563,50 @@ public void clusterStatusTest() throws Exception { .addReference("addEvcs", EVCS1) // .addReference("addEvcs", EVCS2) // .addReference("addEvcs", EVCS3) // - .addReference("addEvcs", EVCS4) // .addReference("meter", METER) // .addReference("ess", ESS) // .activate(MyConfig.create() // - .setEssId(ESS.id()) // - .setMeterId(METER.id()) // + .setId("evcsCluster0") // + .setEssId("ess0") // + .setMeterId("meter0") // .setHardwarePowerLimit(HARDWARE_POWER_LIMIT_PER_PHASE) // - .setEvcsIds(evcsIds) // + .setEvcsIds("evcs0", "evcs1", "evcs2", "evcs3") // .build()) // .next(new TestCase() // - .input(EVCS0_CHARE_STATE, ChargeState.UNDEFINED) // - .input(EVCS1_CHARGE_STATE, ChargeState.UNDEFINED) // - .input(EVCS2_CHARGE_STATE, ChargeState.UNDEFINED) // - .input(EVCS3_CHARGE_STATE, ChargeState.UNDEFINED)) // + .input("evcs0", CHARGE_STATE, ChargeState.UNDEFINED) // + .input("evcs1", CHARGE_STATE, ChargeState.UNDEFINED) // + .input("evcs2", CHARGE_STATE, ChargeState.UNDEFINED) // + .input("evcs3", CHARGE_STATE, ChargeState.UNDEFINED)) // .next(new TestCase() // - .output(EVCS_CLUSTER_STATUS, EvcsClusterStatus.UNDEFINED)) // + .output("evcsCluster0", EVCS_CLUSTER_STATUS, EvcsClusterStatus.UNDEFINED)) // .next(new TestCase() // - .input(EVCS0_CHARE_STATE, ChargeState.INCREASING) // - .input(EVCS1_CHARGE_STATE, ChargeState.UNDEFINED) // - .input(EVCS2_CHARGE_STATE, ChargeState.UNDEFINED) // - .input(EVCS3_CHARGE_STATE, ChargeState.UNDEFINED)) // + .input("evcs0", CHARGE_STATE, ChargeState.INCREASING) // + .input("evcs1", CHARGE_STATE, ChargeState.UNDEFINED) // + .input("evcs2", CHARGE_STATE, ChargeState.UNDEFINED) // + .input("evcs3", CHARGE_STATE, ChargeState.UNDEFINED)) // .next(new TestCase() // - .output(EVCS_CLUSTER_STATUS, EvcsClusterStatus.INCREASING)) // + .output("evcsCluster0", EVCS_CLUSTER_STATUS, EvcsClusterStatus.INCREASING)) // .next(new TestCase() // - .input(EVCS0_CHARE_STATE, ChargeState.CHARGING) // - .input(EVCS1_CHARGE_STATE, ChargeState.UNDEFINED) // - .input(EVCS2_CHARGE_STATE, ChargeState.UNDEFINED) // - .input(EVCS3_CHARGE_STATE, ChargeState.UNDEFINED)) // + .input("evcs0", CHARGE_STATE, ChargeState.CHARGING) // + .input("evcs1", CHARGE_STATE, ChargeState.UNDEFINED) // + .input("evcs2", CHARGE_STATE, ChargeState.UNDEFINED) // + .input("evcs3", CHARGE_STATE, ChargeState.UNDEFINED)) // .next(new TestCase() // - .output(EVCS_CLUSTER_STATUS, EvcsClusterStatus.REGULAR)) // + .output("evcsCluster0", EVCS_CLUSTER_STATUS, EvcsClusterStatus.REGULAR)) // .next(new TestCase() // - .input(EVCS0_CHARE_STATE, ChargeState.CHARGING) // - .input(EVCS1_CHARGE_STATE, ChargeState.DECREASING) // - .input(EVCS2_CHARGE_STATE, ChargeState.INCREASING) // - .input(EVCS3_CHARGE_STATE, ChargeState.UNDEFINED)) // + .input("evcs0", CHARGE_STATE, ChargeState.CHARGING) // + .input("evcs1", CHARGE_STATE, ChargeState.DECREASING) // + .input("evcs2", CHARGE_STATE, ChargeState.INCREASING) // + .input("evcs3", CHARGE_STATE, ChargeState.UNDEFINED)) // .next(new TestCase() // - .output(EVCS_CLUSTER_STATUS, EvcsClusterStatus.DECREASING)) // + .output("evcsCluster0", EVCS_CLUSTER_STATUS, EvcsClusterStatus.DECREASING)) // .next(new TestCase() // - .input(EVCS0_CHARE_STATE, ChargeState.CHARGING) // - .input(EVCS1_CHARGE_STATE, ChargeState.CHARGING) // - .input(EVCS2_CHARGE_STATE, ChargeState.CHARGING) // - .input(EVCS3_CHARGE_STATE, ChargeState.CHARGING)) // + .input("evcs0", CHARGE_STATE, ChargeState.CHARGING) // + .input("evcs1", CHARGE_STATE, ChargeState.CHARGING) // + .input("evcs2", CHARGE_STATE, ChargeState.CHARGING) // + .input("evcs3", CHARGE_STATE, ChargeState.CHARGING)) // .next(new TestCase() // - .output(EVCS_CLUSTER_STATUS, EvcsClusterStatus.REGULAR)) // + .output("evcsCluster0", EVCS_CLUSTER_STATUS, EvcsClusterStatus.REGULAR)) // ; } } diff --git a/io.openems.edge.evcs.cluster/test/io/openems/edge/evcs/cluster/MyConfig.java b/io.openems.edge.evcs.cluster/test/io/openems/edge/evcs/cluster/MyConfig.java index 8abf3801d3b..3185f98a2ae 100644 --- a/io.openems.edge.evcs.cluster/test/io/openems/edge/evcs/cluster/MyConfig.java +++ b/io.openems.edge.evcs.cluster/test/io/openems/edge/evcs/cluster/MyConfig.java @@ -8,12 +8,12 @@ public class MyConfig extends AbstractComponentConfig implements Config { protected static class Builder { - private String id = "evcsCluster0"; - private boolean debugMode = false; - private int hardwarePowerLimitPerPhase = 7000; - private String[] evcsIds = { "evcs0", "evcs1" }; - private String essId = "ess0"; - private String meterId = "meter0"; + private String id; + private boolean debugMode; + private int hardwarePowerLimitPerPhase; + private String[] evcsIds; + private String essId; + private String meterId; private Builder() { } @@ -33,7 +33,7 @@ public Builder setHardwarePowerLimit(int hardwarePowerLimitPerPhase) { return this; } - public Builder setEvcsIds(String[] evcsIds) { + public Builder setEvcsIds(String... evcsIds) { this.evcsIds = evcsIds; return this; } diff --git a/io.openems.edge.evcs.core/.classpath b/io.openems.edge.evcs.core/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.evcs.core/.classpath +++ b/io.openems.edge.evcs.core/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.evcs.dezony/.classpath b/io.openems.edge.evcs.dezony/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.evcs.dezony/.classpath +++ b/io.openems.edge.evcs.dezony/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.evcs.dezony/bnd.bnd b/io.openems.edge.evcs.dezony/bnd.bnd index a0816a90967..53163b7f5a4 100644 --- a/io.openems.edge.evcs.dezony/bnd.bnd +++ b/io.openems.edge.evcs.dezony/bnd.bnd @@ -7,7 +7,8 @@ Bundle-Version: 1.0.0.${tstamp} ${buildpath},\ io.openems.common,\ io.openems.edge.common,\ - io.openems.edge.evcs.api + io.openems.edge.evcs.api,\ + io.openems.edge.meter.api,\ -testpath: \ ${testpath} diff --git a/io.openems.edge.evcs.dezony/src/io/openems/edge/evcs/dezony/DezonyApi.java b/io.openems.edge.evcs.dezony/src/io/openems/edge/evcs/dezony/DezonyApi.java index 59b8fa6d9e6..011d6469e2a 100644 --- a/io.openems.edge.evcs.dezony/src/io/openems/edge/evcs/dezony/DezonyApi.java +++ b/io.openems.edge.evcs.dezony/src/io/openems/edge/evcs/dezony/DezonyApi.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.io.InputStreamReader; import java.net.HttpURLConnection; +import java.net.URI; import java.net.URL; import java.util.Optional; @@ -95,7 +96,8 @@ public JsonElement sendGetRequest(String endpoint) throws OpenemsNamedException JsonObject result = null; try { - URL url = new URL(this.baseUrl + endpoint); + URI uri = URI.create(this.baseUrl + endpoint); + URL url = uri.toURL(); HttpURLConnection con = (HttpURLConnection) url.openConnection(); con.setRequestMethod("GET"); @@ -149,7 +151,8 @@ public JsonObject sendPostRequest(String endpoint) throws OpenemsNamedException JsonObject result = null; try { - var url = new URL(this.baseUrl + endpoint); + URI uri = URI.create(this.baseUrl + endpoint); + URL url = uri.toURL(); var connection = (HttpURLConnection) url.openConnection(); // Set general information diff --git a/io.openems.edge.evcs.dezony/src/io/openems/edge/evcs/dezony/DezonyReadWorker.java b/io.openems.edge.evcs.dezony/src/io/openems/edge/evcs/dezony/DezonyReadWorker.java index e148b80e681..0ceaeaa8c12 100644 --- a/io.openems.edge.evcs.dezony/src/io/openems/edge/evcs/dezony/DezonyReadWorker.java +++ b/io.openems.edge.evcs.dezony/src/io/openems/edge/evcs/dezony/DezonyReadWorker.java @@ -1,5 +1,16 @@ package io.openems.edge.evcs.dezony; +import static io.openems.common.types.OpenemsType.DOUBLE; +import static io.openems.common.types.OpenemsType.STRING; +import static io.openems.common.utils.JsonUtils.getAsDouble; +import static io.openems.common.utils.JsonUtils.getAsFloat; +import static io.openems.common.utils.JsonUtils.getAsInt; +import static io.openems.common.utils.JsonUtils.getAsJsonObject; +import static io.openems.common.utils.JsonUtils.getAsLong; +import static io.openems.common.utils.JsonUtils.getAsShort; +import static io.openems.common.utils.JsonUtils.getAsString; +import static io.openems.edge.evcs.api.Phases.THREE_PHASE; + import java.util.Map; import java.util.function.Function; @@ -13,7 +24,6 @@ import io.openems.edge.common.channel.ChannelId; import io.openems.edge.common.type.TypeUtils; import io.openems.edge.evcs.api.Evcs; -import io.openems.edge.evcs.api.Phases; import io.openems.edge.evcs.api.Status; public class DezonyReadWorker extends AbstractCycleWorker { @@ -24,11 +34,8 @@ public class DezonyReadWorker extends AbstractCycleWorker { "IDLE", Status.NOT_READY_FOR_CHARGING, // "CAR_CONNECTED", Status.READY_FOR_CHARGING, // "CHARGING", Status.CHARGING, // - "CHARGING_FINISHED", Status.CHARGING_FINISHED, // "CHARGING_ERROR", Status.ERROR); - private int chargingFinishedCounter = 0; - public DezonyReadWorker(EvcsDezonyImpl parent) { this.parent = parent; } @@ -54,29 +61,28 @@ protected void forever() throws OpenemsNamedException { * @param json Given raw data in JSON */ private void setEvcsChannelIds(JsonElement json) { - final var activeConsumptionEnergyArray = this.getArrayFromJson(json, "currDataPoint"); + final var energyArray = getArrayFromJson(json, "currDataPoint"); - this.parent._setActiveConsumptionEnergy(this.getValueByKey(activeConsumptionEnergyArray, "etotal")); - this.parent._setChargePower(this.getValueByKey(activeConsumptionEnergyArray, "ptotal")); - this.parent._setSetChargePowerLimit(this.getValueByKey(activeConsumptionEnergyArray, "curlhm") - * Phases.THREE_PHASE.getValue() * Evcs.DEFAULT_VOLTAGE); - this.parent._setPhases(this.calculatePhases(activeConsumptionEnergyArray)); + this.parent._setActiveProductionEnergy(getValueByKey(energyArray, "etotal")); + this.parent._setActivePower(getValueByKey(energyArray, "ptotal")); + this.parent._setSetChargePowerLimit( + getValueByKey(energyArray, "curlhm") * THREE_PHASE.getValue() * Evcs.DEFAULT_VOLTAGE); + this.parent._setPhases(this.calculatePhases(energyArray)); this.parent._setStatus(this.getStatus(json)); } private void setEnergySession(JsonElement json) { - final var energy = (Double) this.getValueFromJson(Evcs.ChannelId.ENERGY_SESSION, OpenemsType.DOUBLE, json, - value -> { - return value; - }, "metric", "energy"); + final var energy = (Double) getValueFromJson(Evcs.ChannelId.ENERGY_SESSION, DOUBLE, json, value -> { + return value; + }, "metric", "energy"); this.parent._setEnergySession(energy == null ? null : (int) Math.round(energy)); } private Status getStatus(JsonElement json) { - final var rawChargeStatus = (String) this.getValueFromJson(EvcsDezony.ChannelId.RAW_CHARGE_STATUS_CHARGEPOINT, - json, value -> { - final String state = TypeUtils.getAsType(OpenemsType.STRING, value); + final var rawChargeStatus = (String) getValueFromJson(EvcsDezony.ChannelId.RAW_CHARGE_STATUS_CHARGEPOINT, json, + value -> { + final String state = TypeUtils.getAsType(STRING, value); return state == null ? "" : state; }, "state"); @@ -86,28 +92,18 @@ private Status getStatus(JsonElement json) { int setChargePowerLimit = this.parent.getSetChargePowerLimit().orElse(0); int minimumHardwarePower = this.parent.getMinimumHardwarePower().orElse(0); - if (setChargePowerLimit >= minimumHardwarePower) { - if (this.chargingFinishedCounter >= 90) { - status = Status.CHARGING_FINISHED; - } else { - this.chargingFinishedCounter++; - } - } else { - this.chargingFinishedCounter = 0; - - if (setChargePowerLimit == 0) { - status = Status.CHARGING_REJECTED; - } + if (setChargePowerLimit < minimumHardwarePower && setChargePowerLimit == 0) { + status = Status.CHARGING_REJECTED; } } return status; } - private Integer calculatePhases(JsonArray activeConsumptionEnergyArray) { - final var powerL1 = this.getValueByKey(activeConsumptionEnergyArray, "currl1") * Evcs.DEFAULT_VOLTAGE; - final var powerL2 = this.getValueByKey(activeConsumptionEnergyArray, "currl2") * Evcs.DEFAULT_VOLTAGE; - final var powerL3 = this.getValueByKey(activeConsumptionEnergyArray, "currl3") * Evcs.DEFAULT_VOLTAGE; + private Integer calculatePhases(JsonArray energyArray) { + final var powerL1 = getValueByKey(energyArray, "currl1") * Evcs.DEFAULT_VOLTAGE; + final var powerL2 = getValueByKey(energyArray, "currl2") * Evcs.DEFAULT_VOLTAGE; + final var powerL3 = getValueByKey(energyArray, "currl3") * Evcs.DEFAULT_VOLTAGE; final int maxPower = 900; final int minPower = 300; @@ -138,9 +134,9 @@ private Integer calculatePhases(JsonArray activeConsumptionEnergyArray) { return phases; } - private int getValueByKey(JsonArray activeConsumptionEnergyArray, String searchKey) { - for (var i = 0; i < activeConsumptionEnergyArray.size(); ++i) { - final var object = activeConsumptionEnergyArray.get(i).getAsJsonObject(); + private static int getValueByKey(JsonArray energyArray, String searchKey) { + for (var i = 0; i < energyArray.size(); ++i) { + final var object = energyArray.get(i).getAsJsonObject(); final var key = object.get("short"); if (key.getAsString().equals(searchKey)) { @@ -165,9 +161,9 @@ private int getValueByKey(JsonArray activeConsumptionEnergyArray, String searchK * @return Value of the last JsonElement by running through the specified JSON * path. */ - private Object getValueFromJson(ChannelId channelId, JsonElement json, Function converter, + private static Object getValueFromJson(ChannelId channelId, JsonElement json, Function converter, String... jsonPaths) { - return this.getValueFromJson(channelId, null, json, converter, jsonPaths); + return getValueFromJson(channelId, null, json, converter, jsonPaths); } /** @@ -185,7 +181,7 @@ private Object getValueFromJson(ChannelId channelId, JsonElement json, Function< * @return Value of the last JsonElement by running through the specified JSON * path. */ - private Object getValueFromJson(ChannelId channelId, OpenemsType divergentTypeInRawJson, JsonElement json, + private static Object getValueFromJson(ChannelId channelId, OpenemsType divergentTypeInRawJson, JsonElement json, Function converter, String... jsonPaths) { var currentJsonElement = json; // Go through the whole jsonPath of the current channelId @@ -198,13 +194,13 @@ private Object getValueFromJson(ChannelId channelId, OpenemsType divergentTypeIn : divergentTypeInRawJson; // Last path element - var value = this.getJsonElementValue(currentJsonElement, openemsType, jsonPaths[i]); + var value = getJsonElementValue(currentJsonElement, openemsType, jsonPaths[i]); // Return the converted value return converter.apply(value); } // Not last path element - currentJsonElement = JsonUtils.getAsJsonObject(currentJsonElement, currentPathMember); + currentJsonElement = getAsJsonObject(currentJsonElement, currentPathMember); } catch (OpenemsNamedException e) { return null; } @@ -212,7 +208,7 @@ private Object getValueFromJson(ChannelId channelId, OpenemsType divergentTypeIn return null; } - private JsonArray getArrayFromJson(JsonElement json, String... jsonPaths) { + private static JsonArray getArrayFromJson(JsonElement json, String... jsonPaths) { var currentJsonElement = json; // Go through the whole jsonPath of the current channelId for (var i = 0; i < jsonPaths.length; i++) { @@ -222,7 +218,7 @@ private JsonArray getArrayFromJson(JsonElement json, String... jsonPaths) { return JsonUtils.getAsJsonArray(currentJsonElement, jsonPaths[i]); } // Not last path element - currentJsonElement = JsonUtils.getAsJsonObject(currentJsonElement, currentPathMember); + currentJsonElement = getAsJsonObject(currentJsonElement, currentPathMember); } catch (OpenemsNamedException e) { return null; } @@ -239,23 +235,17 @@ private JsonArray getArrayFromJson(JsonElement json, String... jsonPaths) { * @return Value in the required type. * @throws OpenemsNamedException Failed to get the value. */ - private Object getJsonElementValue(JsonElement jsonElement, OpenemsType openemsType, String memberName) + private static Object getJsonElementValue(JsonElement jsonElement, OpenemsType openemsType, String memberName) throws OpenemsNamedException { + // NOTE: could have used JsonUtils.getAsType() return switch (openemsType) { - case BOOLEAN: - yield JsonUtils.getAsInt(jsonElement, memberName) == 1; - case DOUBLE: - yield JsonUtils.getAsDouble(jsonElement, memberName); - case FLOAT: - yield JsonUtils.getAsFloat(jsonElement, memberName); - case INTEGER: - yield JsonUtils.getAsInt(jsonElement, memberName); - case LONG: - yield JsonUtils.getAsLong(jsonElement, memberName); - case SHORT: - yield JsonUtils.getAsShort(jsonElement, memberName); - case STRING: - yield JsonUtils.getAsString(jsonElement, memberName); + case BOOLEAN -> getAsInt(jsonElement, memberName) == 1; + case DOUBLE -> getAsDouble(jsonElement, memberName); + case FLOAT -> getAsFloat(jsonElement, memberName); + case INTEGER -> getAsInt(jsonElement, memberName); + case LONG -> getAsLong(jsonElement, memberName); + case SHORT -> getAsShort(jsonElement, memberName); + case STRING -> getAsString(jsonElement, memberName); }; } } diff --git a/io.openems.edge.evcs.dezony/src/io/openems/edge/evcs/dezony/EvcsDezonyImpl.java b/io.openems.edge.evcs.dezony/src/io/openems/edge/evcs/dezony/EvcsDezonyImpl.java index e26fb8a168d..805bf8fe5e3 100644 --- a/io.openems.edge.evcs.dezony/src/io/openems/edge/evcs/dezony/EvcsDezonyImpl.java +++ b/io.openems.edge.evcs.dezony/src/io/openems/edge/evcs/dezony/EvcsDezonyImpl.java @@ -1,5 +1,8 @@ package io.openems.edge.evcs.dezony; +import static io.openems.edge.evcs.api.ChargingType.AC; +import static io.openems.edge.evcs.api.Phases.THREE_PHASE; + import org.osgi.service.component.ComponentContext; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; @@ -15,14 +18,14 @@ import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.types.MeterType; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.event.EdgeEventConstants; import io.openems.edge.evcs.api.AbstractManagedEvcsComponent; -import io.openems.edge.evcs.api.ChargingType; import io.openems.edge.evcs.api.Evcs; import io.openems.edge.evcs.api.EvcsPower; import io.openems.edge.evcs.api.ManagedEvcs; -import io.openems.edge.evcs.api.Phases; +import io.openems.edge.meter.api.ElectricityMeter; @Designate(ocd = Config.class, factory = true) @Component(// @@ -35,7 +38,7 @@ EdgeEventConstants.TOPIC_CYCLE_EXECUTE_WRITE, // }) public class EvcsDezonyImpl extends AbstractManagedEvcsComponent - implements OpenemsComponent, EventHandler, EvcsDezony, Evcs, ManagedEvcs { + implements OpenemsComponent, EventHandler, EvcsDezony, Evcs, ManagedEvcs, ElectricityMeter { private final Logger log = LoggerFactory.getLogger(EvcsDezonyImpl.class); private final DezonyReadWorker readWorker = new DezonyReadWorker(this); @@ -48,7 +51,11 @@ public class EvcsDezonyImpl extends AbstractManagedEvcsComponent protected boolean masterEvcs = true; public EvcsDezonyImpl() { - super(OpenemsComponent.ChannelId.values(), Evcs.ChannelId.values(), ManagedEvcs.ChannelId.values(), + super(// + OpenemsComponent.ChannelId.values(), // + ElectricityMeter.ChannelId.values(), // + Evcs.ChannelId.values(), // + ManagedEvcs.ChannelId.values(), // EvcsDezony.ChannelId.values()); } @@ -57,11 +64,9 @@ private void activate(ComponentContext context, Config config) { super.activate(context, config.id(), config.alias(), config.enabled()); this.config = config; - this._setChargingType(ChargingType.AC); - this._setFixedMinimumHardwarePower( - config.minHwCurrent() / 1000 * DEFAULT_VOLTAGE * Phases.THREE_PHASE.getValue()); - this._setFixedMaximumHardwarePower( - config.maxHwCurrent() / 1000 * DEFAULT_VOLTAGE * Phases.THREE_PHASE.getValue()); + this._setChargingType(AC); + this._setFixedMinimumHardwarePower(config.minHwCurrent() / 1000 * DEFAULT_VOLTAGE * THREE_PHASE.getValue()); + this._setFixedMaximumHardwarePower(config.maxHwCurrent() / 1000 * DEFAULT_VOLTAGE * THREE_PHASE.getValue()); this._setPowerPrecision(230); if (config.enabled()) { @@ -97,6 +102,11 @@ public void handleEvent(Event event) { } } + @Override + public MeterType getMeterType() { + return MeterType.MANAGED_CONSUMPTION_METERED; + } + /** * Debug Log. * @@ -158,12 +168,12 @@ public int getMinimumTimeTillChargingLimitTaken() { @Override public int getConfiguredMinimumHardwarePower() { - return Math.round(this.config.minHwCurrent() / 1000f) * DEFAULT_VOLTAGE * Phases.THREE_PHASE.getValue(); + return Math.round(this.config.minHwCurrent() / 1000f) * DEFAULT_VOLTAGE * THREE_PHASE.getValue(); } @Override public int getConfiguredMaximumHardwarePower() { - return Math.round(this.config.maxHwCurrent() / 1000f) * DEFAULT_VOLTAGE * Phases.THREE_PHASE.getValue(); + return Math.round(this.config.maxHwCurrent() / 1000f) * DEFAULT_VOLTAGE * THREE_PHASE.getValue(); } @Override diff --git a/io.openems.edge.evcs.dezony/test/io/openems/edge/evcs/dezony/EvcsDezonyImplTest.java b/io.openems.edge.evcs.dezony/test/io/openems/edge/evcs/dezony/EvcsDezonyImplTest.java index 562d5b84ac9..ec51fd21e79 100644 --- a/io.openems.edge.evcs.dezony/test/io/openems/edge/evcs/dezony/EvcsDezonyImplTest.java +++ b/io.openems.edge.evcs.dezony/test/io/openems/edge/evcs/dezony/EvcsDezonyImplTest.java @@ -7,13 +7,11 @@ public class EvcsDezonyImplTest { - private static final String COMPONENT_ID = "evcs0"; - @Test public void test() throws Exception { new ComponentTest(new EvcsDezonyImpl()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("evcs0") // .setIp("192.168.50.88") // .setPort(5000) // .setMaxHwCurrent(32) // diff --git a/io.openems.edge.evcs.goe.chargerhome/.classpath b/io.openems.edge.evcs.goe.chargerhome/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.evcs.goe.chargerhome/.classpath +++ b/io.openems.edge.evcs.goe.chargerhome/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.evcs.goe.chargerhome/bnd.bnd b/io.openems.edge.evcs.goe.chargerhome/bnd.bnd index 80e3fed5106..f78d7569e6f 100644 --- a/io.openems.edge.evcs.goe.chargerhome/bnd.bnd +++ b/io.openems.edge.evcs.goe.chargerhome/bnd.bnd @@ -7,7 +7,8 @@ Bundle-Version: 1.0.0.${tstamp} ${buildpath},\ io.openems.common,\ io.openems.edge.common,\ - io.openems.edge.evcs.api + io.openems.edge.evcs.api,\ + io.openems.edge.meter.api,\ -testpath: \ ${testpath} diff --git a/io.openems.edge.evcs.goe.chargerhome/src/io/openems/edge/evcs/goe/chargerhome/EvcsGoeChargerHome.java b/io.openems.edge.evcs.goe.chargerhome/src/io/openems/edge/evcs/goe/chargerhome/EvcsGoeChargerHome.java index 1b07248a7d4..4bfc3aabe2c 100644 --- a/io.openems.edge.evcs.goe.chargerhome/src/io/openems/edge/evcs/goe/chargerhome/EvcsGoeChargerHome.java +++ b/io.openems.edge.evcs.goe.chargerhome/src/io/openems/edge/evcs/goe/chargerhome/EvcsGoeChargerHome.java @@ -10,8 +10,9 @@ import io.openems.edge.evcs.api.Evcs; import io.openems.edge.evcs.api.ManagedEvcs; import io.openems.edge.evcs.api.Status; +import io.openems.edge.meter.api.ElectricityMeter; -public interface EvcsGoeChargerHome extends ManagedEvcs, Evcs, OpenemsComponent, EventHandler { +public interface EvcsGoeChargerHome extends ManagedEvcs, Evcs, ElectricityMeter, OpenemsComponent, EventHandler { public enum ChannelId implements io.openems.edge.common.channel.ChannelId { ALIAS(Doc.of(OpenemsType.STRING).text("A human-readable name of this Component")), @@ -21,13 +22,6 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { STATUS_GOE(Doc.of(Status.values()).text("Current state of the charging station")), ERROR(Doc.of(Errors.values()).text("")), CURR_USER(Doc.of(OpenemsType.INTEGER).unit(Unit.MILLIAMPERE).text("Current preset value of the user")), - VOLTAGE_L1(Doc.of(OpenemsType.INTEGER).unit(Unit.VOLT).text("Voltage on L1")), - VOLTAGE_L2(Doc.of(OpenemsType.INTEGER).unit(Unit.VOLT).text("Voltage on L2")), - VOLTAGE_L3(Doc.of(OpenemsType.INTEGER).unit(Unit.VOLT).text("Voltage on L3")), - CURRENT_L1(Doc.of(OpenemsType.INTEGER).unit(Unit.MILLIAMPERE).text("Current on L1")), - CURRENT_L2(Doc.of(OpenemsType.INTEGER).unit(Unit.MILLIAMPERE).text("Current on L2")), - CURRENT_L3(Doc.of(OpenemsType.INTEGER).unit(Unit.MILLIAMPERE).text("Current on L3")), - ACTUAL_POWER(Doc.of(OpenemsType.INTEGER).unit(Unit.MILLIWATT).text("Total real power")), ENERGY_TOTAL(Doc.of(OpenemsType.INTEGER).unit(Unit.CUMULATED_WATT_HOURS).text("Total power consumption")), CHARGINGSTATION_STATE_ERROR(Doc.of(Level.WARNING)); diff --git a/io.openems.edge.evcs.goe.chargerhome/src/io/openems/edge/evcs/goe/chargerhome/EvcsGoeChargerHomeImpl.java b/io.openems.edge.evcs.goe.chargerhome/src/io/openems/edge/evcs/goe/chargerhome/EvcsGoeChargerHomeImpl.java index 7e81a9fb3c8..26fc2a8b95f 100644 --- a/io.openems.edge.evcs.goe.chargerhome/src/io/openems/edge/evcs/goe/chargerhome/EvcsGoeChargerHomeImpl.java +++ b/io.openems.edge.evcs.goe.chargerhome/src/io/openems/edge/evcs/goe/chargerhome/EvcsGoeChargerHomeImpl.java @@ -17,6 +17,7 @@ import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.types.MeterType; import io.openems.common.utils.JsonUtils; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.event.EdgeEventConstants; @@ -27,6 +28,7 @@ import io.openems.edge.evcs.api.ManagedEvcs; import io.openems.edge.evcs.api.Phases; import io.openems.edge.evcs.api.Status; +import io.openems.edge.meter.api.ElectricityMeter; @Designate(ocd = Config.class, factory = true) @Component(// @@ -62,6 +64,7 @@ public class EvcsGoeChargerHomeImpl extends AbstractManagedEvcsComponent public EvcsGoeChargerHomeImpl() { super(// OpenemsComponent.ChannelId.values(), // + ElectricityMeter.ChannelId.values(), // ManagedEvcs.ChannelId.values(), // Evcs.ChannelId.values(), // EvcsGoeChargerHome.ChannelId.values() // @@ -128,18 +131,15 @@ public void handleEvent(Event event) { this.channel(EvcsGoeChargerHome.ChannelId.CURR_USER).setNextValue(this.activeCurrent); var nrg = JsonUtils.getAsJsonArray(json, "nrg"); - this.channel(EvcsGoeChargerHome.ChannelId.VOLTAGE_L1).setNextValue(JsonUtils.getAsInt(nrg, 0)); - this.channel(EvcsGoeChargerHome.ChannelId.VOLTAGE_L2).setNextValue(JsonUtils.getAsInt(nrg, 1)); - this.channel(EvcsGoeChargerHome.ChannelId.VOLTAGE_L3).setNextValue(JsonUtils.getAsInt(nrg, 2)); - this.channel(EvcsGoeChargerHome.ChannelId.CURRENT_L1) - .setNextValue(JsonUtils.getAsInt(nrg, 4) * 100); - this.channel(EvcsGoeChargerHome.ChannelId.CURRENT_L2) - .setNextValue(JsonUtils.getAsInt(nrg, 5) * 100); - this.channel(EvcsGoeChargerHome.ChannelId.CURRENT_L3) - .setNextValue(JsonUtils.getAsInt(nrg, 6) * 100); + this._setVoltageL1(JsonUtils.getAsInt(nrg, 0)); + this._setVoltageL2(JsonUtils.getAsInt(nrg, 1)); + this._setVoltageL3(JsonUtils.getAsInt(nrg, 2)); + this._setCurrentL1(JsonUtils.getAsInt(nrg, 4) * 100); + this._setCurrentL2(JsonUtils.getAsInt(nrg, 5) * 100); + this._setCurrentL3(JsonUtils.getAsInt(nrg, 6) * 100); var power = JsonUtils.getAsInt(nrg, 11); - this.channel(EvcsGoeChargerHome.ChannelId.ACTUAL_POWER).setNextValue(power * 10); - this.channel(Evcs.ChannelId.CHARGE_POWER).setNextValue(power * 10); + // TODO set ActivePowerL1/L2/L3 of ElectricityMeter + this._setActivePower(power * 10); // Hardware limits var cableCurrent = JsonUtils.getAsInt(json, "cbl") * 1000; @@ -157,6 +157,7 @@ public void handleEvent(Event event) { this._setPhases(phases); // Energy + // TODO set ActiveProductionEnergy this.channel(EvcsGoeChargerHome.ChannelId.ENERGY_TOTAL) .setNextValue(JsonUtils.getAsInt(json, "eto") * 100); this.channel(Evcs.ChannelId.ENERGY_SESSION) @@ -176,19 +177,19 @@ public void handleEvent(Event event) { } } + @Override + public MeterType getMeterType() { + return MeterType.MANAGED_CONSUMPTION_METERED; + } + private Status convertGoeStatus(int status) { - switch (status) { - case 1: // ready for charging, car unplugged - return Status.NOT_READY_FOR_CHARGING; - case 2: // charging - return Status.CHARGING; - case 3: // waiting for car - return Status.READY_FOR_CHARGING; - case 4: // charging finished, car plugged - return Status.CHARGING_FINISHED; - default: - return Status.UNDEFINED; - } + return switch (status) { + case 1 -> Status.NOT_READY_FOR_CHARGING; // ready for charging, car unplugged + case 2 -> Status.CHARGING; // charging + case 3 -> Status.READY_FOR_CHARGING; // waiting for car + case 4 -> Status.CHARGING_REJECTED; // charging finished, car plugged + default -> Status.UNDEFINED; + }; } /** @@ -198,17 +199,12 @@ private Status convertGoeStatus(int status) { * @return amount of phases */ private int convertGoePhase(int phase) { - var phasen = (byte) phase & 0b00111000; - switch (phasen) { - case 8: // 0b00001000: Phase 1 is active - return 1; - case 24: // 0b00011000: Phase 1+2 is active - return 2; - case 56: // 0b00111000: Phase1-3 are active - return 3; - default: - return 0; - } + return switch ((byte) phase & 0b00111000) { + case 8 -> 1; // 0b00001000: Phase 1 is active + case 24 -> 2; // 0b00011000: Phase 1+2 is active + case 56 -> 3; // 0b00111000: Phase1-3 are active + default -> 0; // TODO illegal value! + }; } /** diff --git a/io.openems.edge.evcs.goe.chargerhome/src/io/openems/edge/evcs/goe/chargerhome/GoeApi.java b/io.openems.edge.evcs.goe.chargerhome/src/io/openems/edge/evcs/goe/chargerhome/GoeApi.java index c8bbfe747a2..39c9871df65 100644 --- a/io.openems.edge.evcs.goe.chargerhome/src/io/openems/edge/evcs/goe/chargerhome/GoeApi.java +++ b/io.openems.edge.evcs.goe.chargerhome/src/io/openems/edge/evcs/goe/chargerhome/GoeApi.java @@ -4,7 +4,7 @@ import java.io.IOException; import java.io.InputStreamReader; import java.net.HttpURLConnection; -import java.net.URL; +import java.net.URI; import com.google.gson.JsonObject; @@ -177,7 +177,8 @@ public boolean setMaxEnergy(int maxEnergy) { */ private JsonObject sendRequest(String urlString, String requestMethod) throws OpenemsNamedException { try { - var url = new URL(urlString); + var uri = URI.create(urlString); + var url = uri.toURL(); var con = (HttpURLConnection) url.openConnection(); con.setRequestMethod(requestMethod); con.setConnectTimeout(5000); diff --git a/io.openems.edge.evcs.goe.chargerhome/test/io/openems/edge/evcs/goe/chargerhome/EvcsGoeChargerHomeImplTest.java b/io.openems.edge.evcs.goe.chargerhome/test/io/openems/edge/evcs/goe/chargerhome/EvcsGoeChargerHomeImplTest.java index ab8cbb6fd48..6ccf8f42662 100644 --- a/io.openems.edge.evcs.goe.chargerhome/test/io/openems/edge/evcs/goe/chargerhome/EvcsGoeChargerHomeImplTest.java +++ b/io.openems.edge.evcs.goe.chargerhome/test/io/openems/edge/evcs/goe/chargerhome/EvcsGoeChargerHomeImplTest.java @@ -6,13 +6,11 @@ public class EvcsGoeChargerHomeImplTest { - private static final String COMPONENT_ID = "evcs0"; - @Test public void test() throws Exception { new ComponentTest(new EvcsGoeChargerHomeImpl()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("evcs0") // .setIp("192.168.50.88") // .setMaxHwCurrent(32) // .setMinHwCurrent(6) // diff --git a/io.openems.edge.evcs.hardybarth/.classpath b/io.openems.edge.evcs.hardybarth/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.evcs.hardybarth/.classpath +++ b/io.openems.edge.evcs.hardybarth/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.evcs.hardybarth/bnd.bnd b/io.openems.edge.evcs.hardybarth/bnd.bnd index c7861a683c4..060569c9b40 100644 --- a/io.openems.edge.evcs.hardybarth/bnd.bnd +++ b/io.openems.edge.evcs.hardybarth/bnd.bnd @@ -6,8 +6,10 @@ Bundle-Version: 1.0.0.${tstamp} -buildpath: \ ${buildpath},\ io.openems.common,\ + io.openems.edge.bridge.http,\ io.openems.edge.common,\ - io.openems.edge.evcs.api + io.openems.edge.evcs.api,\ + io.openems.edge.meter.api,\ -testpath: \ ${testpath} diff --git a/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/Config.java b/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/Config.java index 86633930c91..33465046fca 100644 --- a/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/Config.java +++ b/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/Config.java @@ -3,6 +3,8 @@ import org.osgi.service.metatype.annotations.AttributeDefinition; import org.osgi.service.metatype.annotations.ObjectClassDefinition; +import io.openems.edge.evcs.api.PhaseRotation; + @ObjectClassDefinition(// name = "EVCS Hardy Barth", // description = "Implements the Hardy Barth - Salia electric vehicle charging station.") @@ -29,6 +31,9 @@ @AttributeDefinition(name = "Maximum hardware current", description = "Maximum current of the Charger in mA.", required = true) int maxHwCurrent() default 32000; + @AttributeDefinition(name = "Phase Rotation", description = "Apply standard or rotated wiring") + PhaseRotation phaseRotation() default PhaseRotation.L1_L2_L3; + String webconsole_configurationFactory_nameHint() default "EVCS Hardy Barth [{id}]"; } \ No newline at end of file diff --git a/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/EvcsHardyBarth.java b/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/EvcsHardyBarth.java index 4029d0495fd..fc47d290944 100644 --- a/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/EvcsHardyBarth.java +++ b/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/EvcsHardyBarth.java @@ -1,35 +1,44 @@ package io.openems.edge.evcs.hardybarth; +import static io.openems.common.channel.Level.WARNING; +import static io.openems.common.channel.Unit.AMPERE; +import static io.openems.common.channel.Unit.CUMULATED_WATT_HOURS; +import static io.openems.common.types.OpenemsType.BOOLEAN; +import static io.openems.common.types.OpenemsType.DOUBLE; +import static io.openems.common.types.OpenemsType.INTEGER; +import static io.openems.common.types.OpenemsType.LONG; +import static io.openems.common.types.OpenemsType.STRING; + import java.util.function.Function; -import io.openems.common.channel.Level; -import io.openems.common.channel.Unit; -import io.openems.common.types.OpenemsType; import io.openems.edge.common.channel.BooleanDoc; import io.openems.edge.common.channel.Doc; -import io.openems.edge.common.type.TypeUtils; public interface EvcsHardyBarth { - public static final double SCALE_FACTOR_MINUS_1 = 0.1; + public static final float SCALE_FACTOR_MINUS_1 = 0.1F; public enum ChannelId implements io.openems.edge.common.channel.ChannelId { // TODO: Correct Type & Unit (Waiting for Manufacturer instructions) // EVSE - RAW_EVSE_GRID_CURRENT_LIMIT(Doc.of(OpenemsType.INTEGER).unit(Unit.AMPERE), "secc", "port0", "ci", "evse", // - "basic", "grid_current_limit", "actual"), // - RAW_PHASE_COUNT(Doc.of(OpenemsType.INTEGER), "secc", "port0", "ci", "evse", "basic", "phase_count"), // + RAW_EVSE_GRID_CURRENT_LIMIT(Doc.of(INTEGER) // + .unit(AMPERE), // + "secc", "port0", "ci", "evse", "basic", "grid_current_limit", "actual"), // + RAW_PHASE_COUNT(Doc.of(INTEGER), // + "secc", "port0", "ci", "evse", "basic", "phase_count"), // // CHARGE - RAW_CHARGE_STATUS_PLUG(Doc.of(OpenemsType.STRING), "secc", "port0", "ci", "charge", "plug", "status"), // - RAW_CHARGE_STATUS_CONTACTOR(Doc.of(OpenemsType.STRING), "secc", "port0", "ci", "charge", "contactor", "status"), // - RAW_CHARGE_STATUS_PWM(Doc.of(OpenemsType.STRING), "secc", "port0", "ci", "charge", "pwm", "status"), // + RAW_CHARGE_STATUS_PLUG(Doc.of(STRING), // + "secc", "port0", "ci", "charge", "plug", "status"), // + RAW_CHARGE_STATUS_CONTACTOR(Doc.of(STRING), // + "secc", "port0", "ci", "charge", "contactor", "status"), // + RAW_CHARGE_STATUS_PWM(Doc.of(STRING), // + "secc", "port0", "ci", "charge", "pwm", "status"), // /** * States of the Hardy Barth. * - *

      *

        *
      • A = Free (no EV connected) *
      • B = EV connected, no charging (pause state) @@ -39,122 +48,141 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { *
      • F = Failure *
      */ - RAW_CHARGE_STATUS_CHARGEPOINT(Doc.of(OpenemsType.STRING), "secc", "port0", "ci", "charge", "cp", "status"), // + RAW_CHARGE_STATUS_CHARGEPOINT(Doc.of(STRING), // + "secc", "port0", "ci", "charge", "cp", "status"), // // SALIA - RAW_SALIA_CHARGE_MODE(Doc.of(OpenemsType.STRING), "secc", "port0", "salia", "chargemode"), // - RAW_SALIA_CHANGE_METER(Doc.of(OpenemsType.STRING), "secc", "port0", "salia", "changemeter"), // - RAW_SALIA_AUTHMODE(Doc.of(OpenemsType.STRING), "secc", "port0", "salia", "authmode"), // - RAW_SALIA_FIRMWARESTATE(Doc.of(OpenemsType.STRING), "secc", "port0", "salia", "firmwarestate"), // - RAW_SALIA_FIRMWAREPROGRESS(Doc.of(OpenemsType.STRING), "secc", "port0", "salia", "firmwareprogress"), // - RAW_SALIA_PUBLISH(Doc.of(OpenemsType.STRING), "secc", "port0", "salia", "publish"), // + RAW_SALIA_CHARGE_MODE(Doc.of(STRING), // + "secc", "port0", "salia", "chargemode"), // + RAW_SALIA_CHANGE_METER(Doc.of(STRING), // + "secc", "port0", "salia", "changemeter"), // + RAW_SALIA_AUTHMODE(Doc.of(STRING), // + "secc", "port0", "salia", "authmode"), // + RAW_SALIA_FIRMWARESTATE(Doc.of(STRING), // + "secc", "port0", "salia", "firmwarestate"), // + RAW_SALIA_FIRMWAREPROGRESS(Doc.of(STRING), // + "secc", "port0", "salia", "firmwareprogress"), // + RAW_SALIA_PUBLISH(Doc.of(STRING), // + "secc", "port0", "salia", "publish"), // // SESSION - RAW_SESSION_STATUS_AUTHORIZATION(Doc.of(OpenemsType.STRING), "secc", "port0", "session", - "authorization_status"), // - RAW_SESSION_SLAC_STARTED(Doc.of(OpenemsType.STRING), "secc", "port0", "session", "slac_started"), // - RAW_SESSION_AUTHORIZATION_METHOD(Doc.of(OpenemsType.STRING), "secc", "port0", "session", - "authorization_method"), // + RAW_SESSION_STATUS_AUTHORIZATION(Doc.of(STRING), // + "secc", "port0", "session", "authorization_status"), // + RAW_SESSION_SLAC_STARTED(Doc.of(STRING), // + "secc", "port0", "session", "slac_started"), // + RAW_SESSION_AUTHORIZATION_METHOD(Doc.of(STRING), // + "secc", "port0", "session", "authorization_method"), // // CONTACTOR - RAW_CONTACTOR_HLC_TARGET(Doc.of(OpenemsType.STRING), "secc", "port0", "contactor", "state", "hlc_target"), // - RAW_CONTACTOR_ACTUAL(Doc.of(OpenemsType.STRING), "secc", "port0", "contactor", "state", "actual"), // - RAW_CONTACTOR_TARGET(Doc.of(OpenemsType.STRING), "secc", "port0", "contactor", "state", "target"), // - RAW_CONTACTOR_ERROR(Doc.of(OpenemsType.STRING), "secc", "port0", "contactor", "error"), // + RAW_CONTACTOR_HLC_TARGET(Doc.of(STRING), // + "secc", "port0", "contactor", "state", "hlc_target"), // + RAW_CONTACTOR_ACTUAL(Doc.of(STRING), // + "secc", "port0", "contactor", "state", "actual"), // + RAW_CONTACTOR_TARGET(Doc.of(STRING), // + "secc", "port0", "contactor", "state", "target"), // + RAW_CONTACTOR_ERROR(Doc.of(STRING), // + "secc", "port0", "contactor", "error"), // // METERING - METER - RAW_METER_SERIALNUMBER(Doc.of(OpenemsType.STRING), "secc", "port0", "metering", "meter", "serialnumber"), // - RAW_METER_TYPE(Doc.of(OpenemsType.STRING), "secc", "port0", "metering", "meter", "type"), // - METER_NOT_AVAILABLE(Doc.of(Level.INFO) // - .text("No meter values available. The communication cable of the internal meter may be loose.")), // + RAW_METER_SERIALNUMBER(Doc.of(STRING), // + "secc", "port0", "metering", "meter", "serialnumber"), // + RAW_METER_TYPE(Doc.of(STRING), // + "secc", "port0", "metering", "meter", "type"), // + METER_NOT_AVAILABLE(Doc.of(WARNING) // + .translationKey(EvcsHardyBarth.class, "noMeterAvailable")), // RAW_METER_AVAILABLE(new BooleanDoc()// .onChannelSetNextValue((hardyBarth, value) -> { var notAvailable = value.get() == null ? null : !value.get(); hardyBarth.channel(EvcsHardyBarth.ChannelId.METER_NOT_AVAILABLE).setNextValue(notAvailable); - }), "secc", "port0", "metering", "meter", "available"), // - - // METERING - POWER - RAW_ACTIVE_POWER_L1(Doc.of(OpenemsType.LONG).unit(Unit.WATT), value -> { - Double doubleValue = TypeUtils.getAsType(OpenemsType.DOUBLE, value); - return TypeUtils.getAsType(OpenemsType.LONG, TypeUtils.multiply(doubleValue, SCALE_FACTOR_MINUS_1)); - }, "secc", "port0", "metering", "power", "active", "ac", "l1", "actual"), // - - RAW_ACTIVE_POWER_L2(Doc.of(OpenemsType.LONG).unit(Unit.WATT), value -> { - Double doubleValue = TypeUtils.getAsType(OpenemsType.DOUBLE, value); - return TypeUtils.getAsType(OpenemsType.LONG, TypeUtils.multiply(doubleValue, SCALE_FACTOR_MINUS_1)); - }, "secc", "port0", "metering", "power", "active", "ac", "l2", "actual"), // - - RAW_ACTIVE_POWER_L3(Doc.of(OpenemsType.LONG).unit(Unit.WATT), value -> { - Double doubleValue = TypeUtils.getAsType(OpenemsType.DOUBLE, value); - return TypeUtils.getAsType(OpenemsType.LONG, TypeUtils.multiply(doubleValue, SCALE_FACTOR_MINUS_1)); - }, "secc", "port0", "metering", "power", "active", "ac", "l3", "actual"), // - - // METERING - CURRENT - RAW_ACTIVE_CURRENT_L1(Doc.of(OpenemsType.LONG).unit(Unit.MILLIAMPERE), "secc", "port0", "metering", "current", - "ac", "l1", "actual"), // - RAW_ACTIVE_CURRENT_L2(Doc.of(OpenemsType.LONG).unit(Unit.MILLIAMPERE), "secc", "port0", "metering", "current", - "ac", "l2", "actual"), // - RAW_ACTIVE_CURRENT_L3(Doc.of(OpenemsType.LONG).unit(Unit.MILLIAMPERE), "secc", "port0", "metering", "current", - "ac", "l3", "actual"), // + }), // + "secc", "port0", "metering", "meter", "available"), // // METERING - ENERGY - RAW_ACTIVE_ENERGY_TOTAL(Doc.of(OpenemsType.DOUBLE).unit(Unit.CUMULATED_WATT_HOURS), "secc", "port0", "metering", - "energy", "active_total", "actual"), // - RAW_ACTIVE_ENERGY_EXPORT(Doc.of(OpenemsType.DOUBLE).unit(Unit.CUMULATED_WATT_HOURS), "secc", "port0", - "metering", "energy", "active_export", "actual"), // + RAW_ACTIVE_ENERGY_TOTAL(Doc.of(DOUBLE) // + .unit(CUMULATED_WATT_HOURS), // + "secc", "port0", "metering", "energy", "active_total", "actual"), // + RAW_ACTIVE_ENERGY_EXPORT(Doc.of(DOUBLE) // + .unit(CUMULATED_WATT_HOURS), // + "secc", "port0", "metering", "energy", "active_export", "actual"), // // EMERGENCY SHUTDOWN - RAW_EMERGENCY_SHUTDOWN(Doc.of(OpenemsType.STRING), "secc", "port0", "emergency_shutdown"), // + RAW_EMERGENCY_SHUTDOWN(Doc.of(STRING), // + "secc", "port0", "emergency_shutdown"), // // RCD - RAW_RCD_AVAILABLE(Doc.of(OpenemsType.BOOLEAN), "secc", "port0", "rcd", "recloser", "available"), // + RAW_RCD_AVAILABLE(Doc.of(BOOLEAN), // + "secc", "port0", "rcd", "recloser", "available"), // // PLUG LOCK - RAW_PLUG_LOCK_STATE_ACTUAL(Doc.of(OpenemsType.STRING), "secc", "port0", "plug_lock", "state", "actual"), // - RAW_PLUG_LOCK_STATE_TARGET(Doc.of(OpenemsType.STRING), "secc", "port0", "plug_lock", "state", "target"), // - RAW_PLUG_LOCK_ERROR(Doc.of(OpenemsType.STRING), "secc", "port0", "plug_lock", "error"), // + RAW_PLUG_LOCK_STATE_ACTUAL(Doc.of(STRING), // + "secc", "port0", "plug_lock", "state", "actual"), // + RAW_PLUG_LOCK_STATE_TARGET(Doc.of(STRING), // + "secc", "port0", "plug_lock", "state", "target"), // + RAW_PLUG_LOCK_ERROR(Doc.of(STRING), // + "secc", "port0", "plug_lock", "error"), // // CHARGE POINT - RAW_CP_STATE(Doc.of(OpenemsType.STRING), "secc", "port0", "cp", "state"), // + RAW_CP_STATE(Doc.of(STRING), // + "secc", "port0", "cp", "state"), // // DIODE PRESENT - RAW_DIODE_PRESENT(Doc.of(OpenemsType.STRING), "secc", "port0", "diode_present"), // + RAW_DIODE_PRESENT(Doc.of(STRING), // + "secc", "port0", "diode_present"), // // CABLE CURRENT LIMIT - RAW_CABLE_CURRENT_LIMIT(Doc.of(OpenemsType.STRING), "secc", "port0", "cable_current_limit"), // + RAW_CABLE_CURRENT_LIMIT(Doc.of(STRING), // + "secc", "port0", "cable_current_limit"), // // VENTILATION - RAW_VENTILATION_STATE_ACTUAL(Doc.of(OpenemsType.STRING), "secc", "port0", "ventilation", "state", "actual"), // - RAW_VENTILATION_STATE_TARGET(Doc.of(OpenemsType.STRING), "secc", "port0", "ventilation", "state", "target"), // - RAW_VENTILATION_AVAILABLE(Doc.of(OpenemsType.BOOLEAN), "secc", "port0", "ventilation", "available"), // + RAW_VENTILATION_STATE_ACTUAL(Doc.of(STRING), // + "secc", "port0", "ventilation", "state", "actual"), // + RAW_VENTILATION_STATE_TARGET(Doc.of(STRING), // + "secc", "port0", "ventilation", "state", "target"), // + RAW_VENTILATION_AVAILABLE(Doc.of(BOOLEAN), // + "secc", "port0", "ventilation", "available"), // // EV - PRESENT - RAW_EV_PRESENT(Doc.of(OpenemsType.STRING), "secc", "port0", "ev_present"), // + RAW_EV_PRESENT(Doc.of(STRING), // + "secc", "port0", "ev_present"), // // CHARGING - RAW_CHARGING(Doc.of(OpenemsType.STRING), "secc", "port0", "charging"), // + RAW_CHARGING(Doc.of(STRING), // + "secc", "port0", "charging"), // // RFID - RAW_RFID_AUTHORIZEREQ(Doc.of(OpenemsType.STRING), "secc", "port0", "rfid", "authorizereq"), // - RAW_RFID_AVAILABLE(Doc.of(OpenemsType.BOOLEAN), "secc", "port0", "rfid", "available"), // + RAW_RFID_AUTHORIZEREQ(Doc.of(STRING), // + "secc", "port0", "rfid", "authorizereq"), // + RAW_RFID_AVAILABLE(Doc.of(BOOLEAN), // + "secc", "port0", "rfid", "available"), // // GRID CURRENT LIMIT - RAW_GRID_CURRENT_LIMIT(Doc.of(OpenemsType.STRING), "secc", "port0", "grid_current_limit"), // + RAW_GRID_CURRENT_LIMIT(Doc.of(STRING), // + "secc", "port0", "grid_current_limit"), // // SLAC ERROR - RAW_SLAC_ERROR(Doc.of(OpenemsType.STRING), "secc", "port0", "slac_error"), // + RAW_SLAC_ERROR(Doc.of(STRING), // + "secc", "port0", "slac_error"), // // DEVICE - RAW_DEVICE_PRODUCT(Doc.of(OpenemsType.STRING), "device", "product"), // - RAW_DEVICE_MODELNAME(Doc.of(OpenemsType.STRING), "device", "modelname"), // - RAW_DEVICE_HARDWARE_VERSION(Doc.of(OpenemsType.STRING), "device", "hardware_version"), // - RAW_DEVICE_SOFTWARE_VERSION(Doc.of(OpenemsType.STRING), "device", "software_version"), // - RAW_DEVICE_VCS_VERSION(Doc.of(OpenemsType.STRING), "device", "vcs_version"), // - RAW_DEVICE_HOSTNAME(Doc.of(OpenemsType.STRING), "device", "hostname"), // - RAW_DEVICE_MAC_ADDRESS(Doc.of(OpenemsType.STRING), "device", "mac_address"), // - RAW_DEVICE_SERIAL(Doc.of(OpenemsType.LONG), "device", "serial"), // - RAW_DEVICE_UUID(Doc.of(OpenemsType.STRING), "device", "uuid"), // + RAW_DEVICE_PRODUCT(Doc.of(STRING), // + "device", "product"), // + RAW_DEVICE_MODELNAME(Doc.of(STRING), // + "device", "modelname"), // + RAW_DEVICE_HARDWARE_VERSION(Doc.of(STRING), // + "device", "hardware_version"), // + RAW_DEVICE_SOFTWARE_VERSION(Doc.of(STRING), // + "device", "software_version"), // + RAW_DEVICE_VCS_VERSION(Doc.of(STRING), // + "device", "vcs_version"), // + RAW_DEVICE_HOSTNAME(Doc.of(STRING), // + "device", "hostname"), // + RAW_DEVICE_MAC_ADDRESS(Doc.of(STRING), // + "device", "mac_address"), // + RAW_DEVICE_SERIAL(Doc.of(LONG), // + "device", "serial"), // + RAW_DEVICE_UUID(Doc.of(STRING), // + "device", "uuid"), // ; private final Doc doc; diff --git a/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/EvcsHardyBarthImpl.java b/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/EvcsHardyBarthImpl.java index 3b77f4df677..87abcc6cfb0 100644 --- a/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/EvcsHardyBarthImpl.java +++ b/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/EvcsHardyBarthImpl.java @@ -1,6 +1,17 @@ package io.openems.edge.evcs.hardybarth; +import static io.openems.edge.bridge.http.api.BridgeHttp.DEFAULT_CONNECT_TIMEOUT; +import static io.openems.edge.bridge.http.api.BridgeHttp.DEFAULT_READ_TIMEOUT; +import static io.openems.edge.bridge.http.api.HttpMethod.GET; +import static io.openems.edge.bridge.http.api.HttpMethod.PUT; +import static io.openems.edge.evcs.api.ChargingType.AC; +import static io.openems.edge.evcs.api.Phases.THREE_PHASE; +import static java.lang.Math.round; +import static java.util.Collections.emptyMap; + import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import org.osgi.service.component.ComponentContext; import org.osgi.service.component.annotations.Activate; @@ -19,16 +30,23 @@ import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.exceptions.OpenemsException; -import io.openems.common.utils.JsonUtils; +import io.openems.common.types.HttpStatus; +import io.openems.common.types.MeterType; +import io.openems.edge.bridge.http.api.BridgeHttp; +import io.openems.edge.bridge.http.api.BridgeHttp.Endpoint; +import io.openems.edge.bridge.http.api.BridgeHttpFactory; +import io.openems.edge.bridge.http.api.HttpMethod; +import io.openems.edge.bridge.http.api.HttpResponse; import io.openems.edge.common.channel.StringReadChannel; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.event.EdgeEventConstants; import io.openems.edge.evcs.api.AbstractManagedEvcsComponent; -import io.openems.edge.evcs.api.ChargingType; +import io.openems.edge.evcs.api.DeprecatedEvcs; import io.openems.edge.evcs.api.Evcs; import io.openems.edge.evcs.api.EvcsPower; import io.openems.edge.evcs.api.ManagedEvcs; -import io.openems.edge.evcs.api.Phases; +import io.openems.edge.evcs.api.WriteHandler; +import io.openems.edge.meter.api.ElectricityMeter; @Designate(ocd = Config.class, factory = true) @Component(// @@ -37,82 +55,118 @@ configurationPolicy = ConfigurationPolicy.REQUIRE // ) @EventTopics({ // - EdgeEventConstants.TOPIC_CYCLE_BEFORE_PROCESS_IMAGE, // EdgeEventConstants.TOPIC_CYCLE_EXECUTE_WRITE, // }) public class EvcsHardyBarthImpl extends AbstractManagedEvcsComponent - implements OpenemsComponent, EventHandler, EvcsHardyBarth, Evcs, ManagedEvcs { + implements OpenemsComponent, EventHandler, EvcsHardyBarth, Evcs, ManagedEvcs, DeprecatedEvcs, ElectricityMeter { - protected final Logger log = LoggerFactory.getLogger(EvcsHardyBarthImpl.class); + private final Logger log = LoggerFactory.getLogger(EvcsHardyBarthImpl.class); - @Reference - private EvcsPower evcsPower; + protected final HardyBarthReadUtils readUtils = new HardyBarthReadUtils(this); - /** API for main REST API functions. */ - protected HardyBarthApi api; - /** ReadWorker and WriteHandler: Reading and sending data to the EVCS. */ - private final HardyBarthReadWorker readWorker = new HardyBarthReadWorker(this); /** * Master EVCS is responsible for RFID authentication (Not implemented for now). */ protected boolean masterEvcs = true; - protected Config config; + private BridgeHttp httpBridge; + private Config config; + + @Reference + private EvcsPower evcsPower; + + @Reference + private BridgeHttpFactory httpBridgeFactory; public EvcsHardyBarthImpl() { super(// OpenemsComponent.ChannelId.values(), // + ElectricityMeter.ChannelId.values(), // Evcs.ChannelId.values(), // ManagedEvcs.ChannelId.values(), // - EvcsHardyBarth.ChannelId.values() // + EvcsHardyBarth.ChannelId.values(), // + DeprecatedEvcs.ChannelId.values() // ); + DeprecatedEvcs.copyToDeprecatedEvcsChannels(this); + ElectricityMeter.calculateSumCurrentFromPhases(this); + ElectricityMeter.calculateAverageVoltageFromPhases(this); } @Activate private void activate(ComponentContext context, Config config) { super.activate(context, config.id(), config.alias(), config.enabled()); + // TODO stop here if not enabled this.config = config; - this._setChargingType(ChargingType.AC); + this._setChargingType(AC); this._setFixedMinimumHardwarePower(config.minHwCurrent() / 1000 * 3 * 230); this._setFixedMaximumHardwarePower(config.maxHwCurrent() / 1000 * 3 * 230); this._setPowerPrecision(230); - this._setPhases(Phases.THREE_PHASE); - - if (config.enabled()) { - this.api = new HardyBarthApi(config.ip(), this); + this._setPhases(THREE_PHASE); - // Reading the given values - this.readWorker.activate(config.id()); - this.readWorker.triggerNextRun(); - } + this.httpBridge = this.httpBridgeFactory.get(); + // formerly .setHeartbeat + // The internal heartbeat is currently too fast - it is not enough to write + // every second by default. We have to disable it to run the evcs + // properly. + // TODO: The manufacturer must be asked if it is possible to read the heartbeat + // status so that we can check if the heartbeat is really disabled and if the + // heartbeat time can be increased to be able to use this feature. + this.httpBridge.subscribeCycle(1, // + this.createEndpoint(PUT, "/api/secc", "{\"salia/heartbeat\":\"off\"}"), // + t -> this._setChargingstationCommunicationFailed(false), + t -> this._setChargingstationCommunicationFailed(true)); + this.httpBridge.subscribeCycle(1, // + this.createEndpoint(GET, "/api", null), // + t -> { + this.readUtils.handleGetApiCallResponse(t, config.phaseRotation()); + this._setChargingstationCommunicationFailed(false); + }, // + t -> this._setChargingstationCommunicationFailed(true)); } @Override @Deactivate protected void deactivate() { super.deactivate(); + this.httpBridgeFactory.unget(this.httpBridge); + this.httpBridge = null; + } - if (this.readWorker != null) { - this.readWorker.deactivate(); - } + @Override + public MeterType getMeterType() { + return MeterType.MANAGED_CONSUMPTION_METERED; + } + + private Endpoint getTargetEndpoint(int target) { + return this.createEndpoint(PUT, "/api/secc", "{\"salia/pausecharging\":\"" + target + "\"}"); + } + + private Endpoint createEndpoint(HttpMethod httpMethod, String url, String body) { + return createEndpoint(this.config.ip(), httpMethod, url, body); + } + + protected static Endpoint createEndpoint(String ip, HttpMethod httpMethod, String url, String body) { + return new Endpoint(// + new StringBuilder("http://").append(ip).append(url).toString(), // + httpMethod, DEFAULT_CONNECT_TIMEOUT, DEFAULT_READ_TIMEOUT, // + body, // + emptyMap()); } @Override - public void handleEvent(Event event) { + protected WriteHandler createWriteHandler() { + return new HardyBarthWriteHandler(this); + } + @Override + public void handleEvent(Event event) { if (!this.isEnabled()) { return; } super.handleEvent(event); switch (event.getTopic()) { - case EdgeEventConstants.TOPIC_CYCLE_BEFORE_PROCESS_IMAGE: - - this.setManualMode(); - this.setHeartbeat(); - this.readWorker.triggerNextRun(); - - // TODO: intelligent firmware update - break; + case EdgeEventConstants.TOPIC_CYCLE_BEFORE_PROCESS_IMAGE // + -> this.setManualMode(); } } @@ -125,39 +179,14 @@ public void handleEvent(Event event) { private void setManualMode() { StringReadChannel channelChargeMode = this.channel(EvcsHardyBarth.ChannelId.RAW_SALIA_CHARGE_MODE); Optional valueOpt = channelChargeMode.value().asOptional(); - if (valueOpt.isPresent()) { - if (!valueOpt.get().equals("manual")) { - // Set to manual mode - try { - this.debugLog("Setting HardyBarth to manual chargemode"); - JsonElement result = this.api.sendPutRequest("/api/secc", "salia/chargemode", "manual"); - this.debugLog(result.toString()); - } catch (OpenemsNamedException e) { - e.printStackTrace(); - } - } + if (valueOpt.map(t -> !t.equals("manual")).orElse(true)) { + return; } - } - - /** - * Set heartbeat. - * - *

      - * Sets the heartbeat to on or off. - */ - private void setHeartbeat() { - // The internal heartbeat is currently too fast - it is not enough to write - // every second by default. We have to disable it to run the evcs - // properly. - // TODO: The manufacturer must be asked if it is possible to read the heartbeat - // status so that we can check if the heartbeat is really disabled and if the - // heartbeat time can be increased to be able to use this feature. + this.debugLog("Setting HardyBarth to manual chargemode"); + this.httpBridge // + .requestJson(this.createEndpoint(PUT, "/api/secc", "{\"salia/chargemode\":\"manual\"}")) // + .thenAccept(t -> this.debugLog(t.toString())); - try { - this.api.sendPutRequest("/api/secc", "salia/heartbeat", "off"); - } catch (OpenemsNamedException e) { - e.printStackTrace(); - } } /** @@ -219,7 +248,6 @@ public boolean getConfiguredDebugMode() { @Override public boolean applyChargePowerLimit(int power) throws OpenemsNamedException { - // TODO: Use power precision to set valid power if it is used in UI part too // e.g. int precision = TypeUtils.getAsType(OpenemsType.INTEGER, // this.getPowerPrecision().orElse(230d)); @@ -227,7 +255,7 @@ public boolean applyChargePowerLimit(int power) throws OpenemsNamedException { // Convert it to ampere and apply hard limits int phases = this.getPhasesAsInt(); - Integer current = (int) Math.round(power / (double) phases / 230.0); + final var current = round(power / (float) phases / 230.F); return this.setTarget(current); } @@ -244,25 +272,25 @@ public boolean pauseChargeProcess() throws OpenemsNamedException { * @return boolean if the target was set * @throws OpenemsNamedException on error */ - private boolean setTarget(int current) throws OpenemsNamedException { - - JsonElement resultPause; + private boolean setTarget(int current) { + CompletableFuture> resultPause = null; if (current > 0) { // Send stop pause request - resultPause = this.api.sendPutRequest("/api/secc", "salia/pausecharging", "" + 0); + resultPause = this.httpBridge.requestJson(this.getTargetEndpoint(0)); } else { - // Send pause charging request - resultPause = this.api.sendPutRequest("/api/secc", "salia/pausecharging", "" + 1); + resultPause = this.httpBridge.requestJson(this.getTargetEndpoint(1)); this.debugLog("Setting HardyBarth " + this.alias() + " to pause"); } // Send charge power limit - JsonElement resultLimit = this.api.sendPutRequest("/api/secc", "grid_current_limit", "" + current); - - Optional resultLimitVal = JsonUtils.getAsOptionalString(resultLimit, "result"); - Optional resultPauseVal = JsonUtils.getAsOptionalString(resultPause, "result"); - - return resultLimitVal.orElse("").equals("ok") && resultPauseVal.orElse("").equals("ok"); + final var resultLimit = this.httpBridge.requestJson( + this.createEndpoint(PUT, "/api/secc", "{\"" + "grid_current_limit" + "\":\"" + current + "\"}")); + try { + return resultLimit.get().status().equals(HttpStatus.OK) && resultPause.get().status().equals(HttpStatus.OK); + } catch (InterruptedException | ExecutionException e) { + this.log.error("Unable to set EVCS Target"); + return false; + } } @Override @@ -277,12 +305,12 @@ public int getMinimumTimeTillChargingLimitTaken() { @Override public int getConfiguredMinimumHardwarePower() { - return Math.round(this.config.minHwCurrent() / 1000f) * DEFAULT_VOLTAGE * Phases.THREE_PHASE.getValue(); + return round(this.config.minHwCurrent() / 1000f) * DEFAULT_VOLTAGE * THREE_PHASE.getValue(); } @Override public int getConfiguredMaximumHardwarePower() { - return Math.round(this.config.maxHwCurrent() / 1000f) * DEFAULT_VOLTAGE * Phases.THREE_PHASE.getValue(); + return round(this.config.maxHwCurrent() / 1000f) * DEFAULT_VOLTAGE * THREE_PHASE.getValue(); } @Override diff --git a/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/HardyBarthApi.java b/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/HardyBarthApi.java deleted file mode 100644 index 4b088d2d5a3..00000000000 --- a/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/HardyBarthApi.java +++ /dev/null @@ -1,154 +0,0 @@ -package io.openems.edge.evcs.hardybarth; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.net.HttpURLConnection; -import java.net.URL; - -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; - -import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.common.exceptions.OpenemsException; -import io.openems.common.utils.JsonUtils; - -/** - * Implements the Hardy Barth Api. - */ -public class HardyBarthApi { - - private final String baseUrl; - private final String authorizationHeader; - private final EvcsHardyBarthImpl hardyBarthImpl; - - public HardyBarthApi(String ip, EvcsHardyBarthImpl hardyBarthImpl) { - this.baseUrl = "http://" + ip; - this.authorizationHeader = "Basic "; - this.hardyBarthImpl = hardyBarthImpl; - } - - /** - * Sends a get request to the Hardy Barth. - * - * @param endpoint the REST Api endpoint - * @return a JsonObject or JsonArray - * @throws OpenemsNamedException on error - */ - public JsonElement sendGetRequest(String endpoint) throws OpenemsNamedException { - var putRequestFailed = false; - JsonObject result = null; - - try { - // Create URL like "http://192.168.8.101/api/" - var url = new URL(this.baseUrl + endpoint); - - // Open http url connection - var con = (HttpURLConnection) url.openConnection(); - - // Set general information - con.setRequestProperty("Authorization", this.authorizationHeader); - con.setRequestMethod("GET"); - con.setConnectTimeout(5000); - con.setReadTimeout(5000); - - // Read response - String body; - try (var in = new BufferedReader(new InputStreamReader(con.getInputStream()))) { - - // Read HTTP response - var content = new StringBuilder(); - String line; - while ((line = in.readLine()) != null) { - content.append(line); - content.append(System.lineSeparator()); - } - body = content.toString(); - } - - // Get response code - var status = con.getResponseCode(); - if (status >= 300) { - putRequestFailed = true; - throw new OpenemsException( - "Error while reading from Hardy Barth API. Response code: " + status + ". " + body); - } - putRequestFailed = false; - // Parse response to JSON - result = JsonUtils.parseToJsonObject(body); - } catch (OpenemsNamedException | IOException e) { - putRequestFailed = true; - } - - // Set state and return result - this.hardyBarthImpl._setChargingstationCommunicationFailed(putRequestFailed); - return result; - } - - /** - * Sends a get request to the Hardy Barth. - * - * @param endpoint the REST Api endpoint @return a JsonObject or - * JsonArray @throws OpenemsNamedException on error @throws - * @param key The key in the properties - * @param value The value of the key property - * @return A JsonObject - * @throws OpenemsNamedException on error - */ - public JsonObject sendPutRequest(String endpoint, String key, String value) throws OpenemsNamedException { - var putRequestFailed = false; - JsonObject result = null; - - try { - // Create URL like "http://192.168.8.101/api/" - var url = new URL(this.baseUrl + endpoint); - - // Open http url connection - var connection = (HttpURLConnection) url.openConnection(); - - // Set general information - connection.setRequestProperty("Authorization", this.authorizationHeader); - connection.setRequestMethod("PUT"); - connection.setDoOutput(true); - connection.setConnectTimeout(5000); - connection.setReadTimeout(5000); - - // Write "topic" and "value" on request properties - var osw = new OutputStreamWriter(connection.getOutputStream()); - osw.write("{\"" + key + "\":\"" + value + "\"}"); - osw.flush(); - osw.close(); - - String body; - try (var in = new BufferedReader(new InputStreamReader(connection.getInputStream()))) { - - // Read HTTP response - var content = new StringBuilder(); - String line; - while ((line = in.readLine()) != null) { - content.append(line); - content.append(System.lineSeparator()); - } - body = content.toString(); - } - - // Get response code - var status = connection.getResponseCode(); - if ((status >= 300) && (status >= 0)) { - // Respond error status-code - putRequestFailed = true; - throw new OpenemsException( - "Error while reading from Hardy Barth API. Response code: " + status + ". " + body); - } - // Result OK - result = JsonUtils.parseToJsonObject(body); - } catch (IOException e) { - putRequestFailed = true; - } - - // Set state and return result - this.hardyBarthImpl._setChargingstationCommunicationFailed(putRequestFailed); - return result; - } -} diff --git a/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/HardyBarthReadUtils.java b/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/HardyBarthReadUtils.java new file mode 100644 index 00000000000..3f0c194c5a1 --- /dev/null +++ b/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/HardyBarthReadUtils.java @@ -0,0 +1,259 @@ +package io.openems.edge.evcs.hardybarth; + +import static io.openems.common.types.OpenemsType.FLOAT; +import static io.openems.common.types.OpenemsType.INTEGER; +import static io.openems.common.types.OpenemsType.LONG; +import static io.openems.common.types.OpenemsType.STRING; +import static io.openems.common.utils.JsonUtils.parseToJsonObject; +import static io.openems.edge.evcs.api.Evcs.evaluatePhaseCount; +import static io.openems.edge.evcs.hardybarth.EvcsHardyBarth.SCALE_FACTOR_MINUS_1; +import static java.lang.Math.round; + +import java.util.Optional; +import java.util.function.Function; + +import com.google.gson.JsonElement; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.types.OpenemsType; +import io.openems.common.utils.JsonUtils; +import io.openems.edge.bridge.http.api.HttpResponse; +import io.openems.edge.common.type.TypeUtils; +import io.openems.edge.evcs.api.PhaseRotation; +import io.openems.edge.evcs.api.PhaseRotation.RotatedPhases; +import io.openems.edge.evcs.api.Status; + +public class HardyBarthReadUtils { + private final EvcsHardyBarthImpl parent; + + private int errorCounter = 0; + + public HardyBarthReadUtils(EvcsHardyBarthImpl parent) { + this.parent = parent; + } + + /** + * Set the value for every Evcs.ChannelId. + * + * @param json given raw data in JSON + * @param phaseRotation the configured {@link PhaseRotation} + */ + protected void setEvcsChannelIds(JsonElement json, PhaseRotation phaseRotation) { + final var hb = this.parent; + + // Energy + hb._setEnergySession(getValueFromJson(STRING, json, // + value -> { + if (value == null) { + return null; + } + var chargedata = TypeUtils.getAsType(STRING, value).split("\\|"); + if (chargedata.length == 3) { + return round(TypeUtils.getAsType(FLOAT, chargedata[2]) * 1000); + } + return null; + }, "secc", "port0", "salia", "chargedata")); + hb._setActiveProductionEnergy(getValueFromJson(LONG, json, // + value -> TypeUtils.getAsType(LONG, value), // + "secc", "port0", "metering", "energy", "active_import", "actual")); + + // Current + final var currentL1 = getAsIntOrElse(json, 0, "secc", "port0", "metering", "current", "ac", "l1", "actual"); + final var currentL2 = getAsIntOrElse(json, 0, "secc", "port0", "metering", "current", "ac", "l2", "actual"); + final var currentL3 = getAsIntOrElse(json, 0, "secc", "port0", "metering", "current", "ac", "l3", "actual"); + + // Power + final var activePowerL1 = getAsInteger(json, SCALE_FACTOR_MINUS_1, // + "secc", "port0", "metering", "power", "active", "ac", "l1", "actual"); + final var activePowerL2 = getAsInteger(json, SCALE_FACTOR_MINUS_1, // + "secc", "port0", "metering", "power", "active", "ac", "l2", "actual"); + final var activePowerL3 = getAsInteger(json, SCALE_FACTOR_MINUS_1, // + "secc", "port0", "metering", "power", "active", "ac", "l3", "actual"); + + // Voltage + final var voltageL1 = activePowerL1 == null ? null : round(activePowerL1 * 1_000_000F / currentL1); + final var voltageL2 = activePowerL2 == null ? null : round(activePowerL2 * 1_000_000F / currentL2); + final var voltageL3 = activePowerL3 == null ? null : round(activePowerL3 * 1_000_000F / currentL3); + + var rp = RotatedPhases.from(phaseRotation, // + voltageL1, currentL1, activePowerL1, // + voltageL2, currentL2, activePowerL2, // + voltageL3, currentL3, activePowerL3); + hb._setVoltageL1(rp.voltageL1()); + hb._setVoltageL2(rp.voltageL2()); + hb._setVoltageL3(rp.voltageL3()); + hb._setCurrentL1(rp.currentL1()); + hb._setCurrentL2(rp.currentL2()); + hb._setCurrentL3(rp.currentL3()); + hb._setActivePowerL1(rp.activePowerL1()); + hb._setActivePowerL2(rp.activePowerL2()); + hb._setActivePowerL3(rp.activePowerL3()); + + // Phases: keep last value if no power value was given + var phases = evaluatePhaseCount(rp.activePowerL1(), rp.activePowerL2(), rp.activePowerL3()); + if (phases != null) { + hb._setPhases(phases); + this.parent.debugLog("Used phases: " + phases); + } + + // ACTIVE_POWER + final var activePower = Optional.ofNullable(getAsInteger(json, SCALE_FACTOR_MINUS_1, // + "secc", "port0", "metering", "power", "active_total", "actual")) // + .map(p -> p < 100 ? 0 : p) // Ignore the consumption of the charger itself + .orElse(null); + this.parent._setActivePower(activePower); + + // STATUS + var status = getValueFromJson(STRING, json, value -> { + var stringValue = TypeUtils.getAsType(STRING, value); + if (stringValue == null) { + this.errorCounter++; + this.parent.debugLog("Hardy Barth RAW_STATUS would be null! Raw value: " + value); + if (this.errorCounter > 3) { + return Status.ERROR; + } + return this.parent.getStatus(); + } + + Status rawStatus = switch (stringValue) { + case "A" -> Status.NOT_READY_FOR_CHARGING; + case "B" -> { + var tmpStatus = Status.READY_FOR_CHARGING; + + // Detect if the car is full + if (!(this.parent.getSetChargePowerLimit().orElse(0) >= this.parent.getMinimumHardwarePower().orElse(0) + && activePower <= 0)) { + // Charging rejected because we are forcing to pause charging + if (this.parent.getSetChargePowerLimit().orElse(0) == 0) { + tmpStatus = Status.CHARGING_REJECTED; + } + } + yield tmpStatus; + } + case "C", "D" -> Status.CHARGING; + case "E", "F" -> { + this.errorCounter++; + this.parent.debugLog("Hardy Barth RAW_STATUS would be an error! Raw value: " + stringValue + + " - Error counter: " + this.errorCounter); + if (this.errorCounter > 3) { + yield Status.ERROR; + } + yield this.parent.getStatus(); + } + default -> { + this.parent.debugLog("State " + stringValue + " is not a valid state"); + yield Status.UNDEFINED; + } + }; + + if (!stringValue.equals("E") || !stringValue.equals("F")) { + this.errorCounter = 0; + } + + return rawStatus; + }, "secc", "port0", "ci", "charge", "cp", "status"); + + this.parent._setStatus(status); + } + + private static Integer getAsInteger(JsonElement json, float scaleFactor, String... jsonPaths) { + return getValueFromJson(INTEGER, json, // + value -> value == null ? null // + : round(TypeUtils.getAsType(INTEGER, value) * scaleFactor), // + jsonPaths); + } + + private static int getAsIntOrElse(JsonElement json, int orElse, String... jsonPaths) { + var result = getValueFromJson(INTEGER, json, // + value -> TypeUtils.getAsType(INTEGER, value), // + jsonPaths); + return result == null // + ? orElse // + : result; + } + + /** + * Get the last JSON element and it's value, by running through the given + * jsonPath. + * + * @param openemsType the {@link OpenemsType}s + * @param json Raw JsonElement. + * @param converter Converter, to convert the raw JSON value into a proper + * Channel. + * @param jsonPaths Whole JSON path, where the JsonElement for the given + * channel is located. + * @param return type + * @return Value of the last JsonElement by running through the specified JSON + * path. + */ + private static T getValueFromJson(OpenemsType openemsType, JsonElement json, Function converter, + String... jsonPaths) { + + var currentJsonElement = json; + // Go through the whole jsonPath of the current channelId + for (var i = 0; i < jsonPaths.length; i++) { + var currentPathMember = jsonPaths[i]; + try { + if (i == jsonPaths.length - 1) { + // Last path element + var value = getJsonElementValue(currentJsonElement, openemsType, jsonPaths[i]); + + // Return the converted value + return converter.apply(value); + } + // Not last path element + currentJsonElement = JsonUtils.getAsJsonObject(currentJsonElement, currentPathMember); + } catch (OpenemsNamedException e) { + return null; + } + } + return null; + } + + /** + * Handles a Response froma http call for the endpoint /api GET. + * + * @param response the {@link HttpResponse} to be handled + * @param phaseRotation the configured {@link PhaseRotation} + * @throws OpenemsNamedException when json can not be parsed + */ + public void handleGetApiCallResponse(HttpResponse response, PhaseRotation phaseRotation) + throws OpenemsNamedException { + final var json = parseToJsonObject(response.data()); + for (var channelId : EvcsHardyBarth.ChannelId.values()) { + var jsonPaths = channelId.getJsonPaths(); + var value = getValueFromJson(channelId.doc().getType(), json, channelId.converter, jsonPaths); + + // Set the channel-value + this.parent.channel(channelId).setNextValue(value); + + if (channelId.equals(EvcsHardyBarth.ChannelId.RAW_SALIA_PUBLISH)) { + this.parent.masterEvcs = false; + } + + } + this.setEvcsChannelIds(json, phaseRotation); + } + + /** + * Get Value of the given JsonElement in the required type. + * + * @param jsonElement Element as JSON. + * @param openemsType Required type. + * @param memberName Member name of the JSON Element. + * @return Value in the required type. + * @throws OpenemsNamedException Failed to get the value. + */ + private static Object getJsonElementValue(JsonElement jsonElement, OpenemsType openemsType, String memberName) + throws OpenemsNamedException { + return switch (openemsType) { + case BOOLEAN -> JsonUtils.getAsInt(jsonElement, memberName) == 1; + case DOUBLE -> JsonUtils.getAsDouble(jsonElement, memberName); + case FLOAT -> JsonUtils.getAsFloat(jsonElement, memberName); + case INTEGER -> JsonUtils.getAsInt(jsonElement, memberName); + case LONG -> JsonUtils.getAsLong(jsonElement, memberName); + case SHORT -> JsonUtils.getAsShort(jsonElement, memberName); + case STRING -> JsonUtils.getAsString(jsonElement, memberName); + }; + } +} \ No newline at end of file diff --git a/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/HardyBarthReadWorker.java b/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/HardyBarthReadWorker.java deleted file mode 100644 index c24f974ba21..00000000000 --- a/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/HardyBarthReadWorker.java +++ /dev/null @@ -1,318 +0,0 @@ -package io.openems.edge.evcs.hardybarth; - -import java.util.function.Function; - -import com.google.gson.JsonElement; - -import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.common.types.OpenemsType; -import io.openems.common.utils.JsonUtils; -import io.openems.common.worker.AbstractCycleWorker; -import io.openems.edge.common.channel.ChannelId; -import io.openems.edge.common.type.TypeUtils; -import io.openems.edge.evcs.api.Evcs; -import io.openems.edge.evcs.api.Status; - -public class HardyBarthReadWorker extends AbstractCycleWorker { - - private final EvcsHardyBarthImpl parent; - private int chargingFinishedCounter = 0; - private int errorCounter = 0; - - public HardyBarthReadWorker(EvcsHardyBarthImpl parent) { - this.parent = parent; - } - - @Override - protected void forever() throws OpenemsNamedException { - - // TODO: Read separate JSON files - // - separate configuration -> this.api.sendGetRequest("/saliaconf.json"); - // e.g. min & max hardware power - // - customeredit values -> this.api.sendGetRequest("/customer.json"); - // - rfidtags -> this.api.sendGetRequest("/rfidtags.json"); - // - chargelogs -> this.api.sendGetRequest("/chargelogs.json"); - // - Read separate saliaconf.json and set minimum and maximum dynamically - - var json = this.parent.api.sendGetRequest("/api"); - if (json == null) { - return; - } - - // Set value for every HardyBarth.ChannelId - for (EvcsHardyBarth.ChannelId channelId : EvcsHardyBarth.ChannelId.values()) { - var jsonPaths = channelId.getJsonPaths(); - var value = this.getValueFromJson(channelId, json, channelId.converter, jsonPaths); - - // Set the channel-value - this.parent.channel(channelId).setNextValue(value); - - if (channelId.equals(EvcsHardyBarth.ChannelId.RAW_SALIA_PUBLISH)) { - this.parent.masterEvcs = false; - } - } - - // Set value for every Evcs.ChannelId - this.setEvcsChannelIds(json); - } - - /** - * Set the value for every Evcs.ChannelId. - * - * @param json Given raw data in JSON - */ - private void setEvcsChannelIds(JsonElement json) { - - // ENERGY_SESSION - var energy = (Double) this.getValueFromJson(Evcs.ChannelId.ENERGY_SESSION, OpenemsType.STRING, json, value -> { - if (value == null) { - return null; - } - Double rawEnergy = null; - String[] chargedata = value.toString().split("\\|"); - if (chargedata.length == 3) { - Double doubleValue = TypeUtils.getAsType(OpenemsType.DOUBLE, chargedata[2]); - rawEnergy = doubleValue * 1000; - } - return rawEnergy; - - }, "secc", "port0", "salia", "chargedata"); - this.parent._setEnergySession(energy == null ? null : (int) Math.round(energy)); - - // ACTIVE_CONSUMPTION_ENERGY - var activeConsumptionEnergy = (Long) this.getValueFromJson(Evcs.ChannelId.ACTIVE_CONSUMPTION_ENERGY, json, - value -> { - Double doubleValue = TypeUtils.getAsType(OpenemsType.DOUBLE, value); - return TypeUtils.getAsType(OpenemsType.LONG, doubleValue); - - }, "secc", "port0", "metering", "energy", "active_import", "actual"); // - this.parent._setActiveConsumptionEnergy(activeConsumptionEnergy); - - // PHASES - var powerL1 = (Long) this.getValueForChannel(EvcsHardyBarth.ChannelId.RAW_ACTIVE_POWER_L1, json); - var powerL2 = (Long) this.getValueForChannel(EvcsHardyBarth.ChannelId.RAW_ACTIVE_POWER_L2, json); - var powerL3 = (Long) this.getValueForChannel(EvcsHardyBarth.ChannelId.RAW_ACTIVE_POWER_L3, json); - - // TODO: Handle phases, having each phase value in the Nature - // Keep last value if no power value was given - var phases = this.parent.getPhasesAsInt(); - if (powerL1 != null && powerL2 != null && powerL3 != null) { - - var sum = powerL1 + powerL2 + powerL3; - - if (sum > 300) { - phases = 0; - - if (powerL1 >= 100) { - phases += 1; - } - if (powerL2 >= 100) { - phases += 1; - } - if (powerL3 >= 100) { - phases += 1; - } - } - } - this.parent._setPhases(phases); - this.parent.debugLog("Used phases: " + phases); - - // CHARGE_POWER - var chargePowerLong = (Long) this.getValueFromJson(Evcs.ChannelId.CHARGE_POWER, json, value -> { - Integer integerValue = TypeUtils.getAsType(OpenemsType.INTEGER, value); - if (integerValue == null) { - return null; - } - - long activePower = Math.round(integerValue * EvcsHardyBarth.SCALE_FACTOR_MINUS_1); - - // Ignore the consumption of the charger itself - return activePower < 100 ? 0 : activePower; - }, "secc", "port0", "metering", "power", "active_total", "actual"); - - this.parent._setChargePower(chargePowerLong == null ? null : chargePowerLong.intValue()); - - // STATUS - var status = (Status) this.getValueFromJson(EvcsHardyBarth.ChannelId.RAW_CHARGE_STATUS_CHARGEPOINT, json, - value -> { - - String stringValue = TypeUtils.getAsType(OpenemsType.STRING, value); - if (stringValue == null) { - this.errorCounter++; - this.parent.debugLog("Hardy Barth RAW_STATUS would be null! Raw value: " + value); - if (this.errorCounter > 3) { - return Status.ERROR; - } - return this.parent.getStatus(); - } - - Status rawStatus = switch (stringValue) { - case "A" -> Status.NOT_READY_FOR_CHARGING; - case "B" -> { - var tmpStatus = Status.READY_FOR_CHARGING; - - // Detect if the car is full - int chargePower = chargePowerLong == null ? 0 : chargePowerLong.intValue(); - if (this.parent.getSetChargePowerLimit().orElse(0) >= this.parent.getMinimumHardwarePower() - .orElse(0) && chargePower <= 0) { - - if (this.chargingFinishedCounter >= 90) { - tmpStatus = Status.CHARGING_FINISHED; - } else { - this.chargingFinishedCounter++; - } - } else { - this.chargingFinishedCounter = 0; - - // Charging rejected because we are forcing to pause charging - if (this.parent.getSetChargePowerLimit().orElse(0) == 0) { - tmpStatus = Status.CHARGING_REJECTED; - } - } - yield tmpStatus; - } - case "C", "D" -> Status.CHARGING; - case "E", "F" -> { - this.errorCounter++; - this.parent.debugLog("Hardy Barth RAW_STATUS would be an error! Raw value: " + stringValue - + " - Error counter: " + this.errorCounter); - if (this.errorCounter > 3) { - yield Status.ERROR; - } - yield this.parent.getStatus(); - } - default -> { - this.parent.debugLog("State " + stringValue + " is not a valid state"); - yield Status.UNDEFINED; - } - }; - - if (!stringValue.equals("B")) { - this.chargingFinishedCounter = 0; - } - if (!stringValue.equals("E") || !stringValue.equals("F")) { - this.errorCounter = 0; - } - - return rawStatus; - }, "secc", "port0", "ci", "charge", "cp", "status"); - - this.parent._setStatus(status); - } - - /** - * Call the getValueFromJson with the detailed information of the channel. - * - * @param channelId Channel that value will be detect. - * @param json Whole JSON path, where the JsonElement for the given channel - * is located. - * @return Value of the last JsonElement by running through the specified JSON - * path. - */ - private Object getValueForChannel(EvcsHardyBarth.ChannelId channelId, JsonElement json) { - return this.getValueFromJson(channelId, json, channelId.converter, channelId.getJsonPaths()); - } - - /** - * Call the getValueFromJson without a divergent type in the raw json. - * - * @param channelId Channel that value will be detect. - * @param json Raw JsonElement. - * @param converter Converter, to convert the raw JSON value into a proper - * Channel. - * @param jsonPaths Whole JSON path, where the JsonElement for the given channel - * is located. - * @return Value of the last JsonElement by running through the specified JSON - * path. - */ - private Object getValueFromJson(ChannelId channelId, JsonElement json, Function converter, - String... jsonPaths) { - return this.getValueFromJson(channelId, null, json, converter, jsonPaths); - } - - /** - * Get the last JSON element and it's value, by running through the given - * jsonPath. - * - * @param channelId Channel that value will be detect. - * @param divergentTypeInRawJson Divergent type of the value in the depending - * JsonElement. - * @param json Raw JsonElement. - * @param converter Converter, to convert the raw JSON value into a - * proper Channel. - * @param jsonPaths Whole JSON path, where the JsonElement for the - * given channel is located. - * @return Value of the last JsonElement by running through the specified JSON - * path. - */ - private Object getValueFromJson(ChannelId channelId, OpenemsType divergentTypeInRawJson, JsonElement json, - Function converter, String... jsonPaths) { - - var currentJsonElement = json; - // Go through the whole jsonPath of the current channelId - for (var i = 0; i < jsonPaths.length; i++) { - var currentPathMember = jsonPaths[i]; - // System.out.println(currentPathMember); - try { - if (i == jsonPaths.length - 1) { - // - var openemsType = divergentTypeInRawJson == null ? channelId.doc().getType() - : divergentTypeInRawJson; - - // Last path element - var value = this.getJsonElementValue(currentJsonElement, openemsType, jsonPaths[i]); - - // Return the converted value - return converter.apply(value); - } - // Not last path element - currentJsonElement = JsonUtils.getAsJsonObject(currentJsonElement, currentPathMember); - } catch (OpenemsNamedException e) { - return null; - } - } - return null; - } - - /** - * Get Value of the given JsonElement in the required type. - * - * @param jsonElement Element as JSON. - * @param openemsType Required type. - * @param memberName Member name of the JSON Element. - * @return Value in the required type. - * @throws OpenemsNamedException Failed to get the value. - */ - private Object getJsonElementValue(JsonElement jsonElement, OpenemsType openemsType, String memberName) - throws OpenemsNamedException { - final Object value; - - switch (openemsType) { - case BOOLEAN: - value = JsonUtils.getAsInt(jsonElement, memberName) == 1; - break; - case DOUBLE: - value = JsonUtils.getAsDouble(jsonElement, memberName); - break; - case FLOAT: - value = JsonUtils.getAsFloat(jsonElement, memberName); - break; - case INTEGER: - value = JsonUtils.getAsInt(jsonElement, memberName); - break; - case LONG: - value = JsonUtils.getAsLong(jsonElement, memberName); - break; - case SHORT: - value = JsonUtils.getAsShort(jsonElement, memberName); - break; - case STRING: - value = JsonUtils.getAsString(jsonElement, memberName); - break; - default: - value = JsonUtils.getAsString(jsonElement, memberName); - break; - } - return value; - } -} diff --git a/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/HardyBarthWriteHandler.java b/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/HardyBarthWriteHandler.java new file mode 100644 index 00000000000..cde25123e84 --- /dev/null +++ b/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/HardyBarthWriteHandler.java @@ -0,0 +1,31 @@ +package io.openems.edge.evcs.hardybarth; + +import java.util.concurrent.CompletableFuture; + +import io.openems.edge.evcs.api.ManagedEvcs; +import io.openems.edge.evcs.api.WriteHandler; + +public class HardyBarthWriteHandler extends WriteHandler { + + private CompletableFuture applyChargePowerTask = CompletableFuture.completedFuture(null); + + public HardyBarthWriteHandler(ManagedEvcs parent) { + super(parent); + } + + @Override + protected synchronized void applyChargePower(int power) { + if (!this.applyChargePowerTask.isDone()) { + return; + } + this.applyChargePowerTask = CompletableFuture.runAsync(() -> { + super.applyChargePower(power); + }); + } + + @Override + public synchronized void cancelChargePower() { + this.applyChargePowerTask.cancel(true); + } + +} \ No newline at end of file diff --git a/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/translation_de.properties b/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/translation_de.properties new file mode 100644 index 00000000000..8c5792bf010 --- /dev/null +++ b/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/translation_de.properties @@ -0,0 +1 @@ +noMeterAvailable = Keine Zhlerwerte verfgbar. Das Kommunikationskabel des (internen) Zhlers ist mglicherweise lose. \ No newline at end of file diff --git a/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/translation_en.properties b/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/translation_en.properties new file mode 100644 index 00000000000..4263f535f21 --- /dev/null +++ b/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/translation_en.properties @@ -0,0 +1 @@ +noMeterAvailable = No meter values available. The communication cable of the (internal) meter may be loose. \ No newline at end of file diff --git a/io.openems.edge.evcs.hardybarth/test/io/openems/edge/evcs/hardybarth/EvcsHardyBarthImplTest.java b/io.openems.edge.evcs.hardybarth/test/io/openems/edge/evcs/hardybarth/EvcsHardyBarthImplTest.java index 2d44360154f..8ecb6c334bc 100644 --- a/io.openems.edge.evcs.hardybarth/test/io/openems/edge/evcs/hardybarth/EvcsHardyBarthImplTest.java +++ b/io.openems.edge.evcs.hardybarth/test/io/openems/edge/evcs/hardybarth/EvcsHardyBarthImplTest.java @@ -1,23 +1,278 @@ package io.openems.edge.evcs.hardybarth; +import static io.openems.common.types.HttpStatus.OK; +import static io.openems.edge.bridge.http.dummy.DummyBridgeHttpFactory.ofDummyBridge; +import static io.openems.edge.evcs.api.PhaseRotation.L2_L3_L1; +import static io.openems.edge.evcs.api.Phases.THREE_PHASE; +import static io.openems.edge.evcs.api.Status.CHARGING; + import org.junit.Test; +import io.openems.edge.bridge.http.api.HttpResponse; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; +import io.openems.edge.evcs.api.DeprecatedEvcs; +import io.openems.edge.evcs.api.Evcs; +import io.openems.edge.meter.api.ElectricityMeter; public class EvcsHardyBarthImplTest { - private static final String COMPONENT_ID = "evcs0"; - @Test public void test() throws Exception { - new ComponentTest(new EvcsHardyBarthImpl()) // + final var phaseRotation = L2_L3_L1; + var sut = new EvcsHardyBarthImpl(); + var ru = sut.readUtils; + new ComponentTest(sut) // + .addReference("httpBridgeFactory", ofDummyBridge()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("evcs0") // .setIp("192.168.8.101") // .setMaxHwCurrent(32_000) // .setMinHwCurrent(6_000) // - .build()) - .next(new TestCase()); + .setPhaseRotation(phaseRotation).build()) + + .next(new TestCase() // + .onBeforeProcessImage(() -> ru + .handleGetApiCallResponse(new HttpResponse(OK, API_RESPONSE), phaseRotation)) // + .output(EvcsHardyBarth.ChannelId.RAW_EVSE_GRID_CURRENT_LIMIT, 16) // + .output(EvcsHardyBarth.ChannelId.RAW_PHASE_COUNT, 3) // + .output(EvcsHardyBarth.ChannelId.RAW_CHARGE_STATUS_PLUG, "locked") // + .output(EvcsHardyBarth.ChannelId.RAW_CHARGE_STATUS_CONTACTOR, "closed") // + .output(EvcsHardyBarth.ChannelId.RAW_CHARGE_STATUS_PWM, "10.00") // + .output(EvcsHardyBarth.ChannelId.RAW_CHARGE_STATUS_CHARGEPOINT, "C") // + .output(EvcsHardyBarth.ChannelId.RAW_SALIA_CHARGE_MODE, "manual") // + .output(EvcsHardyBarth.ChannelId.RAW_SALIA_CHANGE_METER, null) // + .output(EvcsHardyBarth.ChannelId.RAW_SALIA_AUTHMODE, "free") // + .output(EvcsHardyBarth.ChannelId.RAW_SALIA_FIRMWARESTATE, "idle") // + .output(EvcsHardyBarth.ChannelId.RAW_SALIA_FIRMWAREPROGRESS, "0") // + .output(EvcsHardyBarth.ChannelId.RAW_SALIA_PUBLISH, null) // + .output(EvcsHardyBarth.ChannelId.RAW_SESSION_STATUS_AUTHORIZATION, "") // + .output(EvcsHardyBarth.ChannelId.RAW_SESSION_SLAC_STARTED, null) // + .output(EvcsHardyBarth.ChannelId.RAW_SESSION_AUTHORIZATION_METHOD, null) // + .output(EvcsHardyBarth.ChannelId.RAW_CONTACTOR_HLC_TARGET, "0") // + .output(EvcsHardyBarth.ChannelId.RAW_CONTACTOR_ACTUAL, "1") // + .output(EvcsHardyBarth.ChannelId.RAW_CONTACTOR_TARGET, "1") // + .output(EvcsHardyBarth.ChannelId.RAW_CONTACTOR_ERROR, "0") // + .output(EvcsHardyBarth.ChannelId.RAW_METER_SERIALNUMBER, "21031835") // + .output(EvcsHardyBarth.ChannelId.RAW_METER_TYPE, "klefr") // + .output(EvcsHardyBarth.ChannelId.RAW_METER_AVAILABLE, true) // + .output(EvcsHardyBarth.ChannelId.METER_NOT_AVAILABLE, false) // + .output(EvcsHardyBarth.ChannelId.RAW_ACTIVE_ENERGY_TOTAL, 4658050.0) // + .output(EvcsHardyBarth.ChannelId.RAW_ACTIVE_ENERGY_EXPORT, 0.0) // + .output(EvcsHardyBarth.ChannelId.RAW_EMERGENCY_SHUTDOWN, "0") // + .output(EvcsHardyBarth.ChannelId.RAW_RCD_AVAILABLE, false) // + .output(EvcsHardyBarth.ChannelId.RAW_PLUG_LOCK_STATE_ACTUAL, "1") // + .output(EvcsHardyBarth.ChannelId.RAW_PLUG_LOCK_STATE_TARGET, "1") // + .output(EvcsHardyBarth.ChannelId.RAW_PLUG_LOCK_ERROR, "0") // + .output(EvcsHardyBarth.ChannelId.RAW_CP_STATE, "C") // + .output(EvcsHardyBarth.ChannelId.RAW_DIODE_PRESENT, "1") // + .output(EvcsHardyBarth.ChannelId.RAW_CABLE_CURRENT_LIMIT, "-1") // + .output(EvcsHardyBarth.ChannelId.RAW_VENTILATION_STATE_ACTUAL, "0") // + .output(EvcsHardyBarth.ChannelId.RAW_VENTILATION_STATE_TARGET, null) // + .output(EvcsHardyBarth.ChannelId.RAW_VENTILATION_AVAILABLE, false) // + .output(EvcsHardyBarth.ChannelId.RAW_EV_PRESENT, "1") // + .output(EvcsHardyBarth.ChannelId.RAW_CHARGING, "1") // + .output(EvcsHardyBarth.ChannelId.RAW_RFID_AUTHORIZEREQ, "") // + .output(EvcsHardyBarth.ChannelId.RAW_RFID_AVAILABLE, false) // + .output(EvcsHardyBarth.ChannelId.RAW_GRID_CURRENT_LIMIT, "6") // + .output(EvcsHardyBarth.ChannelId.RAW_SLAC_ERROR, null) // + .output(EvcsHardyBarth.ChannelId.RAW_DEVICE_PRODUCT, "2310007") // + .output(EvcsHardyBarth.ChannelId.RAW_DEVICE_MODELNAME, "Salia PLCC Slave") // + .output(EvcsHardyBarth.ChannelId.RAW_DEVICE_HARDWARE_VERSION, "1.0") // + .output(EvcsHardyBarth.ChannelId.RAW_DEVICE_SOFTWARE_VERSION, "1.50.0") // + .output(EvcsHardyBarth.ChannelId.RAW_DEVICE_VCS_VERSION, "V0R5e") // + .output(EvcsHardyBarth.ChannelId.RAW_DEVICE_HOSTNAME, "salia") // + .output(EvcsHardyBarth.ChannelId.RAW_DEVICE_MAC_ADDRESS, "00:01:87:13:12:34") // + .output(EvcsHardyBarth.ChannelId.RAW_DEVICE_SERIAL, 101249323L) // + .output(EvcsHardyBarth.ChannelId.RAW_DEVICE_UUID, "5491ad62-022a-4356-a32c-00018713102x") // + + .output(Evcs.ChannelId.ENERGY_SESSION, 3460) // + .output(ElectricityMeter.ChannelId.ACTIVE_PRODUCTION_ENERGY, 4658050L) // + .output(ElectricityMeter.ChannelId.ACTIVE_CONSUMPTION_ENERGY, 4658050L) // + .output(Evcs.ChannelId.PHASES, THREE_PHASE) // + .output(Evcs.ChannelId.STATUS, CHARGING) // + .output(DeprecatedEvcs.ChannelId.CHARGE_POWER, 3192) // + .output(ElectricityMeter.ChannelId.ACTIVE_POWER, 3192) // + .output(ElectricityMeter.ChannelId.ACTIVE_POWER_L1, 1044) // + .output(ElectricityMeter.ChannelId.ACTIVE_POWER_L2, 1075) // + .output(ElectricityMeter.ChannelId.ACTIVE_POWER_L3, 1073) // + .output(ElectricityMeter.ChannelId.CURRENT, 14_770) // + .output(ElectricityMeter.ChannelId.CURRENT_L1, 4_770) // + .output(ElectricityMeter.ChannelId.CURRENT_L2, 5_000) // + .output(ElectricityMeter.ChannelId.CURRENT_L3, 5_000) // + .output(ElectricityMeter.ChannelId.VOLTAGE, 216_156) // + .output(ElectricityMeter.ChannelId.VOLTAGE_L1, 218_868) // + .output(ElectricityMeter.ChannelId.VOLTAGE_L2, 215_000) // + .output(ElectricityMeter.ChannelId.VOLTAGE_L3, 214_600) // + ); } + + private static final String API_RESPONSE = """ + { + "device":{ + "product":"2310007", + "modelname":"Salia PLCC Slave", + "hardware_version":"1.0", + "software_version":"1.50.0", + "vcs_version":"V0R5e", + "hostname":"salia", + "mac_address":"00:01:87:13:12:34", + "serial":"101249323", + "uuid":"5491ad62-022a-4356-a32c-00018713102x", + "internal_id":"412009" + }, + "secc":{ + "port0":{ + "ci":{ + "evse":{ + "basic":{ + "grid_current_limit":{ + "actual":"16" + }, + "phase_count":"3", + "physical_current_limit":"16", + "offered_current_limit":"6.0" + }, + "phase":{ + "actual":"3" + } + }, + "charge":{ + "cp":{ + "status":"C" + }, + "plug":{ + "status":"locked" + }, + "contactor":{ + "status":"closed" + }, + "pwm":{ + "status":"10.00" + } + } + }, + "salia":{ + "chargemode":"manual", + "thermal":"52893", + "mem":"392276", + "uptime":" 1:04", + "load":"0.37", + "chargedata":"3813|3192|3.46|", + "authmode":"free", + "firmwarestate":"idle", + "firmwareprogress":"0", + "heartbeat":"off", + "pausecharging":"0" + }, + "session":{ + "authorization_status":"" + }, + "contactor":{ + "state":{ + "hlc_target":"0", + "actual":"1", + "target":"1" + }, + "error":"0" + }, + "metering":{ + "meter":{ + "serialnumber":"21031835", + "type":"klefr", + "available":"1" + }, + "eichrecht_protocol":"none", + "power":{ + "active":{ + "ac":{ + "l1":{ + "actual":"10750" + }, + "l2":{ + "actual":"10730" + }, + "l3":{ + "actual":"10440" + } + } + }, + "active_total":{ + "actual":"31920" + } + }, + "current":{ + "ac":{ + "l1":{ + "actual":"5000" + }, + "l2":{ + "actual":"5000" + }, + "l3":{ + "actual":"4770" + } + } + }, + "energy":{ + "active_total":{ + "actual":"4658050" + }, + "active_export":{ + "actual":"0" + }, + "active_import":{ + "actual":"4658050" + } + } + }, + "emergency_shutdown":"0", + "rcd":{ + "feedback":{ + "available":"1" + }, + "state":{ + "actual":"1" + }, + "recloser":{ + "available":"0" + } + }, + "plug_lock":{ + "state":{ + "actual":"1", + "target":"1" + }, + "error":"0" + }, + "availability":{ + "actual":"operative" + }, + "cp":{ + "pwm_state":{ + "actual":"1" + }, + "state":"C", + "duty_cycle":"10.00" + }, + "rfid":{ + "available":"0", + "authorizereq":"" + }, + "diode_present":"1", + "cable_current_limit":"-1", + "ready_for_slac":"0", + "ev_present":"1", + "ventilation":{ + "state":{ + "actual":"0" + }, + "available":"0" + }, + "charging":"1", + "grid_current_limit":"6" + } + } + } + """; } diff --git a/io.openems.edge.evcs.hardybarth/test/io/openems/edge/evcs/hardybarth/MyConfig.java b/io.openems.edge.evcs.hardybarth/test/io/openems/edge/evcs/hardybarth/MyConfig.java index 98cc8717cff..d3a6ee1c74a 100644 --- a/io.openems.edge.evcs.hardybarth/test/io/openems/edge/evcs/hardybarth/MyConfig.java +++ b/io.openems.edge.evcs.hardybarth/test/io/openems/edge/evcs/hardybarth/MyConfig.java @@ -1,6 +1,7 @@ package io.openems.edge.evcs.hardybarth; import io.openems.common.test.AbstractComponentConfig; +import io.openems.edge.evcs.api.PhaseRotation; @SuppressWarnings("all") public class MyConfig extends AbstractComponentConfig implements Config { @@ -10,6 +11,7 @@ protected static class Builder { private String ip; private int minHwCurrent; private int maxHwCurrent; + private PhaseRotation phaseRotation; private Builder() { } @@ -34,6 +36,11 @@ public Builder setMaxHwCurrent(int maxHwCurrent) { return this; } + public Builder setPhaseRotation(PhaseRotation phaseRotation) { + this.phaseRotation = phaseRotation; + return this; + } + public MyConfig build() { return new MyConfig(this); } @@ -74,4 +81,9 @@ public int minHwCurrent() { public int maxHwCurrent() { return this.builder.maxHwCurrent; } + + @Override + public PhaseRotation phaseRotation() { + return this.builder.phaseRotation; + } } \ No newline at end of file diff --git a/io.openems.edge.evcs.keba.kecontact/.classpath b/io.openems.edge.evcs.keba.kecontact/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.evcs.keba.kecontact/.classpath +++ b/io.openems.edge.evcs.keba.kecontact/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.evcs.keba.kecontact/bnd.bnd b/io.openems.edge.evcs.keba.kecontact/bnd.bnd index c1057f1716f..507c805f4d6 100644 --- a/io.openems.edge.evcs.keba.kecontact/bnd.bnd +++ b/io.openems.edge.evcs.keba.kecontact/bnd.bnd @@ -7,7 +7,8 @@ Bundle-Version: 1.0.0.${tstamp} ${buildpath},\ io.openems.common,\ io.openems.edge.common,\ - io.openems.edge.evcs.api + io.openems.edge.evcs.api,\ + io.openems.edge.meter.api,\ -testpath: \ ${testpath} diff --git a/io.openems.edge.evcs.keba.kecontact/src/io/openems/edge/evcs/keba/kecontact/Config.java b/io.openems.edge.evcs.keba.kecontact/src/io/openems/edge/evcs/keba/kecontact/Config.java index 66e9e587fde..5bed47bdad4 100644 --- a/io.openems.edge.evcs.keba.kecontact/src/io/openems/edge/evcs/keba/kecontact/Config.java +++ b/io.openems.edge.evcs.keba.kecontact/src/io/openems/edge/evcs/keba/kecontact/Config.java @@ -3,6 +3,8 @@ import org.osgi.service.metatype.annotations.AttributeDefinition; import org.osgi.service.metatype.annotations.ObjectClassDefinition; +import io.openems.edge.evcs.api.PhaseRotation; + @ObjectClassDefinition(name = "EVCS KEBA KeContact", // description = "Implements the KEBA KeContact P20/P30 electric vehicle charging station.") @interface Config { @@ -25,6 +27,9 @@ @AttributeDefinition(name = "Minimum power", description = "Minimum current of the Charger in mA.", required = true) int minHwCurrent() default 6000; + @AttributeDefinition(name = "Phase Rotation", description = "Apply standard or rotated wiring") + PhaseRotation phaseRotation() default PhaseRotation.L1_L2_L3; + @AttributeDefinition(name = "Use display?", description = "Activates the KEBA display to show the current power or states.", required = true) boolean useDisplay() default true; diff --git a/io.openems.edge.evcs.keba.kecontact/src/io/openems/edge/evcs/keba/kecontact/EvcsKebaKeContact.java b/io.openems.edge.evcs.keba.kecontact/src/io/openems/edge/evcs/keba/kecontact/EvcsKebaKeContact.java index e392f6cec85..2de80ca9f6b 100644 --- a/io.openems.edge.evcs.keba.kecontact/src/io/openems/edge/evcs/keba/kecontact/EvcsKebaKeContact.java +++ b/io.openems.edge.evcs.keba.kecontact/src/io/openems/edge/evcs/keba/kecontact/EvcsKebaKeContact.java @@ -16,8 +16,10 @@ import io.openems.edge.evcs.api.Evcs; import io.openems.edge.evcs.api.ManagedEvcs; import io.openems.edge.evcs.api.Status; +import io.openems.edge.meter.api.ElectricityMeter; -public interface EvcsKebaKeContact extends ManagedEvcs, Evcs, OpenemsComponent, EventHandler, ModbusSlave { +public interface EvcsKebaKeContact + extends ManagedEvcs, Evcs, ElectricityMeter, OpenemsComponent, EventHandler, ModbusSlave { public static final int UDP_PORT = 7090; @@ -85,42 +87,16 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { /* * Report 3 */ - VOLTAGE_L1(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.VOLT) // - .text("Voltage on L1")), // - VOLTAGE_L2(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.VOLT) // - .text("Voltage on L2")), // - VOLTAGE_L3(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.VOLT) // - .text("Voltage on L3")), // - CURRENT_L1(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.MILLIAMPERE) // - .text("Current on L1")), // - CURRENT_L2(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.MILLIAMPERE) // - .text("Current on L2")), // - CURRENT_L3(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.MILLIAMPERE) // - .text("Current on L3")), // - ACTUAL_POWER(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.MILLIWATT) // - .text("Total real power")), // COS_PHI(Doc.of(OpenemsType.INTEGER) // .unit(Unit.PERCENT) // .text("Power factor")), // - ENERGY_TOTAL(Doc.of(OpenemsType.LONG) // - .unit(Unit.CUMULATED_WATT_HOURS) // - .text("Total power consumption (persistent) without current loading session. " - + "Is summed up after each completed charging session")), // DIP_SWITCH_ERROR_1_3_NOT_SET_FOR_COMM(Doc.of(Level.FAULT) // .debounce(5, Debounce.TRUE_VALUES_IN_A_ROW_TO_SET_TRUE) // .text("Dip-Switch 1.3. for communication must be on")), // DIP_SWITCH_ERROR_2_6_NOT_SET_FOR_STATIC_IP(Doc.of(Level.FAULT) // .debounce(5, Debounce.TRUE_VALUES_IN_A_ROW_TO_SET_TRUE) // .text("A static ip is configured. The Dip-Switch 2.6. must be on")), // - DIP_SWITCH_ERROR_2_6_SET_FOR_DYNAMIC_IP(Doc.of(Level.FAULT) // - .debounce(5, Debounce.TRUE_VALUES_IN_A_ROW_TO_SET_TRUE) // + DIP_SWITCH_ERROR_2_6_SET_FOR_DYNAMIC_IP(Doc.of(OpenemsType.BOOLEAN) // .text("A dynamic ip is configured. Either the Dip-Switch 2.6. must be off or a static ip has to be configured")), // DIP_SWITCH_INFO_2_5_SET_FOR_MASTER_SLAVE_COMM(Doc.of(Level.INFO) // .debounce(5, Debounce.TRUE_VALUES_IN_A_ROW_TO_SET_TRUE) // @@ -180,19 +156,10 @@ private ModbusSlaveNatureTable getModbusSlaveNatureTable(AccessMode accessMode) .channel(72, EvcsKebaKeContact.ChannelId.CURR_FAILSAFE, ModbusType.UINT16) .channel(73, EvcsKebaKeContact.ChannelId.TIMEOUT_FAILSAFE, ModbusType.UINT16) .channel(74, EvcsKebaKeContact.ChannelId.CURR_TIMER, ModbusType.UINT16) - .channel(75, EvcsKebaKeContact.ChannelId.TIMEOUT_CT, ModbusType.UINT16).uint16Reserved(76) + .channel(75, EvcsKebaKeContact.ChannelId.TIMEOUT_CT, ModbusType.UINT16) // + .uint16Reserved(76) // .channel(77, EvcsKebaKeContact.ChannelId.OUTPUT, ModbusType.UINT16) - .channel(78, EvcsKebaKeContact.ChannelId.INPUT, ModbusType.UINT16) - - // Report 3 - .channel(79, EvcsKebaKeContact.ChannelId.VOLTAGE_L1, ModbusType.UINT16) - .channel(80, EvcsKebaKeContact.ChannelId.VOLTAGE_L2, ModbusType.UINT16) - .channel(81, EvcsKebaKeContact.ChannelId.VOLTAGE_L3, ModbusType.UINT16) - .channel(82, EvcsKebaKeContact.ChannelId.CURRENT_L1, ModbusType.UINT16) - .channel(83, EvcsKebaKeContact.ChannelId.CURRENT_L2, ModbusType.UINT16) - .channel(84, EvcsKebaKeContact.ChannelId.CURRENT_L3, ModbusType.UINT16) - .channel(85, EvcsKebaKeContact.ChannelId.ACTUAL_POWER, ModbusType.UINT16) - .channel(86, EvcsKebaKeContact.ChannelId.COS_PHI, ModbusType.UINT16).uint16Reserved(87) - .channel(88, EvcsKebaKeContact.ChannelId.ENERGY_TOTAL, ModbusType.UINT16).build(); + .channel(78, EvcsKebaKeContact.ChannelId.INPUT, ModbusType.UINT16) // + .build(); } } diff --git a/io.openems.edge.evcs.keba.kecontact/src/io/openems/edge/evcs/keba/kecontact/EvcsKebaKeContactImpl.java b/io.openems.edge.evcs.keba.kecontact/src/io/openems/edge/evcs/keba/kecontact/EvcsKebaKeContactImpl.java index c036301b188..3df576d33cc 100644 --- a/io.openems.edge.evcs.keba.kecontact/src/io/openems/edge/evcs/keba/kecontact/EvcsKebaKeContactImpl.java +++ b/io.openems.edge.evcs.keba.kecontact/src/io/openems/edge/evcs/keba/kecontact/EvcsKebaKeContactImpl.java @@ -23,17 +23,20 @@ import org.slf4j.LoggerFactory; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.types.MeterType; import io.openems.edge.common.channel.Channel; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.event.EdgeEventConstants; import io.openems.edge.common.modbusslave.ModbusSlave; import io.openems.edge.evcs.api.AbstractManagedEvcsComponent; import io.openems.edge.evcs.api.ChargingType; +import io.openems.edge.evcs.api.DeprecatedEvcs; import io.openems.edge.evcs.api.Evcs; import io.openems.edge.evcs.api.EvcsPower; import io.openems.edge.evcs.api.ManagedEvcs; import io.openems.edge.evcs.api.Phases; import io.openems.edge.evcs.keba.kecontact.core.EvcsKebaKeContactCore; +import io.openems.edge.meter.api.ElectricityMeter; @Designate(ocd = Config.class, factory = true) @Component(// @@ -44,12 +47,13 @@ @EventTopics({ // EdgeEventConstants.TOPIC_CYCLE_EXECUTE_WRITE, // }) -public class EvcsKebaKeContactImpl extends AbstractManagedEvcsComponent - implements EvcsKebaKeContact, ManagedEvcs, Evcs, OpenemsComponent, EventHandler, ModbusSlave { +public class EvcsKebaKeContactImpl extends AbstractManagedEvcsComponent implements EvcsKebaKeContact, ManagedEvcs, Evcs, + DeprecatedEvcs, ElectricityMeter, OpenemsComponent, EventHandler, ModbusSlave { + + protected final ReadHandler readHandler = new ReadHandler(this); private final Logger log = LoggerFactory.getLogger(EvcsKebaKeContactImpl.class); private final ReadWorker readWorker = new ReadWorker(this); - private final ReadHandler readHandler = new ReadHandler(this); @Reference private EvcsPower evcsPower; @@ -65,10 +69,21 @@ public class EvcsKebaKeContactImpl extends AbstractManagedEvcsComponent public EvcsKebaKeContactImpl() { super(// OpenemsComponent.ChannelId.values(), // + ElectricityMeter.ChannelId.values(), // ManagedEvcs.ChannelId.values(), // Evcs.ChannelId.values(), // + DeprecatedEvcs.ChannelId.values(), // EvcsKebaKeContact.ChannelId.values() // ); + DeprecatedEvcs.copyToDeprecatedEvcsChannels(this); + ElectricityMeter.calculateSumCurrentFromPhases(this); + ElectricityMeter.calculateAverageVoltageFromPhases(this); + + // Set ReactivePower defaults + this._setReactivePower(0); + this._setReactivePowerL1(0); + this._setReactivePowerL2(0); + this._setReactivePowerL3(0); } @Activate @@ -127,6 +142,11 @@ public void handleEvent(Event event) { } } + @Override + public MeterType getMeterType() { + return MeterType.MANAGED_CONSUMPTION_METERED; + } + /** * Send UDP message to KEBA KeContact. Returns true if sent successfully * diff --git a/io.openems.edge.evcs.keba.kecontact/src/io/openems/edge/evcs/keba/kecontact/ReadHandler.java b/io.openems.edge.evcs.keba.kecontact/src/io/openems/edge/evcs/keba/kecontact/ReadHandler.java index 3fafa14550a..69208c18c90 100644 --- a/io.openems.edge.evcs.keba.kecontact/src/io/openems/edge/evcs/keba/kecontact/ReadHandler.java +++ b/io.openems.edge.evcs.keba.kecontact/src/io/openems/edge/evcs/keba/kecontact/ReadHandler.java @@ -1,19 +1,27 @@ package io.openems.edge.evcs.keba.kecontact; +import static io.openems.common.utils.JsonUtils.getAsOptionalInt; +import static io.openems.common.utils.JsonUtils.getAsOptionalLong; +import static io.openems.common.utils.JsonUtils.getAsOptionalString; +import static io.openems.edge.evcs.api.Evcs.evaluatePhaseCount; +import static io.openems.edge.evcs.api.Phases.THREE_PHASE; +import static io.openems.edge.evcs.api.Status.CHARGING; +import static java.lang.Math.round; + import java.math.BigInteger; import java.util.function.Consumer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.gson.JsonElement; import com.google.gson.JsonObject; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.utils.JsonUtils; import io.openems.edge.common.channel.Channel; +import io.openems.edge.common.channel.ChannelId; import io.openems.edge.evcs.api.Evcs; -import io.openems.edge.evcs.api.Phases; +import io.openems.edge.evcs.api.PhaseRotation.RotatedPhases; import io.openems.edge.evcs.api.Status; /** @@ -34,240 +42,239 @@ public ReadHandler(EvcsKebaKeContactImpl parent) { @Override public void accept(String message) { + final var keba = this.parent; if (message.startsWith("TCH-OK")) { this.log.debug("KEBA confirmed reception of command: TCH-OK"); - this.parent.triggerQuery(); + keba.triggerQuery(); + return; + } - } else if (message.startsWith("TCH-ERR")) { + if (message.startsWith("TCH-ERR")) { this.log.warn("KEBA reported command error: TCH-ERR"); - this.parent.triggerQuery(); + keba.triggerQuery(); + return; + } - } else { - JsonElement jsonMessageElement; - try { - jsonMessageElement = JsonUtils.parse(message); - } catch (OpenemsNamedException e) { - this.log.error("Error while parsing KEBA message: " + e.getMessage()); - return; + keba.logInfoInDebugmode(this.log, message); + + // Parse JsonObject + final JsonObject j; + try { + j = JsonUtils.parseToJsonObject(message); + } catch (OpenemsNamedException e) { + this.log.error("Error while parsing KEBA message: " + e.getMessage()); + return; + } + + switch (getAsOptionalString(j, "ID").orElse("")) { + /* + * report 1 + */ + case "1" -> { + this.receiveReport1 = true; + this.setString(EvcsKebaKeContact.ChannelId.SERIAL, j, "Serial"); + this.setString(EvcsKebaKeContact.ChannelId.FIRMWARE, j, "Firmware"); + this.setInt(EvcsKebaKeContact.ChannelId.COM_MODULE, j, "COM-module"); + + // Dip-Switches + var dipSwitch1 = getAsOptionalString(j, "DIP-Sw1"); + var dipSwitch2 = getAsOptionalString(j, "DIP-Sw2"); + + if (dipSwitch1.isPresent() && dipSwitch2.isPresent()) { + this.checkDipSwitchSettings(dipSwitch1.get(), dipSwitch2.get()); + } + + // Product information + var product = getAsOptionalString(j, "Product"); + keba.channel(EvcsKebaKeContact.ChannelId.PRODUCT).setNextValue(product.orElse(null)); + if (product.isPresent()) { + this.checkProductInformation(product.get()); } - this.parent.logInfoInDebugmode(this.log, message); - - var jsonMessage = jsonMessageElement.getAsJsonObject(); - // JsonUtils.prettyPrint(jMessage); - var idOpt = JsonUtils.getAsOptionalString(jsonMessage, "ID"); - if (idOpt.isPresent()) { - // message with ID - var id = idOpt.get(); - if (id.equals("1")) { - /* - * Reply to report 1 - */ - this.receiveReport1 = true; - this.setString(EvcsKebaKeContact.ChannelId.SERIAL, jsonMessage, "Serial"); - this.setString(EvcsKebaKeContact.ChannelId.FIRMWARE, jsonMessage, "Firmware"); - this.setInt(EvcsKebaKeContact.ChannelId.COM_MODULE, jsonMessage, "COM-module"); - - // Dip-Switches - var dipSwitch1 = JsonUtils.getAsOptionalString(jsonMessage, "DIP-Sw1"); - var dipSwitch2 = JsonUtils.getAsOptionalString(jsonMessage, "DIP-Sw2"); - - if (dipSwitch1.isPresent() && dipSwitch2.isPresent()) { - this.checkDipSwitchSettings(dipSwitch1.get(), dipSwitch2.get()); - } - - // Product information - var product = JsonUtils.getAsOptionalString(jsonMessage, "Product"); - if (product.isPresent()) { - this.parent.channel(EvcsKebaKeContact.ChannelId.PRODUCT).setNextValue(product.get()); - this.checkProductInformation(product.get()); - } - - } else if (id.equals("2")) { - /* - * Reply to report 2 - */ - this.receiveReport2 = true; - this.setInt(EvcsKebaKeContact.ChannelId.STATUS_KEBA, jsonMessage, "State"); - - // Value "setenergy" not used, because it is reset by the currtime 0 1 command - - // Set Evcs status - Channel stateChannel = this.parent.channel(EvcsKebaKeContact.ChannelId.STATUS_KEBA); - Channel plugChannel = this.parent.channel(EvcsKebaKeContact.ChannelId.PLUG); - - Plug plug = plugChannel.value().asEnum(); - Status status = stateChannel.value().asEnum(); - if (plug.equals(Plug.PLUGGED_ON_EVCS_AND_ON_EV_AND_LOCKED)) { - - // Charging is rejected (by the Software) if the plug is connected but the EVCS - // still not ready for charging. - if (status.equals(Status.NOT_READY_FOR_CHARGING)) { - status = Status.CHARGING_REJECTED; - } - - // Charging is Finished if 'Plug' is connected, State was charging or already - // finished and the EVCS is still ready for charging. - var evcsStatus = this.parent.getStatus(); - switch (evcsStatus) { - case CHARGING_REJECTED: - case ENERGY_LIMIT_REACHED: - case ERROR: - case NOT_READY_FOR_CHARGING: - case STARTING: - case UNDEFINED: - break; - case READY_FOR_CHARGING: - case CHARGING: - case CHARGING_FINISHED: - if (status.equals(Status.READY_FOR_CHARGING) - && this.parent.getSetChargePowerLimit().orElse(0) > 0) { - status = Status.CHARGING_FINISHED; - } - break; - } - - /* - * Check if the maximum energy limit is reached, informs the user and sets the - * status - */ - int limit = this.parent.getSetEnergyLimit().orElse(0); - int energy = this.parent.getEnergySession().orElse(0); - if (energy >= limit && limit != 0) { - status = Status.ENERGY_LIMIT_REACHED; - } - } else { - // Plug not fully connected - status = Status.NOT_READY_FOR_CHARGING; - } - - this.parent._setStatus(status); - var errorState = status == Status.ERROR == true; - this.parent.channel(EvcsKebaKeContact.ChannelId.CHARGINGSTATION_STATE_ERROR) - .setNextValue(errorState); - - this.setInt(EvcsKebaKeContact.ChannelId.ERROR_1, jsonMessage, "Error1"); - this.setInt(EvcsKebaKeContact.ChannelId.ERROR_2, jsonMessage, "Error2"); - this.setInt(EvcsKebaKeContact.ChannelId.PLUG, jsonMessage, "Plug"); - this.setBoolean(EvcsKebaKeContact.ChannelId.ENABLE_SYS, jsonMessage, "Enable sys"); - this.setBoolean(EvcsKebaKeContact.ChannelId.ENABLE_USER, jsonMessage, "Enable user"); - this.setInt(EvcsKebaKeContact.ChannelId.MAX_CURR_PERCENT, jsonMessage, "Max curr %"); - this.setInt(EvcsKebaKeContact.ChannelId.CURR_FAILSAFE, jsonMessage, "Curr FS"); - this.setInt(EvcsKebaKeContact.ChannelId.TIMEOUT_FAILSAFE, jsonMessage, "Tmo FS"); - this.setInt(EvcsKebaKeContact.ChannelId.CURR_TIMER, jsonMessage, "Curr timer"); - this.setInt(EvcsKebaKeContact.ChannelId.TIMEOUT_CT, jsonMessage, "Tmo CT"); - this.setBoolean(EvcsKebaKeContact.ChannelId.OUTPUT, jsonMessage, "Output"); - this.setBoolean(EvcsKebaKeContact.ChannelId.INPUT, jsonMessage, "Input"); - this.setInt(EvcsKebaKeContact.ChannelId.MAX_CURR, jsonMessage, "Curr HW"); - this.setInt(EvcsKebaKeContact.ChannelId.CURR_USER, jsonMessage, "Curr user"); - - } else if (id.equals("3")) { - /* - * Reply to report 3 - */ - this.receiveReport3 = true; - this.setInt(EvcsKebaKeContact.ChannelId.VOLTAGE_L1, jsonMessage, "U1"); - this.setInt(EvcsKebaKeContact.ChannelId.VOLTAGE_L2, jsonMessage, "U2"); - this.setInt(EvcsKebaKeContact.ChannelId.VOLTAGE_L3, jsonMessage, "U3"); - this.setInt(EvcsKebaKeContact.ChannelId.CURRENT_L1, jsonMessage, "I1"); - this.setInt(EvcsKebaKeContact.ChannelId.CURRENT_L2, jsonMessage, "I2"); - this.setInt(EvcsKebaKeContact.ChannelId.CURRENT_L3, jsonMessage, "I3"); - this.setInt(EvcsKebaKeContact.ChannelId.ACTUAL_POWER, jsonMessage, "P"); - this.setInt(EvcsKebaKeContact.ChannelId.COS_PHI, jsonMessage, "PF"); - - long totalEnergy = Math - .round(JsonUtils.getAsOptionalLong(jsonMessage, "E total").orElse(0L) * 0.1F); - this.parent.channel(EvcsKebaKeContact.ChannelId.ENERGY_TOTAL).setNextValue(totalEnergy); - this.parent._setActiveConsumptionEnergy(totalEnergy); - - // Set the count of the Phases that are currently used - Channel currentL1 = this.parent.channel(EvcsKebaKeContact.ChannelId.CURRENT_L1); - Channel currentL2 = this.parent.channel(EvcsKebaKeContact.ChannelId.CURRENT_L2); - Channel currentL3 = this.parent.channel(EvcsKebaKeContact.ChannelId.CURRENT_L3); - var currentSum = currentL1.getNextValue().orElse(0) + currentL2.getNextValue().orElse(0) - + currentL3.getNextValue().orElse(0); - - if (currentSum > 300) { - - this.parent._setStatus(Status.CHARGING); - var phases = 0; - - if (currentL1.getNextValue().orElse(0) >= 100) { - phases += 1; - } - if (currentL2.getNextValue().orElse(0) >= 100) { - phases += 1; - } - if (currentL3.getNextValue().orElse(0) >= 100) { - phases += 1; - } - this.parent._setPhases(phases); - - this.parent.logInfoInDebugmode(this.log, "Used phases: " + phases); - } - - /* - * Set FIXED_MAXIMUM_HARDWARE_POWER of Evcs - this is setting internally the - * dynamically calculated MAXIMUM_HARDWARE_POWER including the current used - * phases. - */ - Channel maxDipSwitchLimitChannel = this.parent - .channel(EvcsKebaKeContact.ChannelId.DIP_SWITCH_MAX_HW); - int maxDipSwitchPowerLimit = Math.round( - maxDipSwitchLimitChannel.value().orElse(Evcs.DEFAULT_MAXIMUM_HARDWARE_CURRENT) / 1000f) - * Evcs.DEFAULT_VOLTAGE * Phases.THREE_PHASE.getValue(); - - // Minimum of hardware setting and component configuration will be set. - int maximumHardwareLimit = Math.min(maxDipSwitchPowerLimit, - this.parent.getConfiguredMaximumHardwarePower()); - - this.parent._setFixedMaximumHardwarePower(maximumHardwareLimit); - - /* - * Set FIXED_MINIMUM_HARDWARE_POWER of Evcs - this is setting internally the - * dynamically calculated MINIMUM_HARDWARE_POWER including the current used - * phases. - */ - this.parent._setFixedMinimumHardwarePower(this.parent.getConfiguredMinimumHardwarePower()); - - /* - * Set CHARGE_POWER of Evcs - */ - var powerMw = JsonUtils.getAsOptionalInt(jsonMessage, "P"); // in [mW] - Integer power = null; - if (powerMw.isPresent()) { - power = powerMw.get() / 1000; // convert to [W] - } - this.parent.channel(Evcs.ChannelId.CHARGE_POWER).setNextValue(power); - - /* - * Set ENERGY_SESSION of Evcs - */ - this.parent.channel(Evcs.ChannelId.ENERGY_SESSION) - .setNextValue(JsonUtils.getAsOptionalInt(jsonMessage, "E pres").orElse(0) * 0.1); + } + + /* + * report 2 + */ + case "2" -> { + this.receiveReport2 = true; + this.setInt(EvcsKebaKeContact.ChannelId.STATUS_KEBA, j, "State"); + + // Value "setenergy" not used, because it is reset by the currtime 0 1 command + + // Set Evcs status + Channel stateChannel = keba.channel(EvcsKebaKeContact.ChannelId.STATUS_KEBA); + Channel plugChannel = keba.channel(EvcsKebaKeContact.ChannelId.PLUG); + + Plug plug = plugChannel.value().asEnum(); + Status status = stateChannel.value().asEnum(); + if (plug.equals(Plug.PLUGGED_ON_EVCS_AND_ON_EV_AND_LOCKED)) { + + // Charging is rejected (by the Software) if the plug is connected but the EVCS + // still not ready for charging. + if (status.equals(Status.NOT_READY_FOR_CHARGING)) { + status = Status.CHARGING_REJECTED; } - } else { /* - * message without ID -> UDP broadcast + * Check if the maximum energy limit is reached, informs the user and sets the + * status */ - if (jsonMessage.has("State")) { - this.setInt(EvcsKebaKeContact.ChannelId.STATUS_KEBA, jsonMessage, "State"); - } - if (jsonMessage.has("Plug")) { - this.setInt(EvcsKebaKeContact.ChannelId.PLUG, jsonMessage, "Plug"); - } - if (jsonMessage.has("Input")) { - this.setBoolean(EvcsKebaKeContact.ChannelId.INPUT, jsonMessage, "Input"); - } - if (jsonMessage.has("Enable sys")) { - this.setBoolean(EvcsKebaKeContact.ChannelId.ENABLE_SYS, jsonMessage, "Enable sys"); - } - if (jsonMessage.has("E pres")) { - this.parent.channel(Evcs.ChannelId.ENERGY_SESSION) - .setNextValue(JsonUtils.getAsOptionalInt(jsonMessage, "E pres").orElse(0) * 0.1); + int limit = keba.getSetEnergyLimit().orElse(0); + int energy = keba.getEnergySession().orElse(0); + if (energy >= limit && limit != 0) { + status = Status.ENERGY_LIMIT_REACHED; } + } else { + // Plug not fully connected + status = Status.NOT_READY_FOR_CHARGING; + } + + keba._setStatus(status); + var errorState = status == Status.ERROR == true; + keba.channel(EvcsKebaKeContact.ChannelId.CHARGINGSTATION_STATE_ERROR).setNextValue(errorState); + + this.setInt(EvcsKebaKeContact.ChannelId.ERROR_1, j, "Error1"); + this.setInt(EvcsKebaKeContact.ChannelId.ERROR_2, j, "Error2"); + this.setInt(EvcsKebaKeContact.ChannelId.PLUG, j, "Plug"); + this.setBoolean(EvcsKebaKeContact.ChannelId.ENABLE_SYS, j, "Enable sys"); + this.setBoolean(EvcsKebaKeContact.ChannelId.ENABLE_USER, j, "Enable user"); + this.setInt(EvcsKebaKeContact.ChannelId.MAX_CURR_PERCENT, j, "Max curr %"); + this.setInt(EvcsKebaKeContact.ChannelId.CURR_FAILSAFE, j, "Curr FS"); + this.setInt(EvcsKebaKeContact.ChannelId.TIMEOUT_FAILSAFE, j, "Tmo FS"); + this.setInt(EvcsKebaKeContact.ChannelId.CURR_TIMER, j, "Curr timer"); + this.setInt(EvcsKebaKeContact.ChannelId.TIMEOUT_CT, j, "Tmo CT"); + this.setBoolean(EvcsKebaKeContact.ChannelId.OUTPUT, j, "Output"); + this.setBoolean(EvcsKebaKeContact.ChannelId.INPUT, j, "Input"); + this.setInt(EvcsKebaKeContact.ChannelId.MAX_CURR, j, "Curr HW"); + this.setInt(EvcsKebaKeContact.ChannelId.CURR_USER, j, "Curr user"); + } + + /* + * report 3 + */ + case "3" -> { + /* + * Reply to report 3 + */ + this.receiveReport3 = true; + + // Voltage + final var voltageL1 = getAsOptionalInt(j, "U1").map(v -> v != 0 ? v * 1000 : null).orElse(null); + final var voltageL2 = getAsOptionalInt(j, "U2").map(v -> v != 0 ? v * 1000 : null).orElse(null); + final var voltageL3 = getAsOptionalInt(j, "U3").map(v -> v != 0 ? v * 1000 : null).orElse(null); + + // Current + final var currentL1 = getAsOptionalInt(j, "I1").orElse(0).intValue(); + final var currentL2 = getAsOptionalInt(j, "I2").orElse(0).intValue(); + final var currentL3 = getAsOptionalInt(j, "I3").orElse(0).intValue(); + + // Power + final var activePower = getAsOptionalInt(j, "P") // + .map(p -> p / 1000) // convert [mW] to [W] + .orElse(null); + keba._setActivePower(activePower); + + // Round power per phase and apply rotated phases + var appp = ActivePowerPerPhase.from(activePower, // + voltageL1, currentL1, voltageL2, currentL2, voltageL3, currentL3); + var rp = RotatedPhases.from(keba.config.phaseRotation(), // + voltageL1, currentL1, appp.activePowerL1, // + voltageL2, currentL2, appp.activePowerL2, // + voltageL3, currentL3, appp.activePowerL3); + keba._setVoltageL1(rp.voltageL1()); + keba._setVoltageL2(rp.voltageL2()); + keba._setVoltageL3(rp.voltageL3()); + keba._setCurrentL1(rp.currentL1()); + keba._setCurrentL2(rp.currentL2()); + keba._setCurrentL3(rp.currentL3()); + keba._setActivePowerL1(rp.activePowerL1()); + keba._setActivePowerL2(rp.activePowerL2()); + keba._setActivePowerL3(rp.activePowerL3()); + + // Energy + keba._setActiveProductionEnergy(// + getAsOptionalLong(j, "E total") // + .map(e -> round(e * 0.1F)) // + .orElse(null)); + keba._setEnergySession(// + getAsOptionalInt(j, "E pres") // + .map(e -> round(e * 0.1F)) // + .orElse(null)); + + // TODO use COS_PHI to calculate ReactivePower + this.setInt(EvcsKebaKeContact.ChannelId.COS_PHI, j, "PF"); + + final var phases = evaluatePhaseCount(appp.activePowerL1, appp.activePowerL2, appp.activePowerL3); + keba._setPhases(phases); + if (phases != null) { + keba.logInfoInDebugmode(this.log, "Used phases: " + phases); + keba._setStatus(CHARGING); + } + + /* + * Set FIXED_MAXIMUM_HARDWARE_POWER of Evcs - this is setting internally the + * dynamically calculated MAXIMUM_HARDWARE_POWER including the current used + * phases. + */ + Channel maxDipSwitchLimitChannel = keba.channel(EvcsKebaKeContact.ChannelId.DIP_SWITCH_MAX_HW); + int maxDipSwitchPowerLimit = round(maxDipSwitchLimitChannel.value() // + .orElse(Evcs.DEFAULT_MAXIMUM_HARDWARE_CURRENT) / 1000f) * Evcs.DEFAULT_VOLTAGE + * THREE_PHASE.getValue(); + + // Minimum of hardware setting and component configuration will be set. + int maximumHardwareLimit = Math.min(maxDipSwitchPowerLimit, keba.getConfiguredMaximumHardwarePower()); + + keba._setFixedMaximumHardwarePower(maximumHardwareLimit); + + /* + * Set FIXED_MINIMUM_HARDWARE_POWER of Evcs - this is setting internally the + * dynamically calculated MINIMUM_HARDWARE_POWER including the current used + * phases. + */ + keba._setFixedMinimumHardwarePower(keba.getConfiguredMinimumHardwarePower()); + } + + /* + * message without ID -> UDP broadcast + */ + default -> { + if (j.has("State")) { + this.setInt(EvcsKebaKeContact.ChannelId.STATUS_KEBA, j, "State"); + } + if (j.has("Plug")) { + this.setInt(EvcsKebaKeContact.ChannelId.PLUG, j, "Plug"); + } + if (j.has("Input")) { + this.setBoolean(EvcsKebaKeContact.ChannelId.INPUT, j, "Input"); + } + if (j.has("Enable sys")) { + this.setBoolean(EvcsKebaKeContact.ChannelId.ENABLE_SYS, j, "Enable sys"); + } + if (j.has("E pres")) { + keba._setEnergySession(// + getAsOptionalInt(j, "E pres") // + .map(e -> round(e * 0.1F)) // + .orElse(null)); + } + } + } + } + + public record ActivePowerPerPhase(Integer activePowerL1, Integer activePowerL2, Integer activePowerL3) { + protected static ActivePowerPerPhase from(Integer activePowerSum, Integer voltageL1, int currentL1, + Integer voltageL2, int currentL2, Integer voltageL3, int currentL3) { + if (activePowerSum == null) { + return new ActivePowerPerPhase(null, null, null); } + + var pL1 = voltageL1 != null ? voltageL1 / 1000 * currentL1 : 0; + var pL2 = voltageL2 != null ? voltageL2 / 1000 * currentL2 : 0; + var pL3 = voltageL3 != null ? voltageL3 / 1000 * currentL3 : 0; + var pSum = pL1 + pL2 + pL3; + var factor = activePowerSum / (float) pSum; // distribute power to match sum + + return new ActivePowerPerPhase(round(pL1 * factor), round(pL2 * factor), round(pL3 * factor)); } } @@ -318,29 +325,15 @@ private void checkDipSwitchSettings(String dipSwitch1, String dipSwitch2) { this.setnextStateChannelValue(EvcsKebaKeContact.ChannelId.DIP_SWITCH_INFO_2_8_SET_FOR_INSTALLATION, setState); // Set Channel for the configured maximum limit in mA - Integer hwLimit = null; - var hwLimitDips = dipSwitch1.substring(5); - - switch (hwLimitDips) { - case "000": - hwLimit = 10_000; - break; - case "100": - hwLimit = 13_000; - break; - case "010": - hwLimit = 16_000; - break; - case "110": - hwLimit = 20_000; - break; - case "001": - hwLimit = 25_000; - break; - case "101": - hwLimit = 32_000; - break; - } + var hwLimit = switch (dipSwitch1.substring(5)) { + case "000" -> 10_000; + case "100" -> 13_000; + case "010" -> 16_000; + case "110" -> 20_000; + case "001" -> 25_000; + case "101" -> 32_000; + default -> null; + }; this.parent.channel(EvcsKebaKeContact.ChannelId.DIP_SWITCH_MAX_HW).setNextValue(hwLimit); } @@ -383,20 +376,20 @@ protected static String hexStringToBinaryString(String dipSwitches) { return binaryString; } - private void set(EvcsKebaKeContact.ChannelId channelId, Object value) { + private void set(ChannelId channelId, Object value) { this.parent.channel(channelId).setNextValue(value); } - private void setString(EvcsKebaKeContact.ChannelId channelId, JsonObject jMessage, String name) { - this.set(channelId, JsonUtils.getAsOptionalString(jMessage, name).orElse(null)); + private void setString(ChannelId channelId, JsonObject jMessage, String name) { + this.set(channelId, getAsOptionalString(jMessage, name).orElse(null)); } - private void setInt(EvcsKebaKeContact.ChannelId channelId, JsonObject jMessage, String name) { - this.set(channelId, JsonUtils.getAsOptionalInt(jMessage, name).orElse(null)); + private void setInt(ChannelId channelId, JsonObject jMessage, String name) { + this.set(channelId, getAsOptionalInt(jMessage, name).orElse(null)); } - private void setBoolean(EvcsKebaKeContact.ChannelId channelId, JsonObject jMessage, String name) { - var enableSysOpt = JsonUtils.getAsOptionalInt(jMessage, name); + private void setBoolean(ChannelId channelId, JsonObject jMessage, String name) { + var enableSysOpt = getAsOptionalInt(jMessage, name); if (enableSysOpt.isPresent()) { this.set(channelId, enableSysOpt.get() == 1); } else { diff --git a/io.openems.edge.evcs.keba.kecontact/src/io/openems/edge/evcs/keba/kecontact/ReadWorker.java b/io.openems.edge.evcs.keba.kecontact/src/io/openems/edge/evcs/keba/kecontact/ReadWorker.java index 14c23d0e9c7..e5c078d0a27 100644 --- a/io.openems.edge.evcs.keba.kecontact/src/io/openems/edge/evcs/keba/kecontact/ReadWorker.java +++ b/io.openems.edge.evcs.keba.kecontact/src/io/openems/edge/evcs/keba/kecontact/ReadWorker.java @@ -80,27 +80,46 @@ protected void forever() throws InterruptedException { @Override protected int getCycleTime() { // get minimum required time till next report - var now = LocalDateTime.now(); - if (this.lastReport1.isBefore(now.minusSeconds(Report.REPORT1.getRequestSeconds())) - || this.lastReport2.isBefore(now.minusSeconds(Report.REPORT2.getRequestSeconds())) - || this.lastReport3.isBefore(now.minusSeconds(Report.REPORT3.getRequestSeconds()))) { + return getCycleTimeLogic(this.lastReport1, this.lastReport2, this.lastReport3, LocalDateTime.now()); + } + + /** + * Calculates the cycletime for given dateTimes. + * + * @param lastReport1 last time report 1 was read + * @param lastReport2 last time report 2 was read + * @param lastReport3 last time report 3 was read + * @param now current time + * @return the time until the next cycle + */ + public static int getCycleTimeLogic(LocalDateTime lastReport1, LocalDateTime lastReport2, LocalDateTime lastReport3, + LocalDateTime now) { + if (lastReport1.isBefore(now.minusSeconds(Report.REPORT1.getRequestSeconds())) + || lastReport2.isBefore(now.minusSeconds(Report.REPORT2.getRequestSeconds())) + || lastReport3.isBefore(now.minusSeconds(Report.REPORT3.getRequestSeconds()))) { return 0; } - var tillReport1 = ChronoUnit.MILLIS.between(now.minusSeconds(Report.REPORT1.getRequestSeconds()), - this.lastReport1); - var tillReport2 = ChronoUnit.MILLIS.between(now.minusSeconds(Report.REPORT2.getRequestSeconds()), - this.lastReport2); - var tillReport3 = ChronoUnit.MILLIS.between(now.minusSeconds(Report.REPORT3.getRequestSeconds()), - this.lastReport3); - var min = Math.min(Math.min(tillReport1, tillReport2), tillReport3); - if (min < 0) { + try { + var tillReport1 = ChronoUnit.MILLIS.between(now.minusSeconds(Report.REPORT1.getRequestSeconds()), + lastReport1); + var tillReport2 = ChronoUnit.MILLIS.between(now.minusSeconds(Report.REPORT2.getRequestSeconds()), + lastReport2); + var tillReport3 = ChronoUnit.MILLIS.between(now.minusSeconds(Report.REPORT3.getRequestSeconds()), + lastReport3); + var min = Math.min(Math.min(tillReport1, tillReport2), tillReport3); + if (min < 0) { + return 0; + } + if (min > Integer.MAX_VALUE) { + return Integer.MAX_VALUE; + } else { + return (int) min; + } + } catch (ArithmeticException e) { + // if difference in tillReportX is too large a longOverflow might be thrown return 0; } - if (min > Integer.MAX_VALUE) { - return Integer.MAX_VALUE; - } else { - return (int) min; - } + } @Override diff --git a/io.openems.edge.evcs.keba.kecontact/test/io/openems/edge/evcs/keba/kecontact/EvcsKebaKeContactImplTest.java b/io.openems.edge.evcs.keba.kecontact/test/io/openems/edge/evcs/keba/kecontact/EvcsKebaKeContactImplTest.java index 68b6c75ecd2..db60737ebb8 100644 --- a/io.openems.edge.evcs.keba.kecontact/test/io/openems/edge/evcs/keba/kecontact/EvcsKebaKeContactImplTest.java +++ b/io.openems.edge.evcs.keba.kecontact/test/io/openems/edge/evcs/keba/kecontact/EvcsKebaKeContactImplTest.java @@ -1,27 +1,143 @@ package io.openems.edge.evcs.keba.kecontact; +import static io.openems.edge.evcs.api.PhaseRotation.L2_L3_L1; +import static io.openems.edge.evcs.api.Status.CHARGING_REJECTED; +import static io.openems.edge.evcs.keba.kecontact.Plug.PLUGGED_ON_EVCS_AND_ON_EV_AND_LOCKED; + import org.junit.Test; +import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; +import io.openems.edge.evcs.api.Evcs; import io.openems.edge.evcs.keba.kecontact.core.EvcsKebaKeContactCoreImpl; import io.openems.edge.evcs.test.DummyEvcsPower; +import io.openems.edge.meter.api.ElectricityMeter; public class EvcsKebaKeContactImplTest { - private static final String COMPONENT_ID = "evcs0"; - @Test public void test() throws Exception { - new ComponentTest(new EvcsKebaKeContactImpl()) // + var sut = new EvcsKebaKeContactImpl(); + var rh = sut.readHandler; + new ComponentTest(sut) // .addReference("evcsPower", new DummyEvcsPower()) // .addReference("kebaKeContactCore", new EvcsKebaKeContactCoreImpl()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("evcs0") // .setDebugMode(false) // .setIp("172.0.0.1") // .setMinHwCurrent(6000) // + .setPhaseRotation(L2_L3_L1) // .setUseDisplay(false) // - .build()); // + .build()) // + + .next(new TestCase() // + .onBeforeProcessImage(() -> rh.accept(REPORT_1)) // + .output(EvcsKebaKeContact.ChannelId.SERIAL, "12345678") // + .output(EvcsKebaKeContact.ChannelId.FIRMWARE, "P30 v 3.10.57 (240521-093236)") // + .output(EvcsKebaKeContact.ChannelId.COM_MODULE, "0") // + .output(EvcsKebaKeContact.ChannelId.DIP_SWITCH_1, "00100101") // + .output(EvcsKebaKeContact.ChannelId.DIP_SWITCH_2, "00000010") // + .output(EvcsKebaKeContact.ChannelId.PRODUCT, "KC-P30-EC240422-E00")) // + + .next(new TestCase() // + .onBeforeProcessImage(() -> rh.accept(REPORT_2)) // + .output(EvcsKebaKeContact.ChannelId.STATUS_KEBA, CHARGING_REJECTED) // + .output(EvcsKebaKeContact.ChannelId.ERROR_1, 0) // + .output(EvcsKebaKeContact.ChannelId.ERROR_2, 0) // + .output(EvcsKebaKeContact.ChannelId.PLUG, PLUGGED_ON_EVCS_AND_ON_EV_AND_LOCKED) // + .output(EvcsKebaKeContact.ChannelId.ENABLE_SYS, false) // + .output(EvcsKebaKeContact.ChannelId.ENABLE_USER, false) // + .output(EvcsKebaKeContact.ChannelId.MAX_CURR_PERCENT, 1_000) // + .output(EvcsKebaKeContact.ChannelId.CURR_FAILSAFE, 0) // + .output(EvcsKebaKeContact.ChannelId.TIMEOUT_FAILSAFE, 0) // + .output(EvcsKebaKeContact.ChannelId.CURR_TIMER, 0) // + .output(EvcsKebaKeContact.ChannelId.TIMEOUT_CT, 0) // + .output(EvcsKebaKeContact.ChannelId.OUTPUT, false) // + .output(EvcsKebaKeContact.ChannelId.INPUT, false) // + .output(EvcsKebaKeContact.ChannelId.MAX_CURR, 32_000) // + .output(EvcsKebaKeContact.ChannelId.CURR_USER, 1_0000)) // + + .next(new TestCase() // + .onBeforeProcessImage(() -> rh.accept(REPORT_3)) // + .output(ElectricityMeter.ChannelId.VOLTAGE, 227_500) // + .output(ElectricityMeter.ChannelId.VOLTAGE_L1, null) // + .output(ElectricityMeter.ChannelId.VOLTAGE_L2, 228_000) // + .output(ElectricityMeter.ChannelId.VOLTAGE_L3, 227_000) // + .output(ElectricityMeter.ChannelId.CURRENT, 9_075) // + .output(ElectricityMeter.ChannelId.CURRENT_L1, 0) // + .output(ElectricityMeter.ChannelId.CURRENT_L2, 9_075) // + .output(ElectricityMeter.ChannelId.CURRENT_L3, 0) // + .output(ElectricityMeter.ChannelId.ACTIVE_POWER, 1_866) // + .output(ElectricityMeter.ChannelId.ACTIVE_POWER_L1, 0) // + .output(ElectricityMeter.ChannelId.ACTIVE_POWER_L2, 1_866) // + .output(ElectricityMeter.ChannelId.ACTIVE_POWER_L3, 0) // + .output(ElectricityMeter.ChannelId.ACTIVE_PRODUCTION_ENERGY, 7747834L) // + .output(Evcs.ChannelId.ENERGY_SESSION, 6530) // + .output(EvcsKebaKeContact.ChannelId.COS_PHI, 905) // + + ); } + private static final String REPORT_1 = """ + { + "ID": "1", + "Product": "KC-P30-EC240422-E00", + "Serial": "12345678", + "Firmware":"P30 v 3.10.57 (240521-093236)", + "COM-module": 0, + "Backend": 0, + "timeQ": 3, + "setBoot": 0, + "DIP-Sw1": "0x25", + "DIP-Sw2": "0x02", + "Sec": 530786 + } + """; + private static final String REPORT_2 = """ + { + "ID": "2", + "State": 5, + "Error1": 0, + "Error2": 0, + "Plug": 7, + "AuthON": 0, + "Authreq": 0, + "Enable sys": 0, + "Enable user": 0, + "Max curr": 0, + "Max curr %": 1000, + "Curr HW": 32000, + "Curr user": 10000, + "Curr FS": 0, + "Tmo FS": 0, + "Curr timer": 0, + "Tmo CT": 0, + "Setenergy": 0, + "Output": 0, + "Input": 0, + "X2 phaseSwitch source": 0, + "X2 phaseSwitch": 0, + "Serial": "22054282", + "Sec": 530786 + } + """; + private static final String REPORT_3 = """ + { + "ID": "3", + "U1": 228, + "U2": 227, + "U3": 0, + "I1": 9075, + "I2": 0, + "I3": 0, + "P": 1866156, + "PF": 905, + "E pres": 65302, + "E total": 77478335, + "Serial": "22054282", + "Sec": 534926 + } + """; + } diff --git a/io.openems.edge.evcs.keba.kecontact/test/io/openems/edge/evcs/keba/kecontact/MyConfig.java b/io.openems.edge.evcs.keba.kecontact/test/io/openems/edge/evcs/keba/kecontact/MyConfig.java index 005b60b872a..6920b20339f 100644 --- a/io.openems.edge.evcs.keba.kecontact/test/io/openems/edge/evcs/keba/kecontact/MyConfig.java +++ b/io.openems.edge.evcs.keba.kecontact/test/io/openems/edge/evcs/keba/kecontact/MyConfig.java @@ -1,15 +1,17 @@ package io.openems.edge.evcs.keba.kecontact; import io.openems.common.test.AbstractComponentConfig; +import io.openems.edge.evcs.api.PhaseRotation; @SuppressWarnings("all") public class MyConfig extends AbstractComponentConfig implements Config { protected static class Builder { private String id = null; - private int minHwCurrent; private String ip; private boolean debugMode; + private int minHwCurrent; + private PhaseRotation phaseRotation; private boolean useDisplay; private Builder() { @@ -35,6 +37,11 @@ public Builder setDebugMode(boolean debugMode) { return this; } + public Builder setPhaseRotation(PhaseRotation phaseRotation) { + this.phaseRotation = phaseRotation; + return this; + } + public Builder setUseDisplay(boolean useDisplay) { this.useDisplay = useDisplay; return this; @@ -76,6 +83,11 @@ public int minHwCurrent() { return this.builder.minHwCurrent; } + @Override + public PhaseRotation phaseRotation() { + return this.builder.phaseRotation; + } + @Override public boolean useDisplay() { return this.builder.useDisplay; diff --git a/io.openems.edge.evcs.keba.kecontact/test/io/openems/edge/evcs/keba/kecontact/ReadWorkerTest.java b/io.openems.edge.evcs.keba.kecontact/test/io/openems/edge/evcs/keba/kecontact/ReadWorkerTest.java new file mode 100644 index 00000000000..9d39b3073a4 --- /dev/null +++ b/io.openems.edge.evcs.keba.kecontact/test/io/openems/edge/evcs/keba/kecontact/ReadWorkerTest.java @@ -0,0 +1,72 @@ +package io.openems.edge.evcs.keba.kecontact; + +import static org.junit.Assert.assertEquals; + +import java.time.LocalDateTime; + +import org.junit.Test; + +public class ReadWorkerTest { + + private LocalDateTime now = LocalDateTime.now(); + + @Test + public void testAllReportsDue() { + LocalDateTime lastReport1 = LocalDateTime.MIN; + LocalDateTime lastReport2 = LocalDateTime.MIN; + LocalDateTime lastReport3 = LocalDateTime.MIN; + + int result = ReadWorker.getCycleTimeLogic(lastReport1, lastReport2, lastReport3, this.now); + assertEquals(0, result); + } + + @Test + public void testNoReportsDue() { + LocalDateTime lastReport1 = this.now.minusSeconds(5); + LocalDateTime lastReport2 = this.now.minusSeconds(5); + LocalDateTime lastReport3 = this.now.minusSeconds(5); + + int result = ReadWorker.getCycleTimeLogic(lastReport1, lastReport2, lastReport3, this.now); + assertEquals(5000, result); + } + + @Test + public void testOneReportDue() { + LocalDateTime lastReport1 = this.now.minusHours(1); + LocalDateTime lastReport2 = this.now.minusSeconds(1); + LocalDateTime lastReport3 = this.now.minusSeconds(1); + + int result = ReadWorker.getCycleTimeLogic(lastReport1, lastReport2, lastReport3, this.now); + assertEquals(0, result); + } + + @Test + public void testReportsInFuture() { + LocalDateTime lastReport1 = this.now.plusSeconds(5); + LocalDateTime lastReport2 = this.now.plusSeconds(5); + LocalDateTime lastReport3 = this.now.plusSeconds(5); + + int result = ReadWorker.getCycleTimeLogic(lastReport1, lastReport2, lastReport3, this.now); + assertEquals(15000, result); + } + + @Test + public void testReportsFarInFuture() { + LocalDateTime lastReport1 = LocalDateTime.MAX; + LocalDateTime lastReport2 = LocalDateTime.MAX; + LocalDateTime lastReport3 = LocalDateTime.MAX; + + int result = ReadWorker.getCycleTimeLogic(lastReport1, lastReport2, lastReport3, this.now); + assertEquals(0, result); + } + + @Test + public void testEdgeCaseReportNearNow() { + LocalDateTime lastReport1 = this.now.minusSeconds(Report.REPORT1.getRequestSeconds() - 1); + LocalDateTime lastReport2 = this.now.minusSeconds(Report.REPORT2.getRequestSeconds() - 1); + LocalDateTime lastReport3 = this.now.minusSeconds(Report.REPORT3.getRequestSeconds() - 1); + + int result = ReadWorker.getCycleTimeLogic(lastReport1, lastReport2, lastReport3, this.now); + assertEquals(1000, result); + } +} \ No newline at end of file diff --git a/io.openems.edge.evcs.mennekes/.classpath b/io.openems.edge.evcs.mennekes/.classpath new file mode 100644 index 00000000000..b4cffd0fe60 --- /dev/null +++ b/io.openems.edge.evcs.mennekes/.classpath @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/io.openems.edge.evcs.mennekes/.gitignore b/io.openems.edge.evcs.mennekes/.gitignore new file mode 100644 index 00000000000..c2b941a96de --- /dev/null +++ b/io.openems.edge.evcs.mennekes/.gitignore @@ -0,0 +1,2 @@ +/bin_test/ +/generated/ diff --git a/io.openems.edge.evcs.mennekes/.project b/io.openems.edge.evcs.mennekes/.project new file mode 100644 index 00000000000..f68af579e0f --- /dev/null +++ b/io.openems.edge.evcs.mennekes/.project @@ -0,0 +1,23 @@ + + + io.openems.edge.evcs.mennekes + + + + + + org.eclipse.jdt.core.javabuilder + + + + + bndtools.core.bndbuilder + + + + + + org.eclipse.jdt.core.javanature + bndtools.core.bndnature + + diff --git a/io.openems.edge.evcs.mennekes/.settings/org.eclipse.core.resources.prefs b/io.openems.edge.evcs.mennekes/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 00000000000..99f26c0203a --- /dev/null +++ b/io.openems.edge.evcs.mennekes/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding/=UTF-8 diff --git a/io.openems.edge.evcs.mennekes/bnd.bnd b/io.openems.edge.evcs.mennekes/bnd.bnd new file mode 100644 index 00000000000..4169b61baee --- /dev/null +++ b/io.openems.edge.evcs.mennekes/bnd.bnd @@ -0,0 +1,17 @@ +Bundle-Name: OpenEMS Edge EVCS Mennekes Amtron Professional +Bundle-Vendor: FENECON GmbH +Bundle-License: https://opensource.org/licenses/EPL-2.0 +Bundle-Version: 1.0.0.${tstamp} + +-buildpath: \ + ${buildpath},\ + com.ghgande.j2mod,\ + io.openems.common,\ + io.openems.edge.bridge.modbus,\ + io.openems.edge.common,\ + io.openems.edge.evcs.api,\ + io.openems.edge.meter.api,\ + io.openems.edge.timedata.api,\ + +-testpath: \ + ${testpath} diff --git a/io.openems.edge.evcs.mennekes/readme.adoc b/io.openems.edge.evcs.mennekes/readme.adoc new file mode 100644 index 00000000000..a7cebf8d7e7 --- /dev/null +++ b/io.openems.edge.evcs.mennekes/readme.adoc @@ -0,0 +1,15 @@ += Mennekes Amtron Professional Charging Station + +This component implements the Mennekes Professional charging station by the manufacturer Mennekes. It collects all relevant information into the given Nature Channels and its own Channels and sends charging commands that have been set by another controllers. + +== https://en.wikipedia.org/wiki/Software_release_life_cycle[Software release life cycle]: BETA + +This implementation has been carried out during OpenEMS Hackathon Q1/2023 and is not yet fully feature tested. Please consider it BETA quality + +=== Technical Data + +Implemented Natures: +* Evcs (Electric Vehicle Charging Station) +* ManagedEvcs + +https://github.com/OpenEMS/openems/tree/develop/io.openems.edge.evcs.mennekes[Source Code icon:github[]] \ No newline at end of file diff --git a/io.openems.edge.evcs.mennekes/src/io/openems/edge/evcs/mennekes/AvailableState.java b/io.openems.edge.evcs.mennekes/src/io/openems/edge/evcs/mennekes/AvailableState.java new file mode 100644 index 00000000000..9a2f1d227b0 --- /dev/null +++ b/io.openems.edge.evcs.mennekes/src/io/openems/edge/evcs/mennekes/AvailableState.java @@ -0,0 +1,45 @@ +package io.openems.edge.evcs.mennekes; + +import io.openems.common.types.OptionsEnum; + +/** + * Shows the status of the Mennekes charger. + */ +public enum AvailableState implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + AVAILABLE(0, "Available"), // + PREPARING_TAG_ID_READY(1, "Ready to prepare the tag ID"), // + PREPARING_EV_READY(2, "Charging"), // + CHARGING(3, "Charging"), // + SUSPENDED_EV(4, "Suspended electric vehicle"), // + SUSPENDED_EV_SE(5, "Suspended electric vehicle se"), // + FINISHING(6, "Finishing"), // + RESERVED(7, "Reseved"), // + UNAVAILABLE(8, "Unavailable"), // + UNAVAILABLE_FW_UPDATE(9, "Unavailable firmware update"), // + FAULTED(10, "Faulted"), // + UNAVAILABLE_CONNECTION_OBJECT(11, "Unavailable connection"); + + private final int value; + private final String name; + + private AvailableState(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } +} diff --git a/io.openems.edge.evcs.mennekes/src/io/openems/edge/evcs/mennekes/Config.java b/io.openems.edge.evcs.mennekes/src/io/openems/edge/evcs/mennekes/Config.java new file mode 100644 index 00000000000..cad70e25e01 --- /dev/null +++ b/io.openems.edge.evcs.mennekes/src/io/openems/edge/evcs/mennekes/Config.java @@ -0,0 +1,42 @@ +package io.openems.edge.evcs.mennekes; + +import org.osgi.service.metatype.annotations.AttributeDefinition; +import org.osgi.service.metatype.annotations.ObjectClassDefinition; + +@ObjectClassDefinition(// + name = "EVCS Mennekes Amtron Professional", // + description = "Implements the Mennekes Amtron professioanl electric vehicle charging station.") +@interface Config { + + @AttributeDefinition(name = "Component-ID", description = "Unique ID of this Component") + String id() default "evcs0"; + + @AttributeDefinition(name = "Alias", description = "Human-readable name of this Component; defaults to Component-ID") + String alias() default ""; + + @AttributeDefinition(name = "Is enabled?", description = "Is this Component enabled?") + boolean enabled() default true; + + @AttributeDefinition(name = "Modbus-ID", description = "ID of Modbus bridge") + String modbus_id() default "modbus0"; + + @AttributeDefinition(name = "Modbus Unit-ID", description = "The Unit-ID of the Modbus device.") + int modbusUnitId() default 1; + + @AttributeDefinition(name = "Minimum hardware current", description = "Minimum current of the Charger in mA.", required = true) + int minHwCurrent() default 6000; + + @AttributeDefinition(name = "Maximum hardware current", description = "Maximum current of the Charger in mA.", required = true) + int maxHwCurrent() default 32000; + + @AttributeDefinition(name = "Debug Mode", description = "Activates the debug mode") + boolean debugMode() default false; + + @AttributeDefinition(name = "Read only", description = "Defines that this evcs is read only.", required = true) + boolean readOnly() default true; + + @AttributeDefinition(name = "Modbus target filter", description = "This is auto-generated by 'Modbus-ID'.") + String Modbus_target() default "(enabled=true)"; + + String webconsole_configurationFactory_nameHint() default "EVCS Mennekes Amtron Professional [{id}]"; +} diff --git a/io.openems.edge.evcs.mennekes/src/io/openems/edge/evcs/mennekes/EvcsMennekes.java b/io.openems.edge.evcs.mennekes/src/io/openems/edge/evcs/mennekes/EvcsMennekes.java new file mode 100644 index 00000000000..cc43b4f95cd --- /dev/null +++ b/io.openems.edge.evcs.mennekes/src/io/openems/edge/evcs/mennekes/EvcsMennekes.java @@ -0,0 +1,546 @@ +package io.openems.edge.evcs.mennekes; + +import io.openems.common.channel.AccessMode; +import io.openems.common.channel.Level; +import io.openems.common.channel.PersistencePriority; +import io.openems.common.channel.Unit; +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.types.OpenemsType; +import io.openems.edge.common.channel.BooleanReadChannel; +import io.openems.edge.common.channel.Channel; +import io.openems.edge.common.channel.Doc; +import io.openems.edge.common.channel.IntegerReadChannel; +import io.openems.edge.common.channel.IntegerWriteChannel; +import io.openems.edge.common.channel.StateChannel; +import io.openems.edge.common.channel.StringReadChannel; +import io.openems.edge.common.channel.value.Value; +import io.openems.edge.common.component.OpenemsComponent; + +/** + * Mennekes Amtron Professional charging protocol interface. + * + *

      + * Defines the interface for Mennekes Amtron Professional + */ +public interface EvcsMennekes extends OpenemsComponent { + + public enum ChannelId implements io.openems.edge.common.channel.ChannelId { + + /** + * Apply charge power limit. + * + *

      + * WriteChannel for the modbus register to apply the charge power given by the + * applyChargePowerLimit method + */ + APPLY_CURRENT_LIMIT(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.WATT) // + .accessMode(AccessMode.READ_WRITE)), // + + FIRMWARE_VERSION(Doc.of(OpenemsType.STRING)// + .unit(Unit.NONE)// + .text("Readable Firmware Version")), + + FIRMWARE_OUTDATED(Doc.of(Level.INFO)// + .translationKey(EvcsMennekes.class, "firmwareOutdated")), + + RAW_FIRMWARE_VERSION(Doc.of(OpenemsType.INTEGER)// + .unit(Unit.NONE)// + .text("Firmware Version as Long; has to converted to hex to interpret")), // + + OCPP_CP_STATUS(Doc.of(MennekesOcppState.values())// + .persistencePriority(PersistencePriority.HIGH)// + ), // + + /** + * ERR_RCMB_TRIGGERED. + * + *

        + *
      • Interface: MennekesAmtron + *
      • Type: State + *
      + */ + ERR_RCMB_TRIGGERED(Doc.of(Level.WARNING)// + .translationKey(EvcsMennekes.class, "errRcmbTriggered")), // + + /** + * ERR_VEHICLE_STATE_E. + * + *
        + *
      • Interface: MennekesAmtron + *
      • Type: State + *
      + */ + ERR_VEHICLE_STATE_E(Doc.of(Level.WARNING)// + .translationKey(EvcsMennekes.class, "errVehicleStateE")), // + + /** + * ERR_MODE3_DIODE_CHECK. + * + *
        + *
      • Interface: MennekesAmtron + *
      • Type: State + *
      + */ + ERR_MODE3_DIODE_CHECK(Doc.of(Level.WARNING)// + .translationKey(EvcsMennekes.class, "errMode3DiodeCheck")), // + + /** + * ERR_MCB_TYPE2_TRIGGERED. + * + *
        + *
      • Interface: MennekesAmtron + *
      • Type: State + *
      + */ + ERR_MCB_TYPE2_TRIGGERED(Doc.of(Level.WARNING)// + .translationKey(EvcsMennekes.class, "errMcbType2Triggered")), // + + /** + * ERR_MCB_SCHUKO_TRIGGERED. + * + *
        + *
      • Interface: MennekesAmtron + *
      • Type: State + *
      + */ + ERR_MCB_SCHUKO_TRIGGERED(Doc.of(Level.WARNING)// + .translationKey(EvcsMennekes.class, "errMcbSchukoTriggered")), // + + /** + * ERR_RCD_TRIGGERED. + * + *
        + *
      • Interface: MennekesAmtron + *
      • Type: State + *
      + */ + ERR_RCD_TRIGGERED(Doc.of(Level.WARNING)// + .translationKey(EvcsMennekes.class, "errRcdTriggered")), // + + /** + * ERR_CONTACTOR_WELD. + * + *
        + *
      • Interface: MennekesAmtron + *
      • Type: State + *
      + */ + ERR_CONTACTOR_WELD(Doc.of(Level.WARNING)// + .translationKey(EvcsMennekes.class, "errContactorWeld")), // + + /** + * ERR_BACKEND_DISCONNECTED. + * + *
        + *
      • Interface: MennekesAmtron + *
      • Type: State + *
      + */ + ERR_BACKEND_DISCONNECTED(Doc.of(Level.WARNING)// + .translationKey(EvcsMennekes.class, "errBackendDisconnected")), // + + /** + * ERR_ACTUATOR_LOCKING_FAILED. + * + *
        + *
      • Interface: MennekesAmtron + *
      • Type: State + *
      + */ + ERR_ACTUATOR_LOCKING_FAILED(Doc.of(Level.WARNING)// + .translationKey(EvcsMennekes.class, "errActuatorLockingFailed")), // + + /** + * ERR_ACTUATOR_LOCKING_WITHOUT_PLUG_FAILED. + * + *
        + *
      • Interface: MennekesAmtron + *
      • Type: State + *
      + */ + ERR_ACTUATOR_LOCKING_WITHOUT_PLUG_FAILED(Doc.of(Level.WARNING)// + .translationKey(EvcsMennekes.class, "errActuatorLockingWithoutPlugFailed")), // + + /** + * ERR_ACTUATOR_STUCK. + * + *
        + *
      • Interface: MennekesAmtron + *
      • Type: State + *
      + */ + ERR_ACTUATOR_STUCK(Doc.of(Level.WARNING)// + .translationKey(EvcsMennekes.class, "errActuatorStuck")), // + + /** + * ERR_ACTUATOR_DETECTION_FAILED. + * + *
        + *
      • Interface: MennekesAmtron + *
      • Type: State + *
      + */ + ERR_ACTUATOR_DETECTION_FAILED(Doc.of(Level.WARNING)// + .translationKey(EvcsMennekes.class, "errActuatorDetectionFailed")), // + + /** + * ERR_FW_UPDATE_RUNNING. + * + *
        + *
      • Interface: MennekesAmtron + *
      • Type: State + *
      + */ + ERR_FW_UPDATE_RUNNING(Doc.of(Level.WARNING)// + .translationKey(EvcsMennekes.class, "errFwUpdateRunning")), // + + /** + * ERR_TILT. + * + *
        + *
      • Interface: MennekesAmtron + *
      • Type: State + *
      + */ + ERR_TILT(Doc.of(Level.WARNING)// + .translationKey(EvcsMennekes.class, "errTilt")), // + + /** + * ERR_WRONG_CP_PR_WIRING. + * + *
        + *
      • Interface: MennekesAmtron + *
      • Type: State + *
      + */ + ERR_WRONG_CP_PR_WIRING(Doc.of(Level.WARNING)// + .translationKey(EvcsMennekes.class, "errWrongCpPrWiring")), // + + /** + * ERR_TYPE2_OVERLOAD_THR_2. + * + *
        + *
      • Interface: MennekesAmtron + *
      • Type: State + *
      + */ + ERR_TYPE2_OVERLOAD_THR_2(Doc.of(Level.WARNING)// + .translationKey(EvcsMennekes.class, "errType2OverloadThr2")), // + + /** + * ERR_ACTUATOR_UNLOCKED_WHILE_CHARGING. + * + *
        + *
      • Interface: MennekesAmtron + *
      • Type: State + *
      + */ + ERR_ACTUATOR_UNLOCKED_WHILE_CHARGING(Doc.of(Level.WARNING)// + .translationKey(EvcsMennekes.class, "errActuatorUnlockedWhileCharging")), // + + /** + * ERR_TILT_PREVENT_CHARGING_UNTIL_REBOOT. + * + *
        + *
      • Interface: MennekesAmtron + *
      • Type: State + *
      + */ + ERR_TILT_PREVENT_CHARGING_UNTIL_REBOOT(Doc.of(Level.WARNING)// + .translationKey(EvcsMennekes.class, "errTiltPreventChargingUntilReboot")), // + + /** + * ERR_PIC24. + * + *
        + *
      • Interface: MennekesAmtron + *
      • Type: State + *
      + */ + ERR_PIC24(Doc.of(Level.WARNING)// + .translationKey(EvcsMennekes.class, "errPic24")), // + + /** + * ERR_USB_STICK_HANDLING. + * + *
        + *
      • Interface: MennekesAmtron + *
      • Type: State + *
      + */ + ERR_USB_STICK_HANDLING(Doc.of(Level.WARNING)// + .translationKey(EvcsMennekes.class, "errUsbStickHandling")), // + + /** + * ERR_INCORRECT_PHASE_INSTALLATION. + * + *
        + *
      • Interface: MennekesAmtron + *
      • Type: State + *
      + */ + ERR_INCORRECT_PHASE_INSTALLATION(Doc.of(Level.WARNING)// + .translationKey(EvcsMennekes.class, "errIncorrectPhaseInstallation")), // + + /** + * ERR_NO_POWER. + * + *
        + *
      • Interface: MennekesAmtron + *
      • Type: State + *
      + */ + ERR_NO_POWER(Doc.of(Level.WARNING)// + .translationKey(EvcsMennekes.class, "errNoPower")), // + + /** + * VEHICLE STATE. + * + *
        + *
      • Interface: MennekesAmtron + *
      • Type: VehicleState + *
      • Unit: None + *
      + */ + VEHICLE_STATE(Doc.of(VehicleState.values())// + .initialValue(VehicleState.UNDEFINED)), // + + /** + * CP AVAILABILITY. + * + *
        + *
      • Interface: MennekesAmtron + *
      • Type: Boolean + *
      • Unit: None + *
      + */ + CP_AVAILABILITY(Doc.of(OpenemsType.BOOLEAN)// + .unit(Unit.NONE)), // + + /** + * SAFE_CURRENT. + * + *
        + *
      • Interface: MennekesAmtron + *
      • Type: Boolean + *
      • Unit: None + *
      + */ + SAFE_CURRENT(Doc.of(OpenemsType.FLOAT)// + .unit(Unit.AMPERE)), // + + /** + * MAX CURRENT EV. + * + *
        + *
      • Interface: MennekesAmtron + *
      • Type: Integer + *
      • Unit: Ampere + *
      + */ + MAX_CURRENT_EV(Doc.of(OpenemsType.INTEGER)// + .unit(Unit.AMPERE)), // + + /** + * MIN CURRENT LIMIT. + * + *
        + *
      • Interface: MennekesAmtron + *
      • Type: Integer + *
      • Unit: Ampere + *
      + */ + MIN_CURRENT_LIMIT(Doc.of(OpenemsType.INTEGER)// + .unit(Unit.AMPERE)), // + + /** + * CHARGE DURATION. + * + *
        + *
      • Interface: MennekesAmtron + *
      • Type: Integer + *
      • Unit: SECONDS + *
      + */ + CHARGE_DURATION(Doc.of(OpenemsType.INTEGER)// + .unit(Unit.SECONDS)), // + + /** + * EMS CURRENT LIMIT. + * + *
        + *
      • Interface: MennekesAmtron + *
      • Readable + *
      • Type: Integer + *
      • Unit: AMPERE + *
      + */ + EMS_CURRENT_LIMIT(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.AMPERE) // + .accessMode(AccessMode.READ_ONLY)), // + ; + + private final Doc doc; + + private ChannelId(Doc doc) { + this.doc = doc; + } + + @Override + public Doc doc() { + return this.doc; + } + + } + + /** + * Gets the Channel for {@link ChannelId#APPLY_CURRENT_LIMIT}. + * + * @return the Channel + */ + public default IntegerWriteChannel getApplyCurrentLimitChannel() { + return this.channel(ChannelId.APPLY_CURRENT_LIMIT); + } + + /** + * Sets the charge current limit of the EVCS in [A] on + * {@link ChannelId#APPLY_CURRENT_LIMIT} Channel. + * + * @param value the next value + * @throws OpenemsNamedException on error + */ + public default void setApplyCurrentLimit(Integer value) throws OpenemsNamedException { + this.getApplyCurrentLimitChannel().setNextWriteValue(value); + } + + /** + * Gets the Channel for {@link ChannelId#FIRMWARE_VERSION}. + * + * @return returns the channel + * + */ + public default StringReadChannel getFirmwareVersionChannel() { + return this.channel(ChannelId.FIRMWARE_VERSION); + } + + /** + * Internal method to get the 'nextValue' of {@link ChannelId#FIRMWARE_VERSION} + * Channel. + * + * @return value of firmware version value + */ + public default Value getFirmwareVersionValue() { + return this.getFirmwareVersionChannel().getNextValue(); + } + + /** + * Internal method to set the 'nextValue' on {@link ChannelId#FIRMWARE_VERSION} + * Channel. + * + * @param value the next value + */ + public default void _setFirmwareVersion(String value) { + this.getFirmwareVersionChannel().setNextValue(value); + } + + /** + * Gets the Channel for {@link ChannelId#RAW_FIRMWARE_VERSION}. + * + * @return the Channel + */ + public default StateChannel getFirmwareOutdatedChannel() { + return this.channel(ChannelId.FIRMWARE_OUTDATED); + } + + /** + * Gets the Channel for {@link ChannelId#OCPP_CP_STATUS}. + * + * @return the Channel + */ + public default Channel getOcppCpStatusChannel() { + return this.channel(ChannelId.OCPP_CP_STATUS); + } + + /** + * Gets the OCPP Status of the EVCS charging station. See + * {@link ChannelId#OCPP_CP_STATUS}. + * + * @return the Channel {@link Value} + */ + public default MennekesOcppState getOcppCpStatus() { + return this.getOcppCpStatusChannel().value().asEnum(); + } + + /** + * Gets the Channel for {@link ChannelId#CP_AVAILABILITY}. + * + * @return the Channel + */ + + public default BooleanReadChannel getCpAvailabilityChannel() { + return this.channel(ChannelId.CP_AVAILABILITY); + } + + /** + * Gets the Channel for {@link ChannelId#MAX_CURRENT_EV}. + * + * @return the Channel + */ + + public default IntegerReadChannel getMaxCurrentEvChannel() { + return this.channel(ChannelId.MAX_CURRENT_EV); + } + + /** + * Gets the Channel for {@link ChannelId#VEHICLE_STATE}. + * + * @return the Channel + */ + + public default Channel getVehicleStateChannel() { + return this.channel(ChannelId.VEHICLE_STATE); + } + + /** + * Gets the Channel for {@link ChannelId#MIN_CURRENT_LIMIT}. + * + * @return the Channel + */ + + public default IntegerReadChannel getMinCurrentLimitChannel() { + return this.channel(ChannelId.MIN_CURRENT_LIMIT); + } + + /** + * Gets the Minimum current limit in [A] of the AC charger. See + * {@link ChannelId#MIN_CURRENT_LIMIT}. + * + * @return the Channel {@link Value} + */ + + public default Value getMinCurrentLimit() { + return this.getMinCurrentLimitChannel().value(); + } + + /** + * Internal method to set the 'nextValue' on {@link ChannelId#EMS_CURRENT_LIMIT} + * Channel. Sets the value in Ampere as an Integer-Value. + * + * @return returns the EMS current limit + */ + + public default Value getEmsCurrentLimit() { + return this.getEmsCurrentLimitChannel().value(); + } + + /** + * Gets the Channel for {@link ChannelId#EMS_CURRENT_LIMIT}. + * + * @return the Channel + */ + public default IntegerReadChannel getEmsCurrentLimitChannel() { + return this.channel(ChannelId.EMS_CURRENT_LIMIT); + } + +} diff --git a/io.openems.edge.evcs.mennekes/src/io/openems/edge/evcs/mennekes/EvcsMennekesImpl.java b/io.openems.edge.evcs.mennekes/src/io/openems/edge/evcs/mennekes/EvcsMennekesImpl.java new file mode 100644 index 00000000000..d89f15aa54c --- /dev/null +++ b/io.openems.edge.evcs.mennekes/src/io/openems/edge/evcs/mennekes/EvcsMennekesImpl.java @@ -0,0 +1,403 @@ +package io.openems.edge.evcs.mennekes; + +import static io.openems.edge.bridge.modbus.api.ModbusUtils.readElementOnce; +import static io.openems.edge.bridge.modbus.api.ModbusUtils.FunctionCode.FC3; + +import java.util.function.Consumer; + +import org.osgi.service.cm.ConfigurationAdmin; +import org.osgi.service.component.ComponentContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.ConfigurationPolicy; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.Modified; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; +import org.osgi.service.component.annotations.ReferencePolicyOption; +import org.osgi.service.event.Event; +import org.osgi.service.event.EventHandler; +import org.osgi.service.event.propertytypes.EventTopics; +import org.osgi.service.metatype.annotations.Designate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.exceptions.OpenemsException; +import io.openems.common.types.MeterType; +import io.openems.common.types.SemanticVersion; +import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent; +import io.openems.edge.bridge.modbus.api.BridgeModbus; +import io.openems.edge.bridge.modbus.api.ModbusComponent; +import io.openems.edge.bridge.modbus.api.ModbusProtocol; +import io.openems.edge.bridge.modbus.api.ModbusUtils; +import io.openems.edge.bridge.modbus.api.element.BitsWordElement; +import io.openems.edge.bridge.modbus.api.element.DummyRegisterElement; +import io.openems.edge.bridge.modbus.api.element.UnsignedDoublewordElement; +import io.openems.edge.bridge.modbus.api.element.UnsignedWordElement; +import io.openems.edge.bridge.modbus.api.element.WordOrder; +import io.openems.edge.bridge.modbus.api.task.FC16WriteRegistersTask; +import io.openems.edge.bridge.modbus.api.task.FC3ReadRegistersTask; +import io.openems.edge.common.channel.value.Value; +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.common.event.EdgeEventConstants; +import io.openems.edge.common.taskmanager.Priority; +import io.openems.edge.common.type.TypeUtils; +import io.openems.edge.evcs.api.ChargeStateHandler; +import io.openems.edge.evcs.api.ChargingType; +import io.openems.edge.evcs.api.Evcs; +import io.openems.edge.evcs.api.EvcsPower; +import io.openems.edge.evcs.api.ManagedEvcs; +import io.openems.edge.evcs.api.Phases; +import io.openems.edge.evcs.api.Status; +import io.openems.edge.evcs.api.WriteHandler; +import io.openems.edge.meter.api.ElectricityMeter; + +@Designate(ocd = Config.class, factory = true) +@Component(// + name = "Evcs.Mennekes", // + immediate = true, // + configurationPolicy = ConfigurationPolicy.REQUIRE // +) +@EventTopics({ // + EdgeEventConstants.TOPIC_CYCLE_EXECUTE_WRITE // +}) +public class EvcsMennekesImpl extends AbstractOpenemsModbusComponent + implements Evcs, ElectricityMeter, ManagedEvcs, OpenemsComponent, ModbusComponent, EventHandler, EvcsMennekes { + + private final Logger log = LoggerFactory.getLogger(EvcsMennekesImpl.class); + + // TODO: Add functionality to distinguish between firmware version. For firmware + // version >= 5.22 there are several new registers. Currently it is programmed + // for firmware version 5.14. + // private boolean softwareVersionSmallerThan_5_22 = true; + + private Config config; + + @Reference + private EvcsPower evcsPower; + + @Reference + protected ConfigurationAdmin cm; + + /** + * Handles charge states. + */ + private final ChargeStateHandler chargeStateHandler = new ChargeStateHandler(this); + + /** + * Processes the controller's writes to this evcs component. + */ + private final WriteHandler writeHandler = new WriteHandler(this); + + public EvcsMennekesImpl() { + super(// + OpenemsComponent.ChannelId.values(), // + ModbusComponent.ChannelId.values(), // + Evcs.ChannelId.values(), // + ManagedEvcs.ChannelId.values(), // + ElectricityMeter.ChannelId.values(), // + EvcsMennekes.ChannelId.values()); + } + + @Override + @Reference(policy = ReferencePolicy.STATIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.MANDATORY) + protected void setModbus(BridgeModbus modbus) { + super.setModbus(modbus); + } + + @Activate + private void activate(ComponentContext context, Config config) throws OpenemsNamedException { + this.config = config; + if (super.activate(context, config.id(), config.alias(), config.enabled(), config.modbusUnitId(), this.cm, + "Modbus", config.modbus_id())) { + return; + } + + /* + * Calculates the maximum and minimum hardware power dynamically by listening on + * the fixed hardware limit and the phases used for charging + */ + Evcs.addCalculatePowerLimitListeners(this); + this.applyConfig(config); + + this.detectSoftwareVersion(); + } + + @Modified + private void modified(ComponentContext context, Config config) throws OpenemsNamedException { + this.applyConfig(config); + if (super.modified(context, config.id(), config.alias(), config.enabled(), config.modbusUnitId(), this.cm, + "Modbus", config.modbus_id())) { + return; + } + } + + private void applyConfig(Config config) { + this.config = config; + this._setChargingType(ChargingType.AC); + this._setFixedMaximumHardwarePower(this.getConfiguredMaximumHardwarePower()); + this._setFixedMinimumHardwarePower(this.getConfiguredMinimumHardwarePower()); + this._setPowerPrecision(230); + this._setPhases(3); + } + + @Override + @Deactivate + protected void deactivate() { + super.deactivate(); + } + + @Override + public EvcsPower getEvcsPower() { + return this.evcsPower; + } + + @Override + public void handleEvent(Event event) { + if (!this.isEnabled()) { + return; + } + switch (event.getTopic()) { + case EdgeEventConstants.TOPIC_CYCLE_EXECUTE_WRITE -> { + if (!this.isReadOnly()) { + this.writeHandler.run(); + } + break; + } + } + } + + @Override + protected ModbusProtocol defineModbusProtocol() { + // TODO: Distinguish between firmware version. For firmware version >= 5.22 + // there are several new registers. + ModbusProtocol modbusProtocol = new ModbusProtocol(this, + new FC3ReadRegistersTask(104, Priority.HIGH, + m(EvcsMennekes.ChannelId.OCPP_CP_STATUS, new UnsignedWordElement(104))), + new FC3ReadRegistersTask(111, Priority.LOW, // + m(new BitsWordElement(111, this)) // + .bit(0, EvcsMennekes.ChannelId.ERR_ACTUATOR_UNLOCKED_WHILE_CHARGING) // + .bit(1, EvcsMennekes.ChannelId.ERR_TILT_PREVENT_CHARGING_UNTIL_REBOOT) // + .bit(2, EvcsMennekes.ChannelId.ERR_PIC24) // + .bit(3, EvcsMennekes.ChannelId.ERR_USB_STICK_HANDLING) // + .bit(4, EvcsMennekes.ChannelId.ERR_INCORRECT_PHASE_INSTALLATION) // + .bit(5, EvcsMennekes.ChannelId.ERR_NO_POWER), + m(new BitsWordElement(112, this)).bit(0, EvcsMennekes.ChannelId.ERR_RCMB_TRIGGERED) // + .bit(1, EvcsMennekes.ChannelId.ERR_VEHICLE_STATE_E) // + .bit(2, EvcsMennekes.ChannelId.ERR_MODE3_DIODE_CHECK) // + .bit(3, EvcsMennekes.ChannelId.ERR_MCB_TYPE2_TRIGGERED) // + .bit(4, EvcsMennekes.ChannelId.ERR_MCB_SCHUKO_TRIGGERED) // + .bit(5, EvcsMennekes.ChannelId.ERR_RCD_TRIGGERED) // + .bit(6, EvcsMennekes.ChannelId.ERR_CONTACTOR_WELD) // + .bit(7, EvcsMennekes.ChannelId.ERR_BACKEND_DISCONNECTED) // + .bit(8, EvcsMennekes.ChannelId.ERR_ACTUATOR_LOCKING_FAILED) // + .bit(9, EvcsMennekes.ChannelId.ERR_ACTUATOR_LOCKING_WITHOUT_PLUG_FAILED) // + .bit(10, EvcsMennekes.ChannelId.ERR_ACTUATOR_STUCK) // + .bit(11, EvcsMennekes.ChannelId.ERR_ACTUATOR_DETECTION_FAILED) // + .bit(12, EvcsMennekes.ChannelId.ERR_FW_UPDATE_RUNNING) // + .bit(13, EvcsMennekes.ChannelId.ERR_TILT) // + .bit(14, EvcsMennekes.ChannelId.ERR_WRONG_CP_PR_WIRING) // + .bit(15, EvcsMennekes.ChannelId.ERR_TYPE2_OVERLOAD_THR_2)), + new FC3ReadRegistersTask(122, Priority.HIGH, + m(EvcsMennekes.ChannelId.VEHICLE_STATE, new UnsignedWordElement(122))), + new FC3ReadRegistersTask(131, Priority.LOW, + m(EvcsMennekes.ChannelId.SAFE_CURRENT, new UnsignedWordElement(131))), + new FC3ReadRegistersTask(200, Priority.HIGH, + m(ElectricityMeter.ChannelId.ACTIVE_CONSUMPTION_ENERGY, new UnsignedDoublewordElement(200))), + new FC3ReadRegistersTask(206, Priority.HIGH, + m(ElectricityMeter.ChannelId.ACTIVE_POWER_L1, new UnsignedDoublewordElement(206)), + m(ElectricityMeter.ChannelId.ACTIVE_POWER_L2, new UnsignedDoublewordElement(208)), + m(ElectricityMeter.ChannelId.ACTIVE_POWER_L3, new UnsignedDoublewordElement(210)), + m(ElectricityMeter.ChannelId.CURRENT_L1, new UnsignedDoublewordElement(212)), + m(ElectricityMeter.ChannelId.CURRENT_L2, new UnsignedDoublewordElement(214)), + m(ElectricityMeter.ChannelId.CURRENT_L3, new UnsignedDoublewordElement(216))), + new FC3ReadRegistersTask(1000, Priority.LOW, + m(EvcsMennekes.ChannelId.EMS_CURRENT_LIMIT, new UnsignedWordElement(1000))), + new FC16WriteRegistersTask(1000, + m(EvcsMennekes.ChannelId.APPLY_CURRENT_LIMIT, new UnsignedWordElement(1000)))); + + // Calculates required Channels from other existing Channels. + this.addCalculateChannelListeners(); + + this.addStatusListener(); + + return modbusProtocol; + } + + private void addCalculateChannelListeners() { + + // TODO: Power is given by the charger since firmware 5.22 + final Consumer> powerChannels = ignore -> { + this._setActivePower(TypeUtils.sum(this.getActivePowerL1().orElse(0), this.getActivePowerL2().orElse(0), + this.getActivePowerL3().orElse(0))); + final var phases = Evcs.evaluatePhaseCount(// + this.getActivePowerL1().orElse(0), // + this.getActivePowerL2().orElse(0), // + this.getActivePowerL3().orElse(0)); + this._setPhases(phases); + }; + + this.getActivePowerL1Channel().onSetNextValue(powerChannels); + this.getActivePowerL2Channel().onSetNextValue(powerChannels); + this.getActivePowerL3Channel().onSetNextValue(powerChannels); + } + + private void addStatusListener() { + this.channel(EvcsMennekes.ChannelId.OCPP_CP_STATUS).onSetNextValue(s -> { + var currentStatus = Status.UNDEFINED; + MennekesOcppState rawState = s.asEnum(); + /** + * Maps the raw state into a {@link Status}. + */ + currentStatus = switch (rawState) { + case CHARGING, FINISHING -> Status.CHARGING; + case FAULTED -> Status.ERROR; + case PREPARING -> Status.READY_FOR_CHARGING; + case RESERVED -> Status.NOT_READY_FOR_CHARGING; + case AVAILABLE, SUSPENDEDEV, SUSPENDEDEVSE -> Status.CHARGING_REJECTED; + case OCCUPIED -> this.getActivePower().orElse(0) > 0 ? Status.CHARGING : Status.CHARGING_REJECTED; + case UNAVAILABLE -> Status.ERROR; + case UNDEFINED -> currentStatus; + }; + this._setStatus(currentStatus); + }); + } + + @Override + public boolean isReadOnly() { + return this.config.readOnly(); + } + + @Override + public int getConfiguredMinimumHardwarePower() { + return Math.round(this.config.minHwCurrent() / 1000f) * DEFAULT_VOLTAGE * Phases.THREE_PHASE.getValue(); + } + + @Override + public int getConfiguredMaximumHardwarePower() { + return Math.round(this.config.maxHwCurrent() / 1000f) * DEFAULT_VOLTAGE * Phases.THREE_PHASE.getValue(); + } + + @Override + public boolean getConfiguredDebugMode() { + return this.config.debugMode(); + } + + @Override + public boolean applyDisplayText(String text) throws OpenemsException { + return false; + } + + @Override + public int getMinimumTimeTillChargingLimitTaken() { + return 30; + } + + @Override + public ChargeStateHandler getChargeStateHandler() { + return this.chargeStateHandler; + } + + @Override + public void logDebug(String message) { + if (this.config.debugMode()) { + this.logInfo(this.log, message); + } + } + + @Override + public String debugLog() { + return "Limit:" + this.getSetChargePowerLimit().orElse(null) + "|" + this.getStatus().getName(); + } + + public boolean isCharging() { + return this.getActivePower().orElse(0) > 0; + } + + @Override + public boolean applyChargePowerLimit(int power) throws Exception { + if (this.isReadOnly()) { + return false; + } + var phases = this.getPhasesAsInt(); + var current = Math.round(power / phases / Evcs.DEFAULT_VOLTAGE); + + /* + * Limits the charging value because Mennekes knows only values between 6 and 32 + * A + */ + current = Math.min(current, Evcs.DEFAULT_MAXIMUM_HARDWARE_CURRENT / 1000); + + if (current < Evcs.DEFAULT_MINIMUM_HARDWARE_CURRENT / 1000) { + current = 0; + } + + this.setApplyCurrentLimit(current); + return true; + } + + @Override + public boolean pauseChargeProcess() throws Exception { + return this.applyChargePowerLimit(0); + } + + @Override + public MeterType getMeterType() { + if (this.config.readOnly()) { + return MeterType.CONSUMPTION_METERED; + } else { + return MeterType.MANAGED_CONSUMPTION_METERED; + } + } + + private void detectSoftwareVersion() throws OpenemsException { + readElementOnce(FC3, this.getModbusProtocol(), ModbusUtils::retryOnNull, + new UnsignedDoublewordElement(100).wordOrder(WordOrder.MSWLSW)).thenAccept(registerValue -> { + if (registerValue == null) { + return; + } + + this.channel(EvcsMennekes.ChannelId.RAW_FIRMWARE_VERSION).setNextValue(registerValue); + final var firmwareVersion = parseSoftwareVersion(registerValue.intValue()); + this.channel(EvcsMennekes.ChannelId.FIRMWARE_VERSION.id()).setNextValue(firmwareVersion); + final var outdated = !SemanticVersion.fromString(firmwareVersion) + .isAtLeast(SemanticVersion.fromString("5.22")); + this.getFirmwareOutdatedChannel().setNextValue(outdated); + + if (!outdated) { + this.getModbusProtocol().addTask(// + new FC3ReadRegistersTask(705, Priority.HIGH, + m(Evcs.ChannelId.ENERGY_SESSION, new UnsignedWordElement(705)))); + this.getModbusProtocol().addTask(// + new FC3ReadRegistersTask(706, Priority.LOW, + m(EvcsMennekes.ChannelId.MAX_CURRENT_EV, new UnsignedWordElement(706)), + new DummyRegisterElement(707, 708), + m(EvcsMennekes.ChannelId.CHARGE_DURATION, new UnsignedWordElement(709)), + new DummyRegisterElement(710, 711), // + m(EvcsMennekes.ChannelId.MIN_CURRENT_LIMIT, new UnsignedWordElement(712)))); + } + }); + } + + protected static String parseSoftwareVersion(int registerValue) { + + byte[] bytes = new byte[4]; + bytes[0] = (byte) ((registerValue >> 24) & 0xFF); + bytes[1] = (byte) ((registerValue >> 16) & 0xFF); + bytes[2] = (byte) ((registerValue >> 8) & 0xFF); + bytes[3] = (byte) (registerValue & 0xFF); + + // Convert bytes to a string + StringBuilder firmwareVersionBuilder = new StringBuilder(); + for (byte b : bytes) { + if (b != 0) { + firmwareVersionBuilder.append((char) b); + } + } + + final var firmwareVersion = firmwareVersionBuilder.toString(); + return firmwareVersion; + } +} diff --git a/io.openems.edge.evcs.mennekes/src/io/openems/edge/evcs/mennekes/MennekesErrorStates.java b/io.openems.edge.evcs.mennekes/src/io/openems/edge/evcs/mennekes/MennekesErrorStates.java new file mode 100644 index 00000000000..1aa8dbf96c1 --- /dev/null +++ b/io.openems.edge.evcs.mennekes/src/io/openems/edge/evcs/mennekes/MennekesErrorStates.java @@ -0,0 +1,84 @@ +package io.openems.edge.evcs.mennekes; + +import io.openems.common.types.OptionsEnum; +import io.openems.edge.evcs.mennekes.EvcsMennekes.ChannelId; + +public enum MennekesErrorStates implements OptionsEnum { + UNDEFINED(0x0, // + "Undefined", null), // + ERR_RCMB_TRIGGERED(0x1, // + "Residual current detected via sensor", ChannelId.ERR_RCMB_TRIGGERED), // + ERR_VEHICLE_STATE_E(0x2, // + "Vehicle signals error", ChannelId.ERR_VEHICLE_STATE_E), // + ERR_MODE3_DIDE_CHECK(0x4, // + "ReseVehicle diode check failed - tamper detection", ChannelId.ERR_MODE3_DIODE_CHECK), // + ERR_MCB_TYPE2_TRIGGERED(0x8, // + "MCB of type 2 socket triggered", ChannelId.ERR_MCB_TYPE2_TRIGGERED), // + ERR_MCB_SCHUKO_TRIGGERED(0x10, // + "MCB of domestic socket triggered", ChannelId.ERR_MCB_SCHUKO_TRIGGERED), // + ERR_RCD_TRIGGERED(0x20, // + "RCD triggered", ChannelId.ERR_RCD_TRIGGERED), // + ERR_CONTACTOR_WELD(0x40, // + "Contactor welded", ChannelId.ERR_CONTACTOR_WELD), // + ERR_BACKEND_DISCONNECTED(0x80, // + "Backend disconnected", ChannelId.ERR_BACKEND_DISCONNECTED), // + ERR_ACTUATOR_LOCKING_FAILED(0x100, // + "Plug locking failed", ChannelId.ERR_ACTUATOR_LOCKING_FAILED), // + ERR_ACTUATOR_LOCKING_WITHOUT_PLUG_FAILED(0x200, // + "Locking without plug error", ChannelId.ERR_ACTUATOR_LOCKING_WITHOUT_PLUG_FAILED), // + ERR_ACTUATOR_STUCK(0x400, // + "Actuator stuck cannot unlock", ChannelId.ERR_ACTUATOR_STUCK), // + ERR_ACTUATOR_DETECTION_FAILED(0x800, // + "Actuator detection failed", ChannelId.ERR_ACTUATOR_DETECTION_FAILED), // + ERR_FW_UPDATE_RUNNING(0x1000, // + "FW Update in progress", ChannelId.ERR_FW_UPDATE_RUNNING), // + ERR_TILT(0x2000, // + "The charge point is tilted", ChannelId.ERR_TILT), // + ERR_WRONG_CP_PR_WIRING(0x4000, // + "CP/PR wiring issue", ChannelId.ERR_WRONG_CP_PR_WIRING), // + ERR_TYPE2_OVERLOAD_THR_2(0x8000, // + "Car current overload, charging stopped", ChannelId.ERR_TYPE2_OVERLOAD_THR_2), // + ERR_ACTUATOR_UNLOCKED_WHILE_CHARGING(0x10000, // + "Actuator unlocked while charging", ChannelId.ERR_ACTUATOR_UNLOCKED_WHILE_CHARGING), // + ERR_TILT_PREVENT_CHARGING_UNTIL_REBOOT(0x20000, // + "The charge point was tilted and it is not allowed to charge until the charge point is rebooted", + ChannelId.ERR_TILT_PREVENT_CHARGING_UNTIL_REBOOT), // + ERR_PIC24(0x40000, // + "PIC24 error", ChannelId.ERR_PIC24), // + ERR_USB_STICK_HANDLING(0x80000, // + "USB stick handling in progress", ChannelId.ERR_USB_STICK_HANDLING), // + ERR_INCORRECT_PHASE_INSTALLATION(0x100000, // + "Incorrect phase rotation direction detected", ChannelId.ERR_INCORRECT_PHASE_INSTALLATION), // + ERR_NO_POWER(0x200000, // + "No power on mains detected", ChannelId.ERR_NO_POWER) // + ; + + private final int value; + private final String name; + private final ChannelId channel; + + private MennekesErrorStates(int value, String name, ChannelId channel) { + this.value = value; + this.name = name; + this.channel = channel; + } + + public ChannelId getChannel() { + return this.channel; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } +} diff --git a/io.openems.edge.evcs.mennekes/src/io/openems/edge/evcs/mennekes/MennekesOcppState.java b/io.openems.edge.evcs.mennekes/src/io/openems/edge/evcs/mennekes/MennekesOcppState.java new file mode 100644 index 00000000000..742d8b64b21 --- /dev/null +++ b/io.openems.edge.evcs.mennekes/src/io/openems/edge/evcs/mennekes/MennekesOcppState.java @@ -0,0 +1,41 @@ +package io.openems.edge.evcs.mennekes; + +import io.openems.common.types.OptionsEnum; + +public enum MennekesOcppState implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + AVAILABLE(0, "Available"), // + OCCUPIED(1, "Occupied"), // + RESERVED(2, "Reserved"), // + UNAVAILABLE(3, "Unavailable"), // + FAULTED(4, "Faulted"), // + PREPARING(5, "Preparing"), // + CHARGING(6, "Charging"), // + SUSPENDEDEVSE(7, "SuspendedEVSE"), // + SUSPENDEDEV(8, "SuspendedEV"), // + FINISHING(9, "Finishing"), // + ; + + private final int value; + private final String name; + + private MennekesOcppState(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } +} diff --git a/io.openems.edge.evcs.mennekes/src/io/openems/edge/evcs/mennekes/VehicleState.java b/io.openems.edge.evcs.mennekes/src/io/openems/edge/evcs/mennekes/VehicleState.java new file mode 100644 index 00000000000..11f1d1b64db --- /dev/null +++ b/io.openems.edge.evcs.mennekes/src/io/openems/edge/evcs/mennekes/VehicleState.java @@ -0,0 +1,36 @@ +package io.openems.edge.evcs.mennekes; + +import io.openems.common.types.OptionsEnum; + +public enum VehicleState implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + STATE_A(1, "No EV connected to the EVSE "), // + STATE_B(2, "EV connected to the EVSE, but not ready for charging "), // + STATE_C(3, "Connected and ready for charging, ventilation is not required "), // + STATE_D(4, "Connected, ready for charging and ventilation is required "), // + STATE_E(5, "Electrical short to earth on the controller of the EVSE, no power supply") // + ; + + private final int value; + private final String name; + + private VehicleState(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } +} diff --git a/io.openems.edge.evcs.mennekes/src/io/openems/edge/evcs/mennekes/translation_de.properties b/io.openems.edge.evcs.mennekes/src/io/openems/edge/evcs/mennekes/translation_de.properties new file mode 100644 index 00000000000..c86d805eb42 --- /dev/null +++ b/io.openems.edge.evcs.mennekes/src/io/openems/edge/evcs/mennekes/translation_de.properties @@ -0,0 +1,23 @@ +errRcmbTriggered = Reststrom durch Sensor erkannt. +errVehicleStateE = Fahrzeugsignalfehler. +errMode3DiodeCheck = Fahrzeug-Diodentest fehlgeschlagen - Manipulationserkennung. +errMcbType2Triggered = Schutzschalter des Typ-2-Anschlusses ausgelst. +errMcbSchukoTriggered = Schutzschalter der Haushaltssteckdose ausgelst. +errRcdTriggered = FI-Schutzschalter ausgelst. +errContactorWeld = Schtz geschweit. +errBackendDisconnected = Backend getrennt. +errActuatorLockingFailed = Verriegelung des Steckers fehlgeschlagen. +errActuatorLockingWithoutPlugFailed = Verriegelung ohne Stecker fehlgeschlagen. +errActuatorStuck = Aktor blockiert, kann nicht entriegeln. +errActuatorDetectionFailed = Aktor-Erkennung fehlgeschlagen. +errFwUpdateRunning = Firmware-Update luft. +errTilt = Die Ladestation blockiert einen mglichen Ladevorgang. +errWrongCpPrWiring = Problem mit CP/PR-Verkabelung. +errType2OverloadThr2 = Fahrzeugstromberlastung, Ladevorgang gestoppt. +errActuatorUnlockedWhileCharging = Aktor entriegelt whrend des Ladevorgangs. +errTiltPreventChargingUntilReboot = Die Ladestation blockiert einen mglichen Ladevorgang, Laden erst nach Neustart mglich. +errPic24 = PIC24 Fehler. +errUsbStickHandling = USB-Stick-Verarbeitung luft. +errIncorrectPhaseInstallation = Falsche Phasenrotation erkannt. +errNoPower = Kein Strom an der Hauptversorgung erkannt. +firmwareOutdated = Hinweis: Fr Ihre Ladestation ist ein Update verfgbar. Um "Energie seit Ladebeginn" nutzen zu knnen wird die Firmware-Version 5.22 oder neuer bentigt. \ No newline at end of file diff --git a/io.openems.edge.evcs.mennekes/src/io/openems/edge/evcs/mennekes/translation_en.properties b/io.openems.edge.evcs.mennekes/src/io/openems/edge/evcs/mennekes/translation_en.properties new file mode 100644 index 00000000000..d417a49964c --- /dev/null +++ b/io.openems.edge.evcs.mennekes/src/io/openems/edge/evcs/mennekes/translation_en.properties @@ -0,0 +1,23 @@ +errRcmbTriggered = Residual current detected via sensor. +errVehicleStateE = Vehicle signals error. +errMode3DiodeCheck = Vehicle diode check failed - tamper detection. +errMcbType2Triggered = MCB of type 2 socket triggered. +errMcbSchukoTriggered = MCB of domestic socket triggered. +errRcdTriggered = RCD triggered. +errContactorWeld = Contactor welded. +errBackendDisconnected = Backend disconnected. +errActuatorLockingFailed = Plug locking failed. +errActuatorLockingWithoutPlugFailed = Locking without plug error. +errActuatorStuck = Actuator stuck, cannot unlock. +errActuatorDetectionFailed = Actuator detection failed. +errFwUpdateRunning = Firmware update in progress. +errTilt = The charge point is tilted. +errWrongCpPrWiring = CP/PR wiring issue. +errType2OverloadThr2 = Car current overload, charging stopped. +errActuatorUnlockedWhileCharging = Actuator unlocked while charging. +errTiltPreventChargingUntilReboot = The charge point was tilted and charging is prevented until reboot. +errPic24 = PIC24 error. +errUsbStickHandling = USB stick handling in progress. +errIncorrectPhaseInstallation = Incorrect phase rotation direction detected. +errNoPower = No power on mains detected. +firmwareOutdated = Note: An update is available for your charging station. Firmware version 5.22 or newer is required to use the "Energy since start of charging" function. \ No newline at end of file diff --git a/io.openems.edge.evcs.mennekes/test/.gitignore b/io.openems.edge.evcs.mennekes/test/.gitignore new file mode 100644 index 00000000000..e69de29bb2d diff --git a/io.openems.edge.evcs.mennekes/test/io/openems/edge/evcs/mennekes/MennekesTest.java b/io.openems.edge.evcs.mennekes/test/io/openems/edge/evcs/mennekes/MennekesTest.java new file mode 100644 index 00000000000..9727e0f0df1 --- /dev/null +++ b/io.openems.edge.evcs.mennekes/test/io/openems/edge/evcs/mennekes/MennekesTest.java @@ -0,0 +1,53 @@ +package io.openems.edge.evcs.mennekes; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +import io.openems.common.types.ChannelAddress; +import io.openems.edge.bridge.modbus.test.DummyModbusBridge; +import io.openems.edge.common.test.AbstractComponentTest.TestCase; +import io.openems.edge.common.test.ComponentTest; +import io.openems.edge.common.test.DummyConfigurationAdmin; +import io.openems.edge.evcs.api.Evcs; +import io.openems.edge.evcs.api.Status; + +public class MennekesTest { + + private static final String EVCS_ID = "evcs0"; + private static final String MODBUS_ID = "modbus0"; + + @Test + public void test() throws Exception { + new ComponentTest(new EvcsMennekesImpl()) // + .addReference("cm", new DummyConfigurationAdmin()) // + .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)// + // Task 2 + .withRegister(104, 6))// + + .activate(MyConfig.create() // + .setModbusId(MODBUS_ID) // + .setId(EVCS_ID) // + .setModbusUnitId(1) // + .setMaxHwPower(10_000) // + .setMinHwPower(5_000) // + .build()) // + + .next(new TestCase()// + .output(new ChannelAddress(EVCS_ID, EvcsMennekes.ChannelId.OCPP_CP_STATUS.id()), + MennekesOcppState.CHARGING) + .output(new ChannelAddress(EVCS_ID, Evcs.ChannelId.STATUS.id()), Status.CHARGING)); + } + + @Test + public void parseSoftwareVersionTest() { + // raw value from fems4 test + int registerValue = 892219954; + var firmwareVersion = EvcsMennekesImpl.parseSoftwareVersion(registerValue); + assertEquals("5.22", firmwareVersion); + // raw value from fems4 test + registerValue = 892219698; + firmwareVersion = EvcsMennekesImpl.parseSoftwareVersion(registerValue); + assertEquals("5.12", firmwareVersion); + } +} diff --git a/io.openems.edge.evcs.mennekes/test/io/openems/edge/evcs/mennekes/MyConfig.java b/io.openems.edge.evcs.mennekes/test/io/openems/edge/evcs/mennekes/MyConfig.java new file mode 100644 index 00000000000..fc6425b8470 --- /dev/null +++ b/io.openems.edge.evcs.mennekes/test/io/openems/edge/evcs/mennekes/MyConfig.java @@ -0,0 +1,100 @@ +package io.openems.edge.evcs.mennekes; + +import io.openems.common.test.AbstractComponentConfig; +import io.openems.common.utils.ConfigUtils; + +@SuppressWarnings("all") +public class MyConfig extends AbstractComponentConfig implements Config { + + protected static class Builder { + private String id; + private String modbusId; + private int modbusUnitId; + private int minHwPower; + private int maxHwPower; + + private Builder() { + } + + public Builder setId(String id) { + this.id = id; + return this; + } + + public Builder setModbusId(String modbusId) { + this.modbusId = modbusId; + return this; + } + + public Builder setModbusUnitId(int modbusUnitId) { + this.modbusUnitId = modbusUnitId; + return this; + } + + public Builder setMinHwPower(int minHwPower) { + this.minHwPower = minHwPower; + return this; + } + + public Builder setMaxHwPower(int maxHwPower) { + this.maxHwPower = maxHwPower; + return this; + } + + public MyConfig build() { + return new MyConfig(this); + } + } + + /** + * Create a Config builder. + * + * @return a {@link Builder} + */ + public static Builder create() { + return new Builder(); + } + + private final Builder builder; + + private MyConfig(Builder builder) { + super(Config.class, builder.id); + this.builder = builder; + } + + @Override + public boolean debugMode() { + return false; + } + + @Override + public int minHwCurrent() { + return this.builder.minHwPower; + } + + @Override + public int maxHwCurrent() { + return this.builder.maxHwPower; + } + + @Override + public String modbus_id() { + return this.builder.modbusId; + } + + @Override + public String Modbus_target() { + return ConfigUtils.generateReferenceTargetFilter(this.id(), this.modbus_id()); + } + + @Override + public int modbusUnitId() { + return this.builder.modbusUnitId; + } + + @Override + public boolean readOnly() { + return true; + } + +} diff --git a/io.openems.edge.evcs.ocpp.abl/.classpath b/io.openems.edge.evcs.ocpp.abl/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.evcs.ocpp.abl/.classpath +++ b/io.openems.edge.evcs.ocpp.abl/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.evcs.ocpp.abl/src/io/openems/edge/evcs/ocpp/abl/EvcsOcppAbl.java b/io.openems.edge.evcs.ocpp.abl/src/io/openems/edge/evcs/ocpp/abl/EvcsOcppAbl.java index 1cbcf9933a7..d76f92df33b 100644 --- a/io.openems.edge.evcs.ocpp.abl/src/io/openems/edge/evcs/ocpp/abl/EvcsOcppAbl.java +++ b/io.openems.edge.evcs.ocpp.abl/src/io/openems/edge/evcs/ocpp/abl/EvcsOcppAbl.java @@ -7,8 +7,10 @@ import io.openems.edge.evcs.api.Evcs; import io.openems.edge.evcs.api.ManagedEvcs; import io.openems.edge.evcs.api.MeasuringEvcs; +import io.openems.edge.meter.api.ElectricityMeter; -public interface EvcsOcppAbl extends Evcs, MeasuringEvcs, ManagedEvcs, OpenemsComponent, EventHandler { +public interface EvcsOcppAbl + extends Evcs, MeasuringEvcs, ManagedEvcs, ElectricityMeter, OpenemsComponent, EventHandler { public enum ChannelId implements io.openems.edge.common.channel.ChannelId { ; diff --git a/io.openems.edge.evcs.ocpp.abl/src/io/openems/edge/evcs/ocpp/abl/EvcsOcppAblImpl.java b/io.openems.edge.evcs.ocpp.abl/src/io/openems/edge/evcs/ocpp/abl/EvcsOcppAblImpl.java index 30901ec28f0..d36fc398443 100644 --- a/io.openems.edge.evcs.ocpp.abl/src/io/openems/edge/evcs/ocpp/abl/EvcsOcppAblImpl.java +++ b/io.openems.edge.evcs.ocpp.abl/src/io/openems/edge/evcs/ocpp/abl/EvcsOcppAblImpl.java @@ -25,6 +25,7 @@ import eu.chargetime.ocpp.model.core.DataTransferRequest; import eu.chargetime.ocpp.model.remotetrigger.TriggerMessageRequest; import eu.chargetime.ocpp.model.remotetrigger.TriggerMessageRequestType; +import io.openems.common.types.MeterType; import io.openems.edge.common.component.ComponentManager; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.event.EdgeEventConstants; @@ -38,6 +39,7 @@ import io.openems.edge.evcs.ocpp.common.OcppInformations; import io.openems.edge.evcs.ocpp.common.OcppProfileType; import io.openems.edge.evcs.ocpp.common.OcppStandardRequests; +import io.openems.edge.meter.api.ElectricityMeter; import io.openems.edge.timedata.api.Timedata; @Designate(ocd = Config.class, factory = true) @@ -51,7 +53,7 @@ EdgeEventConstants.TOPIC_CYCLE_AFTER_PROCESS_IMAGE // }) public class EvcsOcppAblImpl extends AbstractManagedOcppEvcsComponent - implements EvcsOcppAbl, Evcs, MeasuringEvcs, ManagedEvcs, OpenemsComponent, EventHandler { + implements EvcsOcppAbl, Evcs, MeasuringEvcs, ManagedEvcs, ElectricityMeter, OpenemsComponent, EventHandler { // Default value for the hardware limit private static final Integer DEFAULT_HARDWARE_LIMIT = 22080; @@ -86,8 +88,8 @@ public EvcsOcppAblImpl() { super(// PROFILE_TYPES, // OpenemsComponent.ChannelId.values(), // + ElectricityMeter.ChannelId.values(), // Evcs.ChannelId.values(), // - AbstractManagedOcppEvcsComponent.ChannelId.values(), // ManagedEvcs.ChannelId.values(), // MeasuringEvcs.ChannelId.values(), // EvcsOcppAbl.ChannelId.values() // @@ -128,6 +130,11 @@ public void handleEvent(Event event) { super.handleEvent(event); } + @Override + public MeterType getMeterType() { + return MeterType.MANAGED_CONSUMPTION_METERED; + } + @Override public OcppStandardRequests getStandardRequests() { AbstractManagedOcppEvcsComponent evcs = this; diff --git a/io.openems.edge.evcs.ocpp.abl/test/io/openems/edge/evcs/ocpp/abl/EvcsOcppAblImplTest.java b/io.openems.edge.evcs.ocpp.abl/test/io/openems/edge/evcs/ocpp/abl/EvcsOcppAblImplTest.java index c3dc9a84329..13123f22149 100644 --- a/io.openems.edge.evcs.ocpp.abl/test/io/openems/edge/evcs/ocpp/abl/EvcsOcppAblImplTest.java +++ b/io.openems.edge.evcs.ocpp.abl/test/io/openems/edge/evcs/ocpp/abl/EvcsOcppAblImplTest.java @@ -8,15 +8,13 @@ public class EvcsOcppAblImplTest { - private static final String COMPONENT_ID = "evcs0"; - @Test public void test() throws Exception { new ComponentTest(new EvcsOcppAblImpl()) // .addReference("componentManager", new DummyComponentManager()) // .addReference("evcsPower", new DummyEvcsPower()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("evcs0") // .setConnectorId(0) // .setOcppId("") // .setLogicalId("") // diff --git a/io.openems.edge.evcs.ocpp.common/.classpath b/io.openems.edge.evcs.ocpp.common/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.evcs.ocpp.common/.classpath +++ b/io.openems.edge.evcs.ocpp.common/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.evcs.ocpp.common/src/io/openems/edge/evcs/ocpp/common/AbstractManagedOcppEvcsComponent.java b/io.openems.edge.evcs.ocpp.common/src/io/openems/edge/evcs/ocpp/common/AbstractManagedOcppEvcsComponent.java index f4724d48eff..604b03ad94e 100644 --- a/io.openems.edge.evcs.ocpp.common/src/io/openems/edge/evcs/ocpp/common/AbstractManagedOcppEvcsComponent.java +++ b/io.openems.edge.evcs.ocpp.common/src/io/openems/edge/evcs/ocpp/common/AbstractManagedOcppEvcsComponent.java @@ -1,5 +1,8 @@ package io.openems.edge.evcs.ocpp.common; +import static io.openems.common.utils.FunctionUtils.doNothing; +import static io.openems.edge.common.type.TypeUtils.getAsType; + import java.util.Arrays; import java.util.HashSet; import java.util.List; @@ -20,14 +23,13 @@ import io.openems.common.types.ChannelAddress; import io.openems.common.types.OpenemsType; import io.openems.edge.common.channel.Channel; -import io.openems.edge.common.channel.Doc; import io.openems.edge.common.event.EdgeEventConstants; -import io.openems.edge.common.type.TypeUtils; import io.openems.edge.evcs.api.AbstractManagedEvcsComponent; import io.openems.edge.evcs.api.Evcs; import io.openems.edge.evcs.api.ManagedEvcs; import io.openems.edge.evcs.api.MeasuringEvcs; import io.openems.edge.evcs.api.Status; +import io.openems.edge.meter.api.ElectricityMeter; import io.openems.edge.timedata.api.TimedataProvider; /** @@ -59,7 +61,7 @@ *
  • */ public abstract class AbstractManagedOcppEvcsComponent extends AbstractManagedEvcsComponent - implements Evcs, ManagedEvcs, MeasuringEvcs, EventHandler, TimedataProvider { + implements Evcs, ManagedEvcs, MeasuringEvcs, ElectricityMeter, EventHandler, TimedataProvider { private final Logger log = LoggerFactory.getLogger(AbstractManagedOcppEvcsComponent.class); @@ -83,20 +85,6 @@ protected AbstractManagedOcppEvcsComponent(OcppProfileType[] profileTypes, this.profileTypes = new HashSet<>(Arrays.asList(profileTypes)); } - public enum ChannelId implements io.openems.edge.common.channel.ChannelId { - ; - private final Doc doc; - - private ChannelId(Doc doc) { - this.doc = doc; - } - - @Override - public Doc doc() { - return this.doc; - } - } - @Override protected void activate(ComponentContext context, String id, String alias, boolean enabled) { super.activate(context, id, alias, enabled); @@ -114,11 +102,10 @@ protected void modified(ComponentContext context, String id, String alias, boole private void setInitialSettings() { // Normally the limits are set automatically when the phase channel is set, but // not every OCPP charger provides the information about the number of phases. - int fixedMaximum = this.getFixedMaximumHardwarePower().orElse(DEFAULT_MAXIMUM_HARDWARE_POWER); - int fixedMinimum = this.getFixedMinimumHardwarePower().orElse(DEFAULT_MINIMUM_HARDWARE_POWER); - - this.getMaximumHardwarePowerChannel().setNextValue(fixedMaximum); - this.getMinimumHardwarePowerChannel().setNextValue(fixedMinimum); + this.getMaximumHardwarePowerChannel().setNextValue(// + this.getFixedMaximumHardwarePower().orElse(DEFAULT_MAXIMUM_HARDWARE_POWER)); + this.getMinimumHardwarePowerChannel().setNextValue(// + this.getFixedMinimumHardwarePower().orElse(DEFAULT_MINIMUM_HARDWARE_POWER)); } @Override @@ -157,22 +144,22 @@ private void setInitialTotalEnergyFromTimedata() { if (timedata == null || componentId == null) { return; } else { - timedata.getLatestValue(new ChannelAddress(componentId, Evcs.ChannelId.ACTIVE_CONSUMPTION_ENERGY.id())) + timedata.getLatestValue( + new ChannelAddress(componentId, ElectricityMeter.ChannelId.ACTIVE_PRODUCTION_ENERGY.id())) .thenAccept(totalEnergyOpt -> { - if (this.getActiveConsumptionEnergy().isDefined()) { + if (this.getActiveProductionEnergy().isDefined()) { // Value has been read from device in the meantime return; } if (totalEnergyOpt.isPresent()) { try { - this._setActiveConsumptionEnergy( - TypeUtils.getAsType(OpenemsType.LONG, totalEnergyOpt.get())); + this._setActiveProductionEnergy(getAsType(OpenemsType.LONG, totalEnergyOpt.get())); } catch (IllegalArgumentException e) { - this._setActiveConsumptionEnergy(TypeUtils.getAsType(OpenemsType.LONG, 0L)); + this._setActiveProductionEnergy(getAsType(OpenemsType.LONG, 0L)); } } else { - this._setActiveConsumptionEnergy(TypeUtils.getAsType(OpenemsType.LONG, 0L)); + this._setActiveConsumptionEnergy(getAsType(OpenemsType.LONG, 0L)); } }); } @@ -284,7 +271,7 @@ private void resetMeasuredChannelValues() { Channel channel = this.channel(c); channel.setNextValue(null); } - this._setChargePower(0); + this._setActivePower(0); } /** @@ -293,20 +280,10 @@ private void resetMeasuredChannelValues() { private void checkCurrentState() { var state = this.getStatus(); switch (state) { - case CHARGING: - case READY_FOR_CHARGING: - break; - case CHARGING_FINISHED: - this.resetMeasuredChannelValues(); - break; - case CHARGING_REJECTED: - case ENERGY_LIMIT_REACHED: - case ERROR: - case NOT_READY_FOR_CHARGING: - case STARTING: - case UNDEFINED: - this._setChargePower(0); - break; + case CHARGING, READY_FOR_CHARGING // + -> doNothing(); + case CHARGING_REJECTED, ENERGY_LIMIT_REACHED, ERROR, NOT_READY_FOR_CHARGING, STARTING, UNDEFINED // + -> this._setActivePower(0); } } @@ -338,11 +315,9 @@ protected void logWarn(Logger log, String message) { @Override public String debugLog() { - if (this instanceof ManagedEvcs) { - return "Limit:" + ((ManagedEvcs) this).getSetChargePowerLimit().orElse(null) + "|" - + this.getStatus().getName(); - } - return "Power:" + this.getChargePower().orElse(0) + "|" + this.getStatus().getName(); + return "P:" + this.getActivePower().orElse(null) // + + "|Limit:" + this.getSetChargePowerLimit().orElse(null) // + + "|" + this.getStatus().getName(); } @Override diff --git a/io.openems.edge.evcs.ocpp.common/src/io/openems/edge/evcs/ocpp/common/OcppInformations.java b/io.openems.edge.evcs.ocpp.common/src/io/openems/edge/evcs/ocpp/common/OcppInformations.java index f4f4d7be976..6751117b3fb 100644 --- a/io.openems.edge.evcs.ocpp.common/src/io/openems/edge/evcs/ocpp/common/OcppInformations.java +++ b/io.openems.edge.evcs.ocpp.common/src/io/openems/edge/evcs/ocpp/common/OcppInformations.java @@ -1,9 +1,9 @@ package io.openems.edge.evcs.ocpp.common; import io.openems.edge.common.channel.ChannelId; -import io.openems.edge.evcs.api.Evcs; import io.openems.edge.evcs.api.MeasuringEvcs; import io.openems.edge.evcs.api.SocEvcs; +import io.openems.edge.meter.api.ElectricityMeter; public enum OcppInformations { @@ -95,7 +95,7 @@ public enum OcppInformations { * UnitOfMeasure for frequency, the UnitOfMeasure for any SampledValue with * measurand: Frequency is Hertz. */ - CORE_METER_VALUES_FREQUENCY("Frequency", MeasuringEvcs.ChannelId.FREQUENCY), + CORE_METER_VALUES_FREQUENCY("Frequency", ElectricityMeter.ChannelId.FREQUENCY), /** * Instantaneous active power exported by EV. (W) @@ -105,7 +105,7 @@ public enum OcppInformations { /** * Instantaneous active power imported by EV. (W) */ - CORE_METER_VALUES_POWER_ACTIVE_IMPORT("Power.Active.Import", Evcs.ChannelId.CHARGE_POWER), + CORE_METER_VALUES_POWER_ACTIVE_IMPORT("Power.Active.Import", ElectricityMeter.ChannelId.ACTIVE_POWER), /** * Instantaneous power factor of total energy flow. @@ -146,7 +146,7 @@ public enum OcppInformations { /** * Instantaneous AC RMS supply voltage. */ - CORE_METER_VALUES_VOLTAGE("Voltage", MeasuringEvcs.ChannelId.VOLTAGE); + CORE_METER_VALUES_VOLTAGE("Voltage", ElectricityMeter.ChannelId.VOLTAGE); private final String ocppValue; private final ChannelId channelId; diff --git a/io.openems.edge.evcs.ocpp.ies.keywatt.singleccs/.classpath b/io.openems.edge.evcs.ocpp.ies.keywatt.singleccs/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.evcs.ocpp.ies.keywatt.singleccs/.classpath +++ b/io.openems.edge.evcs.ocpp.ies.keywatt.singleccs/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.evcs.ocpp.ies.keywatt.singleccs/src/io/openems/edge/evcs/ocpp/ies/keywatt/singleccs/EvcsOcppIesKeywattSingle.java b/io.openems.edge.evcs.ocpp.ies.keywatt.singleccs/src/io/openems/edge/evcs/ocpp/ies/keywatt/singleccs/EvcsOcppIesKeywattSingle.java index fed8dedd6f2..4f3ac815d89 100644 --- a/io.openems.edge.evcs.ocpp.ies.keywatt.singleccs/src/io/openems/edge/evcs/ocpp/ies/keywatt/singleccs/EvcsOcppIesKeywattSingle.java +++ b/io.openems.edge.evcs.ocpp.ies.keywatt.singleccs/src/io/openems/edge/evcs/ocpp/ies/keywatt/singleccs/EvcsOcppIesKeywattSingle.java @@ -8,9 +8,10 @@ import io.openems.edge.evcs.api.ManagedEvcs; import io.openems.edge.evcs.api.MeasuringEvcs; import io.openems.edge.evcs.api.SocEvcs; +import io.openems.edge.meter.api.ElectricityMeter; public interface EvcsOcppIesKeywattSingle - extends Evcs, ManagedEvcs, MeasuringEvcs, OpenemsComponent, EventHandler, SocEvcs { + extends Evcs, ManagedEvcs, MeasuringEvcs, ElectricityMeter, OpenemsComponent, EventHandler, SocEvcs { public enum ChannelId implements io.openems.edge.common.channel.ChannelId { ; diff --git a/io.openems.edge.evcs.ocpp.ies.keywatt.singleccs/src/io/openems/edge/evcs/ocpp/ies/keywatt/singleccs/EvcsOcppIesKeywattSingleImpl.java b/io.openems.edge.evcs.ocpp.ies.keywatt.singleccs/src/io/openems/edge/evcs/ocpp/ies/keywatt/singleccs/EvcsOcppIesKeywattSingleImpl.java index b27dde46b05..b344667a3ae 100644 --- a/io.openems.edge.evcs.ocpp.ies.keywatt.singleccs/src/io/openems/edge/evcs/ocpp/ies/keywatt/singleccs/EvcsOcppIesKeywattSingleImpl.java +++ b/io.openems.edge.evcs.ocpp.ies.keywatt.singleccs/src/io/openems/edge/evcs/ocpp/ies/keywatt/singleccs/EvcsOcppIesKeywattSingleImpl.java @@ -23,6 +23,7 @@ import eu.chargetime.ocpp.model.Request; import eu.chargetime.ocpp.model.core.ChangeConfigurationRequest; +import io.openems.common.types.MeterType; import io.openems.edge.common.component.ComponentManager; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.event.EdgeEventConstants; @@ -36,6 +37,7 @@ import io.openems.edge.evcs.ocpp.common.OcppInformations; import io.openems.edge.evcs.ocpp.common.OcppProfileType; import io.openems.edge.evcs.ocpp.common.OcppStandardRequests; +import io.openems.edge.meter.api.ElectricityMeter; import io.openems.edge.timedata.api.Timedata; @Designate(ocd = Config.class, factory = true) @@ -48,8 +50,8 @@ EdgeEventConstants.TOPIC_CYCLE_EXECUTE_WRITE, // EdgeEventConstants.TOPIC_CYCLE_AFTER_PROCESS_IMAGE // }) -public class EvcsOcppIesKeywattSingleImpl extends AbstractManagedOcppEvcsComponent - implements EvcsOcppIesKeywattSingle, Evcs, ManagedEvcs, MeasuringEvcs, OpenemsComponent, EventHandler, SocEvcs { +public class EvcsOcppIesKeywattSingleImpl extends AbstractManagedOcppEvcsComponent implements EvcsOcppIesKeywattSingle, + Evcs, ManagedEvcs, MeasuringEvcs, ElectricityMeter, OpenemsComponent, EventHandler, SocEvcs { // Profiles that a Ies KeyWatt is supporting private static final OcppProfileType[] PROFILE_TYPES = { // @@ -76,8 +78,8 @@ public EvcsOcppIesKeywattSingleImpl() { super(// PROFILE_TYPES, // OpenemsComponent.ChannelId.values(), // + ElectricityMeter.ChannelId.values(), // Evcs.ChannelId.values(), // - AbstractManagedOcppEvcsComponent.ChannelId.values(), // ManagedEvcs.ChannelId.values(), // MeasuringEvcs.ChannelId.values(), // SocEvcs.ChannelId.values(), // @@ -102,6 +104,11 @@ protected void deactivate() { super.deactivate(); } + @Override + public MeterType getMeterType() { + return MeterType.MANAGED_CONSUMPTION_METERED; + } + private void setInitalSettings(Config config) { this.config = config; this._setPowerPrecision(1); diff --git a/io.openems.edge.evcs.ocpp.ies.keywatt.singleccs/test/io/openems/edge/evcs/ocpp/ies/keywatt/singleccs/EvcsOcppIesKeywattSingleImplTest.java b/io.openems.edge.evcs.ocpp.ies.keywatt.singleccs/test/io/openems/edge/evcs/ocpp/ies/keywatt/singleccs/EvcsOcppIesKeywattSingleImplTest.java index 8d0bea95587..313bd8288ff 100644 --- a/io.openems.edge.evcs.ocpp.ies.keywatt.singleccs/test/io/openems/edge/evcs/ocpp/ies/keywatt/singleccs/EvcsOcppIesKeywattSingleImplTest.java +++ b/io.openems.edge.evcs.ocpp.ies.keywatt.singleccs/test/io/openems/edge/evcs/ocpp/ies/keywatt/singleccs/EvcsOcppIesKeywattSingleImplTest.java @@ -8,15 +8,13 @@ public class EvcsOcppIesKeywattSingleImplTest { - private static final String COMPONENT_ID = "evcs0"; - @Test public void test() throws Exception { new ComponentTest(new EvcsOcppIesKeywattSingleImpl()) // .addReference("componentManager", new DummyComponentManager()) // .addReference("evcsPower", new DummyEvcsPower()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("evcs0") // .setConnectorId(0) // .setOcppId("") // .setDebugMode(false) // diff --git a/io.openems.edge.evcs.ocpp.server/.classpath b/io.openems.edge.evcs.ocpp.server/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.evcs.ocpp.server/.classpath +++ b/io.openems.edge.evcs.ocpp.server/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.evcs.ocpp.server/bnd.bnd b/io.openems.edge.evcs.ocpp.server/bnd.bnd index 500e91981af..da432b59483 100644 --- a/io.openems.edge.evcs.ocpp.server/bnd.bnd +++ b/io.openems.edge.evcs.ocpp.server/bnd.bnd @@ -10,6 +10,7 @@ Bundle-Version: 1.0.0.${tstamp} io.openems.edge.common,\ io.openems.edge.evcs.api,\ io.openems.edge.evcs.ocpp.common,\ + io.openems.edge.meter.api,\ io.openems.edge.timedata.api,\ io.openems.wrapper.eu.chargetime.ocpp,\ diff --git a/io.openems.edge.evcs.ocpp.server/src/io/openems/edge/evcs/ocpp/server/CoreEventHandlerImpl.java b/io.openems.edge.evcs.ocpp.server/src/io/openems/edge/evcs/ocpp/server/CoreEventHandlerImpl.java index 1342ff71913..a5703c38370 100644 --- a/io.openems.edge.evcs.ocpp.server/src/io/openems/edge/evcs/ocpp/server/CoreEventHandlerImpl.java +++ b/io.openems.edge.evcs.ocpp.server/src/io/openems/edge/evcs/ocpp/server/CoreEventHandlerImpl.java @@ -121,7 +121,7 @@ public MeterValuesConfirmation handleMeterValuesRequest(UUID sessionIndex, Meter */ var format = value.getFormat(); if (format.equals(ValueFormat.SignedData)) { - val = this.fromHexToDezString(val); + val = fromHexToDezString(val); } var measurand = OcppInformations @@ -143,14 +143,14 @@ public MeterValuesConfirmation handleMeterValuesRequest(UUID sessionIndex, Meter case CORE_METER_VALUES_ENERGY_ACTIVE_IMPORT_INTERVAL: case CORE_METER_VALUES_ENERGY_ACTIVE_EXPORT_INTERVAL: if (unit.equals(Unit.KWH)) { - val = this.multipliedByThousand(val); + val = multipliedByThousand(val); } correctValue = Double.valueOf(val); break; case CORE_METER_VALUES_ENERGY_ACTIVE_IMPORT_REGISTER: if (unit.equals(Unit.KWH)) { - val = this.multipliedByThousand(val); + val = multipliedByThousand(val); } correctValue = Double.valueOf(val); @@ -165,13 +165,12 @@ public MeterValuesConfirmation handleMeterValuesRequest(UUID sessionIndex, Meter break; } - var sessionEnergy = 0; - var totalEnergy = 0L; - /* * Calculating the energy in this session and in total for the given energy * value. */ + final int sessionEnergy; + final long totalEnergy; if (evcs.returnsSessionEnergy()) { sessionEnergy = (int) energy; totalEnergy = evcs.getSessionStart().getEnergy() + energy; @@ -180,7 +179,7 @@ public MeterValuesConfirmation handleMeterValuesRequest(UUID sessionIndex, Meter totalEnergy = energy; } evcs._setEnergySession(sessionEnergy); - evcs._setActiveConsumptionEnergy(totalEnergy); + evcs._setActiveProductionEnergy(totalEnergy); break; case CORE_METER_VALUES_ENERGY_REACTIVE_EXPORT_REGISTER: @@ -188,7 +187,7 @@ public MeterValuesConfirmation handleMeterValuesRequest(UUID sessionIndex, Meter case CORE_METER_VALUES_ENERGY_REACTIVE_EXPORT_INTERVAL: case CORE_METER_VALUES_ENERGY_REACTIVE_IMPORT_INTERVAL: if (unit.equals(Unit.KVARH)) { - val = this.multipliedByThousand(val); + val = multipliedByThousand(val); } correctValue = Double.valueOf(val); break; @@ -197,7 +196,7 @@ public MeterValuesConfirmation handleMeterValuesRequest(UUID sessionIndex, Meter case CORE_METER_VALUES_POWER_ACTIVE_IMPORT: case CORE_METER_VALUES_POWER_OFFERED: if (unit.equals(Unit.KW)) { - val = this.multipliedByThousand(val); + val = multipliedByThousand(val); } correctValue = (int) Math.round(Double.parseDouble(val)); @@ -226,7 +225,7 @@ public MeterValuesConfirmation handleMeterValuesRequest(UUID sessionIndex, Meter case CORE_METER_VALUES_POWER_REACTIVE_EXPORT: case CORE_METER_VALUES_POWER_REACTIVE_IMPORT: if (unit.equals(Unit.KVAR)) { - val = this.multipliedByThousand(val); + val = multipliedByThousand(val); } correctValue = (int) Math.round(Double.parseDouble(val)); break; @@ -266,49 +265,52 @@ public StatusNotificationConfirmation handleStatusNotificationRequest(UUID sessi Status evcsStatus = null; var ocppStatus = request.getStatus(); switch (ocppStatus) { - case Available: + case Available -> { evcs._setChargingstationCommunicationFailed(false); evcsStatus = Status.NOT_READY_FOR_CHARGING; - break; - case Charging: + } + case Charging -> { evcsStatus = Status.CHARGING; // Reset the end charge session stamp evcs.getSessionEnd().resetChargeSessionStampIfPresent(); // Set the start charge session stamp - evcs.getSessionStart().setChargeSessionStampIfNotPresent( - Instant.now(this.parent.componentManager.getClock()), evcs.getActiveConsumptionEnergy().orElse(0L)); - break; - case Faulted: + evcs.getSessionStart().setChargeSessionStampIfNotPresent(// + Instant.now(this.parent.componentManager.getClock()), // + evcs.getActiveProductionEnergy().orElse(0L)); + } + case Faulted -> { evcsStatus = Status.ERROR; - break; - case Finishing: - evcsStatus = Status.CHARGING_FINISHED; + } + case Finishing -> { + evcsStatus = Status.CHARGING_REJECTED; // Reset the start charge session stamp evcs.getSessionStart().resetChargeSessionStampIfPresent(); - evcs.getSessionEnd().setChargeSessionStampIfNotPresent(Instant.now(this.parent.componentManager.getClock()), - evcs.getActiveConsumptionEnergy().orElse(0L)); - break; - case Preparing: + evcs.getSessionEnd().setChargeSessionStampIfNotPresent(// + Instant.now(this.parent.componentManager.getClock()), // + evcs.getActiveProductionEnergy().orElse(0L)); + + } + case Preparing -> { evcsStatus = Status.READY_FOR_CHARGING; - break; - case Reserved: + } + case Reserved -> { this.logDebug("Reservation currently not supported"); - break; - case SuspendedEV: + } + case SuspendedEV -> { evcsStatus = Status.CHARGING_REJECTED; - break; - case SuspendedEVSE: + } + case SuspendedEVSE -> { evcsStatus = Status.CHARGING_REJECTED; - break; - case Unavailable: + } + case Unavailable -> { this.logDebug("Charging Station is Unavailable."); evcs._setChargingstationCommunicationFailed(true); evcsStatus = Status.ERROR; - break; + } } if (ocppStatus != ChargePointStatus.Unavailable) { @@ -348,7 +350,7 @@ public StopTransactionConfirmation handleStopTransactionRequest(UUID sessionInde } else { evcs = this.getEvcsBySessionIndexAndConnector(sessionIndex, request.getTransactionId()); } - evcs._setStatus(Status.CHARGING_FINISHED); + evcs._setStatus(Status.CHARGING_REJECTED); var response = new StopTransactionConfirmation(); response.setIdTagInfo(tag); @@ -403,7 +405,7 @@ private AbstractManagedOcppEvcsComponent getEvcsBySessionIndexAndConnector(UUID * @param hex given value in hex * @return Decimal value as String */ - public String fromHexToDezString(String hex) { + public static String fromHexToDezString(String hex) { var dezValue = Integer.parseInt(hex, 16); return String.valueOf(dezValue); } @@ -414,7 +416,7 @@ public String fromHexToDezString(String hex) { * @param val value * @return Value / 1000 as String */ - private String multipliedByThousand(String val) { + private static String multipliedByThousand(String val) { if (val.isEmpty()) { return val; } @@ -435,8 +437,8 @@ private void setPowerDependingOnEnergy(AbstractManagedOcppEvcsComponent evcs, Do var power = 0; if (lastChargingProperty != null) { - power = this.calculateChargePower(lastChargingProperty, currentEnergy, timestamp); - evcs._setChargePower(power); + power = this.calculateActivePower(lastChargingProperty, currentEnergy, timestamp); + evcs._setActivePower(power); } evcs.setLastChargingProperty(new ChargingProperty(power, currentEnergy, timestamp)); } @@ -449,7 +451,7 @@ private void setPowerDependingOnEnergy(AbstractManagedOcppEvcsComponent evcs, Do * @param timestamp Time when the current Energy was measured. * @return current power */ - private int calculateChargePower(ChargingProperty lastMeterValue, double currentEnergy, ZonedDateTime timestamp) { + private int calculateActivePower(ChargingProperty lastMeterValue, double currentEnergy, ZonedDateTime timestamp) { double diffseconds = Duration.between(timestamp, lastMeterValue.getTimestamp()).getSeconds(); diff --git a/io.openems.edge.evcs.spelsberg/.classpath b/io.openems.edge.evcs.spelsberg/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.evcs.spelsberg/.classpath +++ b/io.openems.edge.evcs.spelsberg/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.evcs.spelsberg/bnd.bnd b/io.openems.edge.evcs.spelsberg/bnd.bnd index 352d8d2c11e..be0c8b9d00b 100644 --- a/io.openems.edge.evcs.spelsberg/bnd.bnd +++ b/io.openems.edge.evcs.spelsberg/bnd.bnd @@ -10,6 +10,7 @@ Bundle-Version: 1.0.0.${tstamp} io.openems.edge.bridge.modbus,\ io.openems.edge.common,\ io.openems.edge.evcs.api,\ + io.openems.edge.meter.api,\ -testpath: \ ${testpath} \ No newline at end of file diff --git a/io.openems.edge.evcs.spelsberg/src/io/openems/edge/evcs/spelsberg/smart/EvcsSpelsbergSmart.java b/io.openems.edge.evcs.spelsberg/src/io/openems/edge/evcs/spelsberg/smart/EvcsSpelsbergSmart.java index 4b05ab4ca21..5e16a5ffacf 100644 --- a/io.openems.edge.evcs.spelsberg/src/io/openems/edge/evcs/spelsberg/smart/EvcsSpelsbergSmart.java +++ b/io.openems.edge.evcs.spelsberg/src/io/openems/edge/evcs/spelsberg/smart/EvcsSpelsbergSmart.java @@ -64,30 +64,6 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { .accessMode(AccessMode.READ_ONLY) // .text("Maximum current signaled to the EV for charging")), - CURRENT_L1(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.MILLIAMPERE).accessMode(AccessMode.READ_ONLY) // - .text("Current on L1")), - - CURRENT_L2(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.MILLIAMPERE).accessMode(AccessMode.READ_ONLY) // - .text("Current on L2")), - - CURRENT_L3(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.MILLIAMPERE).accessMode(AccessMode.READ_ONLY) // - .text("Current on L3")), - - POWER_L1(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.WATT).accessMode(AccessMode.READ_ONLY) // - .text("Charging power on L1")), - - POWER_L2(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.WATT).accessMode(AccessMode.READ_ONLY) // - .text("Charging power on L2")), - - POWER_L3(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.WATT).accessMode(AccessMode.READ_ONLY) // - .text("Charging power on L3")), - POWER_TOTAL(Doc.of(OpenemsType.INTEGER) // .unit(Unit.WATT).accessMode(AccessMode.READ_ONLY) // .text("Sum of active charging power")), @@ -132,60 +108,6 @@ public default void setApplyChargePowerLimit(Integer value) throws OpenemsNamedE this.getApplyChargePowerLimitChannel().setNextWriteValue(value); } - /** - * Gets the Channel for {@link ChannelId#POWER_L1}. - * - * @return the Channel - */ - public default Channel getChargePowerL1Channel() { - return this.channel(ChannelId.POWER_L1); - } - - /** - * Gets the Power on phase L1 in [W]. See {@link ChannelId#POWER_L1}. - * - * @return the Channel {@link Value} - */ - public default Value getChargePowerL1() { - return this.getChargePowerL1Channel().value(); - } - - /** - * Gets the Channel for {@link ChannelId#POWER_L2}. - * - * @return the Channel - */ - public default Channel getChargePowerL2Channel() { - return this.channel(ChannelId.POWER_L2); - } - - /** - * Gets the Power on phase L2 in [W]. See {@link ChannelId#POWER_L2}. - * - * @return the Channel {@link Value} - */ - public default Value getChargePowerL2() { - return this.getChargePowerL2Channel().value(); - } - - /** - * Gets the Channel for {@link ChannelId#POWER_L3}. - * - * @return the Channel - */ - public default Channel getChargePowerL3Channel() { - return this.channel(ChannelId.POWER_L3); - } - - /** - * Gets the Power on phase L3 in [W]. See {@link ChannelId#POWER_L3}. - * - * @return the Channel {@link Value} - */ - public default Value getChargePowerL3() { - return this.getChargePowerL3Channel().value(); - } - /** * Gets the Channel for {@link ChannelId#POWER_TOTAL}. * diff --git a/io.openems.edge.evcs.spelsberg/src/io/openems/edge/evcs/spelsberg/smart/EvcsSpelsbergSmartImpl.java b/io.openems.edge.evcs.spelsberg/src/io/openems/edge/evcs/spelsberg/smart/EvcsSpelsbergSmartImpl.java index 71e6df3342a..e8901fc7d8c 100644 --- a/io.openems.edge.evcs.spelsberg/src/io/openems/edge/evcs/spelsberg/smart/EvcsSpelsbergSmartImpl.java +++ b/io.openems.edge.evcs.spelsberg/src/io/openems/edge/evcs/spelsberg/smart/EvcsSpelsbergSmartImpl.java @@ -22,6 +22,7 @@ import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.types.MeterType; import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent; import io.openems.edge.bridge.modbus.api.BridgeModbus; import io.openems.edge.bridge.modbus.api.ModbusComponent; @@ -44,6 +45,7 @@ import io.openems.edge.evcs.api.Phases; import io.openems.edge.evcs.api.Status; import io.openems.edge.evcs.api.WriteHandler; +import io.openems.edge.meter.api.ElectricityMeter; @Designate(ocd = Config.class, factory = true) @Component(// @@ -54,8 +56,8 @@ @EventTopics({ // EdgeEventConstants.TOPIC_CYCLE_EXECUTE_WRITE // }) -public class EvcsSpelsbergSmartImpl extends AbstractOpenemsModbusComponent - implements EvcsSpelsbergSmart, Evcs, ManagedEvcs, ModbusComponent, OpenemsComponent, EventHandler { +public class EvcsSpelsbergSmartImpl extends AbstractOpenemsModbusComponent implements EvcsSpelsbergSmart, Evcs, + ManagedEvcs, ModbusComponent, OpenemsComponent, EventHandler, ElectricityMeter { private final Logger log = LoggerFactory.getLogger(EvcsSpelsbergSmartImpl.class); private final ChargeStateHandler chargeStateHandler = new ChargeStateHandler(this); @@ -77,6 +79,7 @@ protected void setModbus(BridgeModbus modbus) { public EvcsSpelsbergSmartImpl() { super(// OpenemsComponent.ChannelId.values(), // + ElectricityMeter.ChannelId.values(), // ModbusComponent.ChannelId.values(), // Evcs.ChannelId.values(), // ManagedEvcs.ChannelId.values(), // @@ -127,23 +130,24 @@ protected ModbusProtocol defineModbusProtocol() { m(EvcsSpelsbergSmart.ChannelId.CABLE_STATE, new UnsignedWordElement(1004))), new FC3ReadRegistersTask(1008, Priority.LOW, - m(EvcsSpelsbergSmart.ChannelId.CURRENT_L1, new UnsignedWordElement(1008)), + m(ElectricityMeter.ChannelId.CURRENT_L1, new UnsignedWordElement(1008)), new DummyRegisterElement(1009), - m(EvcsSpelsbergSmart.ChannelId.CURRENT_L2, new UnsignedWordElement(1010)), + m(ElectricityMeter.ChannelId.CURRENT_L2, new UnsignedWordElement(1010)), new DummyRegisterElement(1011), - m(EvcsSpelsbergSmart.ChannelId.CURRENT_L3, new UnsignedWordElement(1012))), + m(ElectricityMeter.ChannelId.CURRENT_L3, new UnsignedWordElement(1012))), new FC3ReadRegistersTask(1020, Priority.HIGH, - m(Evcs.ChannelId.CHARGE_POWER, new UnsignedDoublewordElement(1020)), + m(ElectricityMeter.ChannelId.ACTIVE_POWER, new UnsignedDoublewordElement(1020)), + // TODO whats the difference between 1020 and 1022? m(EvcsSpelsbergSmart.ChannelId.POWER_TOTAL, new UnsignedDoublewordElement(1022)), - m(EvcsSpelsbergSmart.ChannelId.POWER_L1, new UnsignedDoublewordElement(1024)), + m(ElectricityMeter.ChannelId.ACTIVE_POWER_L1, new UnsignedDoublewordElement(1024)), new DummyRegisterElement(1026, 1027), - m(EvcsSpelsbergSmart.ChannelId.POWER_L2, new UnsignedDoublewordElement(1028)), + m(ElectricityMeter.ChannelId.ACTIVE_POWER_L2, new UnsignedDoublewordElement(1028)), new DummyRegisterElement(1030, 1031), - m(EvcsSpelsbergSmart.ChannelId.POWER_L3, new UnsignedDoublewordElement(1032))), + m(ElectricityMeter.ChannelId.ACTIVE_POWER_L3, new UnsignedDoublewordElement(1032))), new FC3ReadRegistersTask(1036, Priority.LOW, - m(Evcs.ChannelId.ACTIVE_CONSUMPTION_ENERGY, new UnsignedDoublewordElement(1036))), + m(ElectricityMeter.ChannelId.ACTIVE_PRODUCTION_ENERGY, new UnsignedDoublewordElement(1036))), new FC3ReadRegistersTask(1100, Priority.LOW, m(EvcsSpelsbergSmart.ChannelId.MAX_HARDWARE_CURRENT, new UnsignedWordElement(1100)), @@ -181,6 +185,11 @@ protected ModbusProtocol defineModbusProtocol() { return modbusProtocol; } + @Override + public MeterType getMeterType() { + return MeterType.MANAGED_CONSUMPTION_METERED; + } + @Override public String debugLog() { return "Status: " + getStatus().getName() + " | " + "Charging Power: " + getChargePowerTotal(); @@ -290,21 +299,13 @@ private void addStatusCallback() { */ private void addPhaseDetectionCallback() { final Consumer> setPhasesCallback = ignore -> { - - var phases = 0; - if (this.getChargePowerL1().isDefined() && this.getChargePowerL1().get() > 0) { - phases++; - } - if (this.getChargePowerL2().isDefined() && this.getChargePowerL2().get() > 0) { - phases++; - } - if (this.getChargePowerL3().isDefined() && this.getChargePowerL3().get() > 0) { - phases++; - } - - this._setPhases(phases); + this._setPhases(Evcs.evaluatePhaseCount(// + this.getActivePowerL1().get(), // + this.getActivePowerL2().get(), // + this.getActivePowerL3().get())); }; + // TODO remove this channel this.getChargePowerTotalChannel().onUpdate(setPhasesCallback); } diff --git a/io.openems.edge.evcs.spelsberg/test/io/openems/edge/evcs/spelsberg/smart/EvcsSpelsbergSmartImplTest.java b/io.openems.edge.evcs.spelsberg/test/io/openems/edge/evcs/spelsberg/smart/EvcsSpelsbergSmartImplTest.java index c1b4be1ea43..61854c8a834 100644 --- a/io.openems.edge.evcs.spelsberg/test/io/openems/edge/evcs/spelsberg/smart/EvcsSpelsbergSmartImplTest.java +++ b/io.openems.edge.evcs.spelsberg/test/io/openems/edge/evcs/spelsberg/smart/EvcsSpelsbergSmartImplTest.java @@ -8,17 +8,14 @@ public class EvcsSpelsbergSmartImplTest { - private static final String EVCS_ID = "evcs0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new EvcsSpelsbergSmartImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setModbusId(MODBUS_ID) // - .setId(EVCS_ID) // + .setModbusId("modbus0") // + .setId("evcs0") // .setModbusUnitId(255) // .setMaxHwCurrent(16000) // .setMinHwCurrent(6000) // diff --git a/io.openems.edge.evcs.webasto.next/.classpath b/io.openems.edge.evcs.webasto.next/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.evcs.webasto.next/.classpath +++ b/io.openems.edge.evcs.webasto.next/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.evcs.webasto.next/bnd.bnd b/io.openems.edge.evcs.webasto.next/bnd.bnd index d8f6c880de2..dedafbd9bf3 100644 --- a/io.openems.edge.evcs.webasto.next/bnd.bnd +++ b/io.openems.edge.evcs.webasto.next/bnd.bnd @@ -10,6 +10,7 @@ Bundle-Version: 1.0.0.${tstamp} io.openems.edge.bridge.modbus,\ io.openems.edge.common,\ io.openems.edge.evcs.api,\ + io.openems.edge.meter.api,\ -testpath: \ ${testpath} \ No newline at end of file diff --git a/io.openems.edge.evcs.webasto.next/src/io/openems/edge/evcs/webasto/next/EvcsWebastoNext.java b/io.openems.edge.evcs.webasto.next/src/io/openems/edge/evcs/webasto/next/EvcsWebastoNext.java index fe1dc68577d..06b57a37520 100644 --- a/io.openems.edge.evcs.webasto.next/src/io/openems/edge/evcs/webasto/next/EvcsWebastoNext.java +++ b/io.openems.edge.evcs.webasto.next/src/io/openems/edge/evcs/webasto/next/EvcsWebastoNext.java @@ -5,7 +5,6 @@ import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.types.OpenemsType; import io.openems.edge.common.channel.Doc; -import io.openems.edge.common.channel.IntegerReadChannel; import io.openems.edge.common.channel.IntegerWriteChannel; import io.openems.edge.common.channel.value.Value; import io.openems.edge.common.component.OpenemsComponent; @@ -31,26 +30,6 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { EVSE_ERROR_CODE(Doc.of(EvseErrorCode.values())), // - CURRENT_L1(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.MILLIAMPERE) // - .accessMode(AccessMode.READ_ONLY)), // - - CURRENT_L2(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.MILLIAMPERE) // - .accessMode(AccessMode.READ_ONLY)), // - - CURRENT_L3(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.MILLIAMPERE)), // - - POWER_L1(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.WATT)), // - - POWER_L2(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.WATT)), // - - POWER_L3(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.WATT)), // - MAX_HW_CURRENT(Doc.of(OpenemsType.INTEGER) // .unit(Unit.AMPERE)), // @@ -161,58 +140,4 @@ public default Value getEvSetChargePowerLimit() { public default void setEvSetChargePowerLimit(Integer value) throws OpenemsNamedException { this.getEvSetChargePowerLimitChannel().setNextWriteValue(value); } - - /** - * Gets the Channel for {@link ChannelId#POWER_L1}. - * - * @return the Channel - */ - public default IntegerReadChannel getPowerL1Channel() { - return this.channel(ChannelId.POWER_L1); - } - - /** - * Gets the Power on phase 1 in [W]. See {@link ChannelId#POWER_L1}. - * - * @return the Channel {@link Value} - */ - public default Value getPowerL1() { - return this.getPowerL1Channel().value(); - } - - /** - * Gets the Channel for {@link ChannelId#POWER_L2}. - * - * @return the Channel - */ - public default IntegerReadChannel getPowerL2Channel() { - return this.channel(ChannelId.POWER_L2); - } - - /** - * Gets the Power on phase 2 in [W]. See {@link ChannelId#POWER_L2}. - * - * @return the Channel {@link Value} - */ - public default Value getPowerL2() { - return this.getPowerL2Channel().value(); - } - - /** - * Gets the Channel for {@link ChannelId#POWER_L3}. - * - * @return the Channel - */ - public default IntegerReadChannel getPowerL3Channel() { - return this.channel(ChannelId.POWER_L3); - } - - /** - * Gets the Power on phase 3 in [W]. See {@link ChannelId#POWER_L3}. - * - * @return the Channel {@link Value} - */ - public default Value getPowerL3() { - return this.getPowerL3Channel().value(); - } } diff --git a/io.openems.edge.evcs.webasto.next/src/io/openems/edge/evcs/webasto/next/EvcsWebastoNextImpl.java b/io.openems.edge.evcs.webasto.next/src/io/openems/edge/evcs/webasto/next/EvcsWebastoNextImpl.java index d57c72553c9..a315771672f 100644 --- a/io.openems.edge.evcs.webasto.next/src/io/openems/edge/evcs/webasto/next/EvcsWebastoNextImpl.java +++ b/io.openems.edge.evcs.webasto.next/src/io/openems/edge/evcs/webasto/next/EvcsWebastoNextImpl.java @@ -22,6 +22,7 @@ import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.types.MeterType; import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent; import io.openems.edge.bridge.modbus.api.BridgeModbus; import io.openems.edge.bridge.modbus.api.ModbusComponent; @@ -45,6 +46,7 @@ import io.openems.edge.evcs.api.Status; import io.openems.edge.evcs.api.WriteHandler; import io.openems.edge.evcs.webasto.next.enums.ChargePointState; +import io.openems.edge.meter.api.ElectricityMeter; @Designate(ocd = Config.class, factory = true) @Component(// @@ -55,11 +57,10 @@ @EventTopics({ // EdgeEventConstants.TOPIC_CYCLE_EXECUTE_WRITE, // }) -public class EvcsWebastoNextImpl extends AbstractOpenemsModbusComponent - implements EvcsWebastoNext, Evcs, ManagedEvcs, ModbusComponent, OpenemsComponent, EventHandler { +public class EvcsWebastoNextImpl extends AbstractOpenemsModbusComponent implements EvcsWebastoNext, Evcs, ManagedEvcs, + ElectricityMeter, ModbusComponent, OpenemsComponent, EventHandler { private static final int DEFAULT_LIFE_BIT = 1; - private static final int DETECT_PHASE_ACTIVITY = 100; // W private final Logger log = LoggerFactory.getLogger(EvcsWebastoNext.class); @@ -84,6 +85,7 @@ protected void setModbus(BridgeModbus modbus) { public EvcsWebastoNextImpl() { super(// OpenemsComponent.ChannelId.values(), // + ElectricityMeter.ChannelId.values(), // ModbusComponent.ChannelId.values(), // Evcs.ChannelId.values(), // ManagedEvcs.ChannelId.values(), // @@ -151,21 +153,21 @@ protected ModbusProtocol defineModbusProtocol() { new FC3ReadRegistersTask(1006, Priority.LOW, m(EvcsWebastoNext.ChannelId.EVSE_ERROR_CODE, new UnsignedWordElement(1006))), new FC3ReadRegistersTask(1008, Priority.LOW, - m(EvcsWebastoNext.ChannelId.CURRENT_L1, new UnsignedWordElement(1008))), + m(ElectricityMeter.ChannelId.CURRENT_L1, new UnsignedWordElement(1008))), new FC3ReadRegistersTask(1010, Priority.LOW, - m(EvcsWebastoNext.ChannelId.CURRENT_L2, new UnsignedWordElement(1010))), + m(ElectricityMeter.ChannelId.CURRENT_L2, new UnsignedWordElement(1010))), new FC3ReadRegistersTask(1012, Priority.LOW, - m(EvcsWebastoNext.ChannelId.CURRENT_L3, new UnsignedWordElement(1012))), + m(ElectricityMeter.ChannelId.CURRENT_L3, new UnsignedWordElement(1012))), new FC3ReadRegistersTask(1020, Priority.HIGH, - m(Evcs.ChannelId.CHARGE_POWER, new UnsignedDoublewordElement(1020))), + m(ElectricityMeter.ChannelId.ACTIVE_POWER, new UnsignedDoublewordElement(1020))), new FC3ReadRegistersTask(1024, Priority.LOW, - m(EvcsWebastoNext.ChannelId.POWER_L1, new UnsignedDoublewordElement(1024))), + m(ElectricityMeter.ChannelId.ACTIVE_POWER_L1, new UnsignedDoublewordElement(1024))), new FC3ReadRegistersTask(1028, Priority.LOW, - m(EvcsWebastoNext.ChannelId.POWER_L2, new UnsignedDoublewordElement(1028))), + m(ElectricityMeter.ChannelId.ACTIVE_POWER_L2, new UnsignedDoublewordElement(1028))), new FC3ReadRegistersTask(1032, Priority.LOW, - m(EvcsWebastoNext.ChannelId.POWER_L3, new UnsignedDoublewordElement(1032))), + m(ElectricityMeter.ChannelId.ACTIVE_POWER_L3, new UnsignedDoublewordElement(1032))), new FC3ReadRegistersTask(1036, Priority.LOW, - m(Evcs.ChannelId.ACTIVE_CONSUMPTION_ENERGY, new UnsignedDoublewordElement(1036))), + m(ElectricityMeter.ChannelId.ACTIVE_PRODUCTION_ENERGY, new UnsignedDoublewordElement(1036))), new FC3ReadRegistersTask(1100, Priority.LOW, m(EvcsWebastoNext.ChannelId.MAX_HW_CURRENT, new UnsignedWordElement(1100))), new FC3ReadRegistersTask(1102, Priority.LOW, @@ -216,50 +218,32 @@ private void addStatusListener() { /** * Maps the raw state into a {@link Status}. */ - switch (state) { - case CHARGING: - this._setStatus(Status.CHARGING); - break; - case NO_PERMISSION: - case CHARGING_STATION_RESERVED: - this._setStatus(Status.CHARGING_REJECTED); - break; - case ERROR: - this._setStatus(Status.ERROR); - break; - case NO_VEHICLE_ATTACHED: - this._setStatus(Status.NOT_READY_FOR_CHARGING); - break; - case CHARGING_PAUSED: - this._setStatus(Status.CHARGING_FINISHED); - break; - case UNDEFINED: - default: - this._setStatus(Status.UNDEFINED); - } + this._setStatus(switch (state) { + case CHARGING -> Status.CHARGING; + case NO_PERMISSION, CHARGING_STATION_RESERVED -> Status.CHARGING_REJECTED; + case ERROR -> Status.ERROR; + case NO_VEHICLE_ATTACHED -> Status.NOT_READY_FOR_CHARGING; + case CHARGING_PAUSED -> Status.CHARGING_REJECTED; + case UNDEFINED -> Status.UNDEFINED; + }); }); } private void addPhasesListener() { - final Consumer> setPhases = ignore -> { - var phases = 0; - if (this.getPowerL1().orElse(0) > DETECT_PHASE_ACTIVITY) { - phases++; - } - if (this.getPowerL2().orElse(0) > DETECT_PHASE_ACTIVITY) { - phases++; - } - if (this.getPowerL3().orElse(0) > DETECT_PHASE_ACTIVITY) { - phases++; - } - if (phases == 0) { - phases = 3; - } - this._setPhases(phases); + final Consumer> setPhasesCallback = ignore -> { + this._setPhases(Evcs.evaluatePhaseCount(// + this.getActivePowerL1().get(), // + this.getActivePowerL2().get(), // + this.getActivePowerL3().get())); }; - this.getPowerL1Channel().onUpdate(setPhases); - this.getPowerL2Channel().onUpdate(setPhases); - this.getPowerL3Channel().onUpdate(setPhases); + this.getActivePowerL1Channel().onUpdate(setPhasesCallback); + this.getActivePowerL2Channel().onUpdate(setPhasesCallback); + this.getActivePowerL3Channel().onUpdate(setPhasesCallback); + } + + @Override + public MeterType getMeterType() { + return MeterType.MANAGED_CONSUMPTION_METERED; } @Override diff --git a/io.openems.edge.evcs.webasto.next/test/io/openems/edge/evcs/webasto/next/EvcsWebastoNextImplTest.java b/io.openems.edge.evcs.webasto.next/test/io/openems/edge/evcs/webasto/next/EvcsWebastoNextImplTest.java index b19ec679450..7ffc73c4d6a 100644 --- a/io.openems.edge.evcs.webasto.next/test/io/openems/edge/evcs/webasto/next/EvcsWebastoNextImplTest.java +++ b/io.openems.edge.evcs.webasto.next/test/io/openems/edge/evcs/webasto/next/EvcsWebastoNextImplTest.java @@ -8,17 +8,14 @@ public class EvcsWebastoNextImplTest { - private static final String EVCS_ID = "evcs0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new EvcsWebastoNextImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setModbusId(MODBUS_ID) // - .setId(EVCS_ID) // + .setModbusId("modbus0") // + .setId("evcs0") // .setModbusUnitId(1) // .setMaxHwCurrent(32000) // .setMinHwCurrent(6000) // diff --git a/io.openems.edge.evcs.webasto.unite/.classpath b/io.openems.edge.evcs.webasto.unite/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.evcs.webasto.unite/.classpath +++ b/io.openems.edge.evcs.webasto.unite/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.evcs.webasto.unite/bnd.bnd b/io.openems.edge.evcs.webasto.unite/bnd.bnd index 2f4e7fe8a62..a4ed98727d0 100644 --- a/io.openems.edge.evcs.webasto.unite/bnd.bnd +++ b/io.openems.edge.evcs.webasto.unite/bnd.bnd @@ -10,6 +10,7 @@ Bundle-Version: 1.0.0.${tstamp} io.openems.edge.bridge.modbus,\ io.openems.edge.common,\ io.openems.edge.evcs.api,\ + io.openems.edge.meter.api,\ -testpath: \ ${testpath} \ No newline at end of file diff --git a/io.openems.edge.evcs.webasto.unite/src/io/openems/edge/evcs/webasto/unite/EvcsWebastoUnite.java b/io.openems.edge.evcs.webasto.unite/src/io/openems/edge/evcs/webasto/unite/EvcsWebastoUnite.java index a7a8803ec81..1341e62a1c1 100644 --- a/io.openems.edge.evcs.webasto.unite/src/io/openems/edge/evcs/webasto/unite/EvcsWebastoUnite.java +++ b/io.openems.edge.evcs.webasto.unite/src/io/openems/edge/evcs/webasto/unite/EvcsWebastoUnite.java @@ -8,8 +8,10 @@ import io.openems.edge.common.channel.Doc; import io.openems.edge.common.channel.WriteChannel; import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.evcs.api.Evcs; +import io.openems.edge.meter.api.ElectricityMeter; -public interface EvcsWebastoUnite extends OpenemsComponent { +public interface EvcsWebastoUnite extends ElectricityMeter, Evcs, OpenemsComponent { public enum ChannelId implements io.openems.edge.common.channel.ChannelId { SERIAL_NUMBER(Doc.of(OpenemsType.STRING) // @@ -41,36 +43,6 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { .accessMode(AccessMode.READ_ONLY)), // EVSE_FAULT_CODE(Doc.of(OpenemsType.INTEGER) // .accessMode(AccessMode.READ_ONLY)), // - CURRENT_L1(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.MILLIAMPERE) // - .accessMode(AccessMode.READ_ONLY)), // - CURRENT_L2(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.MILLIAMPERE) // - .accessMode(AccessMode.READ_ONLY)), // - CURRENT_L3(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.MILLIAMPERE) // - .accessMode(AccessMode.READ_ONLY)), // - VOLTAGE_L1(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.VOLT) // - .accessMode(AccessMode.READ_ONLY)), // - VOLTAGE_L2(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.VOLT) // - .accessMode(AccessMode.READ_ONLY)), // - VOLTAGE_L3(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.VOLT) // - .accessMode(AccessMode.READ_ONLY)), // - ACTIVE_POWER_TOTAL(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.WATT) // - .accessMode(AccessMode.READ_ONLY)), // - ACTIVE_POWER_L1(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.WATT) // - .accessMode(AccessMode.READ_ONLY)), // - ACTIVE_POWER_L2(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.WATT) // - .accessMode(AccessMode.READ_ONLY)), // - ACTIVE_POWER_L3(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.WATT) // - .accessMode(AccessMode.READ_ONLY)), // METER_READING(Doc.of(OpenemsType.INTEGER) // .accessMode(AccessMode.READ_ONLY)), // SESSION_MAX_CURRENT(Doc.of(OpenemsType.INTEGER) // @@ -133,25 +105,6 @@ default void _setAliveValue(int value) throws OpenemsError.OpenemsNamedException channel.setNextWriteValue(value); } - /** - * Gets the Channel for {@link EvcsWebastoUnite.ChannelId#ACTIVE_POWER_TOTAL}. - * - * @return the Channel - */ - default Channel getActivePowerChannel() { - return this.channel(ChannelId.ACTIVE_POWER_TOTAL); - } - - /** - * Gets the Channel for {@link EvcsWebastoUnite.ChannelId#ACTIVE_POWER_TOTAL}. - * - * @return the Channel - */ - default int getActivePower() { - Channel channel = this.getActivePowerChannel(); - return channel.value().orElse(channel.getNextValue().orElse(0)); - } - /** * Gets the Channel for {@link EvcsWebastoUnite.ChannelId#CHARGE_POINT_STATE}. * @@ -171,63 +124,6 @@ default int getChargePointState() { return channel.value().orElse(channel.getNextValue().orElse(-1)); } - /** - * Gets the Channel for {@link EvcsWebastoUnite.ChannelId#CURRENT_L1}. - * - * @return the Channel - */ - default Channel getActivePowerL1Channel() { - return this.channel(ChannelId.ACTIVE_POWER_L1); - } - - /** - * Gets the Channel for {@link EvcsWebastoUnite.ChannelId#CURRENT_L1}. - * - * @return the Channel - */ - default int getActivePowerL1() { - Channel channel = this.getActivePowerL1Channel(); - return channel.value().orElse(channel.getNextValue().orElse(-1)); - } - - /** - * Gets the Channel for {@link EvcsWebastoUnite.ChannelId#CURRENT_L2}. - * - * @return the Channel - */ - default Channel getActivePowerL2Channel() { - return this.channel(ChannelId.ACTIVE_POWER_L2); - } - - /** - * Gets the Channel for {@link EvcsWebastoUnite.ChannelId#CURRENT_L2}. - * - * @return the Channel - */ - default int getActivePowerL2() { - Channel channel = this.getActivePowerL2Channel(); - return channel.value().orElse(channel.getNextValue().orElse(-1)); - } - - /** - * Gets the Channel for {@link EvcsWebastoUnite.ChannelId#CURRENT_L3}. - * - * @return the Channel - */ - default Channel getActivePowerL3Channel() { - return this.channel(ChannelId.ACTIVE_POWER_L3); - } - - /** - * Gets the Channel for {@link EvcsWebastoUnite.ChannelId#CURRENT_L3}. - * - * @return the Channel - */ - default int getActivePowerL3() { - Channel channel = this.getActivePowerL3Channel(); - return channel.value().orElse(channel.getNextValue().orElse(-1)); - } - /** * Gets the Channel for {@link EvcsWebastoUnite.ChannelId#CHARGING_CURRENT}. * diff --git a/io.openems.edge.evcs.webasto.unite/src/io/openems/edge/evcs/webasto/unite/EvcsWebastoUniteImpl.java b/io.openems.edge.evcs.webasto.unite/src/io/openems/edge/evcs/webasto/unite/EvcsWebastoUniteImpl.java index 8c0d5a821f6..0baaba59b30 100644 --- a/io.openems.edge.evcs.webasto.unite/src/io/openems/edge/evcs/webasto/unite/EvcsWebastoUniteImpl.java +++ b/io.openems.edge.evcs.webasto.unite/src/io/openems/edge/evcs/webasto/unite/EvcsWebastoUniteImpl.java @@ -20,6 +20,7 @@ import io.openems.common.exceptions.OpenemsError; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.types.MeterType; import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent; import io.openems.edge.bridge.modbus.api.BridgeModbus; import io.openems.edge.bridge.modbus.api.ModbusComponent; @@ -41,6 +42,7 @@ import io.openems.edge.evcs.api.ManagedEvcs; import io.openems.edge.evcs.api.Phases; import io.openems.edge.evcs.api.WriteHandler; +import io.openems.edge.meter.api.ElectricityMeter; @Designate(ocd = Config.class, factory = true) @Component(// @@ -53,7 +55,7 @@ EdgeEventConstants.TOPIC_CYCLE_EXECUTE_WRITE // }) public class EvcsWebastoUniteImpl extends AbstractOpenemsModbusComponent - implements EvcsWebastoUnite, Evcs, ManagedEvcs, EventHandler, OpenemsComponent { + implements EvcsWebastoUnite, Evcs, ManagedEvcs, EventHandler, ElectricityMeter, OpenemsComponent { private final Logger log = LoggerFactory.getLogger(EvcsWebastoUniteImpl.class); @@ -74,6 +76,7 @@ public class EvcsWebastoUniteImpl extends AbstractOpenemsModbusComponent public EvcsWebastoUniteImpl() { super(// OpenemsComponent.ChannelId.values(), // + ElectricityMeter.ChannelId.values(), // Evcs.ChannelId.values(), // ManagedEvcs.ChannelId.values(), // ModbusComponent.ChannelId.values(), // @@ -150,25 +153,25 @@ protected ModbusProtocol defineModbusProtocol() { new FC4ReadInputRegistersTask(1006, Priority.LOW, m(EvcsWebastoUnite.ChannelId.EVSE_FAULT_CODE, new UnsignedDoublewordElement(1006))), new FC4ReadInputRegistersTask(1008, Priority.HIGH, - m(EvcsWebastoUnite.ChannelId.CURRENT_L1, new UnsignedWordElement(1008))), + m(ElectricityMeter.ChannelId.CURRENT_L1, new UnsignedWordElement(1008))), new FC4ReadInputRegistersTask(1010, Priority.HIGH, - m(EvcsWebastoUnite.ChannelId.CURRENT_L2, new UnsignedWordElement(1010))), + m(ElectricityMeter.ChannelId.CURRENT_L2, new UnsignedWordElement(1010))), new FC4ReadInputRegistersTask(1012, Priority.HIGH, - m(EvcsWebastoUnite.ChannelId.CURRENT_L3, new UnsignedWordElement(1012))), + m(ElectricityMeter.ChannelId.CURRENT_L3, new UnsignedWordElement(1012))), new FC4ReadInputRegistersTask(1014, Priority.LOW, - m(EvcsWebastoUnite.ChannelId.VOLTAGE_L1, new UnsignedWordElement(1014))), + m(ElectricityMeter.ChannelId.VOLTAGE_L1, new UnsignedWordElement(1014))), new FC4ReadInputRegistersTask(1016, Priority.LOW, - m(EvcsWebastoUnite.ChannelId.VOLTAGE_L2, new UnsignedWordElement(1016))), + m(ElectricityMeter.ChannelId.VOLTAGE_L2, new UnsignedWordElement(1016))), new FC4ReadInputRegistersTask(1018, Priority.LOW, - m(EvcsWebastoUnite.ChannelId.VOLTAGE_L3, new UnsignedWordElement(1018))), + m(ElectricityMeter.ChannelId.VOLTAGE_L3, new UnsignedWordElement(1018))), new FC4ReadInputRegistersTask(1020, Priority.HIGH, - m(EvcsWebastoUnite.ChannelId.ACTIVE_POWER_TOTAL, new UnsignedDoublewordElement(1020))), + m(ElectricityMeter.ChannelId.ACTIVE_POWER, new UnsignedDoublewordElement(1020))), new FC4ReadInputRegistersTask(1024, Priority.HIGH, - m(EvcsWebastoUnite.ChannelId.ACTIVE_POWER_L1, new UnsignedDoublewordElement(1024))), + m(ElectricityMeter.ChannelId.ACTIVE_POWER_L1, new UnsignedDoublewordElement(1024))), new FC4ReadInputRegistersTask(1028, Priority.HIGH, - m(EvcsWebastoUnite.ChannelId.ACTIVE_POWER_L2, new UnsignedDoublewordElement(1028))), + m(ElectricityMeter.ChannelId.ACTIVE_POWER_L2, new UnsignedDoublewordElement(1028))), new FC4ReadInputRegistersTask(1032, Priority.HIGH, - m(EvcsWebastoUnite.ChannelId.ACTIVE_POWER_L3, new UnsignedDoublewordElement(1032))), + m(ElectricityMeter.ChannelId.ACTIVE_POWER_L3, new UnsignedDoublewordElement(1032))), new FC4ReadInputRegistersTask(1036, Priority.LOW, m(EvcsWebastoUnite.ChannelId.METER_READING, new UnsignedDoublewordElement(1036))), new FC4ReadInputRegistersTask(1100, Priority.LOW, @@ -207,6 +210,11 @@ protected ModbusProtocol defineModbusProtocol() { return modbusProtocol; } + @Override + public MeterType getMeterType() { + return MeterType.MANAGED_CONSUMPTION_METERED; + } + @Override public String debugLog() { return "Limit:" + this.getSetChargePowerLimit().orElse(null) + "|" + this.getStatus().getName(); diff --git a/io.openems.edge.evcs.webasto.unite/src/io/openems/edge/evcs/webasto/unite/WebastoReadHandler.java b/io.openems.edge.evcs.webasto.unite/src/io/openems/edge/evcs/webasto/unite/WebastoReadHandler.java index 3018446dacc..62bb363b4c3 100644 --- a/io.openems.edge.evcs.webasto.unite/src/io/openems/edge/evcs/webasto/unite/WebastoReadHandler.java +++ b/io.openems.edge.evcs.webasto.unite/src/io/openems/edge/evcs/webasto/unite/WebastoReadHandler.java @@ -1,5 +1,7 @@ package io.openems.edge.evcs.webasto.unite; +import static io.openems.edge.evcs.api.Evcs.evaluatePhaseCount; + import io.openems.edge.evcs.api.Status; // TODO: Can also be done by registering onSetNextValue listeners on the depending channel in the WebastoImpl. @@ -14,54 +16,27 @@ protected WebastoReadHandler(EvcsWebastoUniteImpl parent) { protected void run() { this.setPhaseCount(); this.setStatus(); - this.parent._setChargePower((int) this.parent.getActivePower()); } private void setStatus() { - switch (this.parent.getChargePointState()) { - case (0): - this.parent._setStatus(Status.NOT_READY_FOR_CHARGING); - break; - case (1): - this.parent._setStatus(Status.READY_FOR_CHARGING); - break; - case (2): - this.parent._setStatus(Status.CHARGING); - break; - case (3): - case (4): - this.parent._setStatus(Status.CHARGING_REJECTED); - break; - case (5): - // TODO Check if this state is also reached while paused - this.parent._setStatus(Status.CHARGING_FINISHED); - break; - case (7): - case (8): - this.parent._setStatus(Status.ERROR); - } + this.parent._setStatus(switch (this.parent.getChargePointState()) { + case 0 -> Status.NOT_READY_FOR_CHARGING; + case 1 -> Status.READY_FOR_CHARGING; + case 2 -> Status.CHARGING; + case 3, 4, 5 -> Status.CHARGING_REJECTED; + // TODO Check if this state is also reached while paused + case 7, 8 -> Status.ERROR; + default -> null; + }); } /** * Writes the Amount of Phases in the Phase channel. */ private void setPhaseCount() { - int phases = 0; - /* - * The EVCS will pull power from the grid for its own consumption and report - * that on one of the phases. This value is different from EVCS to EVCS but can - * be high. Because of this, this will only register a Phase starting with 100W - * because then we definitively know that this load is caused by a car. - */ - if (this.parent.getActivePowerL1() >= 100) { - phases += 1; - } - if (this.parent.getActivePowerL2() >= 100) { - phases += 1; - } - if (this.parent.getActivePowerL3() >= 100) { - phases += 1; - } - this.parent._setPhases(phases); + this.parent._setPhases(evaluatePhaseCount(// + this.parent.getActivePowerL1().get(), // + this.parent.getActivePowerL2().get(), // + this.parent.getActivePowerL3().get())); } } diff --git a/io.openems.edge.evcs.webasto.unite/test/io/openems/edge/evcs/webasto/unite/EvcsWebastoUniteImplTest.java b/io.openems.edge.evcs.webasto.unite/test/io/openems/edge/evcs/webasto/unite/EvcsWebastoUniteImplTest.java index 41bc0851964..c5bea2c96cb 100644 --- a/io.openems.edge.evcs.webasto.unite/test/io/openems/edge/evcs/webasto/unite/EvcsWebastoUniteImplTest.java +++ b/io.openems.edge.evcs.webasto.unite/test/io/openems/edge/evcs/webasto/unite/EvcsWebastoUniteImplTest.java @@ -8,17 +8,14 @@ public class EvcsWebastoUniteImplTest { - private static final String EVCS_ID = "evcs0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new EvcsWebastoUniteImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setModbusId(MODBUS_ID) // - .setId(EVCS_ID) // + .setModbusId("modbus0") // + .setId("evcs0") // .setModbusUnitId(255) // .setMaxHwCurrent(32_000) // .setMinHwCurrent(6_000) // diff --git a/io.openems.edge.fenecon.dess/.classpath b/io.openems.edge.fenecon.dess/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.fenecon.dess/.classpath +++ b/io.openems.edge.fenecon.dess/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.fenecon.dess/src/io/openems/edge/fenecon/dess/gridmeter/FeneconDessGridMeterImpl.java b/io.openems.edge.fenecon.dess/src/io/openems/edge/fenecon/dess/gridmeter/FeneconDessGridMeterImpl.java index 39580294e38..1c45cd1a254 100644 --- a/io.openems.edge.fenecon.dess/src/io/openems/edge/fenecon/dess/gridmeter/FeneconDessGridMeterImpl.java +++ b/io.openems.edge.fenecon.dess/src/io/openems/edge/fenecon/dess/gridmeter/FeneconDessGridMeterImpl.java @@ -18,6 +18,7 @@ import org.osgi.service.metatype.annotations.Designate; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.types.MeterType; import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent; import io.openems.edge.bridge.modbus.api.BridgeModbus; import io.openems.edge.bridge.modbus.api.ElementToChannelConverter; @@ -32,7 +33,6 @@ import io.openems.edge.common.taskmanager.Priority; import io.openems.edge.fenecon.dess.FeneconDessConstants; import io.openems.edge.meter.api.ElectricityMeter; -import io.openems.edge.meter.api.MeterType; import io.openems.edge.timedata.api.Timedata; import io.openems.edge.timedata.api.TimedataProvider; import io.openems.edge.timedata.api.utils.CalculateEnergyFromPower; diff --git a/io.openems.edge.fenecon.dess/src/io/openems/edge/fenecon/dess/pvmeter/FeneconDessPvMeterImpl.java b/io.openems.edge.fenecon.dess/src/io/openems/edge/fenecon/dess/pvmeter/FeneconDessPvMeterImpl.java index e9fc1e27ddf..f0e8b41de8d 100644 --- a/io.openems.edge.fenecon.dess/src/io/openems/edge/fenecon/dess/pvmeter/FeneconDessPvMeterImpl.java +++ b/io.openems.edge.fenecon.dess/src/io/openems/edge/fenecon/dess/pvmeter/FeneconDessPvMeterImpl.java @@ -16,6 +16,7 @@ import org.osgi.service.metatype.annotations.Designate; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.types.MeterType; import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent; import io.openems.edge.bridge.modbus.api.BridgeModbus; import io.openems.edge.bridge.modbus.api.ElementToChannelConverter; @@ -28,7 +29,6 @@ import io.openems.edge.common.taskmanager.Priority; import io.openems.edge.fenecon.dess.FeneconDessConstants; import io.openems.edge.meter.api.ElectricityMeter; -import io.openems.edge.meter.api.MeterType; import io.openems.edge.timedata.api.Timedata; import io.openems.edge.timedata.api.TimedataProvider; import io.openems.edge.timedata.api.utils.CalculateEnergyFromPower; diff --git a/io.openems.edge.fenecon.dess/test/io/openems/edge/fenecon/dess/charger/FeneconDessCharger1Test.java b/io.openems.edge.fenecon.dess/test/io/openems/edge/fenecon/dess/charger/FeneconDessCharger1Test.java index 20325ec4cbf..146b7240c85 100644 --- a/io.openems.edge.fenecon.dess/test/io/openems/edge/fenecon/dess/charger/FeneconDessCharger1Test.java +++ b/io.openems.edge.fenecon.dess/test/io/openems/edge/fenecon/dess/charger/FeneconDessCharger1Test.java @@ -10,19 +10,15 @@ public class FeneconDessCharger1Test { - private static final String CHARGER_ID = "charger0"; - private static final String ESS_ID = "ess0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { var ess = new FeneconDessEssImpl(); new ComponentTest(ess) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyEssConfig.create() // - .setId(ESS_ID) // - .setModbusId(MODBUS_ID) // + .setId("ess0") // + .setModbusId("modbus0") // .build()) // ; @@ -30,9 +26,9 @@ public void test() throws Exception { .addReference("cm", new DummyConfigurationAdmin()) // .addReference("ess", ess) // .activate(MyChargerConfig.create() // - .setId(CHARGER_ID) // - .setEssId(ESS_ID) // - .setModbusId(MODBUS_ID) // + .setId("charger0") // + .setEssId("ess0") // + .setModbusId("modbus0") // .build()) // ; } diff --git a/io.openems.edge.fenecon.dess/test/io/openems/edge/fenecon/dess/charger/FeneconDessCharger2Test.java b/io.openems.edge.fenecon.dess/test/io/openems/edge/fenecon/dess/charger/FeneconDessCharger2Test.java index 879abb0d8fe..18f69c54492 100644 --- a/io.openems.edge.fenecon.dess/test/io/openems/edge/fenecon/dess/charger/FeneconDessCharger2Test.java +++ b/io.openems.edge.fenecon.dess/test/io/openems/edge/fenecon/dess/charger/FeneconDessCharger2Test.java @@ -10,19 +10,15 @@ public class FeneconDessCharger2Test { - private static final String CHARGER_ID = "charger1"; - private static final String ESS_ID = "ess0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { var ess = new FeneconDessEssImpl(); new ComponentTest(ess) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyEssConfig.create() // - .setId(ESS_ID) // - .setModbusId(MODBUS_ID) // + .setId("ess0") // + .setModbusId("modbus0") // .build()) // ; @@ -30,9 +26,9 @@ public void test() throws Exception { .addReference("cm", new DummyConfigurationAdmin()) // .addReference("ess", ess) // .activate(MyChargerConfig.create() // - .setId(CHARGER_ID) // - .setEssId(ESS_ID) // - .setModbusId(MODBUS_ID) // + .setId("charger1") // + .setEssId("ess0") // + .setModbusId("modbus0") // .build()) // ; } diff --git a/io.openems.edge.fenecon.dess/test/io/openems/edge/fenecon/dess/ess/FeneconDessEssImplTest.java b/io.openems.edge.fenecon.dess/test/io/openems/edge/fenecon/dess/ess/FeneconDessEssImplTest.java index 9a71817d9c8..8fb795dd74d 100644 --- a/io.openems.edge.fenecon.dess/test/io/openems/edge/fenecon/dess/ess/FeneconDessEssImplTest.java +++ b/io.openems.edge.fenecon.dess/test/io/openems/edge/fenecon/dess/ess/FeneconDessEssImplTest.java @@ -8,17 +8,14 @@ public class FeneconDessEssImplTest { - private static final String ESS_ID = "ess0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new FeneconDessEssImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyEssConfig.create() // - .setId(ESS_ID) // - .setModbusId(MODBUS_ID) // + .setId("ess0") // + .setModbusId("modbus0") // .build()) // ; } diff --git a/io.openems.edge.fenecon.dess/test/io/openems/edge/fenecon/dess/gridmeter/FeneconDessGridMeterImplTest.java b/io.openems.edge.fenecon.dess/test/io/openems/edge/fenecon/dess/gridmeter/FeneconDessGridMeterImplTest.java index fd85f499448..a29b0a99e7c 100644 --- a/io.openems.edge.fenecon.dess/test/io/openems/edge/fenecon/dess/gridmeter/FeneconDessGridMeterImplTest.java +++ b/io.openems.edge.fenecon.dess/test/io/openems/edge/fenecon/dess/gridmeter/FeneconDessGridMeterImplTest.java @@ -8,17 +8,14 @@ public class FeneconDessGridMeterImplTest { - private static final String METER_ID = "meter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new FeneconDessGridMeterImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // + .setId("meter0") // + .setModbusId("modbus0") // .build()) // ; } diff --git a/io.openems.edge.fenecon.dess/test/io/openems/edge/fenecon/dess/pvmeter/FeneconDessPvMeterImplTest.java b/io.openems.edge.fenecon.dess/test/io/openems/edge/fenecon/dess/pvmeter/FeneconDessPvMeterImplTest.java index c849a026396..4af1a5dfcd3 100644 --- a/io.openems.edge.fenecon.dess/test/io/openems/edge/fenecon/dess/pvmeter/FeneconDessPvMeterImplTest.java +++ b/io.openems.edge.fenecon.dess/test/io/openems/edge/fenecon/dess/pvmeter/FeneconDessPvMeterImplTest.java @@ -8,17 +8,14 @@ public class FeneconDessPvMeterImplTest { - private static final String METER_ID = "meter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new FeneconDessPvMeterImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // + .setId("meter0") // + .setModbusId("modbus0") // .build()) // ; } diff --git a/io.openems.edge.fenecon.mini/.classpath b/io.openems.edge.fenecon.mini/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.fenecon.mini/.classpath +++ b/io.openems.edge.fenecon.mini/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.fenecon.mini/src/io/openems/edge/fenecon/mini/gridmeter/FeneconMiniGridMeterImpl.java b/io.openems.edge.fenecon.mini/src/io/openems/edge/fenecon/mini/gridmeter/FeneconMiniGridMeterImpl.java index 9968e9c6e59..eeca54aa533 100644 --- a/io.openems.edge.fenecon.mini/src/io/openems/edge/fenecon/mini/gridmeter/FeneconMiniGridMeterImpl.java +++ b/io.openems.edge.fenecon.mini/src/io/openems/edge/fenecon/mini/gridmeter/FeneconMiniGridMeterImpl.java @@ -16,6 +16,7 @@ import org.osgi.service.metatype.annotations.Designate; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.types.MeterType; import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent; import io.openems.edge.bridge.modbus.api.BridgeModbus; import io.openems.edge.bridge.modbus.api.ElementToChannelConverter; @@ -29,7 +30,6 @@ import io.openems.edge.common.taskmanager.Priority; import io.openems.edge.fenecon.mini.FeneconMiniConstants; import io.openems.edge.meter.api.ElectricityMeter; -import io.openems.edge.meter.api.MeterType; import io.openems.edge.timedata.api.Timedata; import io.openems.edge.timedata.api.TimedataProvider; import io.openems.edge.timedata.api.utils.CalculateEnergyFromPower; diff --git a/io.openems.edge.fenecon.mini/src/io/openems/edge/fenecon/mini/pvmeter/FeneconMiniPvMeterImpl.java b/io.openems.edge.fenecon.mini/src/io/openems/edge/fenecon/mini/pvmeter/FeneconMiniPvMeterImpl.java index 009311b915e..39da4f147fe 100644 --- a/io.openems.edge.fenecon.mini/src/io/openems/edge/fenecon/mini/pvmeter/FeneconMiniPvMeterImpl.java +++ b/io.openems.edge.fenecon.mini/src/io/openems/edge/fenecon/mini/pvmeter/FeneconMiniPvMeterImpl.java @@ -16,6 +16,7 @@ import org.osgi.service.metatype.annotations.Designate; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.types.MeterType; import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent; import io.openems.edge.bridge.modbus.api.BridgeModbus; import io.openems.edge.bridge.modbus.api.ModbusComponent; @@ -27,7 +28,6 @@ import io.openems.edge.common.taskmanager.Priority; import io.openems.edge.fenecon.mini.FeneconMiniConstants; import io.openems.edge.meter.api.ElectricityMeter; -import io.openems.edge.meter.api.MeterType; import io.openems.edge.timedata.api.Timedata; import io.openems.edge.timedata.api.TimedataProvider; import io.openems.edge.timedata.api.utils.CalculateEnergyFromPower; diff --git a/io.openems.edge.fenecon.mini/test/io/openems/edge/fenecon/mini/ess/FeneconMiniEssImplTest.java b/io.openems.edge.fenecon.mini/test/io/openems/edge/fenecon/mini/ess/FeneconMiniEssImplTest.java index 7535b25a0c3..b83208448ac 100644 --- a/io.openems.edge.fenecon.mini/test/io/openems/edge/fenecon/mini/ess/FeneconMiniEssImplTest.java +++ b/io.openems.edge.fenecon.mini/test/io/openems/edge/fenecon/mini/ess/FeneconMiniEssImplTest.java @@ -1,8 +1,11 @@ package io.openems.edge.fenecon.mini.ess; +import static io.openems.edge.fenecon.mini.ess.FeneconMiniEss.ChannelId.PCS_MODE; +import static io.openems.edge.fenecon.mini.ess.FeneconMiniEss.ChannelId.SETUP_MODE; +import static io.openems.edge.fenecon.mini.ess.FeneconMiniEss.ChannelId.STATE_MACHINE; + import org.junit.Test; -import io.openems.common.types.ChannelAddress; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyConfigurationAdmin; @@ -13,14 +16,6 @@ public class FeneconMiniEssImplTest { - private static final String MODBUS_ID = "modbus0"; - - private static final String ESS_ID = "ess0"; - - private static final ChannelAddress ESS_STATE_MACHINE = new ChannelAddress(ESS_ID, "StateMachine"); - private static final ChannelAddress ESS_PCS_MODE = new ChannelAddress(ESS_ID, "PcsMode"); - private static final ChannelAddress ESS_SETUP_MODE = new ChannelAddress(ESS_ID, "SetupMode"); - /** * Tests activating write-mode when it was not activated before. * @@ -31,34 +26,34 @@ public void testWriteModeSet() throws Exception { new ManagedSymmetricEssTest(new FeneconMiniEssImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("power", new DummyPower()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(ESS_ID) // - .setModbusId(MODBUS_ID) // + .setId("ess0") // + .setModbusId("modbus0") // .setPhase(SinglePhase.L1) // .setReadonly(false) // .build()) // .next(new TestCase() // - .input(ESS_PCS_MODE, PcsMode.ECONOMIC) // - .input(ESS_SETUP_MODE, SetupMode.OFF) // - .output(ESS_STATE_MACHINE, State.UNDEFINED)) // + .input(PCS_MODE, PcsMode.ECONOMIC) // + .input(SETUP_MODE, SetupMode.OFF) // + .output(STATE_MACHINE, State.UNDEFINED)) // .next(new TestCase() // - .output(ESS_STATE_MACHINE, State.GO_WRITE_MODE)) // + .output(STATE_MACHINE, State.GO_WRITE_MODE)) // .next(new TestCase() // - .output(ESS_STATE_MACHINE, State.ACTIVATE_DEBUG_MODE_1)) // + .output(STATE_MACHINE, State.ACTIVATE_DEBUG_MODE_1)) // .next(new TestCase() // - .input(ESS_SETUP_MODE, SetupMode.ON) // - .output(ESS_STATE_MACHINE, State.ACTIVATE_DEBUG_MODE_2)) // + .input(SETUP_MODE, SetupMode.ON) // + .output(STATE_MACHINE, State.ACTIVATE_DEBUG_MODE_2)) // .next(new TestCase() // - .input(ESS_PCS_MODE, PcsMode.DEBUG) // - .output(ESS_STATE_MACHINE, State.ACTIVATE_DEBUG_MODE_3)) // + .input(PCS_MODE, PcsMode.DEBUG) // + .output(STATE_MACHINE, State.ACTIVATE_DEBUG_MODE_3)) // .next(new TestCase() // - .input(ESS_SETUP_MODE, SetupMode.OFF) // - .output(ESS_STATE_MACHINE, State.ACTIVATE_DEBUG_MODE_4)) // + .input(SETUP_MODE, SetupMode.OFF) // + .output(STATE_MACHINE, State.ACTIVATE_DEBUG_MODE_4)) // .next(new TestCase() // - .output(ESS_STATE_MACHINE, State.GO_WRITE_MODE)) // + .output(STATE_MACHINE, State.GO_WRITE_MODE)) // .next(new TestCase() // - .output(ESS_STATE_MACHINE, State.WRITE_MODE)) // + .output(STATE_MACHINE, State.WRITE_MODE)) // ; } @@ -72,21 +67,21 @@ public void testWriteModeAlreadySet() throws Exception { new ManagedSymmetricEssTest(new FeneconMiniEssImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("power", new DummyPower()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(ESS_ID) // - .setModbusId(MODBUS_ID) // + .setId("ess0") // + .setModbusId("modbus0") // .setPhase(SinglePhase.L1) // .setReadonly(false) // .build()) // .next(new TestCase() // - .input(ESS_PCS_MODE, PcsMode.DEBUG) // - .input(ESS_SETUP_MODE, SetupMode.OFF) // - .output(ESS_STATE_MACHINE, State.UNDEFINED)) // + .input(PCS_MODE, PcsMode.DEBUG) // + .input(SETUP_MODE, SetupMode.OFF) // + .output(STATE_MACHINE, State.UNDEFINED)) // .next(new TestCase() // - .output(ESS_STATE_MACHINE, State.GO_WRITE_MODE)) // + .output(STATE_MACHINE, State.GO_WRITE_MODE)) // .next(new TestCase() // - .output(ESS_STATE_MACHINE, State.WRITE_MODE)) // + .output(STATE_MACHINE, State.WRITE_MODE)) // ; } @@ -100,34 +95,34 @@ public void testReadonlyModeSet() throws Exception { new ManagedSymmetricEssTest(new FeneconMiniEssImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("power", new DummyPower()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(ESS_ID) // - .setModbusId(MODBUS_ID) // + .setId("ess0") // + .setModbusId("modbus0") // .setPhase(SinglePhase.L1) // .setReadonly(true) // .build()) // .next(new TestCase() // - .input(ESS_PCS_MODE, PcsMode.DEBUG) // - .input(ESS_SETUP_MODE, SetupMode.OFF) // - .output(ESS_STATE_MACHINE, State.UNDEFINED)) // + .input(PCS_MODE, PcsMode.DEBUG) // + .input(SETUP_MODE, SetupMode.OFF) // + .output(STATE_MACHINE, State.UNDEFINED)) // .next(new TestCase() // - .output(ESS_STATE_MACHINE, State.GO_READONLY_MODE)) // + .output(STATE_MACHINE, State.GO_READONLY_MODE)) // .next(new TestCase() // - .output(ESS_STATE_MACHINE, State.ACTIVATE_ECONOMIC_MODE_1)) // + .output(STATE_MACHINE, State.ACTIVATE_ECONOMIC_MODE_1)) // .next(new TestCase() // - .input(ESS_SETUP_MODE, SetupMode.ON) // - .output(ESS_STATE_MACHINE, State.ACTIVATE_ECONOMIC_MODE_2)) // + .input(SETUP_MODE, SetupMode.ON) // + .output(STATE_MACHINE, State.ACTIVATE_ECONOMIC_MODE_2)) // .next(new TestCase() // - .input(ESS_PCS_MODE, PcsMode.ECONOMIC) // - .output(ESS_STATE_MACHINE, State.ACTIVATE_ECONOMIC_MODE_3)) // + .input(PCS_MODE, PcsMode.ECONOMIC) // + .output(STATE_MACHINE, State.ACTIVATE_ECONOMIC_MODE_3)) // .next(new TestCase() // - .input(ESS_SETUP_MODE, SetupMode.OFF) // - .output(ESS_STATE_MACHINE, State.ACTIVATE_ECONOMIC_MODE_4)) // + .input(SETUP_MODE, SetupMode.OFF) // + .output(STATE_MACHINE, State.ACTIVATE_ECONOMIC_MODE_4)) // .next(new TestCase() // - .output(ESS_STATE_MACHINE, State.GO_READONLY_MODE)) // + .output(STATE_MACHINE, State.GO_READONLY_MODE)) // .next(new TestCase() // - .output(ESS_STATE_MACHINE, State.READONLY_MODE)) // + .output(STATE_MACHINE, State.READONLY_MODE)) // ; } @@ -141,34 +136,34 @@ public void testReadonlyModeAlreadySet() throws Exception { new ManagedSymmetricEssTest(new FeneconMiniEssImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("power", new DummyPower()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(ESS_ID) // - .setModbusId(MODBUS_ID) // + .setId("ess0") // + .setModbusId("modbus0") // .setPhase(SinglePhase.L1) // .setReadonly(true) // .build()) // .next(new TestCase() // - .input(ESS_PCS_MODE, PcsMode.DEBUG) // - .input(ESS_SETUP_MODE, SetupMode.OFF) // - .output(ESS_STATE_MACHINE, State.UNDEFINED)) // + .input(PCS_MODE, PcsMode.DEBUG) // + .input(SETUP_MODE, SetupMode.OFF) // + .output(STATE_MACHINE, State.UNDEFINED)) // .next(new TestCase() // - .output(ESS_STATE_MACHINE, State.GO_READONLY_MODE)) // + .output(STATE_MACHINE, State.GO_READONLY_MODE)) // .next(new TestCase() // - .output(ESS_STATE_MACHINE, State.ACTIVATE_ECONOMIC_MODE_1)) // + .output(STATE_MACHINE, State.ACTIVATE_ECONOMIC_MODE_1)) // .next(new TestCase() // - .input(ESS_SETUP_MODE, SetupMode.ON) // - .output(ESS_STATE_MACHINE, State.ACTIVATE_ECONOMIC_MODE_2)) // + .input(SETUP_MODE, SetupMode.ON) // + .output(STATE_MACHINE, State.ACTIVATE_ECONOMIC_MODE_2)) // .next(new TestCase() // - .input(ESS_PCS_MODE, PcsMode.ECONOMIC) // - .output(ESS_STATE_MACHINE, State.ACTIVATE_ECONOMIC_MODE_3)) // + .input(PCS_MODE, PcsMode.ECONOMIC) // + .output(STATE_MACHINE, State.ACTIVATE_ECONOMIC_MODE_3)) // .next(new TestCase() // - .input(ESS_SETUP_MODE, SetupMode.OFF) // - .output(ESS_STATE_MACHINE, State.ACTIVATE_ECONOMIC_MODE_4)) // + .input(SETUP_MODE, SetupMode.OFF) // + .output(STATE_MACHINE, State.ACTIVATE_ECONOMIC_MODE_4)) // .next(new TestCase() // - .output(ESS_STATE_MACHINE, State.GO_READONLY_MODE)) // + .output(STATE_MACHINE, State.GO_READONLY_MODE)) // .next(new TestCase() // - .output(ESS_STATE_MACHINE, State.READONLY_MODE)) // + .output(STATE_MACHINE, State.READONLY_MODE)) // ; } } diff --git a/io.openems.edge.fenecon.mini/test/io/openems/edge/fenecon/mini/gridmeter/FeneconMiniGridMeterImplTest.java b/io.openems.edge.fenecon.mini/test/io/openems/edge/fenecon/mini/gridmeter/FeneconMiniGridMeterImplTest.java index 9f22123b8b5..e478c89f29f 100644 --- a/io.openems.edge.fenecon.mini/test/io/openems/edge/fenecon/mini/gridmeter/FeneconMiniGridMeterImplTest.java +++ b/io.openems.edge.fenecon.mini/test/io/openems/edge/fenecon/mini/gridmeter/FeneconMiniGridMeterImplTest.java @@ -8,17 +8,14 @@ public class FeneconMiniGridMeterImplTest { - private static final String METER_ID = "meter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new FeneconMiniGridMeterImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // + .setId("meter0") // + .setModbusId("modbus0") // .build()) // ; } diff --git a/io.openems.edge.fenecon.mini/test/io/openems/edge/fenecon/mini/pvmeter/FeneconMiniPvMeterImplTest.java b/io.openems.edge.fenecon.mini/test/io/openems/edge/fenecon/mini/pvmeter/FeneconMiniPvMeterImplTest.java index 143b1e2d51a..59e45468495 100644 --- a/io.openems.edge.fenecon.mini/test/io/openems/edge/fenecon/mini/pvmeter/FeneconMiniPvMeterImplTest.java +++ b/io.openems.edge.fenecon.mini/test/io/openems/edge/fenecon/mini/pvmeter/FeneconMiniPvMeterImplTest.java @@ -8,17 +8,14 @@ public class FeneconMiniPvMeterImplTest { - private static final String METER_ID = "meter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new FeneconMiniPvMeterImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // + .setId("meter0") // + .setModbusId("modbus0") // .build()) // ; } diff --git a/io.openems.edge.fenecon.pro/.classpath b/io.openems.edge.fenecon.pro/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.fenecon.pro/.classpath +++ b/io.openems.edge.fenecon.pro/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.fenecon.pro/src/io/openems/edge/fenecon/pro/pvmeter/FeneconProPvMeterImpl.java b/io.openems.edge.fenecon.pro/src/io/openems/edge/fenecon/pro/pvmeter/FeneconProPvMeterImpl.java index 896686de264..3e40fd09bd7 100644 --- a/io.openems.edge.fenecon.pro/src/io/openems/edge/fenecon/pro/pvmeter/FeneconProPvMeterImpl.java +++ b/io.openems.edge.fenecon.pro/src/io/openems/edge/fenecon/pro/pvmeter/FeneconProPvMeterImpl.java @@ -17,6 +17,7 @@ import org.osgi.service.metatype.annotations.Designate; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.types.MeterType; import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent; import io.openems.edge.bridge.modbus.api.BridgeModbus; import io.openems.edge.bridge.modbus.api.ModbusComponent; @@ -29,7 +30,6 @@ import io.openems.edge.common.event.EdgeEventConstants; import io.openems.edge.common.taskmanager.Priority; import io.openems.edge.meter.api.ElectricityMeter; -import io.openems.edge.meter.api.MeterType; @Designate(ocd = Config.class, factory = true) @Component(// diff --git a/io.openems.edge.fenecon.pro/test/io/openems/edge/fenecon/pro/ess/FeneconProEssImplTest.java b/io.openems.edge.fenecon.pro/test/io/openems/edge/fenecon/pro/ess/FeneconProEssImplTest.java index 05e9ee82109..31a40767aca 100644 --- a/io.openems.edge.fenecon.pro/test/io/openems/edge/fenecon/pro/ess/FeneconProEssImplTest.java +++ b/io.openems.edge.fenecon.pro/test/io/openems/edge/fenecon/pro/ess/FeneconProEssImplTest.java @@ -9,18 +9,15 @@ public class FeneconProEssImplTest { - private static final String ESS_ID = "ess0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new FeneconProEssImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("power", new DummyPower()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(ESS_ID) // - .setModbusId(MODBUS_ID) // + .setId("ess0") // + .setModbusId("modbus0") // .build()) // ; } diff --git a/io.openems.edge.fenecon.pro/test/io/openems/edge/fenecon/pro/pvmeter/FeneconProPvMeterImplTest.java b/io.openems.edge.fenecon.pro/test/io/openems/edge/fenecon/pro/pvmeter/FeneconProPvMeterImplTest.java index d9163f8438f..d46d42f0cc1 100644 --- a/io.openems.edge.fenecon.pro/test/io/openems/edge/fenecon/pro/pvmeter/FeneconProPvMeterImplTest.java +++ b/io.openems.edge.fenecon.pro/test/io/openems/edge/fenecon/pro/pvmeter/FeneconProPvMeterImplTest.java @@ -8,17 +8,14 @@ public class FeneconProPvMeterImplTest { - private static final String METER_ID = "meter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new FeneconProPvMeterImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // + .setId("meter0") // + .setModbusId("modbus0") // .build()) // ; } diff --git a/io.openems.edge.goodwe/.classpath b/io.openems.edge.goodwe/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.goodwe/.classpath +++ b/io.openems.edge.goodwe/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.goodwe/doc/GoodWeSerialNrRule.png b/io.openems.edge.goodwe/doc/GoodWeSerialNrRule.png new file mode 100644 index 00000000000..1e55c49d82c Binary files /dev/null and b/io.openems.edge.goodwe/doc/GoodWeSerialNrRule.png differ diff --git a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/batteryinverter/Config.java b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/batteryinverter/Config.java index c81b81a823c..ab5a2ec8bcd 100644 --- a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/batteryinverter/Config.java +++ b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/batteryinverter/Config.java @@ -60,5 +60,8 @@ @AttributeDefinition(name = "Enable/disable Ripple Control Receiver", description = "Enable/disable Ripple Control Receiver (RCR) function") EnableDisable rcrEnable() default EnableDisable.DISABLE; + @AttributeDefinition(name = "Enable/disable NA-protection", description = "Enable/disable NA-protection") + EnableDisable naProtectionEnable() default EnableDisable.DISABLE; + String webconsole_configurationFactory_nameHint() default "GoodWe Battery Inverter [{id}]"; } diff --git a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/batteryinverter/GoodWeBatteryInverter.java b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/batteryinverter/GoodWeBatteryInverter.java index fd50c862023..c8e725dca44 100644 --- a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/batteryinverter/GoodWeBatteryInverter.java +++ b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/batteryinverter/GoodWeBatteryInverter.java @@ -16,7 +16,7 @@ public interface GoodWeBatteryInverter public static enum ChannelId implements io.openems.edge.common.channel.ChannelId { STATE_MACHINE(Doc.of(State.values()) // .text("Current State of State-Machine")), // - RUN_FAILED(Doc.of(Level.FAULT) // + RUN_FAILED(Doc.of(Level.WARNING) // .text("Running the Logic failed")); // private final Doc doc; diff --git a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/batteryinverter/GoodWeBatteryInverterImpl.java b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/batteryinverter/GoodWeBatteryInverterImpl.java index 57f17030ddd..49e26e25259 100644 --- a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/batteryinverter/GoodWeBatteryInverterImpl.java +++ b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/batteryinverter/GoodWeBatteryInverterImpl.java @@ -115,7 +115,7 @@ protected void setModbus(BridgeModbus modbus) { protected static record BatteryData(Integer chargeMaxCurrent, Integer voltage) { } - private Config config; + private Config config = null; public GoodWeBatteryInverterImpl() throws OpenemsNamedException { super(// @@ -167,7 +167,7 @@ protected void deactivate() { @Override public void handleEvent(Event event) { - if (!this.isEnabled()) { + if (this.config == null || !this.isEnabled()) { return; } super.handleEvent(event); @@ -337,9 +337,9 @@ private void applyConfigIfNotSet(Config config, boolean onConfigUpdate) throws O } } - // Ripple Control Receiver on / off - setWriteValueIfNotRead(this.channel(GoodWe.ChannelId.RIPPLE_CONTROL_RECEIVER_ENABLE), - config.rcrEnable().booleanValue); + // Multi-functional Block for Ripple Control Receiver and NA protection on / off + setWriteValueIfNotRead(this.channel(GoodWe.ChannelId.DRED_REMOTE_SHUTDOWN_RCR_FUNCTIONS_ENABLE), + config.rcrEnable().booleanValue || config.naProtectionEnable().booleanValue); } /** @@ -365,8 +365,8 @@ private void setBatteryLimits(Battery battery) throws OpenemsNamedException { var bmsOfflineSocUnderMin = bmsOfflineSocUnderMinChannel.value(); var setBatteryStrings = TypeUtils.divide(battery.getDischargeMinVoltage().get(), MODULE_MIN_VOLTAGE); - var setChargeMaxCurrent = this.getGoodweType().maxDcCurrent; - var setDischargeMaxCurrent = this.getGoodweType().maxDcCurrent; + final int setChargeMaxCurrent; + final int setDischargeMaxCurrent; var setChargeMaxVoltage = battery.getChargeMaxVoltage().orElse(210); var setDischargeMinVoltage = battery.getDischargeMinVoltage().orElse(210); Integer setSocUnderMin = 0; // [0-100]; 0 MinSoc = 100 DoD @@ -376,19 +376,26 @@ private void setBatteryLimits(Battery battery) throws OpenemsNamedException { setBatteryStrings = homeBattery.getNumberOfModulesPerTower().orElse(setBatteryStrings); + final var batteryType = homeBattery.getBatteryHardwareType(); + /* * Check combination of GoodWe inverter and FENECON Home battery to avoid * invalid combinations for FENECON Home Systems */ - if (this.getGoodweType().isInvalidBattery.test(homeBattery.getBatteryHardwareType())) { + if (this.getGoodweType().isInvalidBattery.test(batteryType)) { this._setImpossibleFeneconHomeCombination(true); // Set zero limits to avoid charging and discharging setChargeMaxCurrent = 0; setDischargeMaxCurrent = 0; } else { + setChargeMaxCurrent = this.getGoodweType().maxDcCurrent.apply(batteryType); + setDischargeMaxCurrent = this.getGoodweType().maxDcCurrent.apply(batteryType); this._setImpossibleFeneconHomeCombination(false); } + } else { + setChargeMaxCurrent = this.getGoodweType().maxDcCurrent.apply(null); + setDischargeMaxCurrent = this.getGoodweType().maxDcCurrent.apply(null); } /* @@ -551,7 +558,8 @@ public Integer getDcPvPower() { @Override public Integer getSurplusPower() { var productionPower = this.calculatePvProduction(); - return calculateSurplusPower(this.latestBatteryData, productionPower, this.getGoodweType().maxDcCurrent); + return calculateSurplusPower(this.latestBatteryData, productionPower, + this.getGoodweType().maxDcCurrent.apply(null)); } diff --git a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/charger/AbstractGoodWeEtCharger.java b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/charger/AbstractGoodWeEtCharger.java index cf6d669406e..3ae55260959 100644 --- a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/charger/AbstractGoodWeEtCharger.java +++ b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/charger/AbstractGoodWeEtCharger.java @@ -88,17 +88,11 @@ private void updateState() { var goodWe = this.getEssOrBatteryInverter(); Boolean hasNoDcPv = null; if (goodWe != null) { - switch (goodWe.getGoodweType().getSeries()) { - case BT: - hasNoDcPv = true; - break; - case ET: - hasNoDcPv = false; - break; - case UNDEFINED: - hasNoDcPv = null; - break; - } + hasNoDcPv = switch (goodWe.getGoodweType().getSeries()) { + case BT -> true; + case ET, ETT, EUB -> false; + case UNDEFINED -> null; + }; } this._setHasNoDcPv(hasNoDcPv); } diff --git a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/charger/singlestring/ConfigPV3.java b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/charger/singlestring/ConfigPV3.java new file mode 100644 index 00000000000..7013347479f --- /dev/null +++ b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/charger/singlestring/ConfigPV3.java @@ -0,0 +1,38 @@ +package io.openems.edge.goodwe.charger.singlestring; + +import org.osgi.service.metatype.annotations.AttributeDefinition; +import org.osgi.service.metatype.annotations.ObjectClassDefinition; + +import io.openems.edge.goodwe.GoodWeConstants; + +@ObjectClassDefinition(// + name = "GoodWe Charger PV3", // + description = "Implements the GoodWe-ET Charger 3.") + +@interface ConfigPV3 { + @AttributeDefinition(name = "Component-ID", description = "Unique ID of this Component") + String id() default "charger2"; + + @AttributeDefinition(name = "Alias", description = "Human-readable name of this Component; defaults to Component-ID") + String alias() default ""; + + @AttributeDefinition(name = "Is enabled?", description = "Is this Component enabled?") + boolean enabled() default true; + + @AttributeDefinition(name = "GoodWe ESS or Battery-Inverter", description = "ID of GoodWe Energy Storage System or Battery-Inverter.") + String essOrBatteryInverter_id() default "batteryInverter0"; + + @AttributeDefinition(name = "GoodWe ESS or Battery-Inverter target filter", description = "This is auto-generated by 'GoodWe ESS or Battery-Inverter'.") + String essOrBatteryInverter_target() default "(enabled=true)"; + + @AttributeDefinition(name = "Modbus-ID", description = "ID of Modbus bridge.") + String modbus_id() default "modbus0"; + + @AttributeDefinition(name = "Modbus Unit-ID", description = "The Unit-ID of the Modbus device.") + int modbusUnitId() default GoodWeConstants.DEFAULT_UNIT_ID; + + @AttributeDefinition(name = "Modbus target filter", description = "This is auto-generated by 'Modbus-ID'.") + String Modbus_target() default "(enabled=true)"; + + String webconsole_configurationFactory_nameHint() default "GoodWe Charger PV3 [{id}]"; +} diff --git a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/charger/singlestring/GoodWeChargerPv3.java b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/charger/singlestring/GoodWeChargerPv3.java new file mode 100644 index 00000000000..bb1cba2036f --- /dev/null +++ b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/charger/singlestring/GoodWeChargerPv3.java @@ -0,0 +1,94 @@ +package io.openems.edge.goodwe.charger.singlestring; + +import org.osgi.service.cm.ConfigurationAdmin; +import org.osgi.service.component.ComponentContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.ConfigurationPolicy; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; +import org.osgi.service.component.annotations.ReferencePolicyOption; +import org.osgi.service.event.EventHandler; +import org.osgi.service.event.propertytypes.EventTopics; +import org.osgi.service.metatype.annotations.Designate; + +import io.openems.common.exceptions.OpenemsException; +import io.openems.edge.bridge.modbus.api.BridgeModbus; +import io.openems.edge.bridge.modbus.api.ModbusComponent; +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.common.event.EdgeEventConstants; +import io.openems.edge.ess.dccharger.api.EssDcCharger; +import io.openems.edge.goodwe.charger.AbstractGoodWeEtCharger; +import io.openems.edge.goodwe.charger.GoodWeCharger; +import io.openems.edge.goodwe.common.GoodWe; +import io.openems.edge.timedata.api.Timedata; +import io.openems.edge.timedata.api.TimedataProvider; + +@Designate(ocd = ConfigPV3.class, factory = true) +@Component(// + name = "GoodWe.Charger-PV3", // + immediate = true, // + configurationPolicy = ConfigurationPolicy.REQUIRE // +) +@EventTopics({ // + EdgeEventConstants.TOPIC_CYCLE_AFTER_PROCESS_IMAGE // +}) +public class GoodWeChargerPv3 extends AbstractGoodWeEtCharger + implements GoodWeCharger, EssDcCharger, ModbusComponent, OpenemsComponent, EventHandler, TimedataProvider { + + @Reference + private ConfigurationAdmin cm; + + @Reference(policy = ReferencePolicy.STATIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.MANDATORY) + private GoodWe essOrBatteryInverter; + + @Reference(policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.OPTIONAL) + private volatile Timedata timedata = null; + + @Override + @Reference(policy = ReferencePolicy.STATIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.MANDATORY) + protected void setModbus(BridgeModbus modbus) { + super.setModbus(modbus); + } + + @Activate + private void activate(ComponentContext context, ConfigPV3 config) throws OpenemsException { + if (super.activate(context, config.id(), config.alias(), config.enabled(), config.modbusUnitId(), this.cm, + "Modbus", config.modbus_id())) { + return; + } + + if (OpenemsComponent.updateReferenceFilter(this.cm, this.servicePid(), "essOrBatteryInverter", + config.essOrBatteryInverter_id())) { + return; + } + + if (this.essOrBatteryInverter != null) { + this.essOrBatteryInverter.addCharger(this); + } + } + + @Override + @Deactivate + protected void deactivate() { + this.essOrBatteryInverter.removeCharger(this); + super.deactivate(); + } + + @Override + protected int getStartAddress() { + return 35111; + } + + @Override + public Timedata getTimedata() { + return this.timedata; + } + + @Override + protected GoodWe getEssOrBatteryInverter() { + return this.essOrBatteryInverter; + } +} diff --git a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/AbstractGoodWe.java b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/AbstractGoodWe.java index 3fcfec14317..ee3a44735af 100644 --- a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/AbstractGoodWe.java +++ b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/AbstractGoodWe.java @@ -4,7 +4,10 @@ import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SCALE_FACTOR_2; import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SCALE_FACTOR_MINUS_1; import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SCALE_FACTOR_MINUS_2; +import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SET_NULL_FOR_DEFAULT; +import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.chain; import static io.openems.edge.bridge.modbus.api.ModbusUtils.readElementOnce; +import static io.openems.edge.bridge.modbus.api.ModbusUtils.FunctionCode.FC3; import static io.openems.edge.common.type.TypeUtils.fitWithin; import java.util.HashSet; @@ -123,12 +126,17 @@ protected final ModbusProtocol defineModbusProtocol() { new FC3ReadRegistersTask(35111, Priority.LOW, // // Registers for PV1 and PV2 (35103 to 35110) are read via DC-Charger // implementation - m(GoodWe.ChannelId.V_PV3, new UnsignedWordElement(35111), SCALE_FACTOR_MINUS_1), // + m(GoodWe.ChannelId.V_PV3, new UnsignedWordElement(35111), + chain(SET_NULL_FOR_DEFAULT(0xFFFF), SCALE_FACTOR_MINUS_1)), // m(GoodWe.ChannelId.I_PV3, new UnsignedWordElement(35112), SCALE_FACTOR_MINUS_1), // m(GoodWe.ChannelId.P_PV3, new UnsignedDoublewordElement(35113)), // - m(GoodWe.ChannelId.V_PV4, new UnsignedWordElement(35115), SCALE_FACTOR_MINUS_1), // - m(GoodWe.ChannelId.I_PV4, new UnsignedWordElement(35116), SCALE_FACTOR_MINUS_1), // - m(GoodWe.ChannelId.P_PV4, new UnsignedDoublewordElement(35117)), // + m(GoodWe.ChannelId.V_PV4, new UnsignedWordElement(35115), + chain(SET_NULL_FOR_DEFAULT(0xFFFF), SCALE_FACTOR_MINUS_1)), // + m(GoodWe.ChannelId.I_PV4, new UnsignedWordElement(35116), + chain(SET_NULL_FOR_DEFAULT(0xFFFF), SCALE_FACTOR_MINUS_1)), // + m(GoodWe.ChannelId.P_PV4, new UnsignedDoublewordElement(35117), + chain(SET_NULL_FOR_DEFAULT(0xFFFFFFFFL), SCALE_FACTOR_MINUS_1)), // + m(GoodWe.ChannelId.PV_MODE, new UnsignedDoublewordElement(35119)), // // Registers for Grid Smart-Meter (35121 to 35135) are read via GridMeter // implementation @@ -1195,12 +1203,14 @@ protected final ModbusProtocol defineModbusProtocol() { ); /* - * Handles different GoodWe Types. + * Handle different GoodWe Types. + * + * GoodweType Firmware is differing from Type ET-Plus to ETT. * - * Register 35011: GoodWeType as String (Not supported for GoodWe 20 & 30) - * Register 35003: Serial number as String (Fallback for GoodWe 20 & 30) + * Register 35011: GoodWeType as String (Not supported for GoodWe 20 & 30 - ETT) + * Register 35003: Serial number as String (Fallback for GoodWe 20 & 30 - ETT) */ - readElementOnce(protocol, ModbusUtils::retryOnNull, new StringWordElement(35011, 5)) // + readElementOnce(FC3, protocol, ModbusUtils::retryOnNull, new StringWordElement(35011, 5)) // .thenAccept(value -> { /* @@ -1210,56 +1220,57 @@ protected final ModbusProtocol defineModbusProtocol() { TypeUtils.getAsType(OpenemsType.STRING, value)); if (resultFromString != GoodWeType.UNDEFINED) { + + /* + * ET-Plus + */ this.logInfo(this.log, "Identified " + resultFromString.getName()); this._setGoodweType(resultFromString); + + // Handles different ET-Plus DSP versions + ModbusUtils + .readElementOnce(FC3, protocol, ModbusUtils::retryOnNull, + new UnsignedWordElement(35016)) // + .thenAccept(dspVersion -> { + try { + if (dspVersion >= 5) { + this.handleDspVersion5(protocol); + } + if (dspVersion >= 6) { + this.handleDspVersion6(protocol); + } + if (dspVersion >= 7) { + this.handleDspVersion7(protocol); + } + } catch (OpenemsException e) { + this.logError(this.log, "Unable to add task for modbus protocol"); + } + }); return; } /* * Evaluate GoodweType from serial number */ - readElementOnce(protocol, ModbusUtils::retryOnNull, new StringWordElement(35003, 8)) // + readElementOnce(FC3, protocol, ModbusUtils::retryOnNull, new StringWordElement(35003, 8)) // .thenAccept(serialNr -> { + final var hardwareType = getGoodWeTypeFromSerialNr(serialNr); try { this._setGoodweType(hardwareType); + this.handleDspVersion5(protocol); + this.handleDspVersion6(protocol); + this.handleDspVersion7(protocol); if (hardwareType == GoodWeType.FENECON_FHI_20_DAH || hardwareType == GoodWeType.FENECON_FHI_29_9_DAH) { this.handleMultipleStringChargers(protocol); } - } catch (OpenemsException e) { this.logError(this.log, "Unable to add charger tasks for modbus protocol"); } }); }); - // Handles different DSP versions - readElementOnce(protocol, ModbusUtils::retryOnNull, new UnsignedWordElement(35016)) // - .thenAccept(dspVersion -> { - try { - - // GoodWe 30 has DspFmVersionMaster=0 & DspBetaVersion=80 - if (dspVersion == 0) { - this.handleDspVersion5(protocol); - this.handleDspVersion6(protocol); - this.handleDspVersion7(protocol); - return; - } - if (dspVersion >= 5) { - this.handleDspVersion5(protocol); - } - if (dspVersion >= 6) { - this.handleDspVersion6(protocol); - } - if (dspVersion >= 7) { - this.handleDspVersion7(protocol); - } - } catch (OpenemsException e) { - this.logError(this.log, "Unable to add task for modbus protocol"); - } - }); - return protocol; } @@ -1329,7 +1340,7 @@ protected static GoodWeType getGoodWeTypeFromSerialNr(String serialNr) { /** * Handle multiple string chargers. - * + * *

    * For MPPT connectors e.g. two string on one MPPT the power information is * spread over several registers that should be read as complete blocks. @@ -1809,7 +1820,7 @@ private void handleDspVersion6(ModbusProtocol protocol) throws OpenemsException m(GoodWe.ChannelId.DRED_CMD, new UnsignedWordElement(47007)), // new DummyRegisterElement(47008), // m(GoodWe.ChannelId.WIFI_OR_LAN_SWITCH, new UnsignedWordElement(47009)), // - m(GoodWe.ChannelId.RIPPLE_CONTROL_RECEIVER_ENABLE, new UnsignedWordElement(47010)), // + m(GoodWe.ChannelId.DRED_REMOTE_SHUTDOWN_RCR_FUNCTIONS_ENABLE, new UnsignedWordElement(47010)), // new DummyRegisterElement(47011), // m(GoodWe.ChannelId.LED_BLINK_TIME, new UnsignedWordElement(47012)), // m(GoodWe.ChannelId.WIFI_LED_STATE, new UnsignedWordElement(47013)), // @@ -1854,7 +1865,7 @@ private void handleDspVersion6(ModbusProtocol protocol) throws OpenemsException // For wifi+Lan module, to switch to LAN or WiFi communicaiton m(GoodWe.ChannelId.WIFI_OR_LAN_SWITCH, new UnsignedWordElement(47009)), // // Ripple Control Receiver on/off - m(GoodWe.ChannelId.RIPPLE_CONTROL_RECEIVER_ENABLE, new UnsignedWordElement(47010)), // + m(GoodWe.ChannelId.DRED_REMOTE_SHUTDOWN_RCR_FUNCTIONS_ENABLE, new UnsignedWordElement(47010)), // new DummyRegisterElement(47011), // m(GoodWe.ChannelId.LED_BLINK_TIME, new UnsignedWordElement(47012)), // // 1: off, 2: on, 3: flash 1x, 4: flash 2x, 5: flash 4x @@ -1976,7 +1987,7 @@ protected void updatePowerAndEnergyChannels() { * Ignore impossible values of P_BATTERY */ dcDischargePower = postprocessPBattery1(dcDischargePower, this.getWbmsVoltage().get(), - this.getGoodweType().maxDcCurrent, + this.getGoodweType().maxDcCurrent.apply(null), state -> this.channel(GoodWe.ChannelId.IGNORE_IMPOSSIBLE_P_BATTERY_VALUE).setNextValue(state), dcDischargePowerChannel.value().asOptional()); diff --git a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/GoodWe.java b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/GoodWe.java index aa090eaa2b3..1dea994db32 100644 --- a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/GoodWe.java +++ b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/GoodWe.java @@ -69,8 +69,7 @@ public interface GoodWe extends OpenemsComponent { public static enum ChannelId implements io.openems.edge.common.channel.ChannelId { AC_OUTPUT_TYPE(Doc.of(OutputTypeAC.values())), // SERIAL_NUMBER(Doc.of(OpenemsType.STRING) // - .persistencePriority(PersistencePriority.HIGH) // - .accessMode(AccessMode.READ_WRITE)), + .persistencePriority(PersistencePriority.HIGH)), EMS_CHECK_INVERTER_OPERATION_STATUS(Doc.of(EmsCheck.values())), // DSP_FM_VERSION_MASTER(Doc.of(OpenemsType.INTEGER)), // DSP_FM_VERSION_SLAVE(Doc.of(OpenemsType.INTEGER)), // @@ -87,13 +86,13 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId .unit(Unit.VOLT)), // I_PV3(Doc.of(OpenemsType.INTEGER) // .unit(Unit.AMPERE)), // - P_PV3(Doc.of(OpenemsType.INTEGER) // + P_PV3(Doc.of(OpenemsType.LONG) // .unit(Unit.WATT)), // V_PV4(Doc.of(OpenemsType.INTEGER) // .unit(Unit.VOLT)), // I_PV4(Doc.of(OpenemsType.INTEGER) // .unit(Unit.AMPERE)), // - P_PV4(Doc.of(OpenemsType.INTEGER) // + P_PV4(Doc.of(OpenemsType.LONG) // .unit(Unit.WATT)), // PV_MODE(Doc.of(PvMode.values())), // TOTAL_INV_POWER(Doc.of(OpenemsType.INTEGER) // @@ -254,12 +253,12 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId .unit(Unit.KILOWATT_HOURS)), // // Error Message 35189 - STATE_0(Doc.of(Level.FAULT) // + STATE_0(Doc.of(Level.WARNING) // .text("The Ground Fault Circuit Interrupter (GFCI) detecting circuit is abnormal " // + "| Interne Fehlerstrom-Schutzeinrichtung (RCD Einheit) wurde ausgelöst " // + "| Bitte überprüfen Sie den Netzanschluss sowie ggf. Backup-Lasten")), // - STATE_1(Doc.of(Level.FAULT) // + STATE_1(Doc.of(Level.WARNING) // .text("The output current sensor is abnormal " // + "| Der Ausgangs-Stromsensor liefert unplausible Werte " // + "| Bitte überprüfen Sie die Installation")), // @@ -269,12 +268,12 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId STATE_2(Doc.of(Level.WARNING) // .text("Warning Code 1")), // - STATE_3(Doc.of(Level.FAULT) // + STATE_3(Doc.of(Level.WARNING) // .text("DCI Consistency Failure " // + "| Werte der Impedanzmessung (DCI Einheit) sind widersprüchlich/unplausibel " // + "| Bitte überprüfen Sie den Netzanschluss")), // - STATE_4(Doc.of(Level.FAULT) // + STATE_4(Doc.of(Level.WARNING) // .text("Ground Fault Circuit Interrupter (GFCI) Consistency Failure " // + "| Werte der internen Fehlerstrom-Schutzeinrichtung (RCD) sind widersprüchlich/unplausibel " // + "| Bitte überprüfen Sie den Netzanschluss")), // @@ -284,29 +283,29 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId STATE_5(Doc.of(Level.WARNING) // .text("Warning Code 2")), // - STATE_6(Doc.of(Level.FAULT) // + STATE_6(Doc.of(Level.WARNING) // .text("Ground Fault Circuit Interrupter (GFCI) Device Failure " // + "| Interne Fehlerstrom-Schutzeinrichtung (RCD Einheit) befindet sich im Fehlerzustand " // + "| Bitte führen Sie einen Geräteneustart aus")), // - STATE_7(Doc.of(Level.FAULT) // + STATE_7(Doc.of(Level.WARNING) // .text("Relay Device Failure " // + "| Interne Relais befinden sich im Fehlerzustand " // + "| Bitte führen Sie einen Geräteneustart aus")), // - STATE_8(Doc.of(Level.FAULT) // + STATE_8(Doc.of(Level.WARNING) // .text("AC HCT Failure " // + "| Die HCT Einheit befindet sich im Fehlerzustand " // + "| Bitte führen Sie einen Geräteneustart aus")), // - STATE_9(Doc.of(Level.FAULT) // + STATE_9(Doc.of(Level.WARNING) // .text("Utility Loss " // + "| Netzausfall wurde erkannt " // + "| Bitte überprüfen Sie ob das Kommunikationsmodul richtig gesteckt ist")), // // TODO: Use new-lines or html-lists when the UI and edge log are able to handle // them - STATE_10(Doc.of(Level.FAULT) // + STATE_10(Doc.of(Level.WARNING) // .text("Ground I Failure " // + "| Erdungsfehler " // + "| Ggf. N und PE Leiter sind nicht richtig mit dem Netzanschluss des Wechselrichters verbunden. " // @@ -320,7 +319,7 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId + "| Ggf. übersteigt die Leerlauf- oder Betriebsspannung der PV-Module den für diesen Wechselrichter zulässigen Bereich. " // + "Ggf. liegt ein PV-Kriechstrom zur Erde an")), // - STATE_12(Doc.of(Level.FAULT) // + STATE_12(Doc.of(Level.WARNING) // .text("Internal Fan Failure " // + "| Der interne Lüfter meldet einen Defekt")), // @@ -331,13 +330,13 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId + "Ggf. Luftstrom durch den Kühlkörper für Normalbetrieb unzureichend (Aufstellbedingungen beachten!). " + "Ggf. Behinderung des Luftstroms, z.B. Kühlkörper wurde abgedeckt")), // - STATE_14(Doc.of(Level.FAULT) // + STATE_14(Doc.of(Level.WARNING) // .text("Utility Phase Failure " // + "| Phasenfehler " // + "| Überprüfen Sie das Drehfeld am Wechselrichter. " // + "Ggf. Kommunikationsadapter (ET+) nicht (richtig) gesteckt")), // - STATE_15(Doc.of(Level.FAULT) // + STATE_15(Doc.of(Level.WARNING) // .text("PV Over Voltage " // + "| Überspannung PV " // + "| Bitte überprüfen Sie die Installation")), // @@ -346,13 +345,13 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId .text("External Fan Failure " // + "| Externer Lüfter befindet sich im Fehlerzustand")), // - STATE_17(Doc.of(Level.FAULT) // + STATE_17(Doc.of(Level.WARNING) // .text("Vac Failure " // + "| Spannungsfehler " // + "| Die anliegende Spannung am \"On-Grid\" Anschluss befindet sich außerhalb der gültigen Parameter (für DE siehe VDE AR N 4105). " // + "Ggf. Kommunikationsmodul nicht (richtig) gesteckt")), // - STATE_18(Doc.of(Level.FAULT) // + STATE_18(Doc.of(Level.WARNING) // .text("Isolation resistance of PV-plant too low " // + "| Isolationsfehler auf PV-Strings " // + "| Bitte überprüfen Sie die Installation")), // @@ -362,7 +361,7 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId + "| DC-Strom Einspeisung auf \"On-Grid\" Seite ist zu hoch " // + "| Bitte überprüfen Sie die Installation und angeschlossene Verbraucher bzw. Erzeuger")), // - STATE_20(Doc.of(Level.FAULT) // + STATE_20(Doc.of(Level.WARNING) // .text("Back-Up Over Load " // + "| Überlastung Backup-Anschluss " // + "| Bitte beachten Sie die im Datenblatt angegebenen Maximal-Lasten")), // @@ -372,12 +371,12 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId STATE_21(Doc.of(Level.WARNING) // .text("Warning Code 3")), // - STATE_22(Doc.of(Level.FAULT) // + STATE_22(Doc.of(Level.WARNING) // .text("Difference between Master and Slave frequency too high " // + "| Frequenz zwischen Master und Slave weicht zu stark ab " // + "| Bitte führen Sie einen Geräteneustart aus")), // - STATE_23(Doc.of(Level.FAULT) // + STATE_23(Doc.of(Level.WARNING) // .text("Difference between Master and Slave voltage too high " // + "| Spannung zwischen Master und Slave weicht zu stark ab " // + "| Bitte führen Sie einen Geräteneustart aus")), // @@ -387,7 +386,7 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId STATE_24(Doc.of(Level.WARNING) // .text("Warning Code 4")), // - STATE_25(Doc.of(Level.FAULT) // + STATE_25(Doc.of(Level.WARNING) // .text("Relay Check Failure " // + "| Selbsttest der Relais ist Fehlgeschlagen " // + "| Ggf. sind N und PE-Leiter nicht richtig mit den Anschlussklemmen des Wechselrichters verbunden. " // @@ -409,17 +408,17 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId + "| Kommunikation zwischen der ARM und DSP Einheit ist fehlgeschlagen " // + "| Bitte führen Sie einen Geräteneustart aus")), // - STATE_29(Doc.of(Level.FAULT) // + STATE_29(Doc.of(Level.WARNING) // .text("The grid frequency is out of tolerable range " // + "| Die Netz-Frequenz befindet sich außerhalb der zulässigen Parameter " // + "| Bitte überprüfen Sie die Installation und führen anschließend einen Geräteneustart aus")), // - STATE_30(Doc.of(Level.FAULT) // + STATE_30(Doc.of(Level.WARNING) // .text("EEPROM cannot be read or written " // + "| EEPROM kann nicht gelesen oder geschrieben werden " // + "| Bitte führen Sie einen Geräteneustart aus")), // - STATE_31(Doc.of(Level.FAULT) // + STATE_31(Doc.of(Level.WARNING) // .text("Communication failure between microcontrollers " // + "| Die Kommunikation zwischen den einzelnen Microkontrollern ist fehlerhaft " // + "| Bitte führen Sie einen Geräteneustart aus")), // @@ -1312,7 +1311,15 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId FEED_POWER_PARA_SET(Doc.of(OpenemsType.INTEGER) // .unit(Unit.WATT) // .accessMode(AccessMode.READ_WRITE)), // - RIPPLE_CONTROL_RECEIVER_ENABLE(Doc.of(OpenemsType.BOOLEAN) // + /** + * Enable block used for multiple remote functions. + * + *

    + * DRED, Remote shutdown and RCR function must be enabled with one register. + * Depending on the hardware installation each function is activated + * individually. + */ + DRED_REMOTE_SHUTDOWN_RCR_FUNCTIONS_ENABLE(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_WRITE)), // DEBUG_EMS_POWER_MODE(Doc.of(EmsPowerMode.values())), // @@ -1642,7 +1649,7 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId .text("SMART mode does not work correctly with active PID filter")), NO_SMART_METER_DETECTED(Doc.of(Level.WARNING) // .text("No GoodWe Smart Meter detected. Only REMOTE mode can work correctly")), - IMPOSSIBLE_FENECON_HOME_COMBINATION(Doc.of(Level.FAULT) // + IMPOSSIBLE_FENECON_HOME_COMBINATION(Doc.of(Level.WARNING) // .text("The installed inverter and battery combination is not authorised. Operation could cause hardware damages, so charging and discharging is blocked. Please install a complete Home 10, Home 20 or Home 30 system.")), // IGNORE_IMPOSSIBLE_P_BATTERY_VALUE(Doc.of(OpenemsType.BOOLEAN) // .text("Ignore impossible battery power")) // diff --git a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/enums/GoodWeType.java b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/enums/GoodWeType.java index 32e2a1cec1a..c083ed82d48 100644 --- a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/enums/GoodWeType.java +++ b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/enums/GoodWeType.java @@ -1,5 +1,9 @@ package io.openems.edge.goodwe.common.enums; +import static io.openems.edge.battery.fenecon.home.BatteryFeneconHomeHardwareType.BATTERY_52; +import static io.openems.edge.battery.fenecon.home.BatteryFeneconHomeHardwareType.BATTERY_64; + +import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Stream; @@ -15,27 +19,32 @@ public enum GoodWeType implements OptionsEnum { GOODWE_10K_ET(20, "GoodWe GW10K-ET", Series.ET, 25), // GOODWE_8K_ET(21, "GoodWe GW8K-ET", Series.ET, 25), // GOODWE_5K_ET(22, "GoodWe GW5K-ET", Series.ET, 25), // - FENECON_FHI_10_DAH(30, "FENECON FHI 10 DAH", Series.ET, 25, position2Filter("10"), - (batteryType) -> batteryType != BatteryFeneconHomeHardwareType.BATTERY_52), // - FENECON_FHI_20_DAH(120, "FENECON FHI 20 DAH", Series.ET, 50, position2Filter("20"), - (batteryType) -> batteryType != BatteryFeneconHomeHardwareType.BATTERY_64), // - FENECON_FHI_29_9_DAH(130, "FENECON FHI 30 DAH", Series.ET, 50, home30Filter("29K9", "30"), - (batteryType) -> batteryType != BatteryFeneconHomeHardwareType.BATTERY_64); // + FENECON_FHI_10_DAH(30, "FENECON FHI 10 DAH", Series.ET, // + authorisedLimit(25, 25, 0), serialNrFilter("010K", "ETU"), notHomeBattery52Ah()), // + FENECON_FHI_20_DAH(120, "FENECON FHI 20 DAH", Series.ETT, // + authorisedLimit(50, 0, 50), serialNrFilter("020K", "ETT"), notHomeBattery64Ah()), // + FENECON_FHI_29_9_DAH(130, "FENECON FHI 30 DAH", Series.ETT, // + authorisedLimit(50, 0, 50), home30Filter("29K9", "030K"), notHomeBattery64Ah()), + FENECON_GEN2_6K(140, "FENECON ET Gen2 6K", Series.EUB, // + authorisedLimit(40, 25, 40), serialNrFilter("6000", "EUB"), notHomeBattery52Or64Ah()), + FENECON_GEN2_10K(150, "FENECON ET Gen2 10K", Series.EUB, // + authorisedLimit(40, 25, 40), serialNrFilter("010K", "EUB"), notHomeBattery52Or64Ah()), + FENECON_GEN2_15K(160, "FENECON ET Gen2 15K", Series.EUB, // + authorisedLimit(40, 25, 40), serialNrFilter("015K", "EUB"), notHomeBattery52Or64Ah()); // public static enum Series { - UNDEFINED, BT, ET; + UNDEFINED, BT, ET, ETT, EUB; } - // TODO: Change logic of isValidHomeBattery to invalidBattery - private final int value; private final String option; private final Series series; - public final int maxDcCurrent; // [A] + public final Function maxDcCurrent; public final ThrowingFunction serialNrFilter; public final Predicate isInvalidBattery; - private GoodWeType(int value, String option, Series series, int maxDcCurrent, + private GoodWeType(int value, String option, Series series, + Function maxDcCurrent, ThrowingFunction serialNrFilter, Predicate isInvalidBattery) { this.value = value; @@ -48,7 +57,7 @@ private GoodWeType(int value, String option, Series series, int maxDcCurrent, private GoodWeType(int value, String option, Series series, int maxDcCurrent) { // No serial number filter and battery dependency - this(value, option, series, maxDcCurrent, (t) -> false, (t) -> false); + this(value, option, series, (notUsed) -> maxDcCurrent, (t) -> false, (t) -> false); } @Override @@ -100,8 +109,61 @@ public static ThrowingFunction position2Filter(Strin */ public static ThrowingFunction home30Filter(String... match) { return serialNr -> Stream.of(match) // - .filter(t -> t.equals(serialNr.substring(2, 4)) || serialNr.substring(1, 5).contains(t)) // + .filter(t -> { + try { + return serialNrFilter(t, "ETT").apply(serialNr); + } catch (Exception e) { + return false; + } + }) // .findFirst() // .isPresent(); } -} \ No newline at end of file + + /** + * GoodWe serial number filter. + * + *

    + * Check if a serialNr matches a given string at the common position. + * + * @param ratedPower rated power of the inverter + * @param seriesCode internal inverter model series code + * @return filter function + */ + public static ThrowingFunction serialNrFilter(String ratedPower, String seriesCode) { + return serialNr -> serialNr.substring(1, 5).equals(ratedPower) && serialNr.substring(5, 8).equals(seriesCode); + } + + private static Predicate notHomeBattery52Ah() { + return (batteryType) -> batteryType != BATTERY_52; + } + + private static Predicate notHomeBattery64Ah() { + return (batteryType) -> batteryType != BATTERY_64; + } + + private static Predicate notHomeBattery52Or64Ah() { + return (batteryType) -> batteryType != BATTERY_52 && batteryType != BATTERY_64; + } + + /** + * Maximum authorized DC current for a given battery type. + * + * @param defaultLimit default limit if it is not a known battery + * @param limit52AhBattery maximum DC current using a 52Ah battery + * @param limit64AhBattery maximum DC current using a 64Ah battery + * @return maximum DC current + */ + public static Function authorisedLimit(int defaultLimit, + int limit52AhBattery, int limit64AhBattery) { + return (batteryType) -> { + if (batteryType == null) { + return defaultLimit; + } + return switch (batteryType) { + case BATTERY_52 -> limit52AhBattery; + case BATTERY_64 -> limit64AhBattery; + }; + }; + } +} diff --git a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/emergencypowermeter/GoodWeEmergencyPowerMeterImpl.java b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/emergencypowermeter/GoodWeEmergencyPowerMeterImpl.java index a9643334fc1..de98f4d10be 100644 --- a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/emergencypowermeter/GoodWeEmergencyPowerMeterImpl.java +++ b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/emergencypowermeter/GoodWeEmergencyPowerMeterImpl.java @@ -20,6 +20,7 @@ import io.openems.common.channel.AccessMode; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.types.MeterType; import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent; import io.openems.edge.bridge.modbus.api.BridgeModbus; import io.openems.edge.bridge.modbus.api.ModbusComponent; @@ -35,7 +36,6 @@ import io.openems.edge.common.modbusslave.ModbusSlaveTable; import io.openems.edge.common.taskmanager.Priority; import io.openems.edge.meter.api.ElectricityMeter; -import io.openems.edge.meter.api.MeterType; import io.openems.edge.timedata.api.Timedata; import io.openems.edge.timedata.api.TimedataProvider; import io.openems.edge.timedata.api.utils.CalculateEnergyFromPower; diff --git a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/gridmeter/GoodWeGridMeterCategory.java b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/gridmeter/GoodWeGridMeterCategory.java index 4fe5deff296..aea1441f573 100644 --- a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/gridmeter/GoodWeGridMeterCategory.java +++ b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/gridmeter/GoodWeGridMeterCategory.java @@ -1,5 +1,5 @@ package io.openems.edge.goodwe.gridmeter; public enum GoodWeGridMeterCategory { - SMART_METER, COMMERCIAL_METER + SMART_METER, COMMERCIAL_METER, INTEGRATED_METER } diff --git a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/gridmeter/GoodWeGridMeterImpl.java b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/gridmeter/GoodWeGridMeterImpl.java index 3cb9157b12e..d6574501518 100644 --- a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/gridmeter/GoodWeGridMeterImpl.java +++ b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/gridmeter/GoodWeGridMeterImpl.java @@ -1,10 +1,14 @@ package io.openems.edge.goodwe.gridmeter; +import static io.openems.common.types.OpenemsType.INTEGER; import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.INVERT; import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SCALE_FACTOR_1; import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SCALE_FACTOR_2; import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SCALE_FACTOR_MINUS_2; import static io.openems.edge.bridge.modbus.api.ModbusUtils.readElementOnce; +import static io.openems.edge.bridge.modbus.api.ModbusUtils.FunctionCode.FC3; + +import java.util.function.Supplier; import org.osgi.service.cm.ConfigurationAdmin; import org.osgi.service.component.ComponentContext; @@ -26,7 +30,7 @@ import io.openems.common.channel.AccessMode; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.exceptions.OpenemsException; -import io.openems.common.types.OpenemsType; +import io.openems.common.types.MeterType; import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent; import io.openems.edge.bridge.modbus.api.BridgeModbus; import io.openems.edge.bridge.modbus.api.ElementToChannelConverter; @@ -40,6 +44,7 @@ import io.openems.edge.bridge.modbus.api.task.FC3ReadRegistersTask; import io.openems.edge.bridge.modbus.api.task.FC6WriteRegisterTask; import io.openems.edge.common.channel.ChannelUtils; +import io.openems.edge.common.channel.value.Value; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.event.EdgeEventConstants; import io.openems.edge.common.modbusslave.ModbusSlave; @@ -49,7 +54,6 @@ import io.openems.edge.common.type.TypeUtils; import io.openems.edge.ess.power.api.Phase; import io.openems.edge.meter.api.ElectricityMeter; -import io.openems.edge.meter.api.MeterType; import io.openems.edge.timedata.api.Timedata; import io.openems.edge.timedata.api.TimedataProvider; import io.openems.edge.timedata.api.utils.CalculateEnergyFromPower; @@ -127,7 +131,6 @@ protected void deactivate() { @Override protected ModbusProtocol defineModbusProtocol() { var protocol = new ModbusProtocol(this, // - // States new FC3ReadRegistersTask(36003, Priority.LOW, m(new UnsignedWordElement(36003)).build().onUpdateCallback((value) -> { @@ -136,7 +139,7 @@ protected ModbusProtocol defineModbusProtocol() { m(GoodWeGridMeter.ChannelId.HAS_NO_METER, new UnsignedWordElement(36004), new ElementToChannelConverter(value -> { - Integer intValue = TypeUtils.getAsType(OpenemsType.INTEGER, value); + Integer intValue = TypeUtils.getAsType(INTEGER, value); if (intValue != null) { switch (intValue) { case 0: @@ -176,15 +179,16 @@ protected ModbusProtocol defineModbusProtocol() { this.ignoreZeroAndScaleFactor1))); // Handles different DSP versions - readElementOnce(protocol, ModbusUtils::retryOnNull, new UnsignedWordElement(35016)).thenAccept(dspVersion -> { - if (dspVersion >= 4 || dspVersion == 0) { - this.handleDspVersion4(protocol); - } - }); + readElementOnce(FC3, protocol, ModbusUtils::retryOnNull, new UnsignedWordElement(35016)) + .thenAccept(dspVersion -> { + if (dspVersion >= 4 || dspVersion == 0) { + this.handleDspVersion4(protocol); + } + }); switch (this.config.goodWeMeterCategory()) { case COMMERCIAL_METER -> this.handleExternalMeter(protocol); - case SMART_METER -> { + case SMART_METER, INTEGRATED_METER -> { } } @@ -206,11 +210,14 @@ private void handleDspVersion4(ModbusProtocol protocol) { m(ElectricityMeter.ChannelId.VOLTAGE_L3, new UnsignedWordElement(36054), this.ignoreZeroAndScaleFactor2), // m(ElectricityMeter.ChannelId.CURRENT_L1, new UnsignedWordElement(36055), - this.ignoreZeroAndScaleFactor2), // + ElementToChannelConverter.chain(this.ignoreZeroAndScaleFactor2, // + createAdjustCurrentSign(this.getActivePowerL1Channel()::getNextValue))), // m(ElectricityMeter.ChannelId.CURRENT_L2, new UnsignedWordElement(36056), - this.ignoreZeroAndScaleFactor2), // + ElementToChannelConverter.chain(this.ignoreZeroAndScaleFactor2, // + createAdjustCurrentSign(this.getActivePowerL2Channel()::getNextValue))), // m(ElectricityMeter.ChannelId.CURRENT_L3, new UnsignedWordElement(36057), - this.ignoreZeroAndScaleFactor2))); // + ElementToChannelConverter.chain(this.ignoreZeroAndScaleFactor2, // + createAdjustCurrentSign(this.getActivePowerL3Channel()::getNextValue))))); // } private void handleExternalMeter(ModbusProtocol protocol) { @@ -233,7 +240,7 @@ public void handleEvent(Event event) { switch (this.config.goodWeMeterCategory()) { case COMMERCIAL_METER -> this.setExternalMeterValue(); - case SMART_METER -> { + case SMART_METER, INTEGRATED_METER -> { } } } @@ -318,7 +325,7 @@ protected void convertMeterConnectStatus(Integer value) { /** * Get the connection value depending on the phase. - * + * *

    * The information of each phase connection is part of a hex. The part of the * given phase will be returned. @@ -406,4 +413,24 @@ public ModbusSlaveTable getModbusSlaveTable(AccessMode accessMode) { ModbusSlaveNatureTable.of(GoodWeGridMeter.class, accessMode, 100).build() // ); } + + /** + * Creates an {@link ElementToChannelConverter} for + * {@link ElectricityMeter.ChannelId#CURRENT_L1}, + * {@link ElectricityMeter.ChannelId#CURRENT_L2} and + * {@link ElectricityMeter.ChannelId#CURRENT_L3} that adjusts the sign to that + * given by a supplier. + * + * @param getActivePowerNextValue {@link Supplier} for a value with a sign that + * should be copied + * @return the {@link ElementToChannelConverter} + */ + protected static ElementToChannelConverter createAdjustCurrentSign( + Supplier> getActivePowerNextValue) { + return new ElementToChannelConverter(value -> { + var activePower = getActivePowerNextValue.get().orElse(0); + Integer intValue = TypeUtils.getAsType(INTEGER, value); + return Math.abs(intValue) * Integer.signum(activePower); + }); + } } diff --git a/io.openems.edge.goodwe/test/io/openems/edge/goodwe/batteryinverter/GoodWeBatteryInverterImplTest.java b/io.openems.edge.goodwe/test/io/openems/edge/goodwe/batteryinverter/GoodWeBatteryInverterImplTest.java index 5473b431528..4ac3f5fa050 100644 --- a/io.openems.edge.goodwe/test/io/openems/edge/goodwe/batteryinverter/GoodWeBatteryInverterImplTest.java +++ b/io.openems.edge.goodwe/test/io/openems/edge/goodwe/batteryinverter/GoodWeBatteryInverterImplTest.java @@ -1,116 +1,86 @@ package io.openems.edge.goodwe.batteryinverter; +import static io.openems.common.test.TestUtils.createDummyClock; +import static io.openems.edge.battery.api.Battery.ChannelId.CHARGE_MAX_CURRENT; +import static io.openems.edge.batteryinverter.api.SymmetricBatteryInverter.ChannelId.ACTIVE_POWER; +import static io.openems.edge.batteryinverter.api.SymmetricBatteryInverter.ChannelId.MAX_APPARENT_POWER; +import static io.openems.edge.common.sum.Sum.ChannelId.GRID_ACTIVE_POWER; +import static io.openems.edge.ess.dccharger.api.EssDcCharger.ChannelId.ACTUAL_POWER; +import static io.openems.edge.ess.dccharger.api.EssDcCharger.ChannelId.CURRENT; +import static io.openems.edge.ess.dccharger.api.EssDcCharger.ChannelId.VOLTAGE; +import static io.openems.edge.goodwe.GoodWeConstants.DEFAULT_UNIT_ID; import static io.openems.edge.goodwe.batteryinverter.GoodWeBatteryInverterImpl.doSetBmsVoltage; +import static io.openems.edge.goodwe.common.GoodWe.ChannelId.EMS_POWER_MODE; +import static io.openems.edge.goodwe.common.GoodWe.ChannelId.EMS_POWER_SET; +import static io.openems.edge.goodwe.common.GoodWe.ChannelId.MAX_AC_EXPORT; +import static io.openems.edge.goodwe.common.GoodWe.ChannelId.MAX_AC_IMPORT; +import static io.openems.edge.goodwe.common.GoodWe.ChannelId.METER_COMMUNICATE_STATUS; +import static io.openems.edge.goodwe.common.GoodWe.ChannelId.MPPT1_I; +import static io.openems.edge.goodwe.common.GoodWe.ChannelId.MPPT1_P; +import static io.openems.edge.goodwe.common.GoodWe.ChannelId.MPPT2_I; +import static io.openems.edge.goodwe.common.GoodWe.ChannelId.MPPT2_P; +import static io.openems.edge.goodwe.common.GoodWe.ChannelId.MPPT3_I; +import static io.openems.edge.goodwe.common.GoodWe.ChannelId.MPPT3_P; +import static io.openems.edge.goodwe.common.GoodWe.ChannelId.TWO_S_PV1_I; +import static io.openems.edge.goodwe.common.GoodWe.ChannelId.TWO_S_PV1_V; +import static io.openems.edge.goodwe.common.GoodWe.ChannelId.TWO_S_PV2_I; +import static io.openems.edge.goodwe.common.GoodWe.ChannelId.TWO_S_PV2_V; +import static io.openems.edge.goodwe.common.GoodWe.ChannelId.TWO_S_PV3_I; +import static io.openems.edge.goodwe.common.GoodWe.ChannelId.TWO_S_PV3_V; +import static io.openems.edge.goodwe.common.GoodWe.ChannelId.TWO_S_PV4_I; +import static io.openems.edge.goodwe.common.GoodWe.ChannelId.TWO_S_PV4_V; +import static io.openems.edge.goodwe.common.GoodWe.ChannelId.TWO_S_PV5_I; +import static io.openems.edge.goodwe.common.GoodWe.ChannelId.TWO_S_PV5_V; +import static io.openems.edge.goodwe.common.GoodWe.ChannelId.TWO_S_PV6_I; +import static io.openems.edge.goodwe.common.GoodWe.ChannelId.TWO_S_PV6_V; +import static io.openems.edge.goodwe.common.GoodWe.ChannelId.WBMS_CHARGE_MAX_CURRENT; +import static io.openems.edge.goodwe.common.GoodWe.ChannelId.WBMS_DISCHARGE_MAX_CURRENT; +import static io.openems.edge.goodwe.common.GoodWe.ChannelId.WBMS_VOLTAGE; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import org.junit.Test; -import io.openems.common.types.ChannelAddress; -import io.openems.edge.battery.api.Battery; import io.openems.edge.battery.test.DummyBattery; +import io.openems.edge.batteryinverter.api.SymmetricBatteryInverter; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.channel.value.Value; import io.openems.edge.common.startstop.StartStopConfig; import io.openems.edge.common.sum.DummySum; +import io.openems.edge.common.sum.GridMode; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyComponentManager; import io.openems.edge.common.test.DummyConfigurationAdmin; +import io.openems.edge.ess.api.SymmetricEss; import io.openems.edge.ess.test.DummyPower; -import io.openems.edge.goodwe.GoodWeConstants; import io.openems.edge.goodwe.charger.singlestring.GoodWeChargerPv1; import io.openems.edge.goodwe.charger.twostring.GoodWeChargerTwoStringImpl; import io.openems.edge.goodwe.charger.twostring.PvPort; +import io.openems.edge.goodwe.common.GoodWe; import io.openems.edge.goodwe.common.enums.ControlMode; import io.openems.edge.goodwe.common.enums.EmsPowerMode; import io.openems.edge.goodwe.common.enums.EnableDisable; import io.openems.edge.goodwe.common.enums.FeedInPowerSettings; +import io.openems.edge.goodwe.common.enums.GoodWeType; import io.openems.edge.goodwe.common.enums.MeterCommunicateStatus; +import io.openems.edge.goodwe.common.enums.PvMode; import io.openems.edge.goodwe.common.enums.SafetyCountry; @SuppressWarnings("deprecation") public class GoodWeBatteryInverterImplTest { - private static final String MODBUS_ID = "modbus0"; - private static final String BATTERY_ID = "battery0"; - private static final String BATTERY_INVERTER_ID = "batteryInverter0"; - private static final String CHARGER_ID = "charger0"; - private static final String CHARGER_2_ID = "charger1"; - private static final String CHARGER_3_ID = "charger2"; - private static final String CHARGER_4_ID = "charger3"; - private static final String CHARGER_5_ID = "charger4"; - private static final String CHARGER_6_ID = "charger5"; - private static final String SUM_ID = "_sum"; - - private static final Battery BATTERY = new DummyBattery(BATTERY_ID); - - private static final ChannelAddress EMS_POWER_MODE = new ChannelAddress(BATTERY_INVERTER_ID, "EmsPowerMode"); - private static final ChannelAddress EMS_POWER_SET = new ChannelAddress(BATTERY_INVERTER_ID, "EmsPowerSet"); - private static final ChannelAddress METER_COMMUNICATE_STATUS = new ChannelAddress(BATTERY_INVERTER_ID, - "MeterCommunicateStatus"); - private static final ChannelAddress MAX_AC_IMPORT = new ChannelAddress(BATTERY_INVERTER_ID, "MaxAcImport"); - private static final ChannelAddress MAX_AC_EXPORT = new ChannelAddress(BATTERY_INVERTER_ID, "MaxAcExport"); - private static final ChannelAddress GRID_ACTIVE_POWER = new ChannelAddress(SUM_ID, "GridActivePower"); - private static final ChannelAddress ACTIVE_POWER = new ChannelAddress(BATTERY_INVERTER_ID, "ActivePower"); - private static final ChannelAddress CHARGE_MAX_CURRENT = new ChannelAddress(BATTERY_ID, "ChargeMaxCurrent"); - private static final ChannelAddress WBMS_CHARGE_MAX_CURRENT = new ChannelAddress(BATTERY_INVERTER_ID, - "WbmsChargeMaxCurrent"); - private static final ChannelAddress WBMS_DISCHARGE_MAX_CURRENT = new ChannelAddress(BATTERY_INVERTER_ID, - "WbmsDischargeMaxCurrent"); - private static final ChannelAddress WBMS_VOLTAGE = new ChannelAddress(BATTERY_INVERTER_ID, "WbmsVoltage"); - private static final ChannelAddress MAX_APPARENT_POWER = new ChannelAddress(BATTERY_INVERTER_ID, - "MaxApparentPower"); - - private static final ChannelAddress CHARGER_ACTUAL_POWER = new ChannelAddress(CHARGER_ID, "ActualPower"); - private static final ChannelAddress CHARGER_VOLTAGE = new ChannelAddress(CHARGER_ID, "Voltage"); - private static final ChannelAddress CHARGER_CURRENT = new ChannelAddress(CHARGER_ID, "Current"); - private static final ChannelAddress CHARGER_2_ACTUAL_POWER = new ChannelAddress(CHARGER_2_ID, "ActualPower"); - private static final ChannelAddress CHARGER_2_VOLTAGE = new ChannelAddress(CHARGER_2_ID, "Voltage"); - private static final ChannelAddress CHARGER_2_CURRENT = new ChannelAddress(CHARGER_2_ID, "Current"); - private static final ChannelAddress CHARGER_3_ACTUAL_POWER = new ChannelAddress(CHARGER_3_ID, "ActualPower"); - private static final ChannelAddress CHARGER_3_VOLTAGE = new ChannelAddress(CHARGER_3_ID, "Voltage"); - private static final ChannelAddress CHARGER_3_CURRENT = new ChannelAddress(CHARGER_3_ID, "Current"); - private static final ChannelAddress CHARGER_4_ACTUAL_POWER = new ChannelAddress(CHARGER_4_ID, "ActualPower"); - private static final ChannelAddress CHARGER_4_VOLTAGE = new ChannelAddress(CHARGER_4_ID, "Voltage"); - private static final ChannelAddress CHARGER_4_CURRENT = new ChannelAddress(CHARGER_4_ID, "Current"); - private static final ChannelAddress CHARGER_5_ACTUAL_POWER = new ChannelAddress(CHARGER_5_ID, "ActualPower"); - private static final ChannelAddress CHARGER_5_VOLTAGE = new ChannelAddress(CHARGER_5_ID, "Voltage"); - private static final ChannelAddress CHARGER_5_CURRENT = new ChannelAddress(CHARGER_5_ID, "Current"); - private static final ChannelAddress CHARGER_6_ACTUAL_POWER = new ChannelAddress(CHARGER_6_ID, "ActualPower"); - private static final ChannelAddress CHARGER_6_VOLTAGE = new ChannelAddress(CHARGER_6_ID, "Voltage"); - private static final ChannelAddress CHARGER_6_CURRENT = new ChannelAddress(CHARGER_6_ID, "Current"); - - private static final ChannelAddress MPPT1_P = new ChannelAddress(BATTERY_INVERTER_ID, "Mppt1P"); - private static final ChannelAddress MPPT1_I = new ChannelAddress(BATTERY_INVERTER_ID, "Mppt1I"); - private static final ChannelAddress MPPT2_P = new ChannelAddress(BATTERY_INVERTER_ID, "Mppt2P"); - private static final ChannelAddress MPPT2_I = new ChannelAddress(BATTERY_INVERTER_ID, "Mppt2I"); - private static final ChannelAddress MPPT3_P = new ChannelAddress(BATTERY_INVERTER_ID, "Mppt3P"); - private static final ChannelAddress MPPT3_I = new ChannelAddress(BATTERY_INVERTER_ID, "Mppt3I"); - private static final ChannelAddress TWO_S_PV1_I = new ChannelAddress(BATTERY_INVERTER_ID, "TwoSPv1I"); - private static final ChannelAddress TWO_S_PV1_V = new ChannelAddress(BATTERY_INVERTER_ID, "TwoSPv1V"); - private static final ChannelAddress TWO_S_PV2_I = new ChannelAddress(BATTERY_INVERTER_ID, "TwoSPv2I"); - private static final ChannelAddress TWO_S_PV2_V = new ChannelAddress(BATTERY_INVERTER_ID, "TwoSPv2V"); - private static final ChannelAddress TWO_S_PV3_I = new ChannelAddress(BATTERY_INVERTER_ID, "TwoSPv3I"); - private static final ChannelAddress TWO_S_PV3_V = new ChannelAddress(BATTERY_INVERTER_ID, "TwoSPv3V"); - private static final ChannelAddress TWO_S_PV4_I = new ChannelAddress(BATTERY_INVERTER_ID, "TwoSPv4I"); - private static final ChannelAddress TWO_S_PV4_V = new ChannelAddress(BATTERY_INVERTER_ID, "TwoSPv4V"); - private static final ChannelAddress TWO_S_PV5_I = new ChannelAddress(BATTERY_INVERTER_ID, "TwoSPv5I"); - private static final ChannelAddress TWO_S_PV5_V = new ChannelAddress(BATTERY_INVERTER_ID, "TwoSPv5V"); - private static final ChannelAddress TWO_S_PV6_I = new ChannelAddress(BATTERY_INVERTER_ID, "TwoSPv6I"); - private static final ChannelAddress TWO_S_PV6_V = new ChannelAddress(BATTERY_INVERTER_ID, "TwoSPv6V"); - @Test public void testEt() throws Exception { var charger = new GoodWeChargerPv1(); new ComponentTest(charger) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(io.openems.edge.goodwe.charger.singlestring.MyConfig.create() // - .setId(CHARGER_ID) // - .setBatteryInverterId(BATTERY_INVERTER_ID) // - .setModbusId(MODBUS_ID) // - .setModbusUnitId(GoodWeConstants.DEFAULT_UNIT_ID) // + .setId("charger0") // + .setBatteryInverterId("batteryInverter0") // + .setModbusId("modbus0") // + .setModbusUnitId(DEFAULT_UNIT_ID) // .build()); var ess = new GoodWeBatteryInverterImpl(); @@ -119,13 +89,13 @@ public void testEt() throws Exception { .addReference("power", new DummyPower()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .addReference("sum", new DummySum()) // .addComponent(charger) // .activate(MyConfig.create() // - .setId(BATTERY_INVERTER_ID) // - .setModbusId(MODBUS_ID) // - .setModbusUnitId(GoodWeConstants.DEFAULT_UNIT_ID) // + .setId("batteryInverter0") // + .setModbusId("modbus0") // + .setModbusUnitId(DEFAULT_UNIT_ID) // .setSafetyCountry(SafetyCountry.GERMANY) // .setMpptForShadowEnable(EnableDisable.ENABLE) // .setBackupEnable(EnableDisable.ENABLE) // @@ -140,9 +110,9 @@ public void testEt() throws Exception { .input(ACTIVE_POWER, 0) // .input(MAX_AC_IMPORT, 0) // .input(MAX_AC_EXPORT, 0) // - .input(CHARGER_ACTUAL_POWER, 2000) // + .input("charger0", ACTUAL_POWER, 2000) // .onExecuteWriteCallbacks(() -> { - ess.run(BATTERY, 1000, 0); + ess.run(new DummyBattery("battery0"), 1000, 0); }) // .output(EMS_POWER_MODE, EmsPowerMode.CHARGE_BAT) // .output(EMS_POWER_SET, 1000)); @@ -155,12 +125,12 @@ public void testNegativSetActivePoint() throws Exception { .addReference("power", new DummyPower()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .addReference("sum", new DummySum()) // .activate(MyConfig.create() // - .setId(BATTERY_INVERTER_ID) // - .setModbusId(MODBUS_ID) // - .setModbusUnitId(GoodWeConstants.DEFAULT_UNIT_ID) // + .setId("batteryInverter0") // + .setModbusId("modbus0") // + .setModbusUnitId(DEFAULT_UNIT_ID) // .setSafetyCountry(SafetyCountry.GERMANY) // .setMpptForShadowEnable(EnableDisable.ENABLE) // .setBackupEnable(EnableDisable.ENABLE) // @@ -176,7 +146,7 @@ public void testNegativSetActivePoint() throws Exception { .input(MAX_AC_IMPORT, 0) // .input(MAX_AC_EXPORT, 0) // .onExecuteWriteCallbacks(() -> { - ess.run(BATTERY, -1000, 0); + ess.run(new DummyBattery("battery0"), -1000, 0); }) // .output(EMS_POWER_MODE, EmsPowerMode.CHARGE_BAT) // .output(EMS_POWER_SET, 1000)); @@ -189,12 +159,12 @@ public void testDischargeBattery() throws Exception { .addReference("power", new DummyPower()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .addReference("sum", new DummySum()) // .activate(MyConfig.create() // - .setId(BATTERY_INVERTER_ID) // - .setModbusId(MODBUS_ID) // - .setModbusUnitId(GoodWeConstants.DEFAULT_UNIT_ID) // + .setId("batteryInverter0") // + .setModbusId("modbus0") // + .setModbusUnitId(DEFAULT_UNIT_ID) // .setSafetyCountry(SafetyCountry.GERMANY) // .setMpptForShadowEnable(EnableDisable.ENABLE) // .setBackupEnable(EnableDisable.ENABLE) // @@ -210,7 +180,7 @@ public void testDischargeBattery() throws Exception { .input(MAX_AC_IMPORT, 0) // .input(MAX_AC_EXPORT, 0) // .onExecuteWriteCallbacks(() -> { - ess.run(BATTERY, 1000, 0); + ess.run(new DummyBattery("battery0"), 1000, 0); }) // .output(EMS_POWER_MODE, EmsPowerMode.DISCHARGE_BAT) // .output(EMS_POWER_SET, 1000)); @@ -223,12 +193,12 @@ public void testEmsPowerModeAutoWithBalancing() throws Exception { .addReference("power", new DummyPower()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .addReference("sum", new DummySum()) // .activate(MyConfig.create() // - .setId(BATTERY_INVERTER_ID) // - .setModbusId(MODBUS_ID) // - .setModbusUnitId(GoodWeConstants.DEFAULT_UNIT_ID) // + .setId("batteryInverter0") // + .setModbusId("modbus0") // + .setModbusUnitId(DEFAULT_UNIT_ID) // .setSafetyCountry(SafetyCountry.GERMANY) // .setMpptForShadowEnable(EnableDisable.ENABLE) // .setBackupEnable(EnableDisable.ENABLE) // @@ -243,7 +213,7 @@ public void testEmsPowerModeAutoWithBalancing() throws Exception { .input(GRID_ACTIVE_POWER, 2000) // .input(ACTIVE_POWER, 4000) // .onExecuteWriteCallbacks(() -> { - ess.run(BATTERY, 6000, 0); + ess.run(new DummyBattery("battery0"), 6000, 0); }) // .output(EMS_POWER_MODE, EmsPowerMode.AUTO) // .output(EMS_POWER_SET, 0)); @@ -254,12 +224,12 @@ public void testEmsPowerModeAutoWithSurplus() throws Exception { var charger = new GoodWeChargerPv1(); new ComponentTest(charger) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(io.openems.edge.goodwe.charger.singlestring.MyConfig.create() // - .setId(CHARGER_ID) // - .setBatteryInverterId(BATTERY_INVERTER_ID) // - .setModbusId(MODBUS_ID) // - .setModbusUnitId(GoodWeConstants.DEFAULT_UNIT_ID) // + .setId("charger0") // + .setBatteryInverterId("batteryInverter0") // + .setModbusId("modbus0") // + .setModbusUnitId(DEFAULT_UNIT_ID) // .build()); var ess = new GoodWeBatteryInverterImpl(); @@ -267,15 +237,15 @@ public void testEmsPowerModeAutoWithSurplus() throws Exception { new ComponentTest(ess) // .addReference("power", new DummyPower()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .addReference("componentManager", new DummyComponentManager()) // .addReference("sum", new DummySum()) // .addComponent(charger) // - .addComponent(BATTERY) // + .addComponent(new DummyBattery("battery0")) // .activate(MyConfig.create() // - .setId(BATTERY_INVERTER_ID) // - .setModbusId(MODBUS_ID) // - .setModbusUnitId(GoodWeConstants.DEFAULT_UNIT_ID) // + .setId("batteryInverter0") // + .setModbusId("modbus0") // + .setModbusUnitId(DEFAULT_UNIT_ID) // .setSafetyCountry(SafetyCountry.GERMANY) // .setMpptForShadowEnable(EnableDisable.ENABLE) // .setBackupEnable(EnableDisable.ENABLE) // @@ -287,9 +257,9 @@ public void testEmsPowerModeAutoWithSurplus() throws Exception { .build()) // .next(new TestCase() // .input(METER_COMMUNICATE_STATUS, MeterCommunicateStatus.OK) // - .input(CHARGER_ACTUAL_POWER, 10000) // - .input(CHARGE_MAX_CURRENT, 20).onExecuteWriteCallbacks(() -> { - ess.run(BATTERY, 10000, 0); + .input("charger0", ACTUAL_POWER, 10000) // + .input("battery0", CHARGE_MAX_CURRENT, 20).onExecuteWriteCallbacks(() -> { + ess.run(new DummyBattery("battery0"), 10000, 0); }) // .output(EMS_POWER_MODE, EmsPowerMode.AUTO) // .output(EMS_POWER_SET, 0)); @@ -302,12 +272,12 @@ public void testEmsPowerModeAutoWithMaxAcImport() throws Exception { .addReference("power", new DummyPower()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .addReference("sum", new DummySum()) // .activate(MyConfig.create() // - .setId(BATTERY_INVERTER_ID) // - .setModbusId(MODBUS_ID) // - .setModbusUnitId(GoodWeConstants.DEFAULT_UNIT_ID) // + .setId("batteryInverter0") // + .setModbusId("modbus0") // + .setModbusUnitId(DEFAULT_UNIT_ID) // .setSafetyCountry(SafetyCountry.GERMANY) // .setMpptForShadowEnable(EnableDisable.ENABLE) // .setBackupEnable(EnableDisable.ENABLE) // @@ -321,7 +291,7 @@ public void testEmsPowerModeAutoWithMaxAcImport() throws Exception { .input(METER_COMMUNICATE_STATUS, MeterCommunicateStatus.OK) // .input(MAX_AC_IMPORT, 3000) // .onExecuteWriteCallbacks(() -> { - ess.run(BATTERY, 3000, 0); + ess.run(new DummyBattery("battery0"), 3000, 0); }) // .output(EMS_POWER_MODE, EmsPowerMode.AUTO) // .output(EMS_POWER_SET, 0)); @@ -334,12 +304,12 @@ public void testEmsPowerModeAutoWithMaxAcExport() throws Exception { .addReference("power", new DummyPower()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .addReference("sum", new DummySum()) // .activate(MyConfig.create() // - .setId(BATTERY_INVERTER_ID) // - .setModbusId(MODBUS_ID) // - .setModbusUnitId(GoodWeConstants.DEFAULT_UNIT_ID) // + .setId("batteryInverter0") // + .setModbusId("modbus0") // + .setModbusUnitId(DEFAULT_UNIT_ID) // .setSafetyCountry(SafetyCountry.GERMANY) // .setMpptForShadowEnable(EnableDisable.ENABLE) // .setBackupEnable(EnableDisable.ENABLE) // @@ -353,7 +323,7 @@ public void testEmsPowerModeAutoWithMaxAcExport() throws Exception { .input(METER_COMMUNICATE_STATUS, MeterCommunicateStatus.OK) // .input(MAX_AC_EXPORT, 8000) // .onExecuteWriteCallbacks(() -> { - ess.run(BATTERY, 8000, 0); + ess.run(new DummyBattery("battery0"), 8000, 0); }) // .output(EMS_POWER_MODE, EmsPowerMode.AUTO) // .output(EMS_POWER_SET, 0)); @@ -366,12 +336,12 @@ public void testBatteryIsFull() throws Exception { .addReference("power", new DummyPower()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .addReference("sum", new DummySum()) // .activate(MyConfig.create() // - .setId(BATTERY_INVERTER_ID) // - .setModbusId(MODBUS_ID) // - .setModbusUnitId(GoodWeConstants.DEFAULT_UNIT_ID) // + .setId("batteryInverter0") // + .setModbusId("modbus0") // + .setModbusUnitId(DEFAULT_UNIT_ID) // .setSafetyCountry(SafetyCountry.GERMANY) // .setMpptForShadowEnable(EnableDisable.ENABLE) // .setBackupEnable(EnableDisable.ENABLE) // @@ -385,7 +355,7 @@ public void testBatteryIsFull() throws Exception { .input(METER_COMMUNICATE_STATUS, MeterCommunicateStatus.OK) // .input(MAX_AC_IMPORT, 0) // .onExecuteWriteCallbacks(() -> { - ess.run(BATTERY, 0, 0); + ess.run(new DummyBattery("battery0"), 0, 0); }) // .output(EMS_POWER_MODE, EmsPowerMode.AUTO) // .output(EMS_POWER_SET, 0)); @@ -398,12 +368,12 @@ public void testBatteryIsEmpty() throws Exception { .addReference("power", new DummyPower()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .addReference("sum", new DummySum()) // .activate(MyConfig.create() // - .setId(BATTERY_INVERTER_ID) // - .setModbusId(MODBUS_ID) // - .setModbusUnitId(GoodWeConstants.DEFAULT_UNIT_ID) // + .setId("batteryInverter0") // + .setModbusId("modbus0") // + .setModbusUnitId(DEFAULT_UNIT_ID) // .setSafetyCountry(SafetyCountry.GERMANY) // .setMpptForShadowEnable(EnableDisable.ENABLE) // .setBackupEnable(EnableDisable.ENABLE) // @@ -417,7 +387,7 @@ public void testBatteryIsEmpty() throws Exception { .input(METER_COMMUNICATE_STATUS, MeterCommunicateStatus.OK) // .input(MAX_AC_EXPORT, 0) // .onExecuteWriteCallbacks(() -> { - ess.run(BATTERY, 0, 0); + ess.run(new DummyBattery("battery0"), 0, 0); }) // .output(EMS_POWER_MODE, EmsPowerMode.AUTO) // .output(EMS_POWER_SET, 0)); @@ -430,12 +400,12 @@ public void testAcCalculation() throws Exception { .addReference("power", new DummyPower()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .addReference("sum", new DummySum()) // - .addComponent(BATTERY).activate(MyConfig.create() // - .setId(BATTERY_INVERTER_ID) // - .setModbusId(MODBUS_ID) // - .setModbusUnitId(GoodWeConstants.DEFAULT_UNIT_ID) // + .addComponent(new DummyBattery("battery0")).activate(MyConfig.create() // + .setId("batteryInverter0") // + .setModbusId("modbus0") // + .setModbusUnitId(DEFAULT_UNIT_ID) // .setSafetyCountry(SafetyCountry.GERMANY) // .setMpptForShadowEnable(EnableDisable.ENABLE) // .setBackupEnable(EnableDisable.ENABLE) // @@ -451,7 +421,7 @@ public void testAcCalculation() throws Exception { .input(WBMS_VOLTAGE, 325) // .input(MAX_APPARENT_POWER, 10000) // .onExecuteWriteCallbacks(() -> { - ess.run(BATTERY, 0, 0); + ess.run(new DummyBattery("battery0"), 0, 0); }) // .output(MAX_AC_IMPORT, 0) // .output(MAX_AC_EXPORT, 325)); @@ -471,8 +441,8 @@ public void testTwoStringCharger() throws Exception { .addReference("cm", new DummyConfigurationAdmin()) // .addReference("essOrBatteryInverter", ess) // .activate(io.openems.edge.goodwe.charger.twostring.MyConfig.create() // - .setId(CHARGER_ID) // - .setBatteryInverterId(BATTERY_INVERTER_ID) // + .setId("charger0") // + .setBatteryInverterId("batteryInverter0") // .setPvPort(PvPort.PV_1) // .build()); @@ -480,8 +450,8 @@ public void testTwoStringCharger() throws Exception { .addReference("cm", new DummyConfigurationAdmin()) // .addReference("essOrBatteryInverter", ess) // .activate(io.openems.edge.goodwe.charger.twostring.MyConfig.create() // - .setId(CHARGER_2_ID) // - .setBatteryInverterId(BATTERY_INVERTER_ID) // + .setId("charger1") // + .setBatteryInverterId("batteryInverter0") // .setPvPort(PvPort.PV_2) // .build()); @@ -489,8 +459,8 @@ public void testTwoStringCharger() throws Exception { .addReference("cm", new DummyConfigurationAdmin()) // .addReference("essOrBatteryInverter", ess) // .activate(io.openems.edge.goodwe.charger.twostring.MyConfig.create() // - .setId(CHARGER_3_ID) // - .setBatteryInverterId(BATTERY_INVERTER_ID) // + .setId("charger2") // + .setBatteryInverterId("batteryInverter0") // .setPvPort(PvPort.PV_3) // .build()); @@ -498,8 +468,8 @@ public void testTwoStringCharger() throws Exception { .addReference("cm", new DummyConfigurationAdmin()) // .addReference("essOrBatteryInverter", ess) // .activate(io.openems.edge.goodwe.charger.twostring.MyConfig.create() // - .setId(CHARGER_4_ID) // - .setBatteryInverterId(BATTERY_INVERTER_ID) // + .setId("charger3") // + .setBatteryInverterId("batteryInverter0") // .setPvPort(PvPort.PV_4) // .build()); @@ -507,8 +477,8 @@ public void testTwoStringCharger() throws Exception { .addReference("cm", new DummyConfigurationAdmin()) // .addReference("essOrBatteryInverter", ess) // .activate(io.openems.edge.goodwe.charger.twostring.MyConfig.create() // - .setId(CHARGER_5_ID) // - .setBatteryInverterId(BATTERY_INVERTER_ID) // + .setId("charger4") // + .setBatteryInverterId("batteryInverter0") // .setPvPort(PvPort.PV_5) // .build()); @@ -516,8 +486,8 @@ public void testTwoStringCharger() throws Exception { .addReference("cm", new DummyConfigurationAdmin()) // .addReference("essOrBatteryInverter", ess) // .activate(io.openems.edge.goodwe.charger.twostring.MyConfig.create() // - .setId(CHARGER_6_ID) // - .setBatteryInverterId(BATTERY_INVERTER_ID) // + .setId("charger5") // + .setBatteryInverterId("batteryInverter0") // .setPvPort(PvPort.PV_6) // .build()); @@ -527,15 +497,15 @@ public void testTwoStringCharger() throws Exception { .addReference("power", new DummyPower()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .addReference("sum", new DummySum()) // .addComponent(charger1) // .addComponent(charger2) // - .addComponent(BATTERY) // + .addComponent(new DummyBattery("battery0")) // .activate(MyConfig.create() // - .setId(BATTERY_INVERTER_ID) // - .setModbusId(MODBUS_ID) // - .setModbusUnitId(GoodWeConstants.DEFAULT_UNIT_ID) // + .setId("batteryInverter0") // + .setModbusId("modbus0") // + .setModbusUnitId(DEFAULT_UNIT_ID) // .setSafetyCountry(SafetyCountry.GERMANY) // .setMpptForShadowEnable(EnableDisable.ENABLE) // .setBackupEnable(EnableDisable.ENABLE) // @@ -554,19 +524,19 @@ public void testTwoStringCharger() throws Exception { .input(TWO_S_PV2_V, 240) // // Values applied in the next cycle - .output(CHARGER_ACTUAL_POWER, 0) // - .output(CHARGER_2_ACTUAL_POWER, 0) // - .output(CHARGER_CURRENT, null) // - .output(CHARGER_2_CURRENT, null) // - .output(CHARGER_VOLTAGE, null) // - .output(CHARGER_2_VOLTAGE, null)) // + .output("charger0", ACTUAL_POWER, 0) // + .output("charger1", ACTUAL_POWER, 0) // + .output("charger0", CURRENT, null) // + .output("charger1", CURRENT, null) // + .output("charger0", VOLTAGE, null) // + .output("charger1", VOLTAGE, null)) // .next(new TestCase() // - .output(CHARGER_ACTUAL_POWER, 1000) // - .output(CHARGER_2_ACTUAL_POWER, 1000) // - .output(CHARGER_CURRENT, 10) // - .output(CHARGER_2_CURRENT, 10) // - .output(CHARGER_VOLTAGE, 240) // - .output(CHARGER_2_VOLTAGE, 240)) // + .output("charger0", ACTUAL_POWER, 1000) // + .output("charger1", ACTUAL_POWER, 1000) // + .output("charger0", CURRENT, 10) // + .output("charger1", CURRENT, 10) // + .output("charger0", VOLTAGE, 240) // + .output("charger1", VOLTAGE, 240)) // // Chargers with different current values .next(new TestCase() // @@ -574,22 +544,22 @@ public void testTwoStringCharger() throws Exception { .input(MPPT1_P, 2000) // .input(TWO_S_PV1_I, 5) // .input(TWO_S_PV2_I, 15) // - .output(CHARGER_ACTUAL_POWER, 1000) // - .output(CHARGER_2_ACTUAL_POWER, 1000)) // + .output("charger0", ACTUAL_POWER, 1000) // + .output("charger1", ACTUAL_POWER, 1000)) // .next(new TestCase() // - .output(CHARGER_ACTUAL_POWER, 500) // - .output(CHARGER_2_ACTUAL_POWER, 1500)) // + .output("charger0", ACTUAL_POWER, 500) // + .output("charger1", ACTUAL_POWER, 1500)) // .next(new TestCase() // .input(MPPT1_I, 20) // .input(MPPT1_P, 2000) // .input(TWO_S_PV1_I, 20) // .input(TWO_S_PV2_I, 0) // - .output(CHARGER_ACTUAL_POWER, 500) // - .output(CHARGER_2_ACTUAL_POWER, 1500)) // + .output("charger0", ACTUAL_POWER, 500) // + .output("charger1", ACTUAL_POWER, 1500)) // .next(new TestCase() // - .output(CHARGER_ACTUAL_POWER, 2000) // - .output(CHARGER_2_ACTUAL_POWER, 0) // + .output("charger0", ACTUAL_POWER, 2000) // + .output("charger1", ACTUAL_POWER, 0) // ); /* @@ -601,15 +571,15 @@ public void testTwoStringCharger() throws Exception { .addReference("power", new DummyPower()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .addReference("sum", new DummySum()) // .addComponent(charger3) // .addComponent(charger4) // - .addComponent(BATTERY) // + .addComponent(new DummyBattery("battery0")) // .activate(MyConfig.create() // - .setId(BATTERY_INVERTER_ID) // - .setModbusId(MODBUS_ID) // - .setModbusUnitId(GoodWeConstants.DEFAULT_UNIT_ID) // + .setId("batteryInverter0") // + .setModbusId("modbus0") // + .setModbusUnitId(DEFAULT_UNIT_ID) // .setSafetyCountry(SafetyCountry.GERMANY) // .setMpptForShadowEnable(EnableDisable.ENABLE) // .setBackupEnable(EnableDisable.ENABLE) // @@ -628,19 +598,19 @@ public void testTwoStringCharger() throws Exception { .input(TWO_S_PV4_V, 240) // // Values applied in the next cycle - .output(CHARGER_3_ACTUAL_POWER, 0) // - .output(CHARGER_4_ACTUAL_POWER, 0) // - .output(CHARGER_3_CURRENT, null) // - .output(CHARGER_4_CURRENT, null) // - .output(CHARGER_3_VOLTAGE, null) // - .output(CHARGER_4_VOLTAGE, null)) // + .output("charger2", ACTUAL_POWER, 0) // + .output("charger3", ACTUAL_POWER, 0) // + .output("charger2", CURRENT, null) // + .output("charger3", CURRENT, null) // + .output("charger2", VOLTAGE, null) // + .output("charger3", VOLTAGE, null)) // .next(new TestCase() // - .output(CHARGER_3_ACTUAL_POWER, 1000) // - .output(CHARGER_4_ACTUAL_POWER, 1000) // - .output(CHARGER_3_CURRENT, 10) // - .output(CHARGER_4_CURRENT, 10) // - .output(CHARGER_3_VOLTAGE, 240) // - .output(CHARGER_4_VOLTAGE, 240)) // + .output("charger2", ACTUAL_POWER, 1000) // + .output("charger3", ACTUAL_POWER, 1000) // + .output("charger2", CURRENT, 10) // + .output("charger3", CURRENT, 10) // + .output("charger2", VOLTAGE, 240) // + .output("charger3", VOLTAGE, 240)) // // Chargers with different current values .next(new TestCase() // @@ -648,22 +618,22 @@ public void testTwoStringCharger() throws Exception { .input(MPPT2_P, 2000) // .input(TWO_S_PV3_I, 5) // .input(TWO_S_PV4_I, 15) // - .output(CHARGER_3_ACTUAL_POWER, 1000) // - .output(CHARGER_4_ACTUAL_POWER, 1000)) // + .output("charger2", ACTUAL_POWER, 1000) // + .output("charger3", ACTUAL_POWER, 1000)) // .next(new TestCase() // - .output(CHARGER_3_ACTUAL_POWER, 500) // - .output(CHARGER_4_ACTUAL_POWER, 1500)) // + .output("charger2", ACTUAL_POWER, 500) // + .output("charger3", ACTUAL_POWER, 1500)) // .next(new TestCase() // .input(MPPT2_I, 20) // .input(MPPT2_P, 2000) // .input(TWO_S_PV3_I, 20) // .input(TWO_S_PV4_I, 0) // - .output(CHARGER_3_ACTUAL_POWER, 500) // - .output(CHARGER_4_ACTUAL_POWER, 1500)) // + .output("charger2", ACTUAL_POWER, 500) // + .output("charger3", ACTUAL_POWER, 1500)) // .next(new TestCase() // - .output(CHARGER_3_ACTUAL_POWER, 2000) // - .output(CHARGER_4_ACTUAL_POWER, 0) // + .output("charger2", ACTUAL_POWER, 2000) // + .output("charger3", ACTUAL_POWER, 0) // ); /* @@ -675,15 +645,15 @@ public void testTwoStringCharger() throws Exception { .addReference("power", new DummyPower()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .addReference("sum", new DummySum()) // .addComponent(charger5) // .addComponent(charger6) // - .addComponent(BATTERY) // + .addComponent(new DummyBattery("battery0")) // .activate(MyConfig.create() // - .setId(BATTERY_INVERTER_ID) // - .setModbusId(MODBUS_ID) // - .setModbusUnitId(GoodWeConstants.DEFAULT_UNIT_ID) // + .setId("batteryInverter0") // + .setModbusId("modbus0") // + .setModbusUnitId(DEFAULT_UNIT_ID) // .setSafetyCountry(SafetyCountry.GERMANY) // .setMpptForShadowEnable(EnableDisable.ENABLE) // .setBackupEnable(EnableDisable.ENABLE) // @@ -702,19 +672,19 @@ public void testTwoStringCharger() throws Exception { .input(TWO_S_PV6_V, 240) // // Values applied in the next cycle - .output(CHARGER_5_ACTUAL_POWER, 0) // - .output(CHARGER_6_ACTUAL_POWER, 0) // - .output(CHARGER_5_CURRENT, null) // - .output(CHARGER_6_CURRENT, null) // - .output(CHARGER_5_VOLTAGE, null) // - .output(CHARGER_6_VOLTAGE, null)) // + .output("charger4", ACTUAL_POWER, 0) // + .output("charger5", ACTUAL_POWER, 0) // + .output("charger4", CURRENT, null) // + .output("charger5", CURRENT, null) // + .output("charger4", VOLTAGE, null) // + .output("charger5", VOLTAGE, null)) // .next(new TestCase() // - .output(CHARGER_5_ACTUAL_POWER, 1000) // - .output(CHARGER_6_ACTUAL_POWER, 1000) // - .output(CHARGER_5_CURRENT, 10) // - .output(CHARGER_6_CURRENT, 10) // - .output(CHARGER_5_VOLTAGE, 240) // - .output(CHARGER_6_VOLTAGE, 240)) // + .output("charger4", ACTUAL_POWER, 1000) // + .output("charger5", ACTUAL_POWER, 1000) // + .output("charger4", CURRENT, 10) // + .output("charger5", CURRENT, 10) // + .output("charger4", VOLTAGE, 240) // + .output("charger5", VOLTAGE, 240)) // // Chargers with different current values .next(new TestCase() // @@ -722,22 +692,22 @@ public void testTwoStringCharger() throws Exception { .input(MPPT3_P, 2000) // .input(TWO_S_PV5_I, 5) // .input(TWO_S_PV6_I, 15) // - .output(CHARGER_5_ACTUAL_POWER, 1000) // - .output(CHARGER_6_ACTUAL_POWER, 1000)) // + .output("charger4", ACTUAL_POWER, 1000) // + .output("charger5", ACTUAL_POWER, 1000)) // .next(new TestCase() // - .output(CHARGER_5_ACTUAL_POWER, 500) // - .output(CHARGER_6_ACTUAL_POWER, 1500)) // + .output("charger4", ACTUAL_POWER, 500) // + .output("charger5", ACTUAL_POWER, 1500)) // .next(new TestCase() // .input(MPPT3_I, 20) // .input(MPPT3_P, 2000) // .input(TWO_S_PV5_I, 20) // .input(TWO_S_PV6_I, 0) // - .output(CHARGER_5_ACTUAL_POWER, 500) // - .output(CHARGER_6_ACTUAL_POWER, 1500)) // + .output("charger4", ACTUAL_POWER, 500) // + .output("charger5", ACTUAL_POWER, 1500)) // .next(new TestCase() // - .output(CHARGER_5_ACTUAL_POWER, 2000) // - .output(CHARGER_6_ACTUAL_POWER, 0) // + .output("charger4", ACTUAL_POWER, 2000) // + .output("charger5", ACTUAL_POWER, 0) // ); } @@ -773,4 +743,60 @@ public void testDoSetBmsVoltage() { assertTrue(doSetBmsVoltage(battery, bmsChargeMaxVoltage, 1, bmsDischargeMinVoltage, 456)); assertTrue(doSetBmsVoltage(battery, bmsChargeMaxVoltage, 123, bmsDischargeMinVoltage, 1)); } + + @Test + public void testReadFromModbus() throws Exception { + var sut = new GoodWeBatteryInverterImpl(); + new ComponentTest(sut) // + .addReference("cm", new DummyConfigurationAdmin()) // + .addReference("componentManager", + new DummyComponentManager(createDummyClock())) + .addReference("setModbus", new DummyModbusBridge("modbus2") // + .withRegisters(35011, // Deprecated GoodWe type register + new int[] { 0x4757, 0x3135, 0x4b2d, 0x4554, 0x3230 }) + .withRegisters(35001, // Block including GoodWe Serial Number + new int[] { 0x3a98, 0x0001, 0x3730, 0x3135, 0x4b45, 0x5542, 0x3234, 0x3730, 0x3031, + 0x3734 }) + .withRegisters(35180, // Battery values of GoodWe + new int[] { 0x056e, 0x0000, 0xffff, 0xfffb, 0x0002 }) + .withRegisters(35016, // GoodWe Software Versions + new int[] { 0, 0, 0x07df, 0x0006, 0x0185 }) + .withRegisters(35111, // PV data including GridMode + new int[] { 0x8FC, 0, 0, 0, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0, 0x0200, 0x8EF, 0x0054, + 0x1389, 0xFFFF, 0xF869, 0x08E3, 0x0055, 0x138B, 0xFFFF, 0xF870, 0x08EC, 0x0056, + 0x138B, 0xFFFF, 0xF86b, 0x0001 /* GridMode */ })) + .activate(MyConfig.create() // + .setId("batteryInverter0") // + .setModbusId("modbus2") // + .setMpptForShadowEnable(EnableDisable.DISABLE) // + .setModbusUnitId(DEFAULT_UNIT_ID) // + .setSafetyCountry(SafetyCountry.GERMANY) // + .setMpptForShadowEnable(EnableDisable.ENABLE) // + .setBackupEnable(EnableDisable.ENABLE) // + .setFeedPowerEnable(EnableDisable.ENABLE) // + .setFeedPowerPara(3000) // + .setFeedInPowerSettings(FeedInPowerSettings.PU_ENABLE_CURVE) // + .setControlMode(ControlMode.SMART) // + .setStartStop(StartStopConfig.START) // + .build()) // + + .next(new TestCase() // + .output(GoodWe.ChannelId.SERIAL_NUMBER, "7015KEUB24700174") // + .output(SymmetricEss.ChannelId.MAX_APPARENT_POWER, 15_000) // + .output(GoodWe.ChannelId.GOODWE_TYPE, GoodWeType.UNDEFINED)) // + .next(new TestCase() // + .output(GoodWe.ChannelId.GOODWE_TYPE, GoodWeType.FENECON_GEN2_15K)) // read element once + + .next(new TestCase() // register 35111 - 35136 + .output(GoodWe.ChannelId.V_PV3, 230) // register not 0xFFFF + .output(GoodWe.ChannelId.I_PV3, 0) // + .output(GoodWe.ChannelId.P_PV3, 0L) // + .output(GoodWe.ChannelId.V_PV4, null) // + .output(GoodWe.ChannelId.I_PV4, null) // + .output(GoodWe.ChannelId.P_PV4, null) // + .output(GoodWe.ChannelId.PV_MODE, PvMode.UNDEFINED) // + .output(SymmetricBatteryInverter.ChannelId.GRID_MODE, GridMode.ON_GRID)) // + .deactivate(); + } + } diff --git a/io.openems.edge.goodwe/test/io/openems/edge/goodwe/batteryinverter/MyConfig.java b/io.openems.edge.goodwe/test/io/openems/edge/goodwe/batteryinverter/MyConfig.java index 8c8fe64615f..a22228530a9 100644 --- a/io.openems.edge.goodwe/test/io/openems/edge/goodwe/batteryinverter/MyConfig.java +++ b/io.openems.edge.goodwe/test/io/openems/edge/goodwe/batteryinverter/MyConfig.java @@ -24,6 +24,7 @@ public static class Builder { private FeedInPowerSettings feedInPowerSettings; private EnableDisable rcrEnable = EnableDisable.DISABLE; private StartStopConfig startStop; + private EnableDisable naProtectionEnable = EnableDisable.DISABLE; private Builder() { } @@ -83,6 +84,11 @@ public Builder setRcrEnable(EnableDisable rcrEnable) { return this; } + public Builder setNaProtectionEnable(EnableDisable naProtectionEnable) { + this.naProtectionEnable = naProtectionEnable; + return this; + } + public Builder setStartStop(StartStopConfig startStop) { this.startStop = startStop; return this; @@ -168,4 +174,9 @@ public EnableDisable rcrEnable() { public StartStopConfig startStop() { return this.builder.startStop; } + + @Override + public EnableDisable naProtectionEnable() { + return this.builder.naProtectionEnable; + } } \ No newline at end of file diff --git a/io.openems.edge.goodwe/test/io/openems/edge/goodwe/charger/mppt/twostring/GoodWeChargerMpptTwoStringImplTest.java b/io.openems.edge.goodwe/test/io/openems/edge/goodwe/charger/mppt/twostring/GoodWeChargerMpptTwoStringImplTest.java index 591db516cd4..a7ad15707c2 100644 --- a/io.openems.edge.goodwe/test/io/openems/edge/goodwe/charger/mppt/twostring/GoodWeChargerMpptTwoStringImplTest.java +++ b/io.openems.edge.goodwe/test/io/openems/edge/goodwe/charger/mppt/twostring/GoodWeChargerMpptTwoStringImplTest.java @@ -2,8 +2,6 @@ import org.junit.Test; -import io.openems.common.types.ChannelAddress; -import io.openems.edge.battery.api.Battery; import io.openems.edge.battery.test.DummyBattery; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.startstop.StartStopConfig; @@ -12,9 +10,11 @@ import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyComponentManager; import io.openems.edge.common.test.DummyConfigurationAdmin; +import io.openems.edge.ess.dccharger.api.EssDcCharger; import io.openems.edge.ess.test.DummyPower; import io.openems.edge.goodwe.GoodWeConstants; import io.openems.edge.goodwe.batteryinverter.GoodWeBatteryInverterImpl; +import io.openems.edge.goodwe.common.GoodWe; import io.openems.edge.goodwe.common.enums.ControlMode; import io.openems.edge.goodwe.common.enums.EnableDisable; import io.openems.edge.goodwe.common.enums.FeedInPowerSettings; @@ -22,94 +22,57 @@ public class GoodWeChargerMpptTwoStringImplTest { - private static final String MODBUS_ID = "modbus0"; - private static final String BATTERY_ID = "battery0"; - private static final String CHARGER_1_ID = "charger0"; - private static final String CHARGER_2_ID = "charger1"; - private static final String CHARGER_3_ID = "charger2"; - private static final String BATTERY_INVERTER_ID = "batteryInverter0"; - - private static final Battery BATTERY = new DummyBattery(BATTERY_ID); - - private static final ChannelAddress MPPT1_P = new ChannelAddress(BATTERY_INVERTER_ID, "Mppt1P"); - private static final ChannelAddress MPPT1_I = new ChannelAddress(BATTERY_INVERTER_ID, "Mppt1I"); - private static final ChannelAddress MPPT2_P = new ChannelAddress(BATTERY_INVERTER_ID, "Mppt2P"); - private static final ChannelAddress MPPT2_I = new ChannelAddress(BATTERY_INVERTER_ID, "Mppt2I"); - private static final ChannelAddress MPPT3_P = new ChannelAddress(BATTERY_INVERTER_ID, "Mppt3P"); - private static final ChannelAddress MPPT3_I = new ChannelAddress(BATTERY_INVERTER_ID, "Mppt3I"); - private static final ChannelAddress TWO_S_PV1_I = new ChannelAddress(BATTERY_INVERTER_ID, "TwoSPv1I"); - private static final ChannelAddress TWO_S_PV1_V = new ChannelAddress(BATTERY_INVERTER_ID, "TwoSPv1V"); - private static final ChannelAddress TWO_S_PV2_I = new ChannelAddress(BATTERY_INVERTER_ID, "TwoSPv2I"); - private static final ChannelAddress TWO_S_PV2_V = new ChannelAddress(BATTERY_INVERTER_ID, "TwoSPv2V"); - private static final ChannelAddress TWO_S_PV3_I = new ChannelAddress(BATTERY_INVERTER_ID, "TwoSPv3I"); - private static final ChannelAddress TWO_S_PV3_V = new ChannelAddress(BATTERY_INVERTER_ID, "TwoSPv3V"); - private static final ChannelAddress TWO_S_PV4_I = new ChannelAddress(BATTERY_INVERTER_ID, "TwoSPv4I"); - private static final ChannelAddress TWO_S_PV4_V = new ChannelAddress(BATTERY_INVERTER_ID, "TwoSPv4V"); - private static final ChannelAddress TWO_S_PV5_I = new ChannelAddress(BATTERY_INVERTER_ID, "TwoSPv5I"); - private static final ChannelAddress TWO_S_PV5_V = new ChannelAddress(BATTERY_INVERTER_ID, "TwoSPv5V"); - private static final ChannelAddress TWO_S_PV6_I = new ChannelAddress(BATTERY_INVERTER_ID, "TwoSPv6I"); - private static final ChannelAddress TWO_S_PV6_V = new ChannelAddress(BATTERY_INVERTER_ID, "TwoSPv6V"); - private static final ChannelAddress CHARGER_1_ACTUAL_POWER = new ChannelAddress(CHARGER_1_ID, "ActualPower"); - private static final ChannelAddress CHARGER_1_VOLTAGE = new ChannelAddress(CHARGER_1_ID, "Voltage"); - private static final ChannelAddress CHARGER_1_CURRENT = new ChannelAddress(CHARGER_1_ID, "Current"); - private static final ChannelAddress CHARGER_2_ACTUAL_POWER = new ChannelAddress(CHARGER_2_ID, "ActualPower"); - private static final ChannelAddress CHARGER_2_VOLTAGE = new ChannelAddress(CHARGER_2_ID, "Voltage"); - private static final ChannelAddress CHARGER_2_CURRENT = new ChannelAddress(CHARGER_2_ID, "Current"); - private static final ChannelAddress CHARGER_3_ACTUAL_POWER = new ChannelAddress(CHARGER_3_ID, "ActualPower"); - private static final ChannelAddress CHARGER_3_VOLTAGE = new ChannelAddress(CHARGER_3_ID, "Voltage"); - private static final ChannelAddress CHARGER_3_CURRENT = new ChannelAddress(CHARGER_3_ID, "Current"); - @Test public void test() throws Exception { - var ess = new GoodWeBatteryInverterImpl(); + var inverter = new GoodWeBatteryInverterImpl(); var charger1 = new GoodWeChargerMpptTwoStringImpl(); var charger2 = new GoodWeChargerMpptTwoStringImpl(); var charger3 = new GoodWeChargerMpptTwoStringImpl(); new ComponentTest(charger1) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("essOrBatteryInverter", ess) // + .addReference("essOrBatteryInverter", inverter) // .activate(MyConfig.create() // - .setId(CHARGER_1_ID) // - .setBatteryInverterId(BATTERY_INVERTER_ID) // + .setId("charger0") // + .setBatteryInverterId("batteryInverter0") // .setMpptPort(MpptPort.MPPT_1) // .build()); new ComponentTest(charger2) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("essOrBatteryInverter", ess) // + .addReference("essOrBatteryInverter", inverter) // .activate(MyConfig.create() // - .setId(CHARGER_2_ID) // - .setBatteryInverterId(BATTERY_INVERTER_ID) // + .setId("charger1") // + .setBatteryInverterId("batteryInverter0") // .setMpptPort(MpptPort.MPPT_2) // .build()); new ComponentTest(charger3) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("essOrBatteryInverter", ess) // + .addReference("essOrBatteryInverter", inverter) // .activate(MyConfig.create() // - .setId(CHARGER_3_ID) // - .setBatteryInverterId(BATTERY_INVERTER_ID) // + .setId("charger2") // + .setBatteryInverterId("batteryInverter0") // .setMpptPort(MpptPort.MPPT_3) // .build()); - ess.addCharger(charger1); - ess.addCharger(charger2); - ess.addCharger(charger3); + inverter.addCharger(charger1); + inverter.addCharger(charger2); + inverter.addCharger(charger3); - new ComponentTest(ess) // + new ComponentTest(inverter) // .addReference("power", new DummyPower()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .addReference("sum", new DummySum()) // .addComponent(charger1) // .addComponent(charger2) // .addComponent(charger3) // - .addComponent(BATTERY) // + .addComponent(new DummyBattery("battery0")) // .activate(io.openems.edge.goodwe.batteryinverter.MyConfig.create() // - .setId(BATTERY_INVERTER_ID) // - .setModbusId(MODBUS_ID) // + .setId("batteryInverter0") // + .setModbusId("modbus0") // .setModbusUnitId(GoodWeConstants.DEFAULT_UNIT_ID) // .setSafetyCountry(SafetyCountry.GERMANY) // .setMpptForShadowEnable(EnableDisable.ENABLE) // @@ -121,74 +84,74 @@ public void test() throws Exception { .setStartStop(StartStopConfig.START) // .build()) // .next(new TestCase() // - .input(MPPT1_I, 20) // - .input(MPPT1_P, 2000) // - .input(TWO_S_PV1_I, 10) // - .input(TWO_S_PV2_I, 10) // - .input(TWO_S_PV1_V, 240) // - .input(TWO_S_PV2_V, 240)) // + .input(GoodWe.ChannelId.MPPT1_I, 20) // + .input(GoodWe.ChannelId.MPPT1_P, 2000) // + .input(GoodWe.ChannelId.TWO_S_PV1_I, 10) // + .input(GoodWe.ChannelId.TWO_S_PV2_I, 10) // + .input(GoodWe.ChannelId.TWO_S_PV1_V, 240) // + .input(GoodWe.ChannelId.TWO_S_PV2_V, 240)) // // Values applied in the next cycle .next(new TestCase() // - .output(CHARGER_1_ACTUAL_POWER, 2000) // - .output(CHARGER_1_CURRENT, 20) // - .output(CHARGER_1_VOLTAGE, 240) // - .output(CHARGER_2_ACTUAL_POWER, null) // - .output(CHARGER_2_CURRENT, null) // - .output(CHARGER_2_VOLTAGE, null) // - .output(CHARGER_3_ACTUAL_POWER, null) // - .output(CHARGER_3_CURRENT, null) // - .output(CHARGER_3_VOLTAGE, null) // + .output("charger0", EssDcCharger.ChannelId.ACTUAL_POWER, 2000) // + .output("charger0", EssDcCharger.ChannelId.CURRENT, 20) // + .output("charger0", EssDcCharger.ChannelId.VOLTAGE, 240) // + .output("charger1", EssDcCharger.ChannelId.ACTUAL_POWER, null) // + .output("charger1", EssDcCharger.ChannelId.CURRENT, null) // + .output("charger1", EssDcCharger.ChannelId.VOLTAGE, null) // + .output("charger2", EssDcCharger.ChannelId.ACTUAL_POWER, null) // + .output("charger2", EssDcCharger.ChannelId.CURRENT, null) // + .output("charger2", EssDcCharger.ChannelId.VOLTAGE, null) // ) // // Chargers with different current values .next(new TestCase() // - .input(MPPT1_I, 20) // - .input(MPPT1_P, 3000) // - .input(TWO_S_PV1_I, 5) // - .input(TWO_S_PV2_I, 15) // - .input(TWO_S_PV1_V, 250) // - .input(TWO_S_PV2_V, 250)) // + .input(GoodWe.ChannelId.MPPT1_I, 20) // + .input(GoodWe.ChannelId.MPPT1_P, 3000) // + .input(GoodWe.ChannelId.TWO_S_PV1_I, 5) // + .input(GoodWe.ChannelId.TWO_S_PV2_I, 15) // + .input(GoodWe.ChannelId.TWO_S_PV1_V, 250) // + .input(GoodWe.ChannelId.TWO_S_PV2_V, 250)) // .next(new TestCase() // - .output(CHARGER_1_ACTUAL_POWER, 3000) // - .output(CHARGER_1_CURRENT, 20) // - .output(CHARGER_1_VOLTAGE, 250) // - .output(CHARGER_2_ACTUAL_POWER, null) // - .output(CHARGER_2_CURRENT, null) // - .output(CHARGER_2_VOLTAGE, null). // - output(CHARGER_3_ACTUAL_POWER, null) // - .output(CHARGER_3_CURRENT, null) // - .output(CHARGER_3_VOLTAGE, null) // + .output("charger0", EssDcCharger.ChannelId.ACTUAL_POWER, 3000) // + .output("charger0", EssDcCharger.ChannelId.CURRENT, 20) // + .output("charger0", EssDcCharger.ChannelId.VOLTAGE, 250) // + .output("charger1", EssDcCharger.ChannelId.ACTUAL_POWER, null) // + .output("charger1", EssDcCharger.ChannelId.CURRENT, null) // + .output("charger1", EssDcCharger.ChannelId.VOLTAGE, null) // + .output("charger2", EssDcCharger.ChannelId.ACTUAL_POWER, null) // + .output("charger2", EssDcCharger.ChannelId.CURRENT, null) // + .output("charger2", EssDcCharger.ChannelId.VOLTAGE, null) // ) .next(new TestCase() // - .input(MPPT1_I, 20) // - .input(MPPT1_P, 2000) // - .input(MPPT2_I, 30) // - .input(MPPT2_P, 3000) // - .input(MPPT3_I, 40) // - .input(MPPT3_P, 4000) // - .input(TWO_S_PV1_I, 10) // - .input(TWO_S_PV1_V, 250) // - .input(TWO_S_PV2_I, 10) // - .input(TWO_S_PV2_V, 250) // - .input(TWO_S_PV3_I, 15) // - .input(TWO_S_PV3_V, 280) // - .input(TWO_S_PV4_I, 15) // - .input(TWO_S_PV4_V, 280) // - .input(TWO_S_PV5_I, 20) // - .input(TWO_S_PV5_V, 299) // - .input(TWO_S_PV6_I, 20) // - .input(TWO_S_PV6_V, 299)) // + .input(GoodWe.ChannelId.MPPT1_I, 20) // + .input(GoodWe.ChannelId.MPPT1_P, 2000) // + .input(GoodWe.ChannelId.MPPT2_I, 30) // + .input(GoodWe.ChannelId.MPPT2_P, 3000) // + .input(GoodWe.ChannelId.MPPT3_I, 40) // + .input(GoodWe.ChannelId.MPPT3_P, 4000) // + .input(GoodWe.ChannelId.TWO_S_PV1_I, 10) // + .input(GoodWe.ChannelId.TWO_S_PV1_V, 250) // + .input(GoodWe.ChannelId.TWO_S_PV2_I, 10) // + .input(GoodWe.ChannelId.TWO_S_PV2_V, 250) // + .input(GoodWe.ChannelId.TWO_S_PV3_I, 15) // + .input(GoodWe.ChannelId.TWO_S_PV3_V, 280) // + .input(GoodWe.ChannelId.TWO_S_PV4_I, 15) // + .input(GoodWe.ChannelId.TWO_S_PV4_V, 280) // + .input(GoodWe.ChannelId.TWO_S_PV5_I, 20) // + .input(GoodWe.ChannelId.TWO_S_PV5_V, 299) // + .input(GoodWe.ChannelId.TWO_S_PV6_I, 20) // + .input(GoodWe.ChannelId.TWO_S_PV6_V, 299)) // .next(new TestCase() // - .output(CHARGER_1_ACTUAL_POWER, 2000) // - .output(CHARGER_1_CURRENT, 20) // - .output(CHARGER_1_VOLTAGE, 250) // - .output(CHARGER_2_ACTUAL_POWER, 3000) // - .output(CHARGER_2_CURRENT, 30) // - .output(CHARGER_2_VOLTAGE, 280). // - output(CHARGER_3_ACTUAL_POWER, 4000) // - .output(CHARGER_3_CURRENT, 40) // - .output(CHARGER_3_VOLTAGE, 299) // + .output("charger0", EssDcCharger.ChannelId.ACTUAL_POWER, 2000) // + .output("charger0", EssDcCharger.ChannelId.CURRENT, 20) // + .output("charger0", EssDcCharger.ChannelId.VOLTAGE, 250) // + .output("charger1", EssDcCharger.ChannelId.ACTUAL_POWER, 3000) // + .output("charger1", EssDcCharger.ChannelId.CURRENT, 30) // + .output("charger1", EssDcCharger.ChannelId.VOLTAGE, 280) // + .output("charger2", EssDcCharger.ChannelId.ACTUAL_POWER, 4000) // + .output("charger2", EssDcCharger.ChannelId.CURRENT, 40) // + .output("charger2", EssDcCharger.ChannelId.VOLTAGE, 299) // ); } } diff --git a/io.openems.edge.goodwe/test/io/openems/edge/goodwe/charger/singlestring/GoodWeChargerPv1Test.java b/io.openems.edge.goodwe/test/io/openems/edge/goodwe/charger/singlestring/GoodWeChargerPv1Test.java index 36ec9b90b80..e296380ac59 100644 --- a/io.openems.edge.goodwe/test/io/openems/edge/goodwe/charger/singlestring/GoodWeChargerPv1Test.java +++ b/io.openems.edge.goodwe/test/io/openems/edge/goodwe/charger/singlestring/GoodWeChargerPv1Test.java @@ -8,19 +8,15 @@ public class GoodWeChargerPv1Test { - private static final String MODBUS_ID = "modbus0"; - private static final String ESS_ID = "ess0"; - private static final String CHARGER_ID = "charger0"; - @Test public void test() throws Exception { new ComponentTest(new GoodWeChargerPv1()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(CHARGER_ID) // - .setBatteryInverterId(ESS_ID) // - .setModbusId(MODBUS_ID) // + .setId("charger0") // + .setBatteryInverterId("ess0") // + .setModbusId("modbus0") // .build()); } } diff --git a/io.openems.edge.goodwe/test/io/openems/edge/goodwe/charger/singlestring/GoodWeChargerPv2Test.java b/io.openems.edge.goodwe/test/io/openems/edge/goodwe/charger/singlestring/GoodWeChargerPv2Test.java index 754badab30e..77dd4956860 100644 --- a/io.openems.edge.goodwe/test/io/openems/edge/goodwe/charger/singlestring/GoodWeChargerPv2Test.java +++ b/io.openems.edge.goodwe/test/io/openems/edge/goodwe/charger/singlestring/GoodWeChargerPv2Test.java @@ -8,19 +8,15 @@ public class GoodWeChargerPv2Test { - private static final String MODBUS_ID = "modbus0"; - private static final String ESS_ID = "ess0"; - private static final String CHARGER_ID = "charger0"; - @Test public void test() throws Exception { new ComponentTest(new GoodWeChargerPv2()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(CHARGER_ID) // - .setBatteryInverterId(ESS_ID) // - .setModbusId(MODBUS_ID) // + .setId("charger0") // + .setBatteryInverterId("ess0") // + .setModbusId("modbus0") // .build()); } } diff --git a/io.openems.edge.goodwe/test/io/openems/edge/goodwe/charger/twostring/GoodWeChargerTwoStringImplTest.java b/io.openems.edge.goodwe/test/io/openems/edge/goodwe/charger/twostring/GoodWeChargerTwoStringImplTest.java index 3d2e5340e78..2c678e67e38 100644 --- a/io.openems.edge.goodwe/test/io/openems/edge/goodwe/charger/twostring/GoodWeChargerTwoStringImplTest.java +++ b/io.openems.edge.goodwe/test/io/openems/edge/goodwe/charger/twostring/GoodWeChargerTwoStringImplTest.java @@ -8,9 +8,6 @@ public class GoodWeChargerTwoStringImplTest { - private static final String ESS_ID = "ess0"; - private static final String CHARGER_ID = "charger0"; - @SuppressWarnings("deprecation") @Test public void test() throws Exception { @@ -18,8 +15,8 @@ public void test() throws Exception { .addReference("cm", new DummyConfigurationAdmin()) // .addReference("essOrBatteryInverter", new GoodWeEssImpl()) // .activate(MyConfig.create() // - .setId(CHARGER_ID) // - .setBatteryInverterId(ESS_ID) // + .setId("charger0") // + .setBatteryInverterId("ess0") // .setPvPort(PvPort.PV_1) // .build()); } diff --git a/io.openems.edge.goodwe/test/io/openems/edge/goodwe/common/TestStatic.java b/io.openems.edge.goodwe/test/io/openems/edge/goodwe/common/TestStatic.java index 65fe2da9a22..31c8dea5b60 100644 --- a/io.openems.edge.goodwe/test/io/openems/edge/goodwe/common/TestStatic.java +++ b/io.openems.edge.goodwe/test/io/openems/edge/goodwe/common/TestStatic.java @@ -1,5 +1,7 @@ package io.openems.edge.goodwe.common; +import static io.openems.edge.battery.fenecon.home.BatteryFeneconHomeHardwareType.BATTERY_52; +import static io.openems.edge.battery.fenecon.home.BatteryFeneconHomeHardwareType.BATTERY_64; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; @@ -23,11 +25,14 @@ public void testGetHardwareTypeFromSerialNr() { assertNotEquals(GoodWeType.FENECON_FHI_20_DAH, AbstractGoodWe.getGoodWeTypeFromSerialNr("9010KETT22AW0004")); assertEquals(GoodWeType.FENECON_FHI_29_9_DAH, AbstractGoodWe.getGoodWeTypeFromSerialNr("9030KETT228W0004")); + assertEquals(GoodWeType.FENECON_FHI_29_9_DAH, AbstractGoodWe.getGoodWeTypeFromSerialNr("129K9ETT231W0159")); assertNotEquals(GoodWeType.FENECON_FHI_29_9_DAH, AbstractGoodWe.getGoodWeTypeFromSerialNr("9020KETT228W0004")); - assertEquals(GoodWeType.FENECON_FHI_29_9_DAH, AbstractGoodWe.getGoodWeTypeFromSerialNr("929K9ETT231W0159")); assertNotEquals(GoodWeType.FENECON_FHI_29_9_DAH, AbstractGoodWe.getGoodWeTypeFromSerialNr("929KETT231W0159")); assertNotEquals(GoodWeType.FENECON_FHI_29_9_DAH, AbstractGoodWe.getGoodWeTypeFromSerialNr("928K9ETT231W0159")); - assertEquals(GoodWeType.FENECON_FHI_29_9_DAH, AbstractGoodWe.getGoodWeTypeFromSerialNr("929K9ETT231W0160")); + + assertEquals(GoodWeType.FENECON_GEN2_6K, AbstractGoodWe.getGoodWeTypeFromSerialNr("96000EUB246L0002")); + assertEquals(GoodWeType.FENECON_GEN2_10K, AbstractGoodWe.getGoodWeTypeFromSerialNr("9010KEUB246L0001")); + assertEquals(GoodWeType.FENECON_GEN2_15K, AbstractGoodWe.getGoodWeTypeFromSerialNr("9015KEUB246L0003")); assertEquals(GoodWeType.UNDEFINED, AbstractGoodWe.getGoodWeTypeFromSerialNr("9040KETT228W0004")); assertEquals(GoodWeType.UNDEFINED, AbstractGoodWe.getGoodWeTypeFromSerialNr("9000KETT228W0004")); @@ -36,6 +41,30 @@ public void testGetHardwareTypeFromSerialNr() { assertEquals(GoodWeType.UNDEFINED, AbstractGoodWe.getGoodWeTypeFromSerialNr(null)); } + @Test + public void testAuthorisedLimit() { + assertEquals(25, GoodWeType.authorisedLimit(40, 25, 40).apply(BATTERY_52).intValue()); + assertEquals(40, GoodWeType.authorisedLimit(40, 25, 40).apply(BATTERY_64).intValue()); + assertEquals(40, GoodWeType.authorisedLimit(40, 25, 40).apply(null).intValue()); + + assertEquals(25, GoodWeType.FENECON_GEN2_6K.maxDcCurrent.apply(BATTERY_52).intValue()); + assertEquals(40, GoodWeType.FENECON_GEN2_6K.maxDcCurrent.apply(BATTERY_64).intValue()); + assertEquals(25, GoodWeType.FENECON_GEN2_10K.maxDcCurrent.apply(BATTERY_52).intValue()); + assertEquals(40, GoodWeType.FENECON_GEN2_10K.maxDcCurrent.apply(BATTERY_64).intValue()); + assertEquals(25, GoodWeType.FENECON_GEN2_15K.maxDcCurrent.apply(BATTERY_52).intValue()); + assertEquals(40, GoodWeType.FENECON_GEN2_15K.maxDcCurrent.apply(BATTERY_64).intValue()); + assertEquals(25, GoodWeType.FENECON_FHI_10_DAH.maxDcCurrent.apply(BATTERY_52).intValue()); + assertEquals(0, GoodWeType.FENECON_FHI_10_DAH.maxDcCurrent.apply(BATTERY_64).intValue()); + assertEquals(0, GoodWeType.FENECON_FHI_20_DAH.maxDcCurrent.apply(BATTERY_52).intValue()); + assertEquals(50, GoodWeType.FENECON_FHI_20_DAH.maxDcCurrent.apply(BATTERY_64).intValue()); + assertEquals(0, GoodWeType.FENECON_FHI_29_9_DAH.maxDcCurrent.apply(BATTERY_52).intValue()); + assertEquals(50, GoodWeType.FENECON_FHI_29_9_DAH.maxDcCurrent.apply(BATTERY_64).intValue()); + assertEquals(25, GoodWeType.GOODWE_8K_ET.maxDcCurrent.apply(BATTERY_52).intValue()); + assertEquals(25, GoodWeType.GOODWE_8K_ET.maxDcCurrent.apply(BATTERY_64).intValue()); + assertEquals(25, GoodWeType.GOODWE_8K_ET.maxDcCurrent.apply(null).intValue()); + + } + @Test public void testGetHardwareTypeFromGoodWeString() { assertEquals(GoodWeType.GOODWE_10K_BT, AbstractGoodWe.getGoodWeTypeFromStringValue("GW10K-BT")); diff --git a/io.openems.edge.goodwe/test/io/openems/edge/goodwe/emergencypowermeter/GoodWeEmergencyPowerMeterTest.java b/io.openems.edge.goodwe/test/io/openems/edge/goodwe/emergencypowermeter/GoodWeEmergencyPowerMeterTest.java index 6c08cecae9b..463fd4d32f4 100644 --- a/io.openems.edge.goodwe/test/io/openems/edge/goodwe/emergencypowermeter/GoodWeEmergencyPowerMeterTest.java +++ b/io.openems.edge.goodwe/test/io/openems/edge/goodwe/emergencypowermeter/GoodWeEmergencyPowerMeterTest.java @@ -8,18 +8,14 @@ public class GoodWeEmergencyPowerMeterTest { - private static final String MODBUS_ID = "modbus0"; - - private static final String METER_ID = "meter2"; - @Test public void test() throws Exception { new ComponentTest(new GoodWeEmergencyPowerMeterImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // + .setId("meter2") // + .setModbusId("modbus0") // .build()); } } diff --git a/io.openems.edge.goodwe/test/io/openems/edge/goodwe/ess/GoodWeEssImplTest.java b/io.openems.edge.goodwe/test/io/openems/edge/goodwe/ess/GoodWeEssImplTest.java index bfde5088729..f6dec937023 100644 --- a/io.openems.edge.goodwe/test/io/openems/edge/goodwe/ess/GoodWeEssImplTest.java +++ b/io.openems.edge.goodwe/test/io/openems/edge/goodwe/ess/GoodWeEssImplTest.java @@ -1,5 +1,7 @@ package io.openems.edge.goodwe.ess; +import static io.openems.edge.goodwe.GoodWeConstants.DEFAULT_UNIT_ID; + import org.junit.Test; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; @@ -7,27 +9,22 @@ import io.openems.edge.common.test.DummyConfigurationAdmin; import io.openems.edge.ess.test.DummyPower; import io.openems.edge.ess.test.ManagedSymmetricEssTest; -import io.openems.edge.goodwe.GoodWeConstants; import io.openems.edge.goodwe.charger.singlestring.GoodWeChargerPv1; import io.openems.edge.goodwe.common.enums.ControlMode; public class GoodWeEssImplTest { - private static final String ESS_ID = "ess0"; - private static final String MODBUS_ID = "modbus0"; - private static final String CHARGER_ID = "charger0"; - @Test public void testEt() throws Exception { var charger = new GoodWeChargerPv1(); new ComponentTest(charger) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(io.openems.edge.goodwe.charger.singlestring.MyConfig.create() // - .setId(CHARGER_ID) // - .setBatteryInverterId(ESS_ID) // - .setModbusId(MODBUS_ID) // - .setModbusUnitId(GoodWeConstants.DEFAULT_UNIT_ID) // + .setId("charger0") // + .setBatteryInverterId("ess0") // + .setModbusId("modbus0") // + .setModbusUnitId(DEFAULT_UNIT_ID) // .build()); var ess = new GoodWeEssImpl(); @@ -35,12 +32,12 @@ public void testEt() throws Exception { new ManagedSymmetricEssTest(ess) // .addReference("power", new DummyPower()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .addComponent(charger) // .activate(MyConfig.create() // - .setId(ESS_ID) // - .setModbusId(MODBUS_ID) // - .setModbusUnitId(GoodWeConstants.DEFAULT_UNIT_ID) // + .setId("ess0") // + .setModbusId("modbus0") // + .setModbusUnitId(DEFAULT_UNIT_ID) // .setCapacity(9_000) // .setMaxBatteryPower(5_200) // .setControlMode(ControlMode.SMART) // @@ -54,11 +51,11 @@ public void testBt() throws Exception { new ManagedSymmetricEssTest(ess) // .addReference("power", new DummyPower()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(ESS_ID) // - .setModbusId(MODBUS_ID) // - .setModbusUnitId(GoodWeConstants.DEFAULT_UNIT_ID) // + .setId("ess0") // + .setModbusId("modbus0") // + .setModbusUnitId(DEFAULT_UNIT_ID) // .setCapacity(9_000) // .setMaxBatteryPower(5_200) // .setControlMode(ControlMode.SMART) // diff --git a/io.openems.edge.goodwe/test/io/openems/edge/goodwe/gridmeter/GoodWeGridMeterImplTest.java b/io.openems.edge.goodwe/test/io/openems/edge/goodwe/gridmeter/GoodWeGridMeterImplTest.java index 47b532280ad..c32d8e8b7f8 100644 --- a/io.openems.edge.goodwe/test/io/openems/edge/goodwe/gridmeter/GoodWeGridMeterImplTest.java +++ b/io.openems.edge.goodwe/test/io/openems/edge/goodwe/gridmeter/GoodWeGridMeterImplTest.java @@ -1,42 +1,38 @@ package io.openems.edge.goodwe.gridmeter; +import static io.openems.edge.goodwe.gridmeter.GoodWeGridMeter.ChannelId.EXTERNAL_METER_RATIO; +import static io.openems.edge.goodwe.gridmeter.GoodWeGridMeter.ChannelId.METER_CON_CORRECTLY_L1; +import static io.openems.edge.goodwe.gridmeter.GoodWeGridMeter.ChannelId.METER_CON_INCORRECTLY_L1; +import static io.openems.edge.goodwe.gridmeter.GoodWeGridMeter.ChannelId.METER_CON_REVERSE_L1; +import static io.openems.edge.goodwe.gridmeter.GoodWeGridMeterCategory.COMMERCIAL_METER; +import static io.openems.edge.goodwe.gridmeter.GoodWeGridMeterCategory.SMART_METER; +import static io.openems.edge.goodwe.gridmeter.GoodWeGridMeterImpl.calculateRatio; +import static io.openems.edge.goodwe.gridmeter.GoodWeGridMeterImpl.getPhaseConnectionValue; import static org.junit.Assert.assertEquals; import org.junit.Test; -import io.openems.common.types.ChannelAddress; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; +import io.openems.edge.common.channel.value.Value; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; import io.openems.edge.ess.power.api.Phase; +import io.openems.edge.meter.api.ElectricityMeter; public class GoodWeGridMeterImplTest { - private static final String MODBUS_ID = "modbus0"; - - private static final String METER_ID = "meter0"; - - private static final ChannelAddress METER_CON_CORRECTLY_L1 = new ChannelAddress(METER_ID, - GoodWeGridMeter.ChannelId.METER_CON_CORRECTLY_L1.id()); - private static final ChannelAddress METER_CON_INCORRECTLY_L1 = new ChannelAddress(METER_ID, - GoodWeGridMeter.ChannelId.METER_CON_INCORRECTLY_L1.id()); - private static final ChannelAddress METER_CON_REVERSE_L1 = new ChannelAddress(METER_ID, - GoodWeGridMeter.ChannelId.METER_CON_REVERSE_L1.id()); - private static final ChannelAddress EXTERNAL_METER_RATIO = new ChannelAddress(METER_ID, - GoodWeGridMeter.ChannelId.EXTERNAL_METER_RATIO.id()); - @Test public void test() throws Exception { final var sut = new GoodWeGridMeterImpl(); new ComponentTest(sut) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // - .setGoodWeMeterCategory(GoodWeGridMeterCategory.SMART_METER) // + .setId("meter0") // + .setModbusId("modbus0") // + .setGoodWeMeterCategory(SMART_METER) // .setExternalMeterRatioValueA(0) // .setExternalMeterRatioValueB(0) // .build()) // @@ -65,44 +61,104 @@ public void test() throws Exception { @Test public void testMeterConnectStateConverter() throws Exception { - var l1Result = GoodWeGridMeterImpl.getPhaseConnectionValue(Phase.L1, 0x0124); - var l2Result = GoodWeGridMeterImpl.getPhaseConnectionValue(Phase.L2, 0x0124); - var l3Result = GoodWeGridMeterImpl.getPhaseConnectionValue(Phase.L3, 0x0124); + var l1Result = getPhaseConnectionValue(Phase.L1, 0x0124); + var l2Result = getPhaseConnectionValue(Phase.L2, 0x0124); + var l3Result = getPhaseConnectionValue(Phase.L3, 0x0124); assertEquals(4, (int) l1Result); assertEquals(2, (int) l2Result); assertEquals(1, (int) l3Result); - l1Result = GoodWeGridMeterImpl.getPhaseConnectionValue(Phase.L1, 0x0524); - l2Result = GoodWeGridMeterImpl.getPhaseConnectionValue(Phase.L2, 0x0462); - l3Result = GoodWeGridMeterImpl.getPhaseConnectionValue(Phase.L3, 0x1647); + l1Result = getPhaseConnectionValue(Phase.L1, 0x0524); + l2Result = getPhaseConnectionValue(Phase.L2, 0x0462); + l3Result = getPhaseConnectionValue(Phase.L3, 0x1647); assertEquals(4, (int) l1Result); assertEquals(6, (int) l2Result); assertEquals(6, (int) l3Result); - var l1NoResult = GoodWeGridMeterImpl.getPhaseConnectionValue(Phase.L1, 0x000); - var l2NoResult = GoodWeGridMeterImpl.getPhaseConnectionValue(Phase.L2, 0x000); - var l3NoResult = GoodWeGridMeterImpl.getPhaseConnectionValue(Phase.L3, 0x000); + var l1NoResult = getPhaseConnectionValue(Phase.L1, 0x000); + var l2NoResult = getPhaseConnectionValue(Phase.L2, 0x000); + var l3NoResult = getPhaseConnectionValue(Phase.L3, 0x000); assertEquals(0, (int) l1NoResult); assertEquals(0, (int) l2NoResult); assertEquals(0, (int) l3NoResult); - var noResult = GoodWeGridMeterImpl.getPhaseConnectionValue(Phase.L3, 0x000); + var noResult = getPhaseConnectionValue(Phase.L3, 0x000); assert noResult == 0x000; } + @Test + public void testReadFromModbus() throws Exception { + new ComponentTest(new GoodWeGridMeterImpl()) // + .addReference("cm", new DummyConfigurationAdmin()) // + .addReference("setModbus", new DummyModbusBridge("modbus0") // + .withRegisters(36003, 0, 1) // States + .withRegisters(35123, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // F_GRID_R etc. + .withRegister(35016, 4) // DSP Version + .withRegisters(36005, // + /* ACTIVE_POWER */ -1000 /* L1 */, -1320 /* L2 */, 1610 /* L3 */, // + /* reserved */ 0, 0, 0, 0, 0, // + /* METER_POWER_FACTOR */ 0, // + /* FREQUENCY */ 50) + .withRegisters(36052, // + /* VOLTAGE */ 2000 /* L1 */, 2200 /* L2 */, 2300 /* L3 */, // + /* CURRENT */ 50 /* L1 */, 60 /* L2 */, 70 /* L3 */)) + .activate(MyConfig.create() // + .setId("meter0") // + .setModbusId("modbus0") // + .setGoodWeMeterCategory(SMART_METER) // + .setExternalMeterRatioValueA(0) // + .setExternalMeterRatioValueB(0) // + .build()) // + + .next(new TestCase() // + .output(GoodWeGridMeter.ChannelId.HAS_NO_METER, false) // + .output(ElectricityMeter.ChannelId.ACTIVE_POWER_L1, 1000) // inverted + .output(ElectricityMeter.ChannelId.ACTIVE_POWER_L2, 1320) // + .output(ElectricityMeter.ChannelId.ACTIVE_POWER_L3, -1610) // + .output(ElectricityMeter.ChannelId.ACTIVE_POWER, 710)) // + + .next(new TestCase(), 3) // Wait for 36052 + .next(new TestCase() // + .output(ElectricityMeter.ChannelId.VOLTAGE_L1, 200_000) // + .output(ElectricityMeter.ChannelId.VOLTAGE_L2, 220_000) // + .output(ElectricityMeter.ChannelId.VOLTAGE_L3, 230_000) // + .output(ElectricityMeter.ChannelId.CURRENT_L1, 5_000) // + .output(ElectricityMeter.ChannelId.CURRENT_L2, 6_000) // + .output(ElectricityMeter.ChannelId.CURRENT_L3, -7_000)) // inverted + .deactivate(); + } + + @Test + public void testAdjustCurrentSign() { + { + var e2cConverter = GoodWeGridMeterImpl.createAdjustCurrentSign(() -> new Value(null, -5000)); + // postive to negative + assertEquals(-16, e2cConverter.elementToChannel(16)); + // negative stays negative + assertEquals(-16, e2cConverter.elementToChannel(-16)); + } + { + var e2cConverter = GoodWeGridMeterImpl.createAdjustCurrentSign(() -> new Value(null, 5000)); + // positive stays positive + assertEquals(16, e2cConverter.elementToChannel(16)); + // negative to positive + assertEquals(16, e2cConverter.elementToChannel(-16)); + } + } + @Test public void testExternalMeterRatio() throws Exception { new ComponentTest(new GoodWeGridMeterImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // - .setGoodWeMeterCategory(GoodWeGridMeterCategory.COMMERCIAL_METER) // + .setId("meter0") // + .setModbusId("modbus0") // + .setGoodWeMeterCategory(COMMERCIAL_METER) // .setExternalMeterRatioValueA(3000) // .setExternalMeterRatioValueB(5) // .build()) // @@ -111,11 +167,11 @@ public void testExternalMeterRatio() throws Exception { new ComponentTest(new GoodWeGridMeterImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // - .setGoodWeMeterCategory(GoodWeGridMeterCategory.COMMERCIAL_METER) // + .setId("meter0") // + .setModbusId("modbus0") // + .setGoodWeMeterCategory(COMMERCIAL_METER) // .setExternalMeterRatioValueA(500) // .setExternalMeterRatioValueB(5) // .build()) // @@ -124,11 +180,11 @@ public void testExternalMeterRatio() throws Exception { new ComponentTest(new GoodWeGridMeterImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // - .setGoodWeMeterCategory(GoodWeGridMeterCategory.SMART_METER) // + .setId("meter0") // + .setModbusId("modbus0") // + .setGoodWeMeterCategory(SMART_METER) // .setExternalMeterRatioValueA(3000) // .setExternalMeterRatioValueB(5) // .build()) // @@ -138,12 +194,11 @@ public void testExternalMeterRatio() throws Exception { @Test public void testCalculateRatio() { - - assertEquals(600, (int) GoodWeGridMeterImpl.calculateRatio(3000, 5)); - assertEquals(100, (int) GoodWeGridMeterImpl.calculateRatio(500, 5)); - assertEquals(null, GoodWeGridMeterImpl.calculateRatio(-5, 5)); - assertEquals(null, GoodWeGridMeterImpl.calculateRatio(3000, 0)); - assertEquals(null, GoodWeGridMeterImpl.calculateRatio(500, -5)); + assertEquals(600, calculateRatio(3000, 5).intValue()); + assertEquals(100, calculateRatio(500, 5).intValue()); + assertEquals(null, calculateRatio(-5, 5)); + assertEquals(null, calculateRatio(3000, 0)); + assertEquals(null, calculateRatio(500, -5)); } } diff --git a/io.openems.edge.io.api/.classpath b/io.openems.edge.io.api/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.io.api/.classpath +++ b/io.openems.edge.io.api/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.io.api/bnd.bnd b/io.openems.edge.io.api/bnd.bnd index e6f2e03ce02..aa7c0dd5fa0 100644 --- a/io.openems.edge.io.api/bnd.bnd +++ b/io.openems.edge.io.api/bnd.bnd @@ -6,7 +6,7 @@ Bundle-Version: 1.0.0.${tstamp} -buildpath: \ ${buildpath},\ io.openems.common,\ - io.openems.edge.common + io.openems.edge.common,\ -testpath: \ ${testpath} diff --git a/io.openems.edge.io.api/src/io/openems/edge/io/test/DummyCustomInputOutput.java b/io.openems.edge.io.api/src/io/openems/edge/io/test/DummyCustomInputOutput.java new file mode 100644 index 00000000000..694f61e0348 --- /dev/null +++ b/io.openems.edge.io.api/src/io/openems/edge/io/test/DummyCustomInputOutput.java @@ -0,0 +1,57 @@ +package io.openems.edge.io.test; + +import io.openems.common.channel.AccessMode; +import io.openems.common.types.OpenemsType; +import io.openems.edge.common.channel.BooleanReadChannel; +import io.openems.edge.common.channel.BooleanWriteChannel; +import io.openems.edge.common.channel.ChannelId.ChannelIdImpl; +import io.openems.edge.common.channel.Doc; +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.common.test.AbstractDummyOpenemsComponent; +import io.openems.edge.io.api.DigitalInput; +import io.openems.edge.io.api.DigitalOutput; + +/** + * Provides a simple, simulated Digital Input/Output component that can be used + * together with the OpenEMS Component test framework. + */ +public class DummyCustomInputOutput extends AbstractDummyOpenemsComponent + implements DigitalInput, DigitalOutput { + + private final BooleanWriteChannel[] ioChannels; + + public DummyCustomInputOutput(String id) { + this(id, "INPUT_OUTPUT", 0, 10); + } + + public DummyCustomInputOutput(String id, String prefix, int start, int numberOfIOs) { + super(id, // + OpenemsComponent.ChannelId.values(), // + DigitalInput.ChannelId.values(), // + DigitalOutput.ChannelId.values() // + ); + + this.ioChannels = new BooleanWriteChannel[numberOfIOs]; + for (int i = 0; i < numberOfIOs; i++) { + this.ioChannels[i] = (BooleanWriteChannel) this + .addChannel(new ChannelIdImpl(prefix + "_" + (i + start), Doc.of(OpenemsType.BOOLEAN).// + accessMode(AccessMode.READ_WRITE))); + } + } + + @Override + protected DummyCustomInputOutput self() { + return this; + } + + @Override + public BooleanWriteChannel[] digitalOutputChannels() { + return this.ioChannels; + } + + @Override + public BooleanReadChannel[] digitalInputChannels() { + return this.ioChannels; + } + +} diff --git a/io.openems.edge.io.api/src/io/openems/edge/io/test/DummyInputOutput.java b/io.openems.edge.io.api/src/io/openems/edge/io/test/DummyInputOutput.java index f9114bef534..92e6b4a26c1 100644 --- a/io.openems.edge.io.api/src/io/openems/edge/io/test/DummyInputOutput.java +++ b/io.openems.edge.io.api/src/io/openems/edge/io/test/DummyInputOutput.java @@ -1,10 +1,11 @@ package io.openems.edge.io.test; +import java.util.stream.Stream; + import io.openems.common.channel.AccessMode; import io.openems.common.types.OpenemsType; import io.openems.edge.common.channel.BooleanReadChannel; import io.openems.edge.common.channel.BooleanWriteChannel; -import io.openems.edge.common.channel.ChannelId.ChannelIdImpl; import io.openems.edge.common.channel.Doc; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.test.AbstractDummyOpenemsComponent; @@ -18,25 +19,53 @@ public class DummyInputOutput extends AbstractDummyOpenemsComponent implements DigitalInput, DigitalOutput { - private final BooleanWriteChannel[] ioChannels; + public enum ChannelId implements io.openems.edge.common.channel.ChannelId { + INPUT_OUTPUT0(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_WRITE)), // + INPUT_OUTPUT1(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_WRITE)), // + INPUT_OUTPUT2(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_WRITE)), // + INPUT_OUTPUT3(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_WRITE)), // + INPUT_OUTPUT4(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_WRITE)), // + INPUT_OUTPUT5(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_WRITE)), // + INPUT_OUTPUT6(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_WRITE)), // + INPUT_OUTPUT7(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_WRITE)), // + INPUT_OUTPUT8(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_WRITE)), // + INPUT_OUTPUT9(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_WRITE)); - public DummyInputOutput(String id) { - this(id, "INPUT_OUTPUT", 0, 10); + private final Doc doc; + + private ChannelId(Doc doc) { + this.doc = doc; + } + + @Override + public Doc doc() { + return this.doc; + } } - public DummyInputOutput(String id, String prefix, int start, int numberOfIOs) { + private final BooleanWriteChannel[] digitalOutputChannels; + + public DummyInputOutput(String id) { super(id, // OpenemsComponent.ChannelId.values(), // DigitalInput.ChannelId.values(), // - DigitalOutput.ChannelId.values() // + DigitalOutput.ChannelId.values(), // + ChannelId.values() // ); - - this.ioChannels = new BooleanWriteChannel[numberOfIOs]; - for (int i = 0; i < numberOfIOs; i++) { - this.ioChannels[i] = (BooleanWriteChannel) this - .addChannel(new ChannelIdImpl(prefix + "_" + (i + start), Doc.of(OpenemsType.BOOLEAN).// - accessMode(AccessMode.READ_WRITE))); - } + this.digitalOutputChannels = Stream.of(ChannelId.values()) // + .filter(channelId -> channelId.doc().getAccessMode() == AccessMode.READ_WRITE) // + .map(this::channel) // + .toArray(BooleanWriteChannel[]::new); } @Override @@ -46,12 +75,12 @@ protected DummyInputOutput self() { @Override public BooleanWriteChannel[] digitalOutputChannels() { - return this.ioChannels; + return this.digitalOutputChannels; } @Override public BooleanReadChannel[] digitalInputChannels() { - return this.ioChannels; + return this.digitalOutputChannels; } } diff --git a/io.openems.edge.io.filipowski/.classpath b/io.openems.edge.io.filipowski/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.io.filipowski/.classpath +++ b/io.openems.edge.io.filipowski/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.io.filipowski/test/io/openems/edge/io/filipowski/analog/mr/IoFilipowskiMrAo1ImplTest.java b/io.openems.edge.io.filipowski/test/io/openems/edge/io/filipowski/analog/mr/IoFilipowskiMrAo1ImplTest.java index 11ef8cd64dc..dabd16527a6 100644 --- a/io.openems.edge.io.filipowski/test/io/openems/edge/io/filipowski/analog/mr/IoFilipowskiMrAo1ImplTest.java +++ b/io.openems.edge.io.filipowski/test/io/openems/edge/io/filipowski/analog/mr/IoFilipowskiMrAo1ImplTest.java @@ -9,17 +9,14 @@ public class IoFilipowskiMrAo1ImplTest { - private static final String COMPONENT_ID = "component0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new IoFilipowskiMrAo1Impl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // - .setModbusId(MODBUS_ID) // + .setId("component0") // + .setModbusId("modbus0") // .setRelayContact(AnalogOutput.OUTPUT_1) // .build()) .next(new TestCase()); diff --git a/io.openems.edge.io.gpio/.classpath b/io.openems.edge.io.gpio/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.io.gpio/.classpath +++ b/io.openems.edge.io.gpio/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.io.gpio/test/io/openems/edge/io/gpio/ModberryCM4Test.java b/io.openems.edge.io.gpio/test/io/openems/edge/io/gpio/ModberryCM4Test.java index 113c39f7c6a..084079c95f0 100644 --- a/io.openems.edge.io.gpio/test/io/openems/edge/io/gpio/ModberryCM4Test.java +++ b/io.openems.edge.io.gpio/test/io/openems/edge/io/gpio/ModberryCM4Test.java @@ -1,5 +1,6 @@ package io.openems.edge.io.gpio; +import static io.openems.edge.io.gpio.hardware.HardwareType.MODBERRY_X500_M40804_W; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -27,14 +28,11 @@ import io.openems.edge.io.gpio.api.AbstractGpioChannel; import io.openems.edge.io.gpio.api.ReadChannelId; import io.openems.edge.io.gpio.api.WriteChannelId; -import io.openems.edge.io.gpio.hardware.HardwareType; public class ModberryCM4Test { private File root; - private static final String ID = "io0"; - private static final List CHANNEL_IDS = List.of(// new ReadChannelId(18, "DigitalInput1"), // new ReadChannelId(19, "DigitalInput2"), // @@ -93,11 +91,11 @@ private void setGpioFile(File root, int gpioNumber, int value) throws IOExceptio @Test public void testChannelIdsAreCorrect() throws Exception { var config = MyConfig.create() // - .setId(ID) // - .setAlias(ID) // + .setId("io0") // + .setAlias("io0") // .setEnabled(true) // .setGpioPath(this.folder.getRoot().getAbsolutePath()) // - .setHardwareType(HardwareType.MODBERRY_X500_M40804_W) // + .setHardwareType(MODBERRY_X500_M40804_W) // .build(); IoGpio modberryComponent = new IoGpioImpl(); new ComponentTest(modberryComponent).activate(config); @@ -107,11 +105,11 @@ public void testChannelIdsAreCorrect() throws Exception { @Test public void testComponentLoadsSucesfully() throws Exception { var config = MyConfig.create() // - .setId(ID) // - .setAlias(ID) // + .setId("io0") // + .setAlias("io0") // .setEnabled(true) // .setGpioPath(this.folder.getRoot().getAbsolutePath()) // - .setHardwareType(HardwareType.MODBERRY_X500_M40804_W) // + .setHardwareType(MODBERRY_X500_M40804_W) // .build(); new ComponentTest(new IoGpioImpl()) // .activate(config); @@ -120,19 +118,19 @@ public void testComponentLoadsSucesfully() throws Exception { @Test public void testInputValuesAreDefault() throws Exception { var config = MyConfig.create() // - .setId(ID) // - .setAlias(ID) // + .setId("io0") // + .setAlias("io0") // .setEnabled(true) // .setGpioPath(this.folder.getRoot().getAbsolutePath()) // - .setHardwareType(HardwareType.MODBERRY_X500_M40804_W) // + .setHardwareType(MODBERRY_X500_M40804_W) // .build(); new ComponentTest(new IoGpioImpl()) // .activate(config) // .next(new TestCase("Default input values are false") // - .output(new ChannelAddress(ID, "DigitalInput1"), false) // - .output(new ChannelAddress(ID, "DigitalInput2"), false) // - .output(new ChannelAddress(ID, "DigitalInput3"), false) // - .output(new ChannelAddress(ID, "DigitalInput4"), false) // + .output(new ChannelAddress("io0", "DigitalInput1"), false) // + .output(new ChannelAddress("io0", "DigitalInput2"), false) // + .output(new ChannelAddress("io0", "DigitalInput3"), false) // + .output(new ChannelAddress("io0", "DigitalInput4"), false) // ); } @@ -143,19 +141,19 @@ public void testChangeOutputWrittenToFs() throws Exception { assertEquals(this.readGpioFile(this.root, 24), "0"); assertEquals(this.readGpioFile(this.root, 25), "0"); var config = MyConfig.create() // - .setId(ID) // - .setAlias(ID) // + .setId("io0") // + .setAlias("io0") // .setEnabled(true) // .setGpioPath(this.folder.getRoot().getAbsolutePath()) // - .setHardwareType(HardwareType.MODBERRY_X500_M40804_W) // + .setHardwareType(MODBERRY_X500_M40804_W) // .build(); new ComponentTest(new IoGpioImpl()) // .activate(config) // .next(new TestCase("Write values are written to fs.") // - .input(new ChannelAddress(ID, "DigitalOutput1"), true) // - .input(new ChannelAddress(ID, "DigitalOutput2"), true) // - .input(new ChannelAddress(ID, "DigitalOutput3"), true) // - .input(new ChannelAddress(ID, "DigitalOutput4"), true) // + .input(new ChannelAddress("io0", "DigitalOutput1"), true) // + .input(new ChannelAddress("io0", "DigitalOutput2"), true) // + .input(new ChannelAddress("io0", "DigitalOutput3"), true) // + .input(new ChannelAddress("io0", "DigitalOutput4"), true) // ); assertEquals(this.readGpioFile(this.root, 22), "1"); assertEquals(this.readGpioFile(this.root, 23), "1"); @@ -171,20 +169,20 @@ public void testChangeInputIsDetected() throws Exception { assertEquals(this.readGpioFile(this.root, 21), "0"); var config = MyConfig.create() // - .setId(ID) // - .setAlias(ID) // + .setId("io0") // + .setAlias("io0") // .setEnabled(true) // .setGpioPath(this.folder.getRoot().getAbsolutePath()) // - .setHardwareType(HardwareType.MODBERRY_X500_M40804_W) // + .setHardwareType(MODBERRY_X500_M40804_W) // .build(); new ComponentTest(new IoGpioImpl()) // .activate(config) // .next(new TestCase("Read values are detected by the component.") // - .output(new ChannelAddress(ID, "DigitalInput1"), false) // - .output(new ChannelAddress(ID, "DigitalInput2"), false) // - .output(new ChannelAddress(ID, "DigitalInput3"), false) // - .output(new ChannelAddress(ID, "DigitalInput4"), false) // + .output(new ChannelAddress("io0", "DigitalInput1"), false) // + .output(new ChannelAddress("io0", "DigitalInput2"), false) // + .output(new ChannelAddress("io0", "DigitalInput3"), false) // + .output(new ChannelAddress("io0", "DigitalInput4"), false) // ); this.setGpioFile(this.root, 18, 1); @@ -200,10 +198,10 @@ public void testChangeInputIsDetected() throws Exception { new ComponentTest(new IoGpioImpl()) // .activate(config) // .next(new TestCase("Read values are detected by the component.") // - .output(new ChannelAddress(ID, "DigitalInput1"), true) // - .output(new ChannelAddress(ID, "DigitalInput2"), true) // - .output(new ChannelAddress(ID, "DigitalInput3"), true) // - .output(new ChannelAddress(ID, "DigitalInput4"), true) // + .output(new ChannelAddress("io0", "DigitalInput1"), true) // + .output(new ChannelAddress("io0", "DigitalInput2"), true) // + .output(new ChannelAddress("io0", "DigitalInput3"), true) // + .output(new ChannelAddress("io0", "DigitalInput4"), true) // ); } @@ -211,18 +209,19 @@ public void testChangeInputIsDetected() throws Exception { public void testJavaApi() throws Exception { this.setGpioFile(this.root, 22, 0); var config = MyConfig.create() // - .setId(ID) // - .setAlias(ID) // + .setId("io0") // + .setAlias("io0") // .setEnabled(true) // .setGpioPath(this.folder.getRoot().getAbsolutePath()) // - .setHardwareType(HardwareType.MODBERRY_X500_M40804_W) // + .setHardwareType(MODBERRY_X500_M40804_W) // .build(); var componentManager = new DummyComponentManager(); - var componentTest = new ComponentTest(new IoGpioImpl()).activate(config); + var componentTest = new ComponentTest(new IoGpioImpl()) // + .activate(config); componentManager.addComponent(componentTest.getSut()); // Get get component channel value as java reference - WriteChannel writeChannel = componentManager.getChannel(new ChannelAddress(ID, "DigitalOutput1")); + WriteChannel writeChannel = componentManager.getChannel(new ChannelAddress("io0", "DigitalOutput1")); assertFalse(writeChannel.value().isDefined()); writeChannel.setNextValue(true); } @@ -231,11 +230,11 @@ public void testJavaApi() throws Exception { public void testInterfaceDigitalOutputChannels() throws Exception { this.setGpioFile(this.root, 22, 0); var config = MyConfig.create() // - .setId(ID) // - .setAlias(ID) // + .setId("io0") // + .setAlias("io0") // .setEnabled(true) // .setGpioPath(this.folder.getRoot().getAbsolutePath()) // - .setHardwareType(HardwareType.MODBERRY_X500_M40804_W) // + .setHardwareType(MODBERRY_X500_M40804_W) // .build(); var componentManager = new DummyComponentManager(); var componentTest = new ComponentTest(new IoGpioImpl()) // @@ -249,11 +248,11 @@ public void testInterfaceDigitalOutputChannels() throws Exception { public void testInterfaceDigitalInputChannels() throws Exception { this.setGpioFile(this.root, 22, 0); var config = MyConfig.create() // - .setId(ID) // - .setAlias(ID) // + .setId("io0") // + .setAlias("io0") // .setEnabled(true) // .setGpioPath(this.folder.getRoot().getAbsolutePath()) // - .setHardwareType(HardwareType.MODBERRY_X500_M40804_W) // + .setHardwareType(MODBERRY_X500_M40804_W) // .build(); var componentManager = new DummyComponentManager(); var componentTest = new ComponentTest(new IoGpioImpl()) // diff --git a/io.openems.edge.io.kmtronic/.classpath b/io.openems.edge.io.kmtronic/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.io.kmtronic/.classpath +++ b/io.openems.edge.io.kmtronic/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.io.kmtronic/test/io/openems/edge/io/kmtronic/eight/IoKmtronicRelay8PortImplTest.java b/io.openems.edge.io.kmtronic/test/io/openems/edge/io/kmtronic/eight/IoKmtronicRelay8PortImplTest.java index 67b002d2fae..e27c0302a00 100644 --- a/io.openems.edge.io.kmtronic/test/io/openems/edge/io/kmtronic/eight/IoKmtronicRelay8PortImplTest.java +++ b/io.openems.edge.io.kmtronic/test/io/openems/edge/io/kmtronic/eight/IoKmtronicRelay8PortImplTest.java @@ -8,17 +8,14 @@ public class IoKmtronicRelay8PortImplTest { - private static final String IO_ID = "io0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new IoKmtronicRelay8PortImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(IO_ID) // - .setModbusId(MODBUS_ID) // + .setId("io0") // + .setModbusId("modbus0") // .build()) // ; } diff --git a/io.openems.edge.io.kmtronic/test/io/openems/edge/io/kmtronic/four/IoKmtronicRelay4PortImplTest.java b/io.openems.edge.io.kmtronic/test/io/openems/edge/io/kmtronic/four/IoKmtronicRelay4PortImplTest.java index e7ccd9abf4c..36b4ab28c6f 100644 --- a/io.openems.edge.io.kmtronic/test/io/openems/edge/io/kmtronic/four/IoKmtronicRelay4PortImplTest.java +++ b/io.openems.edge.io.kmtronic/test/io/openems/edge/io/kmtronic/four/IoKmtronicRelay4PortImplTest.java @@ -8,17 +8,14 @@ public class IoKmtronicRelay4PortImplTest { - private static final String IO_ID = "io0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new IoKmtronicRelay4PortImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(IO_ID) // - .setModbusId(MODBUS_ID) // + .setId("io0") // + .setModbusId("modbus0") // .build()) // ; } diff --git a/io.openems.edge.io.offgridswitch/.classpath b/io.openems.edge.io.offgridswitch/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.io.offgridswitch/.classpath +++ b/io.openems.edge.io.offgridswitch/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.io.offgridswitch/test/io/openems/edge/iooffgridswitch/IoOffGridSwitchImplTest.java b/io.openems.edge.io.offgridswitch/test/io/openems/edge/iooffgridswitch/IoOffGridSwitchImplTest.java index 9d182edd712..0f2944b67f6 100644 --- a/io.openems.edge.io.offgridswitch/test/io/openems/edge/iooffgridswitch/IoOffGridSwitchImplTest.java +++ b/io.openems.edge.io.offgridswitch/test/io/openems/edge/iooffgridswitch/IoOffGridSwitchImplTest.java @@ -2,7 +2,6 @@ import org.junit.Test; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyComponentManager; @@ -10,29 +9,18 @@ public class IoOffGridSwitchImplTest { - private static final String COMPONENT_ID = "ioOffGridSwitch0"; - - private static final String IO_ID = "io0"; - private static final ChannelAddress INPUT_MAIN_CONTACTOR = new ChannelAddress(IO_ID, "InputOutput0"); - private static final ChannelAddress INPUT_GRID_STATUS = new ChannelAddress(IO_ID, "InputOutput1"); - private static final ChannelAddress INPUT_GROUNDING_CONTACTOR = new ChannelAddress(IO_ID, "InputOutput2"); - private static final ChannelAddress OUTPUT_MAIN_CONTACTOR = new ChannelAddress(IO_ID, "InputOutput3"); - private static final ChannelAddress OUTPUT_GROUNDING_CONTACTOR = new ChannelAddress(IO_ID, "InputOutput4"); - @Test public void test() throws Exception { - var io0 = new DummyInputOutput(IO_ID); - new ComponentTest(new IoOffGridSwitchImpl()) // .addReference("componentManager", new DummyComponentManager()) // - .addComponent(io0) // + .addComponent(new DummyInputOutput("io0")) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // - .setInputMainContactor(INPUT_MAIN_CONTACTOR.toString()) // - .setInputGridStatus(INPUT_GRID_STATUS.toString()) // - .setInputGroundingContactor(INPUT_GROUNDING_CONTACTOR.toString()) // - .setOutputMainContactor(OUTPUT_MAIN_CONTACTOR.toString()) // - .setOutputGroundingContactor(OUTPUT_GROUNDING_CONTACTOR.toString()) // + .setId("ioOffGridSwitch0") // + .setInputMainContactor("io0/InputOutput0") // + .setInputGridStatus("io0/InputOutput1") // + .setInputGroundingContactor("io0/InputOutput2") // + .setOutputMainContactor("io0/InputOutput3") // + .setOutputGroundingContactor("io0/InputOutput4") // .build()) .next(new TestCase()); } diff --git a/io.openems.edge.io.revpi/.classpath b/io.openems.edge.io.revpi/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.io.revpi/.classpath +++ b/io.openems.edge.io.revpi/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.io.revpi/test/io/openems/edge/io/revpi/IoRevolutionPiDigitalIoImplTest.java b/io.openems.edge.io.revpi/test/io/openems/edge/io/revpi/IoRevolutionPiDigitalIoImplTest.java index db1698ef9a2..7f7285b5b65 100644 --- a/io.openems.edge.io.revpi/test/io/openems/edge/io/revpi/IoRevolutionPiDigitalIoImplTest.java +++ b/io.openems.edge.io.revpi/test/io/openems/edge/io/revpi/IoRevolutionPiDigitalIoImplTest.java @@ -6,8 +6,6 @@ public class IoRevolutionPiDigitalIoImplTest { - // private static final String COMPONENT_ID = "io0"; - @Test public void test() throws Exception { new ComponentTest(new IoRevolutionPiDigitalIoImpl()) // diff --git a/io.openems.edge.io.shelly/.classpath b/io.openems.edge.io.shelly/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.io.shelly/.classpath +++ b/io.openems.edge.io.shelly/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/common/Utils.java b/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/common/Utils.java index ca4ac6ff5b4..d5d0edfabeb 100644 --- a/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/common/Utils.java +++ b/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/common/Utils.java @@ -1,7 +1,16 @@ package io.openems.edge.io.shelly.common; +import static java.util.Arrays.stream; +import static java.util.stream.Collectors.joining; + +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +import io.openems.edge.bridge.http.api.BridgeHttp; import io.openems.edge.common.channel.BooleanWriteChannel; import io.openems.edge.common.channel.Channel; +import io.openems.edge.common.channel.WriteChannel; import io.openems.edge.common.component.OpenemsComponent; public class Utils { @@ -13,17 +22,20 @@ private Utils() { * Generates a standard Debug-Log string for Shellys with one relay and power * meter. * - * @param relayChannel the Relay-Channel + * @param relayChannels the Relay-Channel * @param activePowerChannel the ActivePower-Channel * @return suitable for {@link OpenemsComponent#debugLog()} */ - public static String generateDebugLog(Channel relayChannel, Channel activePowerChannel) { + public static String generateDebugLog(Channel[] relayChannels, Channel activePowerChannel) { var b = new StringBuilder(); - relayChannel.value().asOptional().ifPresentOrElse(// - v -> b.append(v ? "On" : "Off"), // - () -> b.append("Unknown")); - b.append("|"); - b.append(activePowerChannel.value().asString()); + for (int i = 0; i < relayChannels.length; i++) { + var relayChannel = relayChannels[i]; + relayChannel.value().asOptional().ifPresentOrElse(v -> b.append(v ? "x" : "-"), () -> b.append("?")); + if (i < relayChannels.length - 1) { + b.append("|"); + } + } + b.append("|").append(activePowerChannel.value().asString()); return b.toString(); } @@ -35,21 +47,58 @@ public static String generateDebugLog(Channel relayChannel, Channel c.value().asOptional().map(v -> v // + ? "x" // + : "-") // + .orElse("?")) // + .collect(joining(" ")); + } + + /** + * Executes a write command to a specified relay channel by constructing and + * sending an HTTP request based on the channel's current and intended state. + * This method compares the current state with the desired state, and only + * proceeds with the HTTP request if they differ, ensuring no unnecessary + * commands are sent. The method returns a CompletableFuture that completes when + * the HTTP request is finished. It completes normally if the HTTP request + * succeeds, and exceptionally if the request fails due to errors. + * + * @param relayChannel the channel for the relay, specifying the current and + * desired states + * @param baseUrl the base URL for constructing the final endpoint URL + * @param httpBridge the HTTP bridge to send the request + * @param index the index of the DigitalChannel to write to (used for the + * URL) + * @return CompletableFuture{@code } that completes when the HTTP + * operation completes. Completes exceptionally if there is an error in + * the HTTP request. + */ + public static CompletableFuture executeWrite(WriteChannel relayChannel, String baseUrl, + BridgeHttp httpBridge, Integer index) { + CompletableFuture future = new CompletableFuture<>(); + Boolean readValue = relayChannel.value().get(); + Optional writeValue = relayChannel.getNextWriteValueAndReset(); + + if (writeValue.isEmpty()) { + future.complete(null); // No action needed + return future; + } + if (Objects.equals(readValue, writeValue.get())) { + future.complete(null); // No change in state + return future; + } + + final String url = baseUrl + "/rpc/Switch.Set?id=" + index + "&on=" + (writeValue.get() ? "true" : "false"); + httpBridge.get(url).whenComplete((response, exception) -> { + if (exception != null) { + future.completeExceptionally(exception); } else { - b.append("Unknown"); + future.complete(null); } - if (i < digitalOutputChannels.length) { - b.append("|"); - } - i++; - } - return b.toString(); + }); + + return future; } -} +} \ No newline at end of file diff --git a/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shelly25/IoShelly25Impl.java b/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shelly25/IoShelly25Impl.java index 3040a02fbbc..ef0297dac35 100644 --- a/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shelly25/IoShelly25Impl.java +++ b/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shelly25/IoShelly25Impl.java @@ -137,14 +137,19 @@ private void processHttpResult(HttpResponse result, HttpError error var relay1State = new RelayState(null, null, null); var relay2State = new RelayState(null, null, null); - try { - final var relays = getAsJsonArray(result.data(), "relays"); - relay1State = RelayState.from(getAsJsonObject(relays.get(0))); - relay2State = RelayState.from(getAsJsonObject(relays.get(1))); - - } catch (OpenemsNamedException | IndexOutOfBoundsException e) { - this.logDebug(this.log, e.getMessage()); - slaveCommunicationFailed = true; + if (error != null) { + this.logDebug(this.log, error.getMessage()); + + } else { + try { + final var relays = getAsJsonArray(result.data(), "relays"); + relay1State = RelayState.from(getAsJsonObject(relays.get(0))); + relay2State = RelayState.from(getAsJsonObject(relays.get(1))); + + } catch (OpenemsNamedException | IndexOutOfBoundsException e) { + this.logDebug(this.log, e.getMessage()); + slaveCommunicationFailed = true; + } } this._setSlaveCommunicationFailed(slaveCommunicationFailed); @@ -182,4 +187,4 @@ private void executeWrite(BooleanWriteChannel channel, int index) { } }); } -} +} \ No newline at end of file diff --git a/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shelly3em/Config.java b/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shelly3em/Config.java index 5ff09dac9e0..e86f0430f61 100644 --- a/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shelly3em/Config.java +++ b/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shelly3em/Config.java @@ -3,7 +3,7 @@ import org.osgi.service.metatype.annotations.AttributeDefinition; import org.osgi.service.metatype.annotations.ObjectClassDefinition; -import io.openems.edge.meter.api.MeterType; +import io.openems.common.types.MeterType; @ObjectClassDefinition(// name = "IO Shelly 3EM", // diff --git a/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shelly3em/IoShelly3EmImpl.java b/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shelly3em/IoShelly3EmImpl.java index ca9eaf27fcb..19a5447471c 100644 --- a/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shelly3em/IoShelly3EmImpl.java +++ b/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shelly3em/IoShelly3EmImpl.java @@ -4,11 +4,10 @@ import static io.openems.common.utils.JsonUtils.getAsFloat; import static io.openems.common.utils.JsonUtils.getAsJsonArray; import static io.openems.common.utils.JsonUtils.getAsJsonObject; +import static io.openems.edge.io.shelly.common.Utils.executeWrite; import static io.openems.edge.io.shelly.common.Utils.generateDebugLog; import static java.lang.Math.round; -import java.util.Objects; - import org.osgi.service.component.ComponentContext; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; @@ -28,6 +27,7 @@ import com.google.gson.JsonElement; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.types.MeterType; import io.openems.edge.bridge.http.api.BridgeHttp; import io.openems.edge.bridge.http.api.BridgeHttpFactory; import io.openems.edge.bridge.http.api.HttpResponse; @@ -37,7 +37,6 @@ import io.openems.edge.common.event.EdgeEventConstants; import io.openems.edge.io.api.DigitalOutput; import io.openems.edge.meter.api.ElectricityMeter; -import io.openems.edge.meter.api.MeterType; import io.openems.edge.timedata.api.Timedata; import io.openems.edge.timedata.api.TimedataProvider; import io.openems.edge.timedata.api.utils.CalculateEnergyFromPower; @@ -83,6 +82,8 @@ public IoShelly3EmImpl() { this.digitalOutputChannels = new BooleanWriteChannel[] { this.channel(IoShelly3Em.ChannelId.RELAY) }; ElectricityMeter.calculateSumActivePowerFromPhases(this); + ElectricityMeter.calculateSumCurrentFromPhases(this); + ElectricityMeter.calculateAverageVoltageFromPhases(this); } @Activate @@ -111,7 +112,7 @@ public BooleanWriteChannel[] digitalOutputChannels() { @Override public String debugLog() { - return generateDebugLog(this.getRelayChannel(), this.getActivePowerChannel()); + return generateDebugLog(this.digitalOutputChannels, this.getActivePowerChannel()); } @Override @@ -124,7 +125,7 @@ public void handleEvent(Event event) { case EdgeEventConstants.TOPIC_CYCLE_AFTER_PROCESS_IMAGE // -> this.calculateEnergy(); case EdgeEventConstants.TOPIC_CYCLE_EXECUTE_WRITE // - -> this.executeWrite(); + -> executeWrite(this.getRelayChannel(), this.baseUrl, this.httpBridge, 0); } } @@ -219,32 +220,6 @@ private void processHttpResult(HttpResponse result, Throwable error this._setCurrentL3(currentL3); } - /** - * Execute on Cycle Event "Execute Write". - */ - private void executeWrite() { - var channel = this.getRelayChannel(); - var index = 0; - var readValue = channel.value().get(); - var writeValue = channel.getNextWriteValueAndReset(); - if (writeValue.isEmpty()) { - return; - } - if (Objects.equals(readValue, writeValue.get())) { - return; - } - final var url = this.baseUrl + "/relay/" + index + "?turn=" + (writeValue.get() ? "on" : "off"); - - this.httpBridge.get(url).whenComplete((t, e) -> { - this._setSlaveCommunicationFailed(e != null); - if (e == null) { - this.logInfo(this.log, "Executed write successfully for URL: " + url); - } else { - this.logError(this.log, "Failed to execute write for URL: " + url + "; Error: " + e.getMessage()); - } - }); - } - /** * Calculate the Energy values from ActivePower. */ diff --git a/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shellyplug/Config.java b/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shellyplug/Config.java index b3613ab4b7f..d8fc37c4ecd 100644 --- a/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shellyplug/Config.java +++ b/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shellyplug/Config.java @@ -3,7 +3,7 @@ import org.osgi.service.metatype.annotations.AttributeDefinition; import org.osgi.service.metatype.annotations.ObjectClassDefinition; -import io.openems.edge.meter.api.MeterType; +import io.openems.common.types.MeterType; import io.openems.edge.meter.api.SinglePhase; @ObjectClassDefinition(// diff --git a/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shellyplug/IoShellyPlugImpl.java b/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shellyplug/IoShellyPlugImpl.java index 2bdad4b9521..24c6efa60d0 100644 --- a/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shellyplug/IoShellyPlugImpl.java +++ b/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shellyplug/IoShellyPlugImpl.java @@ -1,6 +1,12 @@ package io.openems.edge.io.shelly.shellyplug; +import static io.openems.common.utils.JsonUtils.getAsBoolean; +import static io.openems.common.utils.JsonUtils.getAsFloat; +import static io.openems.common.utils.JsonUtils.getAsJsonArray; +import static io.openems.common.utils.JsonUtils.getAsJsonObject; +import static io.openems.common.utils.JsonUtils.getAsLong; import static io.openems.edge.io.shelly.common.Utils.generateDebugLog; +import static java.lang.Math.round; import java.util.Objects; @@ -20,7 +26,7 @@ import com.google.gson.JsonElement; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.common.utils.JsonUtils; +import io.openems.common.types.MeterType; import io.openems.edge.bridge.http.api.BridgeHttp; import io.openems.edge.bridge.http.api.BridgeHttpFactory; import io.openems.edge.bridge.http.api.HttpResponse; @@ -30,7 +36,6 @@ import io.openems.edge.common.event.EdgeEventConstants; import io.openems.edge.io.api.DigitalOutput; import io.openems.edge.meter.api.ElectricityMeter; -import io.openems.edge.meter.api.MeterType; import io.openems.edge.meter.api.SinglePhase; import io.openems.edge.meter.api.SinglePhaseMeter; @@ -102,7 +107,7 @@ public BooleanWriteChannel[] digitalOutputChannels() { @Override public String debugLog() { - return generateDebugLog(this.getRelayChannel(), this.getActivePowerChannel()); + return generateDebugLog(this.digitalOutputChannels, this.getActivePowerChannel()); } @Override @@ -128,17 +133,18 @@ private void processHttpResult(HttpResponse result, Throwable error return; } try { - final var relays = JsonUtils.getAsJsonArray(result.data(), "relays"); - final var relay1 = JsonUtils.getAsJsonObject(relays.get(0)); - final var relayIson = JsonUtils.getAsBoolean(relay1, "ison"); - final var meters = JsonUtils.getAsJsonArray(result.data(), "meters"); - final var meter1 = JsonUtils.getAsJsonObject(meters.get(0)); - final var power = Math.round(JsonUtils.getAsFloat(meter1, "power")); - final var energy = JsonUtils.getAsLong(meter1, "total") /* Unit: Wm */ / 60 /* Wh */; + var relays = getAsJsonArray(result.data(), "relays"); + var relay1 = getAsJsonObject(relays.get(0)); + var relayIson = getAsBoolean(relay1, "ison"); + var meters = getAsJsonArray(result.data(), "meters"); + var meter1 = getAsJsonObject(meters.get(0)); + var power = round(getAsFloat(meter1, "power")); + var energy = getAsLong(meter1, "total")/* Unit: Wm */ / 60 /* Wh */; this._setRelay(relayIson); this._setActivePower(power); this._setActiveProductionEnergy(energy); + } catch (OpenemsNamedException e) { this._setRelay(null); this._setActivePower(null); diff --git a/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shellyplus1pm/Config.java b/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shellyplus1pm/Config.java index 2de9b0a17f0..9317e9e2e4b 100644 --- a/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shellyplus1pm/Config.java +++ b/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shellyplus1pm/Config.java @@ -3,7 +3,7 @@ import org.osgi.service.metatype.annotations.AttributeDefinition; import org.osgi.service.metatype.annotations.ObjectClassDefinition; -import io.openems.edge.meter.api.MeterType; +import io.openems.common.types.MeterType; import io.openems.edge.meter.api.SinglePhase; @ObjectClassDefinition(// diff --git a/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shellyplus1pm/IoShellyPlus1PmImpl.java b/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shellyplus1pm/IoShellyPlus1PmImpl.java index 16bd2cf3af0..2c7ba81f6d9 100644 --- a/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shellyplus1pm/IoShellyPlus1PmImpl.java +++ b/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shellyplus1pm/IoShellyPlus1PmImpl.java @@ -27,6 +27,7 @@ import com.google.gson.JsonElement; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.types.MeterType; import io.openems.edge.bridge.http.api.BridgeHttp; import io.openems.edge.bridge.http.api.BridgeHttpFactory; import io.openems.edge.bridge.http.api.HttpResponse; @@ -36,7 +37,6 @@ import io.openems.edge.common.event.EdgeEventConstants; import io.openems.edge.io.api.DigitalOutput; import io.openems.edge.meter.api.ElectricityMeter; -import io.openems.edge.meter.api.MeterType; import io.openems.edge.meter.api.SinglePhase; import io.openems.edge.meter.api.SinglePhaseMeter; import io.openems.edge.timedata.api.Timedata; @@ -123,7 +123,7 @@ public BooleanWriteChannel[] digitalOutputChannels() { @Override public String debugLog() { - return generateDebugLog(this.getRelayChannel(), this.getActivePowerChannel()); + return generateDebugLog(this.digitalOutputChannels, this.getActivePowerChannel()); } @Override @@ -238,4 +238,4 @@ public Timedata getTimedata() { return this.timedata; } -} +} \ No newline at end of file diff --git a/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shellyplusplugs/Config.java b/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shellyplusplugs/Config.java index 39904456e3f..8dd6be7fcbd 100644 --- a/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shellyplusplugs/Config.java +++ b/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shellyplusplugs/Config.java @@ -3,7 +3,7 @@ import org.osgi.service.metatype.annotations.AttributeDefinition; import org.osgi.service.metatype.annotations.ObjectClassDefinition; -import io.openems.edge.meter.api.MeterType; +import io.openems.common.types.MeterType; import io.openems.edge.meter.api.SinglePhase; @ObjectClassDefinition(// @@ -19,7 +19,7 @@ @AttributeDefinition(name = "Is enabled?", description = "Is this Component enabled?") boolean enabled() default true; - + @AttributeDefinition(name = "Phase", description = "Which Phase is this Shelly Plug connected to?") SinglePhase phase() default SinglePhase.L1; diff --git a/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shellyplusplugs/IoShellyPlusPlugsImpl.java b/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shellyplusplugs/IoShellyPlusPlugsImpl.java index cdd253f120f..1828438a49a 100644 --- a/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shellyplusplugs/IoShellyPlusPlugsImpl.java +++ b/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shellyplusplugs/IoShellyPlusPlugsImpl.java @@ -3,11 +3,10 @@ import static io.openems.common.utils.JsonUtils.getAsBoolean; import static io.openems.common.utils.JsonUtils.getAsFloat; import static io.openems.common.utils.JsonUtils.getAsJsonObject; +import static io.openems.edge.io.shelly.common.Utils.executeWrite; import static io.openems.edge.io.shelly.common.Utils.generateDebugLog; import static java.lang.Math.round; -import java.util.Objects; - import org.osgi.service.component.ComponentContext; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; @@ -26,9 +25,9 @@ import com.google.gson.JsonElement; +import io.openems.common.types.MeterType; import io.openems.edge.bridge.http.api.BridgeHttp; import io.openems.edge.bridge.http.api.BridgeHttpFactory; -import io.openems.edge.bridge.http.api.HttpError; import io.openems.edge.bridge.http.api.HttpResponse; import io.openems.edge.common.channel.BooleanWriteChannel; import io.openems.edge.common.component.AbstractOpenemsComponent; @@ -36,7 +35,6 @@ import io.openems.edge.common.event.EdgeEventConstants; import io.openems.edge.io.api.DigitalOutput; import io.openems.edge.meter.api.ElectricityMeter; -import io.openems.edge.meter.api.MeterType; import io.openems.edge.meter.api.SinglePhase; import io.openems.edge.meter.api.SinglePhaseMeter; import io.openems.edge.timedata.api.Timedata; @@ -123,7 +121,7 @@ public BooleanWriteChannel[] digitalOutputChannels() { @Override public String debugLog() { - return generateDebugLog(this.getRelayChannel(), this.getActivePowerChannel()); + return generateDebugLog(this.digitalOutputChannels, this.getActivePowerChannel()); } @Override @@ -136,11 +134,11 @@ public void handleEvent(Event event) { case EdgeEventConstants.TOPIC_CYCLE_AFTER_PROCESS_IMAGE // -> this.calculateEnergy(); case EdgeEventConstants.TOPIC_CYCLE_EXECUTE_WRITE // - -> this.executeWrite(); + -> executeWrite(this.getRelayChannel(), this.baseUrl, this.httpBridge, 0); } } - private void processHttpResult(HttpResponse result, HttpError error) { + private void processHttpResult(HttpResponse result, Throwable error) { this._setSlaveCommunicationFailed(result == null); Boolean relayStatus = null; @@ -177,32 +175,6 @@ private void processHttpResult(HttpResponse result, HttpError error this.channel(IoShellyPlusPlugs.ChannelId.HAS_UPDATE).setNextValue(updatesAvailable); } - /** - * Execute on Cycle Event "Execute Write". - */ - private void executeWrite() { - var channel = this.getRelayChannel(); - var readValue = channel.value().get(); - var writeValue = channel.getNextWriteValueAndReset(); - if (writeValue.isEmpty()) { - return; - } - if (Objects.equals(readValue, writeValue.get())) { - return; - } - var index = 0; - final var url = this.baseUrl + "/relay/" + index + "?turn=" + (writeValue.get() ? "on" : "off"); - - this.httpBridge.get(url).whenComplete((t, e) -> { - this._setSlaveCommunicationFailed(e != null); - if (e == null) { - this.logDebug(this.log, "Executed write successfully for URL: " + url); - } else { - this.logError(this.log, "Failed to execute write for URL: " + url + "; Error: " + e.getMessage()); - } - }); - } - /** * Calculate the Energy values from ActivePower. */ @@ -236,4 +208,4 @@ public Timedata getTimedata() { return this.timedata; } -} +} \ No newline at end of file diff --git a/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shellypro3/Config.java b/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shellypro3/Config.java index 92064d4d596..a8a268c2551 100644 --- a/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shellypro3/Config.java +++ b/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shellypro3/Config.java @@ -19,6 +19,6 @@ @AttributeDefinition(name = "IP-Address", description = "The IP address of the Shelly device.") String ip(); - - String webconsole_configurationFactory_nameHint() default "IO Shelly Pro 3 [{id}]"; + + String webconsole_configurationFactory_nameHint() default "IO Shelly Pro 3 [{id}]"; } diff --git a/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shellypro3/IoShellyPro3Impl.java b/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shellypro3/IoShellyPro3Impl.java index 888ceb58da3..5de68423610 100644 --- a/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shellypro3/IoShellyPro3Impl.java +++ b/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shellypro3/IoShellyPro3Impl.java @@ -4,8 +4,6 @@ import static io.openems.common.utils.JsonUtils.getAsJsonObject; import static io.openems.edge.io.shelly.common.Utils.generateDebugLog; -import java.util.Objects; - import org.osgi.service.component.ComponentContext; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; @@ -30,6 +28,7 @@ import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.event.EdgeEventConstants; import io.openems.edge.io.api.DigitalOutput; +import io.openems.edge.io.shelly.common.Utils; @Designate(ocd = Config.class, factory = true) @Component(// @@ -106,7 +105,7 @@ public void handleEvent(Event event) { switch (event.getTopic()) { case EdgeEventConstants.TOPIC_CYCLE_EXECUTE_WRITE // - -> this.eventExecuteWrite(); + -> this.executeWrite(); } } @@ -137,31 +136,10 @@ private void processHttpResult(HttpResponse result, HttpError error } } - /** - * Execute on Cycle Event "Execute Write". - */ - private void eventExecuteWrite() { + private void executeWrite() { for (int i = 0; i < this.digitalOutputChannels.length; i++) { - this.executeWrite(this.digitalOutputChannels[i], i); - } - } - - private void executeWrite(BooleanWriteChannel channel, int index) { - var readValue = channel.value().get(); - var writeValue = channel.getNextWriteValueAndReset(); - if (writeValue.isEmpty()) { - return; - } - if (Objects.equals(readValue, writeValue.get())) { - return; + Utils.executeWrite(this.digitalOutputChannels[i], this.baseUrl, this.httpBridge, i); } - final String url = this.baseUrl + "/relay/" + index + "?turn=" + (writeValue.get() ? "on" : "off"); - this.httpBridge.get(url).whenComplete((t, e) -> { - if (e != null) { - this.logError(this.log, "HTTP request failed: " + e.getMessage()); - this._setSlaveCommunicationFailed(true); - } - }); } -} +} \ No newline at end of file diff --git a/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shellypro3em/Config.java b/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shellypro3em/Config.java index b594f9b1603..0f8469ad98f 100644 --- a/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shellypro3em/Config.java +++ b/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shellypro3em/Config.java @@ -3,28 +3,27 @@ import org.osgi.service.metatype.annotations.AttributeDefinition; import org.osgi.service.metatype.annotations.ObjectClassDefinition; -import io.openems.edge.meter.api.MeterType; +import io.openems.common.types.MeterType; -@ObjectClassDefinition( - name = "IO Shelly Pro 3EM", // +@ObjectClassDefinition(name = "IO Shelly Pro 3EM", // description = "Implements the Shelly Pro 3EM Energy Meter.") @interface Config { @AttributeDefinition(name = "Component-ID", description = "Unique ID of this Component") - String id() default "meter0"; + String id() default "meter0"; @AttributeDefinition(name = "Alias", description = "Human-readable name of this Component; defaults to Component-ID") - String alias() default ""; + String alias() default ""; @AttributeDefinition(name = "Is enabled?", description = "Is this Component enabled?") - boolean enabled() default true; + boolean enabled() default true; - @AttributeDefinition(name = "Meter-Type", description = "Grid, Production (=default), Consumption") - MeterType type() default MeterType.GRID; + @AttributeDefinition(name = "Meter-Type", description = "Grid, Production (=default), Consumption") + MeterType type() default MeterType.GRID; @AttributeDefinition(name = "IP-Address", description = "The IP address of the Shelly device.") String ip(); - String webconsole_configurationFactory_nameHint() default "IO Shelly Pro 3EM [{id}]"; + String webconsole_configurationFactory_nameHint() default "IO Shelly Pro 3EM [{id}]"; } \ No newline at end of file diff --git a/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shellypro3em/IoShellyPro3Em.java b/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shellypro3em/IoShellyPro3Em.java index c5899305afe..e545e5bec63 100644 --- a/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shellypro3em/IoShellyPro3Em.java +++ b/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shellypro3em/IoShellyPro3Em.java @@ -3,8 +3,8 @@ import io.openems.common.channel.Level; import io.openems.edge.common.channel.Doc; import io.openems.edge.common.channel.StateChannel; -import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.channel.value.Value; +import io.openems.edge.common.component.OpenemsComponent; public interface IoShellyPro3Em extends OpenemsComponent { diff --git a/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shellypro3em/IoShellyPro3EmImpl.java b/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shellypro3em/IoShellyPro3EmImpl.java index 15db1dac66f..d6f2957eb5a 100644 --- a/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shellypro3em/IoShellyPro3EmImpl.java +++ b/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shellypro3em/IoShellyPro3EmImpl.java @@ -25,6 +25,7 @@ import com.google.gson.JsonElement; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.types.MeterType; import io.openems.edge.bridge.http.api.BridgeHttp; import io.openems.edge.bridge.http.api.BridgeHttpFactory; import io.openems.edge.bridge.http.api.HttpError; @@ -33,7 +34,6 @@ import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.event.EdgeEventConstants; import io.openems.edge.meter.api.ElectricityMeter; -import io.openems.edge.meter.api.MeterType; import io.openems.edge.timedata.api.Timedata; import io.openems.edge.timedata.api.TimedataProvider; import io.openems.edge.timedata.api.utils.CalculateEnergyFromPower; @@ -76,6 +76,8 @@ public IoShellyPro3EmImpl() { ); ElectricityMeter.calculateSumActivePowerFromPhases(this); + ElectricityMeter.calculateSumCurrentFromPhases(this); + ElectricityMeter.calculateAverageVoltageFromPhases(this); } @Activate diff --git a/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shelly25/IoShelly25ImplTest.java b/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shelly25/IoShelly25ImplTest.java index 06b3f7975cf..42f6e4b355a 100644 --- a/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shelly25/IoShelly25ImplTest.java +++ b/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shelly25/IoShelly25ImplTest.java @@ -1,23 +1,75 @@ package io.openems.edge.io.shelly.shelly25; +import static org.junit.Assert.assertEquals; + import org.junit.Test; -import io.openems.edge.bridge.http.dummy.DummyBridgeHttpFactory; +import io.openems.edge.bridge.http.api.HttpError; +import io.openems.edge.bridge.http.api.HttpResponse; +import io.openems.edge.bridge.http.dummy.DummyBridgeHttpBundle; +import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; public class IoShelly25ImplTest { - private static final String COMPONENT_ID = "io0"; - @Test public void test() throws Exception { - new ComponentTest(new IoShelly25Impl()) // - .addReference("httpBridgeFactory", DummyBridgeHttpFactory.ofDummyBridge()) // + final var sut = new IoShelly25Impl(); + final var httpTestBundle = new DummyBridgeHttpBundle(); + new ComponentTest(sut) // + .addReference("httpBridgeFactory", httpTestBundle.factory()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("io0") // .setIp("127.0.0.1") // .build()) // - ; - } + // Test case for a successful JSON response + .next(new TestCase("Successful read response") // + .onBeforeProcessImage(() -> { + httpTestBundle.forceNextSuccessfulResult(HttpResponse.ok(""" + { + "relays": [ + { + "ison": true, + "overtemperature": false, + "overpower": false + }, + { + "ison": false, + "overtemperature": true, + "overpower": true + } + ] + } + """)); + httpTestBundle.triggerNextCycle(); + }) // + .onAfterProcessImage(() -> assertEquals("x -", sut.debugLog())) // + + .output(IoShelly25.ChannelId.RELAY_1, null) // expecting WriteValue + .output(IoShelly25.ChannelId.RELAY_1_OVERPOWER, false) // + .output(IoShelly25.ChannelId.RELAY_1_OVERTEMP, false) // + .output(IoShelly25.ChannelId.RELAY_2, null) // expecting WriteValue + .output(IoShelly25.ChannelId.RELAY_2_OVERPOWER, true) // + .output(IoShelly25.ChannelId.RELAY_2_OVERTEMP, true) // + .output(IoShelly25.ChannelId.SLAVE_COMMUNICATION_FAILED, false)) // + + // Test case for an invalid JSON response + .next(new TestCase("Invalid read response") // + .onBeforeProcessImage(() -> { // + httpTestBundle.forceNextFailedResult(HttpError.ResponseError.notFound()); + httpTestBundle.triggerNextCycle(); + }) // + .onAfterProcessImage(() -> assertEquals("? ?", sut.debugLog())) // + + .output(IoShelly25.ChannelId.RELAY_1, null) // expecting WriteValue + .output(IoShelly25.ChannelId.RELAY_1_OVERPOWER, null) // + .output(IoShelly25.ChannelId.RELAY_1_OVERTEMP, null) // + .output(IoShelly25.ChannelId.RELAY_2, null) // expecting WriteValue + .output(IoShelly25.ChannelId.RELAY_2_OVERPOWER, null) // + .output(IoShelly25.ChannelId.RELAY_2_OVERTEMP, null) // + .output(IoShelly25.ChannelId.SLAVE_COMMUNICATION_FAILED, true)) // + + .deactivate();// + } } \ No newline at end of file diff --git a/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shelly3em/IoShelly3EmImplTest.java b/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shelly3em/IoShelly3EmImplTest.java index 1c06c65f443..6d3671cf46c 100644 --- a/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shelly3em/IoShelly3EmImplTest.java +++ b/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shelly3em/IoShelly3EmImplTest.java @@ -1,25 +1,145 @@ package io.openems.edge.io.shelly.shelly3em; +import static io.openems.common.types.MeterType.CONSUMPTION_METERED; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + import org.junit.Test; -import io.openems.edge.bridge.http.dummy.DummyBridgeHttpFactory; +import io.openems.edge.bridge.http.api.HttpError; +import io.openems.edge.bridge.http.api.HttpResponse; +import io.openems.edge.bridge.http.dummy.DummyBridgeHttpBundle; +import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; -import io.openems.edge.meter.api.MeterType; +import io.openems.edge.meter.api.ElectricityMeter; public class IoShelly3EmImplTest { - private static final String COMPONENT_ID = "io0"; - @Test public void test() throws Exception { - new ComponentTest(new IoShelly3EmImpl()) // - .addReference("httpBridgeFactory", DummyBridgeHttpFactory.ofDummyBridge()) // + final var sut = new IoShelly3EmImpl(); + final var httpTestBundle = new DummyBridgeHttpBundle(); + new ComponentTest(sut) // + .addReference("httpBridgeFactory", httpTestBundle.factory()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("io0") // .setIp("127.0.0.1") // - .setType(MeterType.CONSUMPTION_METERED) // + .setType(CONSUMPTION_METERED) // .build()) // - ; - } + .next(new TestCase("Successful read response") // + .onBeforeProcessImage(() -> { + httpTestBundle.forceNextSuccessfulResult(HttpResponse.ok(""" + { + "relays": [ + { + "ison": true, + "overpower": false + } + ], + "emeters": [ + { + "power": 8.52, + "current": 1, + "voltage": 230, + "is_valid": true + }, + { + "power": 31.39, + "current": 2, + "voltage": 231, + "is_valid": true + }, + { + "power": 58.75, + "current": 3, + "voltage": 232, + "is_valid": false + } + ], + "total_power": 35.88, + "emeter_n": { + "current": 0, + "ixsum": 0.7, + "mismatch": false, + "is_valid": false + }, + "update": { + "status": "idle", + "has_update": false + } + } + """)); + httpTestBundle.triggerNextCycle(); + }) // + .onAfterProcessImage(() -> assertEquals("x|99 W", sut.debugLog())) // + + .output(ElectricityMeter.ChannelId.ACTIVE_POWER, 99) // + .output(ElectricityMeter.ChannelId.ACTIVE_POWER_L1, 9) // + .output(ElectricityMeter.ChannelId.ACTIVE_POWER_L2, 31) // + .output(ElectricityMeter.ChannelId.ACTIVE_POWER_L3, 59) // + .output(ElectricityMeter.ChannelId.VOLTAGE, 231000) // + .output(ElectricityMeter.ChannelId.VOLTAGE_L1, 230000) // + .output(ElectricityMeter.ChannelId.VOLTAGE_L2, 231000) // + .output(ElectricityMeter.ChannelId.VOLTAGE_L3, 232000) // + .output(ElectricityMeter.ChannelId.CURRENT, 6000) // + .output(ElectricityMeter.ChannelId.CURRENT_L1, 1000) // + .output(ElectricityMeter.ChannelId.CURRENT_L2, 2000) // + .output(ElectricityMeter.ChannelId.CURRENT_L3, 3000) // + .output(ElectricityMeter.ChannelId.ACTIVE_CONSUMPTION_ENERGY, null) // + .output(ElectricityMeter.ChannelId.ACTIVE_PRODUCTION_ENERGY, null) // + .output(IoShelly3Em.ChannelId.RELAY_OVERPOWER_EXCEPTION, false) // + .output(IoShelly3Em.ChannelId.HAS_UPDATE, false) // + .output(IoShelly3Em.ChannelId.EMETER1_EXCEPTION, false) // + .output(IoShelly3Em.ChannelId.EMETER2_EXCEPTION, false) // + .output(IoShelly3Em.ChannelId.EMETER3_EXCEPTION, true) // + .output(IoShelly3Em.ChannelId.SLAVE_COMMUNICATION_FAILED, false)) // + + .next(new TestCase("Invalid read response") // + .onBeforeProcessImage(() -> { + httpTestBundle.forceNextFailedResult(HttpError.ResponseError.notFound()); + httpTestBundle.triggerNextCycle(); + }) // + .onAfterProcessImage(() -> assertEquals("?|UNDEFINED", sut.debugLog())) + + .output(ElectricityMeter.ChannelId.ACTIVE_POWER, null) // + .output(ElectricityMeter.ChannelId.ACTIVE_POWER_L1, null) // + .output(ElectricityMeter.ChannelId.ACTIVE_POWER_L2, null) // + .output(ElectricityMeter.ChannelId.ACTIVE_POWER_L3, null) // + .output(ElectricityMeter.ChannelId.VOLTAGE, null) // + .output(ElectricityMeter.ChannelId.VOLTAGE_L1, null) // + .output(ElectricityMeter.ChannelId.VOLTAGE_L2, null) // + .output(ElectricityMeter.ChannelId.VOLTAGE_L3, null) // + .output(ElectricityMeter.ChannelId.CURRENT, null) // + .output(ElectricityMeter.ChannelId.CURRENT_L1, null) // + .output(ElectricityMeter.ChannelId.CURRENT_L2, null) // + .output(ElectricityMeter.ChannelId.CURRENT_L3, null) // + .output(ElectricityMeter.ChannelId.ACTIVE_CONSUMPTION_ENERGY, null) // + .output(ElectricityMeter.ChannelId.ACTIVE_PRODUCTION_ENERGY, null) // + .output(IoShelly3Em.ChannelId.RELAY_OVERPOWER_EXCEPTION, false) // + .output(IoShelly3Em.ChannelId.HAS_UPDATE, false) // + .output(IoShelly3Em.ChannelId.EMETER1_EXCEPTION, false) // + .output(IoShelly3Em.ChannelId.EMETER2_EXCEPTION, false) // + .output(IoShelly3Em.ChannelId.EMETER3_EXCEPTION, true) // + .output(IoShelly3Em.ChannelId.SLAVE_COMMUNICATION_FAILED, true)) // + + // Test case for writing to relay + .next(new TestCase("Write") // + .onBeforeControllersCallbacks(() -> { + sut.setRelay(true); + }) // + .also(testCase -> { + final var relayTurnedOn = httpTestBundle + .expect("http://127.0.0.1/rpc/Switch.Set?id=0&on=true").toBeCalled(); + + testCase.onBeforeControllersCallbacks(() -> { + httpTestBundle.triggerNextCycle(); + }); + testCase.onAfterWriteCallbacks(() -> { + assertTrue("Failed to turn on relay", relayTurnedOn.get()); + }); + })) // + + .deactivate(); + } } diff --git a/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shelly3em/MyConfig.java b/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shelly3em/MyConfig.java index 73b5ec2dc44..aa1e32a5ef9 100644 --- a/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shelly3em/MyConfig.java +++ b/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shelly3em/MyConfig.java @@ -1,7 +1,7 @@ package io.openems.edge.io.shelly.shelly3em; import io.openems.common.test.AbstractComponentConfig; -import io.openems.edge.meter.api.MeterType; +import io.openems.common.types.MeterType; @SuppressWarnings("all") public class MyConfig extends AbstractComponentConfig implements Config { diff --git a/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellyplug/IoShellyPlugImplTest.java b/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellyplug/IoShellyPlugImplTest.java index efef443c6b0..6255b5fb688 100644 --- a/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellyplug/IoShellyPlugImplTest.java +++ b/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellyplug/IoShellyPlugImplTest.java @@ -1,26 +1,112 @@ package io.openems.edge.io.shelly.shellyplug; +import static io.openems.common.types.MeterType.PRODUCTION; +import static io.openems.edge.meter.api.SinglePhase.L1; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + import org.junit.Test; -import io.openems.edge.bridge.http.dummy.DummyBridgeHttpFactory; +import io.openems.edge.bridge.http.api.HttpError; +import io.openems.edge.bridge.http.api.HttpResponse; +import io.openems.edge.bridge.http.dummy.DummyBridgeHttpBundle; +import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; -import io.openems.edge.meter.api.MeterType; -import io.openems.edge.meter.api.SinglePhase; +import io.openems.edge.meter.api.ElectricityMeter; public class IoShellyPlugImplTest { - private static final String COMPONENT_ID = "io0"; - @Test public void test() throws Exception { - new ComponentTest(new IoShellyPlugImpl()) // - .addReference("httpBridgeFactory", DummyBridgeHttpFactory.ofDummyBridge()) // + final var sut = new IoShellyPlugImpl(); + final var httpTestBundle = new DummyBridgeHttpBundle(); + new ComponentTest(sut) // + .addReference("httpBridgeFactory", httpTestBundle.factory()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // - .setPhase(SinglePhase.L1) // + .setId("io0") // + .setPhase(L1) // .setIp("127.0.0.1") // - .setType(MeterType.PRODUCTION) // + .setType(PRODUCTION) // .build()) // - ; + + .next(new TestCase("Successful read response") // + .onBeforeProcessImage(() -> { + httpTestBundle.forceNextSuccessfulResult(HttpResponse.ok(""" + { + "relays": [ + { + "ison": true + } + ], + "meters": [ + { + "power": 789.1, + "total": 72000 + } + ] + } + """)); + httpTestBundle.triggerNextCycle(); + }) // + .onAfterProcessImage(() -> assertEquals("x|789 W", sut.debugLog())) + + .output(ElectricityMeter.ChannelId.ACTIVE_POWER, 789) // + .output(ElectricityMeter.ChannelId.ACTIVE_POWER_L1, 789) // + .output(ElectricityMeter.ChannelId.ACTIVE_POWER_L2, null) // + .output(ElectricityMeter.ChannelId.ACTIVE_POWER_L3, null) // + .output(ElectricityMeter.ChannelId.VOLTAGE, null) // + .output(ElectricityMeter.ChannelId.VOLTAGE_L1, null) // + .output(ElectricityMeter.ChannelId.VOLTAGE_L2, null) // + .output(ElectricityMeter.ChannelId.VOLTAGE_L3, null) // + .output(ElectricityMeter.ChannelId.CURRENT, null) // + .output(ElectricityMeter.ChannelId.CURRENT_L1, null) // + .output(ElectricityMeter.ChannelId.CURRENT_L2, null) // + .output(ElectricityMeter.ChannelId.CURRENT_L3, null) // + .output(ElectricityMeter.ChannelId.ACTIVE_CONSUMPTION_ENERGY, null) // + .output(ElectricityMeter.ChannelId.ACTIVE_PRODUCTION_ENERGY, 1200L) // + .output(IoShellyPlug.ChannelId.RELAY, null) // + .output(IoShellyPlug.ChannelId.SLAVE_COMMUNICATION_FAILED, false)) // + + .next(new TestCase("Invalid read response") // + .onBeforeProcessImage(() -> { + httpTestBundle.forceNextFailedResult(HttpError.ResponseError.notFound()); + httpTestBundle.triggerNextCycle(); + }) // + .onAfterProcessImage(() -> assertEquals("?|UNDEFINED", sut.debugLog())) + + .output(ElectricityMeter.ChannelId.ACTIVE_POWER, null) // + .output(ElectricityMeter.ChannelId.ACTIVE_POWER_L1, null) // + .output(ElectricityMeter.ChannelId.ACTIVE_POWER_L2, null) // + .output(ElectricityMeter.ChannelId.ACTIVE_POWER_L3, null) // + .output(ElectricityMeter.ChannelId.VOLTAGE, null) // + .output(ElectricityMeter.ChannelId.VOLTAGE_L1, null) // + .output(ElectricityMeter.ChannelId.VOLTAGE_L2, null) // + .output(ElectricityMeter.ChannelId.VOLTAGE_L3, null) // + .output(ElectricityMeter.ChannelId.CURRENT, null) // + .output(ElectricityMeter.ChannelId.CURRENT_L1, null) // + .output(ElectricityMeter.ChannelId.CURRENT_L2, null) // + .output(ElectricityMeter.ChannelId.CURRENT_L3, null) // + .output(ElectricityMeter.ChannelId.ACTIVE_CONSUMPTION_ENERGY, null) // + .output(ElectricityMeter.ChannelId.ACTIVE_PRODUCTION_ENERGY, 1200L) // + .output(IoShellyPlug.ChannelId.RELAY, null) // + .output(IoShellyPlug.ChannelId.SLAVE_COMMUNICATION_FAILED, true)) // + + // Test case for writing to relay + .next(new TestCase("Write") // + .onBeforeControllersCallbacks(() -> { + sut.setRelay(true); + }) // + .also(testCase -> { + final var relayTurnedOn = httpTestBundle.expect("http://127.0.0.1/relay/0?turn=on") + .toBeCalled(); + testCase.onBeforeControllersCallbacks(() -> { + httpTestBundle.triggerNextCycle(); + }); + testCase.onAfterWriteCallbacks(() -> { + assertTrue("Failed to turn on relay", relayTurnedOn.get()); + }); + })) // + + .deactivate();// } } diff --git a/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellyplug/MyConfig.java b/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellyplug/MyConfig.java index 3fd0443b386..995335e84d6 100644 --- a/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellyplug/MyConfig.java +++ b/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellyplug/MyConfig.java @@ -1,7 +1,7 @@ package io.openems.edge.io.shelly.shellyplug; import io.openems.common.test.AbstractComponentConfig; -import io.openems.edge.meter.api.MeterType; +import io.openems.common.types.MeterType; import io.openems.edge.meter.api.SinglePhase; @SuppressWarnings("all") diff --git a/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellyplus1pm/IoShellyPlus1PmImplTest.java b/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellyplus1pm/IoShellyPlus1PmImplTest.java index e3443f8d6b1..7ccd5d5a10a 100644 --- a/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellyplus1pm/IoShellyPlus1PmImplTest.java +++ b/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellyplus1pm/IoShellyPlus1PmImplTest.java @@ -1,54 +1,41 @@ package io.openems.edge.io.shelly.shellyplus1pm; -import static io.openems.edge.meter.api.MeterType.CONSUMPTION_METERED; +import static io.openems.common.types.MeterType.CONSUMPTION_METERED; import static io.openems.edge.meter.api.SinglePhase.L1; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import org.junit.Test; -import io.openems.common.types.ChannelAddress; import io.openems.edge.bridge.http.api.HttpError; import io.openems.edge.bridge.http.api.HttpResponse; import io.openems.edge.bridge.http.dummy.DummyBridgeHttpBundle; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; +import io.openems.edge.meter.api.ElectricityMeter; import io.openems.edge.timedata.test.DummyTimedata; public class IoShellyPlus1PmImplTest { - private static final String COMPONENT_ID = "io0"; - - private static final ChannelAddress ACTIVE_POWER = new ChannelAddress(COMPONENT_ID, "ActivePower"); - private static final ChannelAddress ACTIVE_POWER_L1 = new ChannelAddress(COMPONENT_ID, "ActivePowerL1"); - private static final ChannelAddress ACTIVE_POWER_L2 = new ChannelAddress(COMPONENT_ID, "ActivePowerL2"); - private static final ChannelAddress CURRENT = new ChannelAddress(COMPONENT_ID, "Current"); - private static final ChannelAddress VOLTAGE = new ChannelAddress(COMPONENT_ID, "Voltage"); - private static final ChannelAddress PRODUCTION_ENERGY = new ChannelAddress(COMPONENT_ID, "ActiveProductionEnergy"); - private static final ChannelAddress CONSUMPTION_ENERGY = new ChannelAddress(COMPONENT_ID, - "ActiveConsumptionEnergy"); - @Test public void test() throws Exception { - final var httpTestBundle = new DummyBridgeHttpBundle(); final var sut = new IoShellyPlus1PmImpl(); - + final var httpTestBundle = new DummyBridgeHttpBundle(); new ComponentTest(sut) // .addReference("httpBridgeFactory", httpTestBundle.factory()) // .addReference("timedata", new DummyTimedata("timedata0")) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("io0") // .setIp("127.0.0.1") // .setType(CONSUMPTION_METERED) // .setPhase(L1) // .build()) // .next(new TestCase("Successful read response") // - .onBeforeControllersCallbacks(() -> { + .onBeforeProcessImage(() -> { httpTestBundle.forceNextSuccessfulResult(HttpResponse.ok(""" { "ble":{ - }, "cloud":{ "connected":true @@ -96,7 +83,6 @@ public void test() throws Exception { "schedule_rev":0, "webhook_rev":0, "available_updates":{ - }, "reset_reason":3 }, @@ -114,46 +100,61 @@ public void test() throws Exception { """)); httpTestBundle.triggerNextCycle(); }) // - .output(ACTIVE_POWER, 123) // - .output(ACTIVE_POWER_L1, 123) // - .output(ACTIVE_POWER_L2, null) // - .output(CURRENT, 500) // - .output(VOLTAGE, 231300)) // + .onAfterProcessImage(() -> assertEquals("-|123 W", sut.debugLog())) + + .output(ElectricityMeter.ChannelId.ACTIVE_POWER, 123) // + .output(ElectricityMeter.ChannelId.ACTIVE_POWER_L1, 123) // + .output(ElectricityMeter.ChannelId.ACTIVE_POWER_L2, null) // + .output(ElectricityMeter.ChannelId.ACTIVE_POWER_L3, null) // + .output(ElectricityMeter.ChannelId.VOLTAGE, 231300) // + .output(ElectricityMeter.ChannelId.VOLTAGE_L1, 231300) // + .output(ElectricityMeter.ChannelId.VOLTAGE_L2, null) // + .output(ElectricityMeter.ChannelId.VOLTAGE_L3, null) // + .output(ElectricityMeter.ChannelId.CURRENT, 500) // + .output(ElectricityMeter.ChannelId.CURRENT_L1, 500) // + .output(ElectricityMeter.ChannelId.CURRENT_L2, null) // + .output(ElectricityMeter.ChannelId.CURRENT_L3, null) // + .output(ElectricityMeter.ChannelId.ACTIVE_CONSUMPTION_ENERGY, null) // + .output(ElectricityMeter.ChannelId.ACTIVE_PRODUCTION_ENERGY, null) // + .output(IoShellyPlus1Pm.ChannelId.RELAY, null) // + .output(IoShellyPlus1Pm.ChannelId.SLAVE_COMMUNICATION_FAILED, false)) // .next(new TestCase("Invalid read response") // - .onBeforeControllersCallbacks(() -> assertEquals("Off|123 W", sut.debugLog())) - - .onBeforeControllersCallbacks(() -> { + .onBeforeProcessImage(() -> { httpTestBundle.forceNextFailedResult(HttpError.ResponseError.notFound()); httpTestBundle.triggerNextCycle(); }) // - .output(ACTIVE_POWER, null) // - .output(ACTIVE_POWER_L1, null) // - .output(ACTIVE_POWER_L2, null) // - .output(CURRENT, null) // - .output(VOLTAGE, null) // - - .output(PRODUCTION_ENERGY, 0L) // - .output(CONSUMPTION_ENERGY, 0L)) // + .onAfterProcessImage(() -> assertEquals("?|UNDEFINED", sut.debugLog())) + + .output(ElectricityMeter.ChannelId.ACTIVE_POWER, null) // + .output(ElectricityMeter.ChannelId.ACTIVE_POWER_L1, null) // + .output(ElectricityMeter.ChannelId.ACTIVE_POWER_L2, null) // + .output(ElectricityMeter.ChannelId.ACTIVE_POWER_L3, null) // + .output(ElectricityMeter.ChannelId.VOLTAGE, null) // + .output(ElectricityMeter.ChannelId.VOLTAGE_L1, null) // + .output(ElectricityMeter.ChannelId.VOLTAGE_L2, null) // + .output(ElectricityMeter.ChannelId.VOLTAGE_L3, null) // + .output(ElectricityMeter.ChannelId.CURRENT, null) // + .output(ElectricityMeter.ChannelId.CURRENT_L1, null) // + .output(ElectricityMeter.ChannelId.CURRENT_L2, null) // + .output(ElectricityMeter.ChannelId.CURRENT_L3, null) // + .output(ElectricityMeter.ChannelId.ACTIVE_CONSUMPTION_ENERGY, 0L) // + .output(ElectricityMeter.ChannelId.ACTIVE_PRODUCTION_ENERGY, 0L) // + .output(IoShellyPlus1Pm.ChannelId.RELAY, null) // + .output(IoShellyPlus1Pm.ChannelId.SLAVE_COMMUNICATION_FAILED, true)) // .next(new TestCase("Write") // - .onBeforeControllersCallbacks(() -> assertEquals("Unknown|UNDEFINED", sut.debugLog())) - .onBeforeControllersCallbacks(() -> { - sut.setRelay(true); - }) // + .onBeforeControllersCallbacks(() -> sut.setRelay(true)) // .also(testCase -> { final var relayTurnedOn = httpTestBundle.expect("http://127.0.0.1/relay/0?turn=on") .toBeCalled(); - testCase.onBeforeControllersCallbacks(() -> { - httpTestBundle.triggerNextCycle(); - }); - testCase.onAfterWriteCallbacks(() -> { - assertTrue("Failed to turn on relay", relayTurnedOn.get()); - }); + testCase.onBeforeControllersCallbacks(() -> httpTestBundle.triggerNextCycle()); + testCase.onAfterWriteCallbacks( + () -> assertTrue("Failed to turn on relay", relayTurnedOn.get())); })) // .deactivate(); } -} +} \ No newline at end of file diff --git a/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellyplus1pm/MyConfig.java b/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellyplus1pm/MyConfig.java index e653d439747..cd8ab3621bc 100644 --- a/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellyplus1pm/MyConfig.java +++ b/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellyplus1pm/MyConfig.java @@ -1,7 +1,7 @@ package io.openems.edge.io.shelly.shellyplus1pm; import io.openems.common.test.AbstractComponentConfig; -import io.openems.edge.meter.api.MeterType; +import io.openems.common.types.MeterType; import io.openems.edge.meter.api.SinglePhase; @SuppressWarnings("all") diff --git a/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellyplusplugs/IoShellyPlugImplTest.java b/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellyplusplugs/IoShellyPlugImplTest.java deleted file mode 100644 index c91c4c52720..00000000000 --- a/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellyplusplugs/IoShellyPlugImplTest.java +++ /dev/null @@ -1,107 +0,0 @@ -package io.openems.edge.io.shelly.shellyplusplugs; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -import org.junit.Test; - -import io.openems.common.types.ChannelAddress; -import io.openems.edge.bridge.http.api.HttpError; -import io.openems.edge.bridge.http.api.HttpResponse; -import io.openems.edge.bridge.http.dummy.DummyBridgeHttpBundle; -import io.openems.edge.common.test.AbstractComponentTest.TestCase; -import io.openems.edge.common.test.ComponentTest; -import io.openems.edge.meter.api.MeterType; -import io.openems.edge.meter.api.SinglePhase; -import io.openems.edge.timedata.test.DummyTimedata; - -public class IoShellyPlugImplTest { - - private static final String COMPONENT_ID = "io0"; - - private static final ChannelAddress ACTIVE_POWER = new ChannelAddress(COMPONENT_ID, "ActivePower"); - private static final ChannelAddress ACTIVE_POWER_L1 = new ChannelAddress(COMPONENT_ID, "ActivePowerL1"); - private static final ChannelAddress ACTIVE_POWER_L2 = new ChannelAddress(COMPONENT_ID, "ActivePowerL2"); - private static final ChannelAddress CURRENT = new ChannelAddress(COMPONENT_ID, "Current"); - private static final ChannelAddress VOLTAGE = new ChannelAddress(COMPONENT_ID, "Voltage"); - private static final ChannelAddress PRODUCTION_ENERGY = new ChannelAddress(COMPONENT_ID, "ActiveProductionEnergy"); - private static final ChannelAddress CONSUMPTION_ENERGY = new ChannelAddress(COMPONENT_ID, - "ActiveConsumptionEnergy"); - - @Test - public void test() throws Exception { - final var httpTestBundle = new DummyBridgeHttpBundle(); - - final var sut = new IoShellyPlusPlugsImpl(); - new ComponentTest(sut) // - .addReference("httpBridgeFactory", httpTestBundle.factory()) // - .addReference("timedata", new DummyTimedata("timedata0")) // - .activate(MyConfig.create() // - .setId(COMPONENT_ID) // - .setPhase(SinglePhase.L1) // - .setIp("127.0.0.1") // - .setType(MeterType.PRODUCTION) // - .build()) // - - .next(new TestCase("Successful read response") // - .onBeforeControllersCallbacks(() -> { - httpTestBundle.forceNextSuccessfulResult(HttpResponse.ok(""" - { - "sys": { - "available_updates": { - "foo": "bar" - } - }, - "switch:0": { - "current": 1.234, - "voltage": 231.5, - "output": false, - "apower": 789.1 - } - } - """)); - httpTestBundle.triggerNextCycle(); - }) // - .output(ACTIVE_POWER, 789) // - .output(ACTIVE_POWER_L1, 789) // - .output(ACTIVE_POWER_L2, null) // - .output(CURRENT, 1234) // - .output(VOLTAGE, 231500)) // - - .next(new TestCase("Invalid read response") // - .onBeforeControllersCallbacks(() -> assertEquals("Off|789 W", sut.debugLog())) - - .onBeforeControllersCallbacks(() -> { - httpTestBundle.forceNextFailedResult(HttpError.ResponseError.notFound()); - httpTestBundle.triggerNextCycle(); - }) // - .output(ACTIVE_POWER, null) // - .output(ACTIVE_POWER_L1, null) // - .output(ACTIVE_POWER_L2, null) // - .output(CURRENT, null) // - .output(VOLTAGE, null) // - - .output(PRODUCTION_ENERGY, 0L) // - .output(CONSUMPTION_ENERGY, 0L)) // - - .next(new TestCase("Write") // - .onBeforeControllersCallbacks(() -> assertEquals("Unknown|UNDEFINED", sut.debugLog())) - .onBeforeControllersCallbacks(() -> { - sut.setRelay(true); - }) // - .also(testCase -> { - final var relayTurnedOn = httpTestBundle.expect("http://127.0.0.1/relay/0?turn=on") - .toBeCalled(); - - testCase.onBeforeControllersCallbacks(() -> { - httpTestBundle.triggerNextCycle(); - }); - testCase.onAfterWriteCallbacks(() -> { - assertTrue("Failed to turn on relay", relayTurnedOn.get()); - }); - })) // - - .deactivate(); - } - -} diff --git a/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellyplusplugs/IoShellyPlusPlugImplTest.java b/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellyplusplugs/IoShellyPlusPlugImplTest.java new file mode 100644 index 00000000000..144244905c6 --- /dev/null +++ b/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellyplusplugs/IoShellyPlusPlugImplTest.java @@ -0,0 +1,112 @@ +package io.openems.edge.io.shelly.shellyplusplugs; + +import static io.openems.common.types.MeterType.PRODUCTION; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import io.openems.edge.bridge.http.api.HttpError; +import io.openems.edge.bridge.http.api.HttpResponse; +import io.openems.edge.bridge.http.dummy.DummyBridgeHttpBundle; +import io.openems.edge.common.test.AbstractComponentTest.TestCase; +import io.openems.edge.common.test.ComponentTest; +import io.openems.edge.meter.api.ElectricityMeter; +import io.openems.edge.meter.api.SinglePhase; +import io.openems.edge.timedata.test.DummyTimedata; + +public class IoShellyPlusPlugImplTest { + + @Test + public void test() throws Exception { + final var httpTestBundle = new DummyBridgeHttpBundle(); + final var sut = new IoShellyPlusPlugsImpl(); + new ComponentTest(sut) // + .addReference("httpBridgeFactory", httpTestBundle.factory()) // + .addReference("timedata", new DummyTimedata("timedata0")) // + .activate(MyConfig.create() // + .setId("io0") // + .setPhase(SinglePhase.L1) // + .setIp("127.0.0.1") // + .setType(PRODUCTION) // + .build()) // + + .next(new TestCase("Successful read response") // + .onBeforeProcessImage(() -> { + httpTestBundle.forceNextSuccessfulResult(HttpResponse.ok(""" + { + "sys": { + "available_updates": { + "foo": "bar" + } + }, + "switch:0": { + "current": 1.234, + "voltage": 231.5, + "output": false, + "apower": 789.1 + } + } + """)); + httpTestBundle.triggerNextCycle(); + }) // + .onAfterProcessImage(() -> assertEquals("-|789 W", sut.debugLog())) + + .output(ElectricityMeter.ChannelId.ACTIVE_POWER, 789) // + .output(ElectricityMeter.ChannelId.ACTIVE_POWER_L1, 789) // + .output(ElectricityMeter.ChannelId.ACTIVE_POWER_L2, null) // + .output(ElectricityMeter.ChannelId.ACTIVE_POWER_L3, null) // + .output(ElectricityMeter.ChannelId.VOLTAGE, 231500) // + .output(ElectricityMeter.ChannelId.VOLTAGE_L1, 231500) // + .output(ElectricityMeter.ChannelId.VOLTAGE_L2, null) // + .output(ElectricityMeter.ChannelId.VOLTAGE_L3, null) // + .output(ElectricityMeter.ChannelId.CURRENT, 1234) // + .output(ElectricityMeter.ChannelId.CURRENT_L1, 1234) // + .output(ElectricityMeter.ChannelId.CURRENT_L2, null) // + .output(ElectricityMeter.ChannelId.CURRENT_L3, null) // + .output(ElectricityMeter.ChannelId.ACTIVE_CONSUMPTION_ENERGY, null) // + .output(ElectricityMeter.ChannelId.ACTIVE_PRODUCTION_ENERGY, null) // + .output(IoShellyPlusPlugs.ChannelId.RELAY, null) // + .output(IoShellyPlusPlugs.ChannelId.SLAVE_COMMUNICATION_FAILED, false)) // + + .next(new TestCase("Invalid read response") // + .onBeforeProcessImage(() -> { + httpTestBundle.forceNextFailedResult(HttpError.ResponseError.notFound()); + httpTestBundle.triggerNextCycle(); + }) // + .onAfterProcessImage(() -> assertEquals("?|UNDEFINED", sut.debugLog())) + + .output(ElectricityMeter.ChannelId.ACTIVE_POWER, null) // + .output(ElectricityMeter.ChannelId.ACTIVE_POWER_L1, null) // + .output(ElectricityMeter.ChannelId.ACTIVE_POWER_L2, null) // + .output(ElectricityMeter.ChannelId.ACTIVE_POWER_L3, null) // + .output(ElectricityMeter.ChannelId.VOLTAGE, null) // + .output(ElectricityMeter.ChannelId.VOLTAGE_L1, null) // + .output(ElectricityMeter.ChannelId.VOLTAGE_L2, null) // + .output(ElectricityMeter.ChannelId.VOLTAGE_L3, null) // + .output(ElectricityMeter.ChannelId.CURRENT, null) // + .output(ElectricityMeter.ChannelId.CURRENT_L1, null) // + .output(ElectricityMeter.ChannelId.CURRENT_L2, null) // + .output(ElectricityMeter.ChannelId.CURRENT_L3, null) // + .output(ElectricityMeter.ChannelId.ACTIVE_CONSUMPTION_ENERGY, 0L) // + .output(ElectricityMeter.ChannelId.ACTIVE_PRODUCTION_ENERGY, 0L) // + .output(IoShellyPlusPlugs.ChannelId.RELAY, null) // + .output(IoShellyPlusPlugs.ChannelId.SLAVE_COMMUNICATION_FAILED, true)) // + + // Test case for writing to relay + .next(new TestCase("Write") // + .onBeforeControllersCallbacks(() -> { + sut.setRelay(true); + }) // + .also(testCase -> { + final var relayTurnedOn = httpTestBundle + .expect("http://127.0.0.1/rpc/Switch.Set?id=0&on=true").toBeCalled(); + + testCase.onBeforeControllersCallbacks(() -> httpTestBundle.triggerNextCycle()); + testCase.onAfterWriteCallbacks( + () -> assertTrue("Failed to turn on relay", relayTurnedOn.get())); + })) // + + .deactivate();// + } +} diff --git a/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellyplusplugs/MyConfig.java b/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellyplusplugs/MyConfig.java index 81fe32540f0..3f006a2ad90 100644 --- a/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellyplusplugs/MyConfig.java +++ b/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellyplusplugs/MyConfig.java @@ -1,8 +1,7 @@ package io.openems.edge.io.shelly.shellyplusplugs; import io.openems.common.test.AbstractComponentConfig; -import io.openems.edge.io.shelly.shellyplusplugs.Config; -import io.openems.edge.meter.api.MeterType; +import io.openems.common.types.MeterType; import io.openems.edge.meter.api.SinglePhase; @SuppressWarnings("all") diff --git a/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellypro3/IoShellyPro3ImplTest.java b/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellypro3/IoShellyPro3ImplTest.java index ed4f8d960b6..4da9baff117 100644 --- a/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellypro3/IoShellyPro3ImplTest.java +++ b/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellypro3/IoShellyPro3ImplTest.java @@ -1,23 +1,74 @@ package io.openems.edge.io.shelly.shellypro3; +import static org.junit.Assert.assertEquals; + import org.junit.Test; -import io.openems.edge.bridge.http.dummy.DummyBridgeHttpFactory; +import io.openems.edge.bridge.http.api.HttpError; +import io.openems.edge.bridge.http.api.HttpResponse; +import io.openems.edge.bridge.http.dummy.DummyBridgeHttpBundle; +import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; public class IoShellyPro3ImplTest { - private static final String COMPONENT_ID = "io0"; - @Test public void test() throws Exception { - new ComponentTest(new IoShellyPro3Impl()) // - .addReference("httpBridgeFactory", DummyBridgeHttpFactory.ofDummyBridge()) // + final var httpTestBundle = new DummyBridgeHttpBundle(); + final var sut = new IoShellyPro3Impl(); + new ComponentTest(sut) // + .addReference("httpBridgeFactory", httpTestBundle.factory()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("io0") // .setIp("127.0.0.1") // .build()) // - ; - } -} \ No newline at end of file + // Test case for successful JSON responses for all relays + .next(new TestCase("Successful read response for all relays") // + .onBeforeProcessImage(() -> { + httpTestBundle.forceNextSuccessfulResult(HttpResponse.ok(""" + { + "id": 0, + "source": "HTTP", + "output": true + } + """)); + httpTestBundle.forceNextSuccessfulResult(HttpResponse.ok(""" + { + "id": 1, + "source": "HTTP", + "output": false + } + """)); + httpTestBundle.forceNextSuccessfulResult(HttpResponse.ok(""" + { + "id": 2, + "source": "HTTP", + "output": true + } + """)); + httpTestBundle.triggerNextCycle(); + }) // + .onAfterProcessImage(() -> assertEquals("x x -", sut.debugLog())) + + .output(IoShellyPro3.ChannelId.RELAY_1, null) // expecting WriteValue + .output(IoShellyPro3.ChannelId.RELAY_2, null) // expecting WriteValue + .output(IoShellyPro3.ChannelId.RELAY_3, null) // expecting WriteValue + .output(IoShellyPro3.ChannelId.SLAVE_COMMUNICATION_FAILED, false)) // + + // Test case for an invalid JSON response + .next(new TestCase("Invalid read response for all relays") // + .onBeforeProcessImage(() -> { + httpTestBundle.forceNextFailedResult(HttpError.ResponseError.notFound()); + httpTestBundle.triggerNextCycle(); + }) // + .onAfterProcessImage(() -> assertEquals("? ? ?", sut.debugLog())) + + .output(IoShellyPro3.ChannelId.RELAY_1, null) // + .output(IoShellyPro3.ChannelId.RELAY_2, null) // + .output(IoShellyPro3.ChannelId.RELAY_3, null) // + .output(IoShellyPro3.ChannelId.SLAVE_COMMUNICATION_FAILED, true)) // + + .deactivate(); // + } +} diff --git a/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellypro3em/IoShelly3EmImplTest.java b/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellypro3em/IoShelly3EmImplTest.java deleted file mode 100644 index 29b0136a7a6..00000000000 --- a/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellypro3em/IoShelly3EmImplTest.java +++ /dev/null @@ -1,25 +0,0 @@ -package io.openems.edge.io.shelly.shellypro3em; - -import org.junit.Test; - -import io.openems.edge.bridge.http.dummy.DummyBridgeHttpFactory; -import io.openems.edge.common.test.ComponentTest; -import io.openems.edge.meter.api.MeterType; - -public class IoShelly3EmImplTest { - - private static final String COMPONENT_ID = "io0"; - - @Test - public void test() throws Exception { - new ComponentTest(new IoShellyPro3EmImpl()) // - .addReference("httpBridgeFactory", DummyBridgeHttpFactory.ofDummyBridge()) // - .activate(MyConfig.create() // - .setId(COMPONENT_ID) // - .setIp("127.0.0.1") // - .setType(MeterType.CONSUMPTION_METERED) // - .build()) // - ; - } - -} diff --git a/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellypro3em/IoShellyPro3EmImplTest.java b/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellypro3em/IoShellyPro3EmImplTest.java new file mode 100644 index 00000000000..a2ff6c34a54 --- /dev/null +++ b/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellypro3em/IoShellyPro3EmImplTest.java @@ -0,0 +1,112 @@ +package io.openems.edge.io.shelly.shellypro3em; + +import static io.openems.common.types.MeterType.GRID; +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +import io.openems.edge.bridge.http.api.HttpError; +import io.openems.edge.bridge.http.api.HttpResponse; +import io.openems.edge.bridge.http.dummy.DummyBridgeHttpBundle; +import io.openems.edge.common.test.AbstractComponentTest.TestCase; +import io.openems.edge.common.test.ComponentTest; +import io.openems.edge.meter.api.ElectricityMeter; +import io.openems.edge.timedata.test.DummyTimedata; + +public class IoShellyPro3EmImplTest { + + @Test + public void test() throws Exception { + final var sut = new IoShellyPro3EmImpl(); + final var httpTestBundle = new DummyBridgeHttpBundle(); + new ComponentTest(sut) // + .addReference("httpBridgeFactory", httpTestBundle.factory()) // + .addReference("timedata", new DummyTimedata("timedata0")) // + .activate(MyConfig.create() // + .setId("io0") // + .setIp("127.0.0.1") // + .setType(GRID) // + .build()) // + + .next(new TestCase("Successful read response") // + .onBeforeProcessImage(() -> { + httpTestBundle.forceNextSuccessfulResult(HttpResponse.ok(""" + { + "id": 0, + "a_current": 0.593, + "a_voltage": 230.5, + "a_act_power": -75.4, + "a_aprt_power": 136.9, + "a_pf": 0.68, + "a_freq": 50, + "b_current": 11.608, + "b_voltage": 228.5, + "b_act_power": 2655.2, + "b_aprt_power": 2656.6, + "b_pf": 1, + "b_freq": 50, + "c_current": 0.058, + "c_voltage": 232.1, + "c_act_power": 2.1, + "c_aprt_power": 13.5, + "c_pf": 0.54, + "c_freq": 50, + "n_current": null, + "total_current": 12.259, + "total_act_power": 2581.781, + "total_aprt_power": 2806.935, + "user_calibrated_phase": [] + } + """)); + httpTestBundle.triggerNextCycle(); + }) // + .onAfterProcessImage(() -> assertEquals("L:2582 W", sut.debugLog())) + + .output(ElectricityMeter.ChannelId.ACTIVE_POWER, 2582) // + .output(ElectricityMeter.ChannelId.ACTIVE_POWER_L1, -75) // + .output(ElectricityMeter.ChannelId.ACTIVE_POWER_L2, 2655) // + .output(ElectricityMeter.ChannelId.ACTIVE_POWER_L3, 2) // + .output(ElectricityMeter.ChannelId.VOLTAGE, 230367) // + .output(ElectricityMeter.ChannelId.VOLTAGE_L1, 230500) // + .output(ElectricityMeter.ChannelId.VOLTAGE_L2, 228500) // + .output(ElectricityMeter.ChannelId.VOLTAGE_L3, 232100) // + .output(ElectricityMeter.ChannelId.CURRENT, 12259) // + .output(ElectricityMeter.ChannelId.CURRENT_L1, 593) // + .output(ElectricityMeter.ChannelId.CURRENT_L2, 11608) // + .output(ElectricityMeter.ChannelId.CURRENT_L3, 58) // + .output(ElectricityMeter.ChannelId.ACTIVE_CONSUMPTION_ENERGY, null) // + .output(ElectricityMeter.ChannelId.ACTIVE_PRODUCTION_ENERGY, null) // + .output(IoShellyPro3Em.ChannelId.NO_LOAD, false) // + .output(IoShellyPro3Em.ChannelId.PHASE_SEQUENCE_ERROR, false) // + .output(IoShellyPro3Em.ChannelId.POWER_METER_FAILURE, false) // + .output(IoShellyPro3Em.ChannelId.SLAVE_COMMUNICATION_FAILED, false)) // + + .next(new TestCase("Invalid read response") // + .onBeforeProcessImage(() -> { + httpTestBundle.forceNextFailedResult(HttpError.ResponseError.notFound()); + httpTestBundle.triggerNextCycle(); + }) // + .onAfterProcessImage(() -> assertEquals("L:UNDEFINED", sut.debugLog())) + + .output(ElectricityMeter.ChannelId.ACTIVE_POWER, null) // + .output(ElectricityMeter.ChannelId.ACTIVE_POWER_L1, null) // + .output(ElectricityMeter.ChannelId.ACTIVE_POWER_L2, null) // + .output(ElectricityMeter.ChannelId.ACTIVE_POWER_L3, null) // + .output(ElectricityMeter.ChannelId.VOLTAGE, null) // + .output(ElectricityMeter.ChannelId.VOLTAGE_L1, null) // + .output(ElectricityMeter.ChannelId.VOLTAGE_L2, null) // + .output(ElectricityMeter.ChannelId.VOLTAGE_L3, null) // + .output(ElectricityMeter.ChannelId.CURRENT, null) // + .output(ElectricityMeter.ChannelId.CURRENT_L1, null) // + .output(ElectricityMeter.ChannelId.CURRENT_L2, null) // + .output(ElectricityMeter.ChannelId.CURRENT_L3, null) // + .output(ElectricityMeter.ChannelId.ACTIVE_CONSUMPTION_ENERGY, 0L) // + .output(ElectricityMeter.ChannelId.ACTIVE_PRODUCTION_ENERGY, 0L) // + .output(IoShellyPro3Em.ChannelId.NO_LOAD, false) // + .output(IoShellyPro3Em.ChannelId.PHASE_SEQUENCE_ERROR, false) // + .output(IoShellyPro3Em.ChannelId.POWER_METER_FAILURE, false) // + .output(IoShellyPro3Em.ChannelId.SLAVE_COMMUNICATION_FAILED, true)) // + + .deactivate(); + } +} diff --git a/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellypro3em/MyConfig.java b/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellypro3em/MyConfig.java index 68261ee3841..3f524cfd4cf 100644 --- a/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellypro3em/MyConfig.java +++ b/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellypro3em/MyConfig.java @@ -1,7 +1,7 @@ package io.openems.edge.io.shelly.shellypro3em; import io.openems.common.test.AbstractComponentConfig; -import io.openems.edge.meter.api.MeterType; +import io.openems.common.types.MeterType; @SuppressWarnings("all") public class MyConfig extends AbstractComponentConfig implements Config { diff --git a/io.openems.edge.io.siemenslogo/.classpath b/io.openems.edge.io.siemenslogo/.classpath new file mode 100644 index 00000000000..b4cffd0fe60 --- /dev/null +++ b/io.openems.edge.io.siemenslogo/.classpath @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/io.openems.edge.io.siemenslogo/.gitignore b/io.openems.edge.io.siemenslogo/.gitignore new file mode 100644 index 00000000000..c2b941a96de --- /dev/null +++ b/io.openems.edge.io.siemenslogo/.gitignore @@ -0,0 +1,2 @@ +/bin_test/ +/generated/ diff --git a/io.openems.edge.io.siemenslogo/.project b/io.openems.edge.io.siemenslogo/.project new file mode 100644 index 00000000000..075d5c883e7 --- /dev/null +++ b/io.openems.edge.io.siemenslogo/.project @@ -0,0 +1,23 @@ + + + io.openems.edge.io.siemenslogo + + + + + + org.eclipse.jdt.core.javabuilder + + + + + bndtools.core.bndbuilder + + + + + + org.eclipse.jdt.core.javanature + bndtools.core.bndnature + + diff --git a/io.openems.edge.io.siemenslogo/.settings/org.eclipse.core.resources.prefs b/io.openems.edge.io.siemenslogo/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 00000000000..99f26c0203a --- /dev/null +++ b/io.openems.edge.io.siemenslogo/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding/=UTF-8 diff --git a/io.openems.edge.io.siemenslogo/bnd.bnd b/io.openems.edge.io.siemenslogo/bnd.bnd new file mode 100644 index 00000000000..9b8a05149c3 --- /dev/null +++ b/io.openems.edge.io.siemenslogo/bnd.bnd @@ -0,0 +1,19 @@ +Bundle-Name: OpenEMS Edge IO Siemens LOGO! +Bundle-Description: Siemens LOGO! 8 as network relais. Connected via Modbus. Relays starting \ + at VM 101, Bit 0 on SPS. +Bundle-Vendor: OpenEMS Association e.V. +Bundle-License: https://opensource.org/licenses/EPL-2.0 +Bundle-Version: 1.0.0.${tstamp} + +-buildpath: \ + ${buildpath},\ + com.ghgande.j2mod,\ + io.openems.common,\ + io.openems.edge.bridge.modbus,\ + io.openems.edge.common,\ + io.openems.edge.io.api,\ + slf4j.api,\ + +-testpath: \ + ${testpath} + \ No newline at end of file diff --git a/io.openems.edge.io.siemenslogo/readme.adoc b/io.openems.edge.io.siemenslogo/readme.adoc new file mode 100644 index 00000000000..c2ae5678a51 --- /dev/null +++ b/io.openems.edge.io.siemenslogo/readme.adoc @@ -0,0 +1,7 @@ += Siemens LOGO! + +First you have to configure a modbus server in LogoSoft which either accepts all connection sources or you limit this to your EMS-system. Like all other modbus devices you have to configure a modbus-ID (255 is default in LogoSoft!) and a start address, e.g. 100 or use the default address 0. + +If you don´t do any changes the addresses are configured as coils. In Logo! a virtual address (V) is written 0.0 which is the virtual address 0, Bit 0. For the connected device the coil-addresses start with 800 (first bit/coil), 801 is the second coil and so on + +https://github.com/OpenEMS/openems/tree/develop/io.openems.edge.io.siemenslogo[Source Code icon:github[]] \ No newline at end of file diff --git a/io.openems.edge.io.siemenslogo/src/io/openems/edge/io/siemenslogo/Config.java b/io.openems.edge.io.siemenslogo/src/io/openems/edge/io/siemenslogo/Config.java new file mode 100644 index 00000000000..5acb3061327 --- /dev/null +++ b/io.openems.edge.io.siemenslogo/src/io/openems/edge/io/siemenslogo/Config.java @@ -0,0 +1,36 @@ +package io.openems.edge.io.siemenslogo; + +import org.osgi.service.metatype.annotations.AttributeDefinition; +import org.osgi.service.metatype.annotations.ObjectClassDefinition; + +@ObjectClassDefinition(// + name = "Siemens LOGO!", // + description = "Siemens LOGO! 8 digital Input/Output") +@interface Config { + + @AttributeDefinition(name = "Component-ID", description = "Unique ID of this Component") + String id() default "io0"; + + @AttributeDefinition(name = "Alias", description = "Human-readable name of this Component; defaults to Component-ID") + String alias() default ""; + + @AttributeDefinition(name = "Is enabled?", description = "Is this Component enabled?") + boolean enabled() default true; + + @AttributeDefinition(name = "Modbus-ID", description = "ID of Modbus bridge.") + String modbus_id() default "modbus0"; + + @AttributeDefinition(name = "Modbus write address offset", description = "Address offset in LOGO! for writing outputs / relays. This is where the virtual addresses start, e.g. 808 for virtual address 101.0 in Logo!") + int modbusOffsetWriteAddress() default 800; + + @AttributeDefinition(name = "Modbus read address offset", description = "Address offset in LOGO! for reading inputs (DI1-4). This is where the virtual addresses start, e.g. 808 for virtual address 110.0 in Logo!") + int modbusOffsetReadAddress() default 880; + + @AttributeDefinition(name = "Modbus Unit-ID", description = "The Unit-ID of the Modbus device.") + int modbusUnitId() default 1; + + @AttributeDefinition(name = "Modbus target filter", description = "This is auto-generated by 'Modbus-ID'.") + String Modbus_target() default "(enabled=true)"; + + String webconsole_configurationFactory_nameHint() default "Siemens LOGO! [{id}]"; +} \ No newline at end of file diff --git a/io.openems.edge.io.siemenslogo/src/io/openems/edge/io/siemenslogo/SiemensLogoRelay.java b/io.openems.edge.io.siemenslogo/src/io/openems/edge/io/siemenslogo/SiemensLogoRelay.java new file mode 100644 index 00000000000..a4653889ba5 --- /dev/null +++ b/io.openems.edge.io.siemenslogo/src/io/openems/edge/io/siemenslogo/SiemensLogoRelay.java @@ -0,0 +1,275 @@ +package io.openems.edge.io.siemenslogo; + +import static io.openems.common.channel.AccessMode.READ_ONLY; +import static io.openems.common.channel.AccessMode.READ_WRITE; +import static io.openems.common.channel.PersistencePriority.HIGH; +import static io.openems.common.channel.PersistencePriority.MEDIUM; +import static io.openems.common.types.OpenemsType.BOOLEAN; + +import io.openems.edge.common.channel.BooleanDoc; +import io.openems.edge.common.channel.Doc; +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.common.modbusslave.ModbusSlave; +import io.openems.edge.io.api.DigitalOutput; + +public interface SiemensLogoRelay extends DigitalOutput, OpenemsComponent, ModbusSlave { + + public enum ChannelId implements io.openems.edge.common.channel.ChannelId { + /** + * Input 1. + * + *

      + *
    • Interface: SiemensLogoRelayInput + *
    • Type: Boolean + *
    • Range: On/Off + *
    + */ + INPUT_1(new BooleanDoc() // + .accessMode(READ_ONLY) // + .persistencePriority(HIGH)), + /** + * Input 2. + * + *
      + *
    • Interface: SiemensLogoRelayInput + *
    • Type: Boolean + *
    • Range: On/Off + *
    + */ + INPUT_2(new BooleanDoc() // + .accessMode(READ_ONLY) // + .persistencePriority(HIGH)), + /** + * Input 3. + * + *
      + *
    • Interface: SiemensLogoRelayInput + *
    • Type: Boolean + *
    • Range: On/Off + *
    + */ + INPUT_3(new BooleanDoc() // + .accessMode(READ_ONLY) // + .persistencePriority(HIGH)), + /** + * Input 4. + * + *
      + *
    • Interface: SiemensLogoRelayInput + *
    • Type: Boolean + *
    • Range: On/Off + *
    + */ + INPUT_4(new BooleanDoc() // + .accessMode(READ_ONLY) // + .persistencePriority(HIGH)), + /** + * Holds writes to Relay Output 1 for debugging. + * + *
      + *
    • Interface: SiemensLogoRelayOutput + *
    • Type: Boolean + *
    • Range: On/Off + *
    + */ + DEBUG_RELAY_1(Doc.of(BOOLEAN)), // + /** + * Relay 1. + * + *
      + *
    • Interface: SiemensLogoRelayOutput + *
    • Type: Boolean + *
    • Range: On/Off + *
    + */ + RELAY_1(new BooleanDoc() // + .accessMode(READ_WRITE) // + .persistencePriority(MEDIUM) // + .onChannelSetNextWriteMirrorToDebugChannel(ChannelId.DEBUG_RELAY_1)), + /** + * Holds writes to Relay Output 2 for debugging. + * + *
      + *
    • Interface: SiemensLogoRelayOutput + *
    • Type: Boolean + *
    • Range: On/Off + *
    + */ + DEBUG_RELAY_2(Doc.of(BOOLEAN) // + .persistencePriority(MEDIUM)), // + /** + * Relay 2. + * + *
      + *
    • Interface: SiemensLogoRelayOutput + *
    • Type: Boolean + *
    • Range: On/Off + *
    + */ + RELAY_2(new BooleanDoc() // + .accessMode(READ_WRITE) // + .persistencePriority(MEDIUM) // + .onChannelSetNextWriteMirrorToDebugChannel(ChannelId.DEBUG_RELAY_2)), + + /** + * Holds writes to Relay Output 3 for debugging. + * + *
      + *
    • Interface: SiemensLogoRelayOutput + *
    • Type: Boolean + *
    • Range: On/Off + *
    + */ + DEBUG_RELAY_3(Doc.of(BOOLEAN) // + .persistencePriority(MEDIUM)), // + /** + * Relay 3. + * + *
      + *
    • Interface: SiemensLogoRelayOutput + *
    • Type: Boolean + *
    • Range: On/Off + *
    + */ + RELAY_3(new BooleanDoc() // + .accessMode(READ_WRITE) // + .persistencePriority(MEDIUM) // + .onChannelSetNextWriteMirrorToDebugChannel(ChannelId.DEBUG_RELAY_3)), + + /** + * Holds writes to Relay Output 4 for debugging. + * + *
      + *
    • Interface: SiemensLogoRelayOutput + *
    • Type: Boolean + *
    • Range: On/Off + *
    + */ + DEBUG_RELAY_4(Doc.of(BOOLEAN) // + .persistencePriority(MEDIUM)), // + /** + * Relay 4. + * + *
      + *
    • Interface: SiemensLogoRelayOutput + *
    • Type: Boolean + *
    • Range: On/Off + *
    + */ + RELAY_4(new BooleanDoc() // + .accessMode(READ_WRITE) // + .persistencePriority(MEDIUM) // + .onChannelSetNextWriteMirrorToDebugChannel(ChannelId.DEBUG_RELAY_4)), + + /** + * Holds writes to Relay Output 5 for debugging. + * + *
      + *
    • Interface: SiemensLogoRelayOutput + *
    • Type: Boolean + *
    • Range: On/Off + *
    + */ + DEBUG_RELAY_5(Doc.of(BOOLEAN) // + .persistencePriority(MEDIUM)), // + /** + * Relay 5. + * + *
      + *
    • Interface: SiemensLogoRelayOutput + *
    • Type: Boolean + *
    • Range: On/Off + *
    + */ + RELAY_5(new BooleanDoc() // + .accessMode(READ_WRITE) // + .persistencePriority(MEDIUM) // + .onChannelSetNextWriteMirrorToDebugChannel(ChannelId.DEBUG_RELAY_5)), + + /** + * Holds writes to Relay Output 6 for debugging. + * + *
      + *
    • Interface: SiemensLogoRelayOutput + *
    • Type: Boolean + *
    • Range: On/Off + *
    + */ + DEBUG_RELAY_6(Doc.of(BOOLEAN) // + .persistencePriority(MEDIUM)), // + /** + * Relay 6. + * + *
      + *
    • Interface: SiemensLogoRelayOutput + *
    • Type: Boolean + *
    • Range: On/Off + *
    + */ + RELAY_6(new BooleanDoc() // + .accessMode(READ_WRITE) // + .persistencePriority(MEDIUM) // + .onChannelSetNextWriteMirrorToDebugChannel(ChannelId.DEBUG_RELAY_6)), + + /** + * Holds writes to Relay Output 7 for debugging. + * + *
      + *
    • Interface: SiemensLogoRelayOutput + *
    • Type: Boolean + *
    • Range: On/Off + *
    + */ + DEBUG_RELAY_7(Doc.of(BOOLEAN) // + .persistencePriority(MEDIUM)), // + /** + * Relay 7. + * + *
      + *
    • Interface: SiemensLogoRelayOutput + *
    • Type: Boolean + *
    • Range: On/Off + *
    + */ + RELAY_7(new BooleanDoc() // + .accessMode(READ_WRITE) // + .persistencePriority(MEDIUM) // + .onChannelSetNextWriteMirrorToDebugChannel(ChannelId.DEBUG_RELAY_7)), + + /** + * Holds writes to Relay Output 8 for debugging. + * + *
      + *
    • Interface: SiemensLogoRelayOutput + *
    • Type: Boolean + *
    • Range: On/Off + *
    + */ + DEBUG_RELAY_8(Doc.of(BOOLEAN) // + .persistencePriority(MEDIUM)), // + /** + * Relay 8. + * + *
      + *
    • Interface: SiemensLogoRelayOutput + *
    • Type: Boolean + *
    • Range: On/Off + *
    + */ + RELAY_8(new BooleanDoc() // + .accessMode(READ_WRITE) // + .persistencePriority(MEDIUM) // + .onChannelSetNextWriteMirrorToDebugChannel(ChannelId.DEBUG_RELAY_8)); + + private final Doc doc; + + private ChannelId(Doc doc) { + this.doc = doc; + } + + @Override + public Doc doc() { + return this.doc; + } + } +} diff --git a/io.openems.edge.io.siemenslogo/src/io/openems/edge/io/siemenslogo/SiemensLogoRelayImpl.java b/io.openems.edge.io.siemenslogo/src/io/openems/edge/io/siemenslogo/SiemensLogoRelayImpl.java new file mode 100644 index 00000000000..e7308dbdfb3 --- /dev/null +++ b/io.openems.edge.io.siemenslogo/src/io/openems/edge/io/siemenslogo/SiemensLogoRelayImpl.java @@ -0,0 +1,184 @@ +package io.openems.edge.io.siemenslogo; + +import static io.openems.common.channel.AccessMode.READ_ONLY; +import static io.openems.common.channel.AccessMode.READ_WRITE; +import static java.util.Arrays.stream; +import static java.util.stream.Collectors.joining; + +import org.osgi.service.cm.ConfigurationAdmin; +import org.osgi.service.component.ComponentContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.ConfigurationPolicy; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; +import org.osgi.service.component.annotations.ReferencePolicyOption; +import org.osgi.service.metatype.annotations.Designate; + +import io.openems.common.channel.AccessMode; +import io.openems.common.exceptions.OpenemsException; +import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent; +import io.openems.edge.bridge.modbus.api.BridgeModbus; +import io.openems.edge.bridge.modbus.api.ModbusComponent; +import io.openems.edge.bridge.modbus.api.ModbusProtocol; +import io.openems.edge.bridge.modbus.api.element.CoilElement; +import io.openems.edge.bridge.modbus.api.task.FC1ReadCoilsTask; +import io.openems.edge.bridge.modbus.api.task.FC5WriteCoilTask; +import io.openems.edge.common.channel.BooleanReadChannel; +import io.openems.edge.common.channel.BooleanWriteChannel; +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.common.modbusslave.ModbusSlave; +import io.openems.edge.common.modbusslave.ModbusSlaveNatureTable; +import io.openems.edge.common.modbusslave.ModbusSlaveTable; +import io.openems.edge.common.modbusslave.ModbusType; +import io.openems.edge.common.taskmanager.Priority; +import io.openems.edge.io.api.DigitalInput; +import io.openems.edge.io.api.DigitalOutput; + +@Designate(ocd = Config.class, factory = true) +@Component(// + name = "IO.Siemens.LOGO", // + immediate = true, // + configurationPolicy = ConfigurationPolicy.REQUIRE // +) +public class SiemensLogoRelayImpl extends AbstractOpenemsModbusComponent + implements SiemensLogoRelay, DigitalOutput, DigitalInput, ModbusComponent, OpenemsComponent, ModbusSlave { + + private final BooleanWriteChannel[] digitalOutputChannels; + private final BooleanReadChannel[] digitalInputChannels; + + private int writeOffset = 0; + private int readOffset = 0; + + @Reference + private ConfigurationAdmin cm; + + @Reference(policy = ReferencePolicy.STATIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.MANDATORY) + protected void setModbus(BridgeModbus modbus) { + super.setModbus(modbus); + } + + public SiemensLogoRelayImpl() { + super(// + OpenemsComponent.ChannelId.values(), // + ModbusComponent.ChannelId.values(), // + DigitalOutput.ChannelId.values(), // + DigitalInput.ChannelId.values(), // + SiemensLogoRelay.ChannelId.values() // + ); + this.digitalOutputChannels = stream(SiemensLogoRelay.ChannelId.values()) // + .filter(channelId -> channelId.doc().getAccessMode() == READ_WRITE) // + .map(channelId -> this.channel(channelId)) // + .toArray(BooleanWriteChannel[]::new); + this.digitalInputChannels = stream(SiemensLogoRelay.ChannelId.values()) // + .filter(channelId -> channelId.doc().getAccessMode() == READ_ONLY) // + .map(channelId -> this.channel(channelId)) // + .toArray(BooleanReadChannel[]::new); + } + + @Activate + private void activate(ComponentContext context, Config config) throws OpenemsException { + this.writeOffset = config.modbusOffsetWriteAddress(); + this.readOffset = config.modbusOffsetReadAddress(); + super.activate(context, config.id(), config.alias(), config.enabled(), config.modbusUnitId(), this.cm, "Modbus", + config.modbus_id()); + } + + @Deactivate + protected void deactivate() { + super.deactivate(); + } + + @Override + public BooleanWriteChannel[] digitalOutputChannels() { + return this.digitalOutputChannels; + } + + @Override + public BooleanReadChannel[] digitalInputChannels() { + return this.digitalInputChannels; + } + + @Override + protected ModbusProtocol defineModbusProtocol() { + return new ModbusProtocol(this, // + // Read Inputs + new FC1ReadCoilsTask(this.readOffset, Priority.HIGH, // + m(SiemensLogoRelay.ChannelId.INPUT_1, new CoilElement(0 + this.readOffset)), // + m(SiemensLogoRelay.ChannelId.INPUT_2, new CoilElement(1 + this.readOffset)), // + m(SiemensLogoRelay.ChannelId.INPUT_3, new CoilElement(2 + this.readOffset)), // + m(SiemensLogoRelay.ChannelId.INPUT_4, new CoilElement(3 + this.readOffset)) // + ), + + /* + * For Read: Read Coils + */ + new FC1ReadCoilsTask(this.writeOffset, Priority.LOW, // + m(SiemensLogoRelay.ChannelId.RELAY_1, new CoilElement(0 + this.writeOffset)), // + m(SiemensLogoRelay.ChannelId.RELAY_2, new CoilElement(1 + this.writeOffset)), // + m(SiemensLogoRelay.ChannelId.RELAY_3, new CoilElement(2 + this.writeOffset)), // + m(SiemensLogoRelay.ChannelId.RELAY_4, new CoilElement(3 + this.writeOffset)), // + m(SiemensLogoRelay.ChannelId.RELAY_5, new CoilElement(4 + this.writeOffset)), // + m(SiemensLogoRelay.ChannelId.RELAY_6, new CoilElement(5 + this.writeOffset)), // + m(SiemensLogoRelay.ChannelId.RELAY_7, new CoilElement(6 + this.writeOffset)), // + m(SiemensLogoRelay.ChannelId.RELAY_8, new CoilElement(7 + this.writeOffset)) // + ), + /* + * For Write: Write Single Coil + */ + new FC5WriteCoilTask(0 + this.writeOffset, + m(SiemensLogoRelay.ChannelId.RELAY_1, new CoilElement(0 + this.writeOffset))), // + new FC5WriteCoilTask(1 + this.writeOffset, + m(SiemensLogoRelay.ChannelId.RELAY_2, new CoilElement(1 + this.writeOffset))), // + new FC5WriteCoilTask(2 + this.writeOffset, + m(SiemensLogoRelay.ChannelId.RELAY_3, new CoilElement(2 + this.writeOffset))), // + new FC5WriteCoilTask(3 + this.writeOffset, + m(SiemensLogoRelay.ChannelId.RELAY_4, new CoilElement(3 + this.writeOffset))), // + new FC5WriteCoilTask(4 + this.writeOffset, + m(SiemensLogoRelay.ChannelId.RELAY_5, new CoilElement(4 + this.writeOffset))), // + new FC5WriteCoilTask(5 + this.writeOffset, + m(SiemensLogoRelay.ChannelId.RELAY_6, new CoilElement(5 + this.writeOffset))), // + new FC5WriteCoilTask(6 + this.writeOffset, + m(SiemensLogoRelay.ChannelId.RELAY_7, new CoilElement(6 + this.writeOffset))), // + new FC5WriteCoilTask(7 + this.writeOffset, + m(SiemensLogoRelay.ChannelId.RELAY_8, new CoilElement(7 + this.writeOffset))) // + ); + } + + @Override + public ModbusSlaveTable getModbusSlaveTable(AccessMode accessMode) { + return new ModbusSlaveTable(OpenemsComponent.getModbusSlaveNatureTable(accessMode), // + ModbusSlaveNatureTable.of(SiemensLogoRelay.class, accessMode, 100)// + .channel(0 + this.writeOffset, SiemensLogoRelay.ChannelId.RELAY_1, ModbusType.UINT16) // + .channel(1 + this.writeOffset, SiemensLogoRelay.ChannelId.RELAY_2, ModbusType.UINT16) // + .channel(2 + this.writeOffset, SiemensLogoRelay.ChannelId.RELAY_3, ModbusType.UINT16) // + .channel(3 + this.writeOffset, SiemensLogoRelay.ChannelId.RELAY_4, ModbusType.UINT16) // + .channel(4 + this.writeOffset, SiemensLogoRelay.ChannelId.RELAY_5, ModbusType.UINT16) // + .channel(5 + this.writeOffset, SiemensLogoRelay.ChannelId.RELAY_6, ModbusType.UINT16) // + .channel(6 + this.writeOffset, SiemensLogoRelay.ChannelId.RELAY_7, ModbusType.UINT16) // + .channel(7 + this.writeOffset, SiemensLogoRelay.ChannelId.RELAY_8, ModbusType.UINT16) // + + .channel(8 + this.readOffset, SiemensLogoRelay.ChannelId.INPUT_1, ModbusType.UINT16) // + .channel(9 + this.readOffset, SiemensLogoRelay.ChannelId.INPUT_2, ModbusType.UINT16) // + .channel(10 + this.readOffset, SiemensLogoRelay.ChannelId.INPUT_3, ModbusType.UINT16) // + .channel(11 + this.readOffset, SiemensLogoRelay.ChannelId.INPUT_4, ModbusType.UINT16) // + + .build()// + ); + } + + @Override + public String debugLog() { + var outputLog = stream(this.digitalOutputChannels) // + .map(c -> c.value().asOptional()) // + .map(t -> t.isPresent() ? (t.get() ? "X" : "-") : "?") // + .collect(joining("")); + var inputLog = stream(this.digitalInputChannels) // + .map(c -> c.value().asOptional()) // + .map(t -> t.isPresent() ? (t.get() ? "I" : "O") : "?") // + .collect(joining("")); + return "Output:" + outputLog + "|Input:" + inputLog; + } +} diff --git a/io.openems.edge.io.siemenslogo/test/.gitignore b/io.openems.edge.io.siemenslogo/test/.gitignore new file mode 100644 index 00000000000..e69de29bb2d diff --git a/io.openems.edge.io.siemenslogo/test/io/openems/edge/io/siemenslogo/MyConfig.java b/io.openems.edge.io.siemenslogo/test/io/openems/edge/io/siemenslogo/MyConfig.java new file mode 100644 index 00000000000..f80ac31e01e --- /dev/null +++ b/io.openems.edge.io.siemenslogo/test/io/openems/edge/io/siemenslogo/MyConfig.java @@ -0,0 +1,117 @@ +package io.openems.edge.io.siemenslogo; + +import static io.openems.common.utils.ConfigUtils.generateReferenceTargetFilter; + +import io.openems.common.test.AbstractComponentConfig; + +@SuppressWarnings("all") +public class MyConfig extends AbstractComponentConfig implements Config { + + protected static class Builder { + private String id; + private String alias = ""; // Default to empty string + private boolean enabled = true; // Default to true + private String modbusId; + private int modbusUnitId; + private int modbusOffsetWriteAddress; + private int modbusOffsetReadAddress; + + private Builder() { + } + + public Builder setId(String id) { + this.id = id; + return this; + } + + public Builder setAlias(String alias) { + this.alias = alias; + return this; + } + + public Builder setEnabled(boolean enabled) { + this.enabled = enabled; + return this; + } + + public Builder setModbusId(String modbusId) { + this.modbusId = modbusId; + return this; + } + + public Builder setModbusUnitId(int modbusUnitId) { + this.modbusUnitId = modbusUnitId; + return this; + } + + public Builder setModbusOffsetWriteAddress(int modbusOffsetWriteAddress) { + this.modbusOffsetWriteAddress = modbusOffsetWriteAddress; + return this; + } + + public Builder setModbusOffsetReadAddress(int modbusOffsetReadAddress) { + this.modbusOffsetReadAddress = modbusOffsetReadAddress; + return this; + } + + public MyConfig build() { + return new MyConfig(this); + } + } + + /** + * Create a Config builder. + * + * @return a {@link Builder} + */ + public static Builder create() { + return new Builder(); + } + + private final Builder builder; + + private MyConfig(Builder builder) { + super(Config.class, builder.id); + this.builder = builder; + } + + @Override + public String id() { + return this.builder.id; + } + + @Override + public String alias() { + return this.builder.alias; + } + + @Override + public boolean enabled() { + return this.builder.enabled; + } + + @Override + public String modbus_id() { + return this.builder.modbusId; + } + + @Override + public int modbusUnitId() { + return this.builder.modbusUnitId; + } + + @Override + public int modbusOffsetWriteAddress() { + return this.builder.modbusOffsetWriteAddress; + } + + @Override + public int modbusOffsetReadAddress() { + return this.builder.modbusOffsetReadAddress; + } + + @Override + public String Modbus_target() { + return generateReferenceTargetFilter(this.id(), this.modbus_id()); + } +} diff --git a/io.openems.edge.io.siemenslogo/test/io/openems/edge/io/siemenslogo/SiemensLogoRelayImplTest.java b/io.openems.edge.io.siemenslogo/test/io/openems/edge/io/siemenslogo/SiemensLogoRelayImplTest.java new file mode 100644 index 00000000000..48a51617328 --- /dev/null +++ b/io.openems.edge.io.siemenslogo/test/io/openems/edge/io/siemenslogo/SiemensLogoRelayImplTest.java @@ -0,0 +1,36 @@ +package io.openems.edge.io.siemenslogo; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +import io.openems.common.channel.AccessMode; +import io.openems.edge.bridge.modbus.test.DummyModbusBridge; +import io.openems.edge.common.test.AbstractComponentTest.TestCase; +import io.openems.edge.common.test.ComponentTest; +import io.openems.edge.common.test.DummyConfigurationAdmin; + +public class SiemensLogoRelayImplTest { + + @Test + public void test() throws Exception { + var sut = new SiemensLogoRelayImpl(); + new ComponentTest(new SiemensLogoRelayImpl()) // + .addReference("cm", new DummyConfigurationAdmin()) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // + .activate(MyConfig.create() // + .setId("io0") // + .setModbusId("modbus0") // + .build()) // + .next(new TestCase()) // + .deactivate(); + assertEquals("Output:????????|Input:????????????", sut.debugLog()); + + var mst = sut.getModbusSlaveTable(AccessMode.READ_WRITE); + assertEquals(180, mst.getLength()); + + assertEquals(8, sut.digitalOutputChannels().length); + assertEquals(12, sut.digitalInputChannels().length); + } + +} diff --git a/io.openems.edge.io.wago/.classpath b/io.openems.edge.io.wago/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.io.wago/.classpath +++ b/io.openems.edge.io.wago/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.io.wago/src/io/openems/edge/wago/IoWagoImpl.java b/io.openems.edge.io.wago/src/io/openems/edge/wago/IoWagoImpl.java index d00fe280ee6..fae2ebd405d 100644 --- a/io.openems.edge.io.wago/src/io/openems/edge/wago/IoWagoImpl.java +++ b/io.openems.edge.io.wago/src/io/openems/edge/wago/IoWagoImpl.java @@ -5,7 +5,7 @@ import java.io.InputStream; import java.net.HttpURLConnection; import java.net.InetAddress; -import java.net.URL; +import java.net.URI; import java.util.ArrayList; import java.util.Base64; import java.util.Collections; @@ -160,7 +160,8 @@ private static Document downloadConfigXml(InetAddress ip, String username, Strin private static Document downloadConfigXml(InetAddress ip, String filename, String username, String password) throws ParserConfigurationException, SAXException, IOException { - var url = new URL(String.format("http://%s/etc/%s", ip.getHostAddress(), filename)); + var uri = URI.create(String.format("http://%s/etc/%s", ip.getHostAddress(), filename)); + var url = uri.toURL(); var authStr = String.format("%s:%s", username, password); var bytesEncoded = Base64.getEncoder().encode(authStr.getBytes()); var authEncoded = new String(bytesEncoded); diff --git a/io.openems.edge.io.wago/test/io/openems/edge/wago/IoWagoImplTest.java b/io.openems.edge.io.wago/test/io/openems/edge/wago/IoWagoImplTest.java index 012eac53fd3..292deeff3f3 100644 --- a/io.openems.edge.io.wago/test/io/openems/edge/wago/IoWagoImplTest.java +++ b/io.openems.edge.io.wago/test/io/openems/edge/wago/IoWagoImplTest.java @@ -16,9 +16,6 @@ public class IoWagoImplTest { - private static final String IO_ID = "io0"; - private static final String MODBUS_ID = "modbus0"; - /** * This is an example "ea-config.xml" downloaded from a WAGO Fieldbus coupler. */ @@ -56,14 +53,14 @@ public void test() throws Exception { var sut = new IoWagoImpl(); new ComponentTest(sut) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID).withIpAddress("127.0.0.1")) // + .addReference("setModbus", new DummyModbusBridge("modbus0") // + .withIpAddress("127.0.0.1")) // .activate(MyConfig.create() // - .setId(IO_ID) // - .setModbusId(MODBUS_ID) // + .setId("io0") // + .setModbusId("modbus0") // .setUsername("foo") // .setPassword("bar") // - .build()) // - ; + .build()); InputStream dummyXml = new ByteArrayInputStream(EA_CONFIG.getBytes()); var doc = IoWagoImpl.parseXmlToDocument(dummyXml); diff --git a/io.openems.edge.io.weidmueller/.classpath b/io.openems.edge.io.weidmueller/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.io.weidmueller/.classpath +++ b/io.openems.edge.io.weidmueller/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.io.weidmueller/src/io/openems/edge/io/weidmueller/IoWeidmuellerUr20Impl.java b/io.openems.edge.io.weidmueller/src/io/openems/edge/io/weidmueller/IoWeidmuellerUr20Impl.java index e1e444fbc73..1ac395603ea 100644 --- a/io.openems.edge.io.weidmueller/src/io/openems/edge/io/weidmueller/IoWeidmuellerUr20Impl.java +++ b/io.openems.edge.io.weidmueller/src/io/openems/edge/io/weidmueller/IoWeidmuellerUr20Impl.java @@ -2,6 +2,7 @@ import static io.openems.edge.bridge.modbus.api.ModbusUtils.readElementOnce; import static io.openems.edge.bridge.modbus.api.ModbusUtils.readElementsOnce; +import static io.openems.edge.bridge.modbus.api.ModbusUtils.FunctionCode.FC3; import java.util.ArrayList; import java.util.List; @@ -223,7 +224,7 @@ public String debugLog() { } private CompletableFuture readNumberOfEntriesInTheCurrentModuleList() { - return readElementOnce(this.modbusProtocol, ModbusUtils::retryOnNull, new UnsignedWordElement(0x27FE)); + return readElementOnce(FC3, this.modbusProtocol, ModbusUtils::retryOnNull, new UnsignedWordElement(0x27FE)); } @SuppressWarnings("unchecked") @@ -232,7 +233,7 @@ private CompletableFuture> readCurrentModuleList(int numberOfEntries) .map(index -> 0x2A00 + index * 2) // .mapToObj(address -> new UnsignedDoublewordElement(address)) // .toArray(ModbusRegisterElement[]::new); - return readElementsOnce(this.modbusProtocol, ModbusUtils::retryOnNull, elements) // + return readElementsOnce(FC3, this.modbusProtocol, ModbusUtils::retryOnNull, elements) // .thenApply(rsr -> ((ReadElementsResult) rsr).values()); } diff --git a/io.openems.edge.io.weidmueller/test/io/openems/edge/io/weidmueller/IoWeidmuellerUr20ImplTest.java b/io.openems.edge.io.weidmueller/test/io/openems/edge/io/weidmueller/IoWeidmuellerUr20ImplTest.java index 7a8da19e227..eadbf247bb5 100644 --- a/io.openems.edge.io.weidmueller/test/io/openems/edge/io/weidmueller/IoWeidmuellerUr20ImplTest.java +++ b/io.openems.edge.io.weidmueller/test/io/openems/edge/io/weidmueller/IoWeidmuellerUr20ImplTest.java @@ -9,17 +9,14 @@ public class IoWeidmuellerUr20ImplTest { - private static final String COMPONENT_ID = "io0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new IoWeidmuellerUr20Impl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // - .setModbusId(MODBUS_ID) // + .setId("io0") // + .setModbusId("modbus0") // .build()) .next(new TestCase()); } diff --git a/io.openems.edge.kaco.blueplanet.hybrid10/.classpath b/io.openems.edge.kaco.blueplanet.hybrid10/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.kaco.blueplanet.hybrid10/.classpath +++ b/io.openems.edge.kaco.blueplanet.hybrid10/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.kaco.blueplanet.hybrid10/src/io/openems/edge/kaco/blueplanet/hybrid10/vectis/KacoBlueplanetHybrid10GridMeterImpl.java b/io.openems.edge.kaco.blueplanet.hybrid10/src/io/openems/edge/kaco/blueplanet/hybrid10/vectis/KacoBlueplanetHybrid10GridMeterImpl.java index aa8535f91a9..e65e7514c1e 100644 --- a/io.openems.edge.kaco.blueplanet.hybrid10/src/io/openems/edge/kaco/blueplanet/hybrid10/vectis/KacoBlueplanetHybrid10GridMeterImpl.java +++ b/io.openems.edge.kaco.blueplanet.hybrid10/src/io/openems/edge/kaco/blueplanet/hybrid10/vectis/KacoBlueplanetHybrid10GridMeterImpl.java @@ -17,12 +17,12 @@ import org.osgi.service.event.propertytypes.EventTopics; import org.osgi.service.metatype.annotations.Designate; +import io.openems.common.types.MeterType; import io.openems.edge.common.component.AbstractOpenemsComponent; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.event.EdgeEventConstants; import io.openems.edge.kaco.blueplanet.hybrid10.core.KacoBlueplanetHybrid10Core; import io.openems.edge.meter.api.ElectricityMeter; -import io.openems.edge.meter.api.MeterType; import io.openems.edge.timedata.api.Timedata; import io.openems.edge.timedata.api.TimedataProvider; import io.openems.edge.timedata.api.utils.CalculateEnergyFromPower; diff --git a/io.openems.edge.kaco.blueplanet.hybrid10/test/io/openems/edge/kaco/blueplanet/hybrid10/core/KacoBlueplanetHybrid10CoreImplTest.java b/io.openems.edge.kaco.blueplanet.hybrid10/test/io/openems/edge/kaco/blueplanet/hybrid10/core/KacoBlueplanetHybrid10CoreImplTest.java index e0d6d9bbb72..bf8f9736926 100644 --- a/io.openems.edge.kaco.blueplanet.hybrid10/test/io/openems/edge/kaco/blueplanet/hybrid10/core/KacoBlueplanetHybrid10CoreImplTest.java +++ b/io.openems.edge.kaco.blueplanet.hybrid10/test/io/openems/edge/kaco/blueplanet/hybrid10/core/KacoBlueplanetHybrid10CoreImplTest.java @@ -6,13 +6,11 @@ public class KacoBlueplanetHybrid10CoreImplTest { - private static final String CORE_ID = "kacoCore0"; - @Test public void test() throws Exception { new ComponentTest(new KacoBlueplanetHybrid10CoreImpl()) // .activate(MyConfig.create() // - .setId(CORE_ID) // + .setId("kacoCore0") // .setIdentkey("") // .setIp("192.168.0.1") // .setSerialnumber("123456") // diff --git a/io.openems.edge.kaco.blueplanet.hybrid10/test/io/openems/edge/kaco/blueplanet/hybrid10/ess/KacoBlueplanetHybrid10EssImplTest.java b/io.openems.edge.kaco.blueplanet.hybrid10/test/io/openems/edge/kaco/blueplanet/hybrid10/ess/KacoBlueplanetHybrid10EssImplTest.java index cb4ec7953a9..f06f0bbafb1 100644 --- a/io.openems.edge.kaco.blueplanet.hybrid10/test/io/openems/edge/kaco/blueplanet/hybrid10/ess/KacoBlueplanetHybrid10EssImplTest.java +++ b/io.openems.edge.kaco.blueplanet.hybrid10/test/io/openems/edge/kaco/blueplanet/hybrid10/ess/KacoBlueplanetHybrid10EssImplTest.java @@ -8,18 +8,14 @@ public class KacoBlueplanetHybrid10EssImplTest { - private static final String ESS_ID = "ess0"; - private static final String CORE_ID = "kacoCore0"; - private static final String TIMEDATA_ID = "timedata0"; - @Test public void test() throws Exception { new ComponentTest(new KacoBlueplanetHybrid10EssImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("timedata", new DummyTimedata(TIMEDATA_ID)) // + .addReference("timedata", new DummyTimedata("timedata0")) // .activate(MyConfig.create() // - .setId(ESS_ID) // - .setCoreId(CORE_ID) // + .setId("ess0") // + .setCoreId("kacoCore0") // .build()) // ; } diff --git a/io.openems.edge.kaco.blueplanet.hybrid10/test/io/openems/edge/kaco/blueplanet/hybrid10/ess/charger/KacoBlueplanetHybrid10ChargerImplTest.java b/io.openems.edge.kaco.blueplanet.hybrid10/test/io/openems/edge/kaco/blueplanet/hybrid10/ess/charger/KacoBlueplanetHybrid10ChargerImplTest.java index 6d9606bb0b6..e5d00f7b9e2 100644 --- a/io.openems.edge.kaco.blueplanet.hybrid10/test/io/openems/edge/kaco/blueplanet/hybrid10/ess/charger/KacoBlueplanetHybrid10ChargerImplTest.java +++ b/io.openems.edge.kaco.blueplanet.hybrid10/test/io/openems/edge/kaco/blueplanet/hybrid10/ess/charger/KacoBlueplanetHybrid10ChargerImplTest.java @@ -7,16 +7,13 @@ public class KacoBlueplanetHybrid10ChargerImplTest { - private static final String CHARGER_ID = "charger0"; - private static final String CORE_ID = "kacoCore0"; - @Test public void test() throws Exception { new ComponentTest(new KacoBlueplanetHybrid10ChargerImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .activate(MyConfig.create() // - .setId(CHARGER_ID) // - .setCoreId(CORE_ID) // + .setId("charger0") // + .setCoreId("kacoCore0") // .build()) // ; } diff --git a/io.openems.edge.kaco.blueplanet.hybrid10/test/io/openems/edge/kaco/blueplanet/hybrid10/pvinverter/KacoBlueplanetHybrid10PvInverterImplTest.java b/io.openems.edge.kaco.blueplanet.hybrid10/test/io/openems/edge/kaco/blueplanet/hybrid10/pvinverter/KacoBlueplanetHybrid10PvInverterImplTest.java index 2a34e01ddaa..a8c9ee17853 100644 --- a/io.openems.edge.kaco.blueplanet.hybrid10/test/io/openems/edge/kaco/blueplanet/hybrid10/pvinverter/KacoBlueplanetHybrid10PvInverterImplTest.java +++ b/io.openems.edge.kaco.blueplanet.hybrid10/test/io/openems/edge/kaco/blueplanet/hybrid10/pvinverter/KacoBlueplanetHybrid10PvInverterImplTest.java @@ -7,16 +7,13 @@ public class KacoBlueplanetHybrid10PvInverterImplTest { - private static final String PV_INVERTER_ID = "pvInverter0"; - private static final String CORE_ID = "kacoCore0"; - @Test public void test() throws Exception { new ComponentTest(new KacoBlueplanetHybrid10PvInverterImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .activate(MyConfig.create() // - .setId(PV_INVERTER_ID) // - .setCoreId(CORE_ID) // + .setId("pvInverter0") // + .setCoreId("kacoCore0") // .build()) // ; } diff --git a/io.openems.edge.kaco.blueplanet.hybrid10/test/io/openems/edge/kaco/blueplanet/hybrid10/vectis/KacoBlueplanetHybrid10GridMeterImplTest.java b/io.openems.edge.kaco.blueplanet.hybrid10/test/io/openems/edge/kaco/blueplanet/hybrid10/vectis/KacoBlueplanetHybrid10GridMeterImplTest.java index c4d71724b33..cc50bb9d0fd 100644 --- a/io.openems.edge.kaco.blueplanet.hybrid10/test/io/openems/edge/kaco/blueplanet/hybrid10/vectis/KacoBlueplanetHybrid10GridMeterImplTest.java +++ b/io.openems.edge.kaco.blueplanet.hybrid10/test/io/openems/edge/kaco/blueplanet/hybrid10/vectis/KacoBlueplanetHybrid10GridMeterImplTest.java @@ -7,16 +7,13 @@ public class KacoBlueplanetHybrid10GridMeterImplTest { - private static final String METER_ID = "meter0"; - private static final String CORE_ID = "kacoCore0"; - @Test public void test() throws Exception { new ComponentTest(new KacoBlueplanetHybrid10GridMeterImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setCoreId(CORE_ID) // + .setId("meter0") // + .setCoreId("kacoCore0") // .build()) // ; } diff --git a/io.openems.edge.katek.edcom/.classpath b/io.openems.edge.katek.edcom/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.katek.edcom/.classpath +++ b/io.openems.edge.katek.edcom/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.kostal.piko/.classpath b/io.openems.edge.kostal.piko/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.kostal.piko/.classpath +++ b/io.openems.edge.kostal.piko/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.kostal.piko/src/io/openems/edge/kostal/piko/gridmeter/KostalPikoGridMeterImpl.java b/io.openems.edge.kostal.piko/src/io/openems/edge/kostal/piko/gridmeter/KostalPikoGridMeterImpl.java index 609d45c34cc..d906d61b56e 100644 --- a/io.openems.edge.kostal.piko/src/io/openems/edge/kostal/piko/gridmeter/KostalPikoGridMeterImpl.java +++ b/io.openems.edge.kostal.piko/src/io/openems/edge/kostal/piko/gridmeter/KostalPikoGridMeterImpl.java @@ -15,12 +15,12 @@ import org.osgi.service.event.propertytypes.EventTopics; import org.osgi.service.metatype.annotations.Designate; +import io.openems.common.types.MeterType; import io.openems.edge.common.component.AbstractOpenemsComponent; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.event.EdgeEventConstants; import io.openems.edge.kostal.piko.core.api.KostalPikoCore; import io.openems.edge.meter.api.ElectricityMeter; -import io.openems.edge.meter.api.MeterType; @Designate(ocd = Config.class, factory = true) @Component(// diff --git a/io.openems.edge.kostal.piko/test/io/openems/edge/kostal/piko/charger/KostalPikoChargerImplTest.java b/io.openems.edge.kostal.piko/test/io/openems/edge/kostal/piko/charger/KostalPikoChargerImplTest.java index ed2b7b7d5cc..96cbaf11cdd 100644 --- a/io.openems.edge.kostal.piko/test/io/openems/edge/kostal/piko/charger/KostalPikoChargerImplTest.java +++ b/io.openems.edge.kostal.piko/test/io/openems/edge/kostal/piko/charger/KostalPikoChargerImplTest.java @@ -8,15 +8,13 @@ public class KostalPikoChargerImplTest { - private static final String COMPONENT_ID = "charger0"; - @Test public void test() throws Exception { new ComponentTest(new KostalPikoChargerImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // # .addReference("setCore", new KostalPikoCoreImpl()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("charger0") // .setCoreId("core0") // .build()) // ; diff --git a/io.openems.edge.kostal.piko/test/io/openems/edge/kostal/piko/core/impl/KostalPikoCoreImplTest.java b/io.openems.edge.kostal.piko/test/io/openems/edge/kostal/piko/core/impl/KostalPikoCoreImplTest.java index 5dab51e4a65..dff15af4a7e 100644 --- a/io.openems.edge.kostal.piko/test/io/openems/edge/kostal/piko/core/impl/KostalPikoCoreImplTest.java +++ b/io.openems.edge.kostal.piko/test/io/openems/edge/kostal/piko/core/impl/KostalPikoCoreImplTest.java @@ -6,13 +6,11 @@ public class KostalPikoCoreImplTest { - private static final String COMPONENT_ID = "core0"; - @Test public void test() throws Exception { new ComponentTest(new KostalPikoCoreImpl()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("core0") // .setIp("127.0.0.1") // .setPort(81) // .setUnitID(0xff) // diff --git a/io.openems.edge.kostal.piko/test/io/openems/edge/kostal/piko/ess/KostalPikoEssImplTest.java b/io.openems.edge.kostal.piko/test/io/openems/edge/kostal/piko/ess/KostalPikoEssImplTest.java index 1c3e78bfc06..aaeaf69fefa 100644 --- a/io.openems.edge.kostal.piko/test/io/openems/edge/kostal/piko/ess/KostalPikoEssImplTest.java +++ b/io.openems.edge.kostal.piko/test/io/openems/edge/kostal/piko/ess/KostalPikoEssImplTest.java @@ -8,15 +8,13 @@ public class KostalPikoEssImplTest { - private static final String COMPONENT_ID = "ess0"; - @Test public void test() throws Exception { new ComponentTest(new KostalPikoEssImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // # .addReference("setCore", new KostalPikoCoreImpl()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("ess0") // .setCoreId("core0") // .build()) // ; diff --git a/io.openems.edge.kostal.piko/test/io/openems/edge/kostal/piko/gridmeter/KostalPikoGridMeterImplTest.java b/io.openems.edge.kostal.piko/test/io/openems/edge/kostal/piko/gridmeter/KostalPikoGridMeterImplTest.java index 7895e4c50ed..aec78af51d6 100644 --- a/io.openems.edge.kostal.piko/test/io/openems/edge/kostal/piko/gridmeter/KostalPikoGridMeterImplTest.java +++ b/io.openems.edge.kostal.piko/test/io/openems/edge/kostal/piko/gridmeter/KostalPikoGridMeterImplTest.java @@ -8,18 +8,18 @@ public class KostalPikoGridMeterImplTest { - private static final String COMPONENT_ID = "meter0"; - @Test public void test() throws Exception { new ComponentTest(new KostalPikoGridMeterImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // # .addReference("setCore", new KostalPikoCoreImpl()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("meter0") // .setCoreId("core0") // - .build()) // - ; + .build()); // + // TODO This does not work because this.worker == null + // .next(new TestCase()) // + // deactivate(); } } diff --git a/io.openems.edge.meter.abb/.classpath b/io.openems.edge.meter.abb/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.meter.abb/.classpath +++ b/io.openems.edge.meter.abb/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.meter.abb/src/io/openems/edge/meter/abb/b32/Config.java b/io.openems.edge.meter.abb/src/io/openems/edge/meter/abb/b32/Config.java index 707447bbb31..bff5dd7b2fc 100644 --- a/io.openems.edge.meter.abb/src/io/openems/edge/meter/abb/b32/Config.java +++ b/io.openems.edge.meter.abb/src/io/openems/edge/meter/abb/b32/Config.java @@ -3,7 +3,7 @@ import org.osgi.service.metatype.annotations.AttributeDefinition; import org.osgi.service.metatype.annotations.ObjectClassDefinition; -import io.openems.edge.meter.api.MeterType; +import io.openems.common.types.MeterType; @ObjectClassDefinition(// name = "Meter ABB B23 M-Bus", // diff --git a/io.openems.edge.meter.abb/src/io/openems/edge/meter/abb/b32/MeterAbbB23Impl.java b/io.openems.edge.meter.abb/src/io/openems/edge/meter/abb/b32/MeterAbbB23Impl.java index a5b30875667..67c78c5ad7f 100644 --- a/io.openems.edge.meter.abb/src/io/openems/edge/meter/abb/b32/MeterAbbB23Impl.java +++ b/io.openems.edge.meter.abb/src/io/openems/edge/meter/abb/b32/MeterAbbB23Impl.java @@ -12,6 +12,7 @@ import org.osgi.service.component.annotations.ReferencePolicyOption; import org.osgi.service.metatype.annotations.Designate; +import io.openems.common.types.MeterType; import io.openems.edge.bridge.mbus.api.AbstractOpenemsMbusComponent; import io.openems.edge.bridge.mbus.api.BridgeMbus; import io.openems.edge.bridge.mbus.api.ChannelRecord; @@ -19,7 +20,6 @@ import io.openems.edge.bridge.mbus.api.MbusTask; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.meter.api.ElectricityMeter; -import io.openems.edge.meter.api.MeterType; @Designate(ocd = Config.class, factory = true) @Component(// diff --git a/io.openems.edge.meter.abb/test/io/openems/edge/meter/abb/b32/MeterAbbB23ImplTest.java b/io.openems.edge.meter.abb/test/io/openems/edge/meter/abb/b32/MeterAbbB23ImplTest.java index d76e662c8aa..367d1f97dd5 100644 --- a/io.openems.edge.meter.abb/test/io/openems/edge/meter/abb/b32/MeterAbbB23ImplTest.java +++ b/io.openems.edge.meter.abb/test/io/openems/edge/meter/abb/b32/MeterAbbB23ImplTest.java @@ -1,29 +1,28 @@ package io.openems.edge.meter.abb.b32; -import java.lang.reflect.InvocationTargetException; - import org.junit.Test; +import io.openems.common.types.MeterType; +import io.openems.common.utils.ReflectionUtils.ReflectionException; +import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.meter.api.MeterType; public class MeterAbbB23ImplTest { - private static final String COMPONENT_ID = "meter0"; - - @Test(expected = InvocationTargetException.class) + @Test(expected = ReflectionException.class) public void test() throws Exception { new ComponentTest(new MeterAbbB23Impl()) // .addReference("cm", new DummyConfigurationAdmin()) // # .addReference("mbus", null) // TODO create DummyMbusBridge .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("meter0") // .setMbusId("bridge0") // .setPrimaryAddress(10) // .setType(MeterType.PRODUCTION) // .build()) // - ; + .next(new TestCase()) // + .deactivate(); } } diff --git a/io.openems.edge.meter.abb/test/io/openems/edge/meter/abb/b32/MyConfig.java b/io.openems.edge.meter.abb/test/io/openems/edge/meter/abb/b32/MyConfig.java index c6c2482aa3f..ffd21f9f547 100644 --- a/io.openems.edge.meter.abb/test/io/openems/edge/meter/abb/b32/MyConfig.java +++ b/io.openems.edge.meter.abb/test/io/openems/edge/meter/abb/b32/MyConfig.java @@ -1,7 +1,7 @@ package io.openems.edge.meter.abb.b32; import io.openems.common.test.AbstractComponentConfig; -import io.openems.edge.meter.api.MeterType; +import io.openems.common.types.MeterType; @SuppressWarnings("all") public class MyConfig extends AbstractComponentConfig implements Config { diff --git a/io.openems.edge.meter.api/.classpath b/io.openems.edge.meter.api/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.meter.api/.classpath +++ b/io.openems.edge.meter.api/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.meter.api/src/io/openems/edge/meter/api/ElectricityMeter.java b/io.openems.edge.meter.api/src/io/openems/edge/meter/api/ElectricityMeter.java index 5644df4853a..af1c088c8ec 100644 --- a/io.openems.edge.meter.api/src/io/openems/edge/meter/api/ElectricityMeter.java +++ b/io.openems.edge.meter.api/src/io/openems/edge/meter/api/ElectricityMeter.java @@ -7,6 +7,7 @@ import io.openems.common.channel.AccessMode; import io.openems.common.channel.PersistencePriority; import io.openems.common.channel.Unit; +import io.openems.common.types.MeterType; import io.openems.common.types.OpenemsType; import io.openems.edge.common.channel.Doc; import io.openems.edge.common.channel.IntegerDoc; diff --git a/io.openems.edge.meter.api/src/io/openems/edge/meter/test/AbstractDummyElectricityMeter.java b/io.openems.edge.meter.api/src/io/openems/edge/meter/test/AbstractDummyElectricityMeter.java index f16c98cbad3..92aedece446 100644 --- a/io.openems.edge.meter.api/src/io/openems/edge/meter/test/AbstractDummyElectricityMeter.java +++ b/io.openems.edge.meter.api/src/io/openems/edge/meter/test/AbstractDummyElectricityMeter.java @@ -1,10 +1,10 @@ package io.openems.edge.meter.test; +import io.openems.common.types.MeterType; import io.openems.edge.common.channel.Channel; import io.openems.edge.common.component.AbstractOpenemsComponent; import io.openems.edge.common.test.TestUtils; import io.openems.edge.meter.api.ElectricityMeter; -import io.openems.edge.meter.api.MeterType; public abstract class AbstractDummyElectricityMeter> extends AbstractOpenemsComponent implements ElectricityMeter { diff --git a/io.openems.edge.meter.artemes.am2/.classpath b/io.openems.edge.meter.artemes.am2/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.meter.artemes.am2/.classpath +++ b/io.openems.edge.meter.artemes.am2/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.meter.artemes.am2/src/io/openems/edge/meter/artemes/am2/Config.java b/io.openems.edge.meter.artemes.am2/src/io/openems/edge/meter/artemes/am2/Config.java index f7fff701591..0783bc43fcd 100644 --- a/io.openems.edge.meter.artemes.am2/src/io/openems/edge/meter/artemes/am2/Config.java +++ b/io.openems.edge.meter.artemes.am2/src/io/openems/edge/meter/artemes/am2/Config.java @@ -3,7 +3,7 @@ import org.osgi.service.metatype.annotations.AttributeDefinition; import org.osgi.service.metatype.annotations.ObjectClassDefinition; -import io.openems.edge.meter.api.MeterType; +import io.openems.common.types.MeterType; @ObjectClassDefinition(name = "Meter Artemes AM-2", description = "Implements the Artemes AM-2 meter.") @interface Config { diff --git a/io.openems.edge.meter.artemes.am2/src/io/openems/edge/meter/artemes/am2/MeterArtemesAM2Impl.java b/io.openems.edge.meter.artemes.am2/src/io/openems/edge/meter/artemes/am2/MeterArtemesAM2Impl.java index 52db9a3acd0..6a09f0e836a 100644 --- a/io.openems.edge.meter.artemes.am2/src/io/openems/edge/meter/artemes/am2/MeterArtemesAM2Impl.java +++ b/io.openems.edge.meter.artemes.am2/src/io/openems/edge/meter/artemes/am2/MeterArtemesAM2Impl.java @@ -17,6 +17,7 @@ import io.openems.common.channel.AccessMode; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.types.MeterType; import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent; import io.openems.edge.bridge.modbus.api.BridgeModbus; import io.openems.edge.bridge.modbus.api.ModbusComponent; @@ -32,7 +33,6 @@ import io.openems.edge.common.modbusslave.ModbusSlaveTable; import io.openems.edge.common.taskmanager.Priority; import io.openems.edge.meter.api.ElectricityMeter; -import io.openems.edge.meter.api.MeterType; @Designate(ocd = Config.class, factory = true) @Component(// diff --git a/io.openems.edge.meter.artemes.am2/test/io/openems/edge/meter/artemes/am2/MeterArtemesAM2ImplTest.java b/io.openems.edge.meter.artemes.am2/test/io/openems/edge/meter/artemes/am2/MeterArtemesAM2ImplTest.java index cc287731b19..139d9ebe3dd 100644 --- a/io.openems.edge.meter.artemes.am2/test/io/openems/edge/meter/artemes/am2/MeterArtemesAM2ImplTest.java +++ b/io.openems.edge.meter.artemes.am2/test/io/openems/edge/meter/artemes/am2/MeterArtemesAM2ImplTest.java @@ -1,26 +1,24 @@ package io.openems.edge.meter.artemes.am2; +import static io.openems.common.types.MeterType.GRID; + import org.junit.Test; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.meter.api.MeterType; public class MeterArtemesAM2ImplTest { - private static final String METER_ID = "meter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new MeterArtemesAM2Impl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // - .setType(MeterType.GRID) // + .setId("meter0") // + .setModbusId("modbus0") // + .setType(GRID) // .build()) // ; } diff --git a/io.openems.edge.meter.artemes.am2/test/io/openems/edge/meter/artemes/am2/MyConfig.java b/io.openems.edge.meter.artemes.am2/test/io/openems/edge/meter/artemes/am2/MyConfig.java index 2d1f906e3eb..8d115e37de7 100644 --- a/io.openems.edge.meter.artemes.am2/test/io/openems/edge/meter/artemes/am2/MyConfig.java +++ b/io.openems.edge.meter.artemes.am2/test/io/openems/edge/meter/artemes/am2/MyConfig.java @@ -1,8 +1,8 @@ package io.openems.edge.meter.artemes.am2; import io.openems.common.test.AbstractComponentConfig; +import io.openems.common.types.MeterType; import io.openems.common.utils.ConfigUtils; -import io.openems.edge.meter.api.MeterType; @SuppressWarnings("all") public class MyConfig extends AbstractComponentConfig implements Config { diff --git a/io.openems.edge.meter.bcontrol.em300/.classpath b/io.openems.edge.meter.bcontrol.em300/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.meter.bcontrol.em300/.classpath +++ b/io.openems.edge.meter.bcontrol.em300/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.meter.bcontrol.em300/src/io/openems/edge/meter/bcontrol/em300/Config.java b/io.openems.edge.meter.bcontrol.em300/src/io/openems/edge/meter/bcontrol/em300/Config.java index 8358d843460..eed040fa1f5 100644 --- a/io.openems.edge.meter.bcontrol.em300/src/io/openems/edge/meter/bcontrol/em300/Config.java +++ b/io.openems.edge.meter.bcontrol.em300/src/io/openems/edge/meter/bcontrol/em300/Config.java @@ -3,7 +3,7 @@ import org.osgi.service.metatype.annotations.AttributeDefinition; import org.osgi.service.metatype.annotations.ObjectClassDefinition; -import io.openems.edge.meter.api.MeterType; +import io.openems.common.types.MeterType; @ObjectClassDefinition(// name = "Meter B-Control EM300", // diff --git a/io.openems.edge.meter.bcontrol.em300/src/io/openems/edge/meter/bcontrol/em300/MeterBControlEM300Impl.java b/io.openems.edge.meter.bcontrol.em300/src/io/openems/edge/meter/bcontrol/em300/MeterBControlEM300Impl.java index 3cd11ad580e..dd80b522c6d 100644 --- a/io.openems.edge.meter.bcontrol.em300/src/io/openems/edge/meter/bcontrol/em300/MeterBControlEM300Impl.java +++ b/io.openems.edge.meter.bcontrol.em300/src/io/openems/edge/meter/bcontrol/em300/MeterBControlEM300Impl.java @@ -18,6 +18,7 @@ import io.openems.common.channel.AccessMode; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.types.MeterType; import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent; import io.openems.edge.bridge.modbus.api.BridgeModbus; import io.openems.edge.bridge.modbus.api.ModbusComponent; @@ -33,7 +34,6 @@ import io.openems.edge.common.modbusslave.ModbusSlaveTable; import io.openems.edge.common.taskmanager.Priority; import io.openems.edge.meter.api.ElectricityMeter; -import io.openems.edge.meter.api.MeterType; @Designate(ocd = Config.class, factory = true) @Component(// diff --git a/io.openems.edge.meter.bcontrol.em300/test/io/openems/edge/meter/bcontrol/em300/MeterBControlEM300ImplTest.java b/io.openems.edge.meter.bcontrol.em300/test/io/openems/edge/meter/bcontrol/em300/MeterBControlEM300ImplTest.java index 04513e888fc..ae620f78b1d 100644 --- a/io.openems.edge.meter.bcontrol.em300/test/io/openems/edge/meter/bcontrol/em300/MeterBControlEM300ImplTest.java +++ b/io.openems.edge.meter.bcontrol.em300/test/io/openems/edge/meter/bcontrol/em300/MeterBControlEM300ImplTest.java @@ -1,26 +1,24 @@ package io.openems.edge.meter.bcontrol.em300; +import static io.openems.common.types.MeterType.GRID; + import org.junit.Test; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.meter.api.MeterType; public class MeterBControlEM300ImplTest { - private static final String METER_ID = "meter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new MeterBControlEM300Impl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // - .setType(MeterType.GRID) // + .setId("meter0") // + .setModbusId("modbus0") // + .setType(GRID) // .build()) // ; } diff --git a/io.openems.edge.meter.bcontrol.em300/test/io/openems/edge/meter/bcontrol/em300/MyConfig.java b/io.openems.edge.meter.bcontrol.em300/test/io/openems/edge/meter/bcontrol/em300/MyConfig.java index 6b5871b6acb..d7fea6d3e1b 100644 --- a/io.openems.edge.meter.bcontrol.em300/test/io/openems/edge/meter/bcontrol/em300/MyConfig.java +++ b/io.openems.edge.meter.bcontrol.em300/test/io/openems/edge/meter/bcontrol/em300/MyConfig.java @@ -1,8 +1,8 @@ package io.openems.edge.meter.bcontrol.em300; import io.openems.common.test.AbstractComponentConfig; +import io.openems.common.types.MeterType; import io.openems.common.utils.ConfigUtils; -import io.openems.edge.meter.api.MeterType; @SuppressWarnings("all") public class MyConfig extends AbstractComponentConfig implements Config { diff --git a/io.openems.edge.meter.bgetech/.classpath b/io.openems.edge.meter.bgetech/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.meter.bgetech/.classpath +++ b/io.openems.edge.meter.bgetech/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.meter.bgetech/src/io/openems/edge/meter/bgetech/Config.java b/io.openems.edge.meter.bgetech/src/io/openems/edge/meter/bgetech/Config.java index e7dbd3e2af7..d5758485822 100644 --- a/io.openems.edge.meter.bgetech/src/io/openems/edge/meter/bgetech/Config.java +++ b/io.openems.edge.meter.bgetech/src/io/openems/edge/meter/bgetech/Config.java @@ -3,7 +3,7 @@ import org.osgi.service.metatype.annotations.AttributeDefinition; import org.osgi.service.metatype.annotations.ObjectClassDefinition; -import io.openems.edge.meter.api.MeterType; +import io.openems.common.types.MeterType; @ObjectClassDefinition(// name = "Meter B+G E-Tech DRT428M-2", // diff --git a/io.openems.edge.meter.bgetech/src/io/openems/edge/meter/bgetech/MeterBgeTechDrt428M2Impl.java b/io.openems.edge.meter.bgetech/src/io/openems/edge/meter/bgetech/MeterBgeTechDrt428M2Impl.java index 39b7909110c..891bc72d879 100644 --- a/io.openems.edge.meter.bgetech/src/io/openems/edge/meter/bgetech/MeterBgeTechDrt428M2Impl.java +++ b/io.openems.edge.meter.bgetech/src/io/openems/edge/meter/bgetech/MeterBgeTechDrt428M2Impl.java @@ -17,6 +17,7 @@ import io.openems.common.channel.AccessMode; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.types.MeterType; import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent; import io.openems.edge.bridge.modbus.api.BridgeModbus; import io.openems.edge.bridge.modbus.api.ModbusComponent; @@ -28,7 +29,6 @@ import io.openems.edge.common.modbusslave.ModbusSlaveTable; import io.openems.edge.common.taskmanager.Priority; import io.openems.edge.meter.api.ElectricityMeter; -import io.openems.edge.meter.api.MeterType; @Designate(ocd = Config.class, factory = true) @Component(// diff --git a/io.openems.edge.meter.bgetech/test/io/openems/edge/meter/bgetech/MeterBgeTechDrt428M2ImplTest.java b/io.openems.edge.meter.bgetech/test/io/openems/edge/meter/bgetech/MeterBgeTechDrt428M2ImplTest.java index bf6b31f1e20..3cdaaf33098 100644 --- a/io.openems.edge.meter.bgetech/test/io/openems/edge/meter/bgetech/MeterBgeTechDrt428M2ImplTest.java +++ b/io.openems.edge.meter.bgetech/test/io/openems/edge/meter/bgetech/MeterBgeTechDrt428M2ImplTest.java @@ -2,10 +2,11 @@ import org.junit.Test; +import io.openems.common.types.MeterType; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; +import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.meter.api.MeterType; public class MeterBgeTechDrt428M2ImplTest { @@ -22,6 +23,7 @@ public void testActivatePositive() throws Exception { .setModbusId(MODBUS_ID) // .setType(MeterType.GRID) // .build()) // - ; + .next(new TestCase()) // + .deactivate(); } } \ No newline at end of file diff --git a/io.openems.edge.meter.bgetech/test/io/openems/edge/meter/bgetech/MyConfig.java b/io.openems.edge.meter.bgetech/test/io/openems/edge/meter/bgetech/MyConfig.java index d8200980d6e..cad24e35282 100644 --- a/io.openems.edge.meter.bgetech/test/io/openems/edge/meter/bgetech/MyConfig.java +++ b/io.openems.edge.meter.bgetech/test/io/openems/edge/meter/bgetech/MyConfig.java @@ -1,8 +1,8 @@ package io.openems.edge.meter.bgetech; import io.openems.common.test.AbstractComponentConfig; +import io.openems.common.types.MeterType; import io.openems.common.utils.ConfigUtils; -import io.openems.edge.meter.api.MeterType; @SuppressWarnings("all") public class MyConfig extends AbstractComponentConfig implements Config { diff --git a/io.openems.edge.meter.camillebauer.aplus/.classpath b/io.openems.edge.meter.camillebauer.aplus/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.meter.camillebauer.aplus/.classpath +++ b/io.openems.edge.meter.camillebauer.aplus/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.meter.camillebauer.aplus/src/io/openems/edge/meter/camillebauer/aplus/Config.java b/io.openems.edge.meter.camillebauer.aplus/src/io/openems/edge/meter/camillebauer/aplus/Config.java index d5a72ae9cc2..013f474e3fd 100644 --- a/io.openems.edge.meter.camillebauer.aplus/src/io/openems/edge/meter/camillebauer/aplus/Config.java +++ b/io.openems.edge.meter.camillebauer.aplus/src/io/openems/edge/meter/camillebauer/aplus/Config.java @@ -3,7 +3,7 @@ import org.osgi.service.metatype.annotations.AttributeDefinition; import org.osgi.service.metatype.annotations.ObjectClassDefinition; -import io.openems.edge.meter.api.MeterType; +import io.openems.common.types.MeterType; @ObjectClassDefinition(// name = "Meter Camille Bauer APLUS", // diff --git a/io.openems.edge.meter.camillebauer.aplus/src/io/openems/edge/meter/camillebauer/aplus/MeterCamillebauerAplusImpl.java b/io.openems.edge.meter.camillebauer.aplus/src/io/openems/edge/meter/camillebauer/aplus/MeterCamillebauerAplusImpl.java index 77bd4a98c39..c5596bad717 100644 --- a/io.openems.edge.meter.camillebauer.aplus/src/io/openems/edge/meter/camillebauer/aplus/MeterCamillebauerAplusImpl.java +++ b/io.openems.edge.meter.camillebauer.aplus/src/io/openems/edge/meter/camillebauer/aplus/MeterCamillebauerAplusImpl.java @@ -21,6 +21,7 @@ import io.openems.common.channel.AccessMode; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.types.MeterType; import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent; import io.openems.edge.bridge.modbus.api.BridgeModbus; import io.openems.edge.bridge.modbus.api.ModbusComponent; @@ -35,7 +36,6 @@ import io.openems.edge.common.modbusslave.ModbusSlaveTable; import io.openems.edge.common.taskmanager.Priority; import io.openems.edge.meter.api.ElectricityMeter; -import io.openems.edge.meter.api.MeterType; import io.openems.edge.timedata.api.Timedata; import io.openems.edge.timedata.api.TimedataProvider; import io.openems.edge.timedata.api.utils.CalculateEnergyFromPower; diff --git a/io.openems.edge.meter.camillebauer.aplus/test/io/openems/edge/meter/camillebauer/aplus/MeterCamillebauerAplusImplTest.java b/io.openems.edge.meter.camillebauer.aplus/test/io/openems/edge/meter/camillebauer/aplus/MeterCamillebauerAplusImplTest.java index a6ad0bff683..00a6eac5e63 100644 --- a/io.openems.edge.meter.camillebauer.aplus/test/io/openems/edge/meter/camillebauer/aplus/MeterCamillebauerAplusImplTest.java +++ b/io.openems.edge.meter.camillebauer.aplus/test/io/openems/edge/meter/camillebauer/aplus/MeterCamillebauerAplusImplTest.java @@ -1,27 +1,27 @@ package io.openems.edge.meter.camillebauer.aplus; +import static io.openems.common.types.MeterType.GRID; + import org.junit.Test; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.meter.api.MeterType; public class MeterCamillebauerAplusImplTest { - private static final String COMPONENT_ID = "component0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new MeterCamillebauerAplusImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // - .setModbusId(MODBUS_ID) // - .setMeterType(MeterType.GRID).setInvert(false).build()) + .setId("component0") // + .setModbusId("modbus0") // + .setMeterType(GRID) // + .setInvert(false) // + .build()) .next(new TestCase()); } diff --git a/io.openems.edge.meter.camillebauer.aplus/test/io/openems/edge/meter/camillebauer/aplus/MyConfig.java b/io.openems.edge.meter.camillebauer.aplus/test/io/openems/edge/meter/camillebauer/aplus/MyConfig.java index 7f53bea4a47..cb5f6b2612a 100644 --- a/io.openems.edge.meter.camillebauer.aplus/test/io/openems/edge/meter/camillebauer/aplus/MyConfig.java +++ b/io.openems.edge.meter.camillebauer.aplus/test/io/openems/edge/meter/camillebauer/aplus/MyConfig.java @@ -1,8 +1,8 @@ package io.openems.edge.meter.camillebauer.aplus; import io.openems.common.test.AbstractComponentConfig; +import io.openems.common.types.MeterType; import io.openems.common.utils.ConfigUtils; -import io.openems.edge.meter.api.MeterType; @SuppressWarnings("all") public class MyConfig extends AbstractComponentConfig implements Config { diff --git a/io.openems.edge.meter.carlo.gavazzi.em300/.classpath b/io.openems.edge.meter.carlo.gavazzi.em300/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.meter.carlo.gavazzi.em300/.classpath +++ b/io.openems.edge.meter.carlo.gavazzi.em300/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.meter.carlo.gavazzi.em300/src/io/openems/edge/meter/carlo/gavazzi/em300/Config.java b/io.openems.edge.meter.carlo.gavazzi.em300/src/io/openems/edge/meter/carlo/gavazzi/em300/Config.java index 2f90c6c6014..9ca7fffec9a 100644 --- a/io.openems.edge.meter.carlo.gavazzi.em300/src/io/openems/edge/meter/carlo/gavazzi/em300/Config.java +++ b/io.openems.edge.meter.carlo.gavazzi.em300/src/io/openems/edge/meter/carlo/gavazzi/em300/Config.java @@ -3,7 +3,7 @@ import org.osgi.service.metatype.annotations.AttributeDefinition; import org.osgi.service.metatype.annotations.ObjectClassDefinition; -import io.openems.edge.meter.api.MeterType; +import io.openems.common.types.MeterType; @ObjectClassDefinition(// name = "Meter Carlo Gavazzi EM300", // diff --git a/io.openems.edge.meter.carlo.gavazzi.em300/src/io/openems/edge/meter/carlo/gavazzi/em300/MeterCarloGavazziEm300Impl.java b/io.openems.edge.meter.carlo.gavazzi.em300/src/io/openems/edge/meter/carlo/gavazzi/em300/MeterCarloGavazziEm300Impl.java index d5aea0a4d23..4e536e4e94d 100644 --- a/io.openems.edge.meter.carlo.gavazzi.em300/src/io/openems/edge/meter/carlo/gavazzi/em300/MeterCarloGavazziEm300Impl.java +++ b/io.openems.edge.meter.carlo.gavazzi.em300/src/io/openems/edge/meter/carlo/gavazzi/em300/MeterCarloGavazziEm300Impl.java @@ -16,6 +16,7 @@ import org.osgi.service.metatype.annotations.Designate; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.types.MeterType; import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent; import io.openems.edge.bridge.modbus.api.BridgeModbus; import io.openems.edge.bridge.modbus.api.ModbusComponent; @@ -28,7 +29,6 @@ import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.taskmanager.Priority; import io.openems.edge.meter.api.ElectricityMeter; -import io.openems.edge.meter.api.MeterType; @Designate(ocd = Config.class, factory = true) @Component(// @@ -116,14 +116,11 @@ protected ModbusProtocol defineModbusProtocol() { SCALE_FACTOR_2)), new FC4ReadInputRegistersTask(300013 - offset, Priority.HIGH, // m(ElectricityMeter.ChannelId.CURRENT_L1, - new SignedDoublewordElement(300013 - offset).wordOrder(WordOrder.LSWMSW), - SCALE_FACTOR_2), + new SignedDoublewordElement(300013 - offset).wordOrder(WordOrder.LSWMSW)), m(ElectricityMeter.ChannelId.CURRENT_L2, - new SignedDoublewordElement(300015 - offset).wordOrder(WordOrder.LSWMSW), - SCALE_FACTOR_2), + new SignedDoublewordElement(300015 - offset).wordOrder(WordOrder.LSWMSW)), m(ElectricityMeter.ChannelId.CURRENT_L3, - new SignedDoublewordElement(300017 - offset).wordOrder(WordOrder.LSWMSW), - SCALE_FACTOR_2), + new SignedDoublewordElement(300017 - offset).wordOrder(WordOrder.LSWMSW)), m(ElectricityMeter.ChannelId.ACTIVE_POWER_L1, new SignedDoublewordElement(300019 - offset).wordOrder(WordOrder.LSWMSW), SCALE_FACTOR_MINUS_1_AND_INVERT_IF_TRUE(this.config.invert())), diff --git a/io.openems.edge.meter.carlo.gavazzi.em300/test/io/openems/edge/meter/carlo/gavazzi/em300/MeterCarloGavazziEm300ImplTest.java b/io.openems.edge.meter.carlo.gavazzi.em300/test/io/openems/edge/meter/carlo/gavazzi/em300/MeterCarloGavazziEm300ImplTest.java index c628829fba6..4cdb50a76d2 100644 --- a/io.openems.edge.meter.carlo.gavazzi.em300/test/io/openems/edge/meter/carlo/gavazzi/em300/MeterCarloGavazziEm300ImplTest.java +++ b/io.openems.edge.meter.carlo.gavazzi.em300/test/io/openems/edge/meter/carlo/gavazzi/em300/MeterCarloGavazziEm300ImplTest.java @@ -1,27 +1,92 @@ package io.openems.edge.meter.carlo.gavazzi.em300; +import static io.openems.common.test.TestUtils.createDummyClock; +import static io.openems.common.types.MeterType.GRID; + import org.junit.Test; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; +import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; +import io.openems.edge.common.test.DummyComponentManager; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.meter.api.MeterType; +import io.openems.edge.meter.api.ElectricityMeter; public class MeterCarloGavazziEm300ImplTest { - private static final String METER_ID = "meter0"; - private static final String MODBUS_ID = "modbus0"; + final int offset = 300000 + 1; @Test public void test() throws Exception { new ComponentTest(new MeterCarloGavazziEm300Impl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // - .setType(MeterType.GRID) // + .setId("meter0") // + .setModbusId("modbus0") // + .setType(GRID) // .build()) // ; } + + @Test + public void testReadFromModbus() throws Exception { + var sut = new MeterCarloGavazziEm300Impl(); + new ComponentTest(sut) // + .addReference("cm", new DummyConfigurationAdmin()) // + .addComponent(new DummyComponentManager(createDummyClock())) + .addReference("setModbus", new DummyModbusBridge("modbus2") // + .withInputRegisters(300001 - this.offset, // + new int[] { 0x08c3, 0x0000, // + 0x0000, 0x0000, // + 0x0000, 0x0000 }) // + .withInputRegisters(300013 - this.offset, // + new int[] { 0x1102, 0x0000, // + 0x0000, 0x0000, // + 0x0000, 0x0000, // + 0x261c, 0x0000, // + 0x0000, 0x0000, // + 0x0000, 0x0000, // + 0x261d, 0x0000, // + 0x0000, 0x0000, // + 0x0000, 0x0000, // + 0x0090, 0x0000, // + 0x0000, 0x0000, // + 0x0000, 0x0000, // + 0x02eb, 0x0000, // + 0x0000, 0x0000, // + 0x261c, 0x0000, // + 0x261d, 0x0000, // + 0x0090, 0x0000 }) // + ).activate(MyConfig.create() // + .setId("meter0") // + .setModbusId("modbus2") // + .setModbusUnitId(1) // + .setType(GRID) // + .setInvert(false) // + .build()) // + .next(new TestCase() // + .output(ElectricityMeter.ChannelId.VOLTAGE_L1, 224300) // + .output(ElectricityMeter.ChannelId.VOLTAGE_L2, 0) // + .output(ElectricityMeter.ChannelId.VOLTAGE_L3, 0) // + // + .output(ElectricityMeter.ChannelId.CURRENT_L1, 4354) // + .output(ElectricityMeter.ChannelId.CURRENT_L2, 0) // + .output(ElectricityMeter.ChannelId.CURRENT_L3, 0) // + .output(ElectricityMeter.ChannelId.ACTIVE_POWER_L1, 976) // + .output(ElectricityMeter.ChannelId.ACTIVE_POWER_L2, 0) // + .output(ElectricityMeter.ChannelId.ACTIVE_POWER_L3, 0) // + .output(MeterCarloGavazziEm300.ChannelId.APPARENT_POWER_L1, 976) // + .output(MeterCarloGavazziEm300.ChannelId.APPARENT_POWER_L2, 0) // + .output(MeterCarloGavazziEm300.ChannelId.APPARENT_POWER_L3, 0) // + .output(ElectricityMeter.ChannelId.REACTIVE_POWER_L1, 14) // + .output(ElectricityMeter.ChannelId.REACTIVE_POWER_L2, 0) // + .output(ElectricityMeter.ChannelId.REACTIVE_POWER_L3, 0) // + // + .output(ElectricityMeter.ChannelId.ACTIVE_POWER, 976) // + .output(MeterCarloGavazziEm300.ChannelId.APPARENT_POWER, 976) // + .output(ElectricityMeter.ChannelId.REACTIVE_POWER, 14) // + ) // + .deactivate(); + } } \ No newline at end of file diff --git a/io.openems.edge.meter.carlo.gavazzi.em300/test/io/openems/edge/meter/carlo/gavazzi/em300/MyConfig.java b/io.openems.edge.meter.carlo.gavazzi.em300/test/io/openems/edge/meter/carlo/gavazzi/em300/MyConfig.java index df5f25f8945..c3f7691f7e9 100644 --- a/io.openems.edge.meter.carlo.gavazzi.em300/test/io/openems/edge/meter/carlo/gavazzi/em300/MyConfig.java +++ b/io.openems.edge.meter.carlo.gavazzi.em300/test/io/openems/edge/meter/carlo/gavazzi/em300/MyConfig.java @@ -1,8 +1,8 @@ package io.openems.edge.meter.carlo.gavazzi.em300; import io.openems.common.test.AbstractComponentConfig; +import io.openems.common.types.MeterType; import io.openems.common.utils.ConfigUtils; -import io.openems.edge.meter.api.MeterType; @SuppressWarnings("all") public class MyConfig extends AbstractComponentConfig implements Config { @@ -32,6 +32,11 @@ public Builder setType(MeterType type) { return this; } + public Builder setModbusUnitId(int unitId) { + this.modbusUnitId = unitId; + return this; + } + public Builder setInvert(boolean invert) { this.invert = invert; return this; diff --git a/io.openems.edge.meter.discovergy/.classpath b/io.openems.edge.meter.discovergy/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.meter.discovergy/.classpath +++ b/io.openems.edge.meter.discovergy/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.meter.discovergy/src/io/openems/edge/meter/discovergy/Config.java b/io.openems.edge.meter.discovergy/src/io/openems/edge/meter/discovergy/Config.java index 1e99efd16a5..9b158635fe3 100644 --- a/io.openems.edge.meter.discovergy/src/io/openems/edge/meter/discovergy/Config.java +++ b/io.openems.edge.meter.discovergy/src/io/openems/edge/meter/discovergy/Config.java @@ -4,7 +4,7 @@ import org.osgi.service.metatype.annotations.AttributeType; import org.osgi.service.metatype.annotations.ObjectClassDefinition; -import io.openems.edge.meter.api.MeterType; +import io.openems.common.types.MeterType; @ObjectClassDefinition(// name = "Meter Discovergy", // diff --git a/io.openems.edge.meter.discovergy/src/io/openems/edge/meter/discovergy/DiscovergyApiClient.java b/io.openems.edge.meter.discovergy/src/io/openems/edge/meter/discovergy/DiscovergyApiClient.java index 34160ecb35a..e036aff0687 100644 --- a/io.openems.edge.meter.discovergy/src/io/openems/edge/meter/discovergy/DiscovergyApiClient.java +++ b/io.openems.edge.meter.discovergy/src/io/openems/edge/meter/discovergy/DiscovergyApiClient.java @@ -4,7 +4,7 @@ import java.io.IOException; import java.io.InputStreamReader; import java.net.HttpURLConnection; -import java.net.URL; +import java.net.URI; import java.util.Arrays; import java.util.Base64; import java.util.stream.Collectors; @@ -93,7 +93,8 @@ public JsonObject getLastReading(String meterId, Field... fields) throws Openems */ private JsonElement sendGetRequest(String endpoint) throws OpenemsNamedException { try { - var url = new URL(BASE_URL + endpoint); + var uri = URI.create(BASE_URL + endpoint); + var url = uri.toURL(); var con = (HttpURLConnection) url.openConnection(); con.setRequestProperty("Authorization", this.authorizationHeader); con.setRequestMethod("GET"); diff --git a/io.openems.edge.meter.discovergy/src/io/openems/edge/meter/discovergy/DiscovergyWorker.java b/io.openems.edge.meter.discovergy/src/io/openems/edge/meter/discovergy/DiscovergyWorker.java index 3be60cedf0d..8f78e4ff125 100644 --- a/io.openems.edge.meter.discovergy/src/io/openems/edge/meter/discovergy/DiscovergyWorker.java +++ b/io.openems.edge.meter.discovergy/src/io/openems/edge/meter/discovergy/DiscovergyWorker.java @@ -136,6 +136,7 @@ protected void forever() throws OpenemsNamedException { break; case CONSUMPTION_NOT_METERED: // to be validated case CONSUMPTION_METERED: // to be validated + case MANAGED_CONSUMPTION_METERED: case PRODUCTION_AND_CONSUMPTION: case PRODUCTION: this.parent._setActivePower(TypeUtils.multiply(activePower, -1)); // invert diff --git a/io.openems.edge.meter.discovergy/src/io/openems/edge/meter/discovergy/MeterDiscovergyImpl.java b/io.openems.edge.meter.discovergy/src/io/openems/edge/meter/discovergy/MeterDiscovergyImpl.java index 24884b6d2d6..c69107723a9 100644 --- a/io.openems.edge.meter.discovergy/src/io/openems/edge/meter/discovergy/MeterDiscovergyImpl.java +++ b/io.openems.edge.meter.discovergy/src/io/openems/edge/meter/discovergy/MeterDiscovergyImpl.java @@ -17,6 +17,7 @@ import com.google.gson.JsonElement; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.types.MeterType; import io.openems.common.utils.JsonUtils; import io.openems.edge.common.component.AbstractOpenemsComponent; import io.openems.edge.common.component.OpenemsComponent; @@ -26,7 +27,6 @@ import io.openems.edge.common.jsonapi.JsonApiBuilder; import io.openems.edge.common.user.User; import io.openems.edge.meter.api.ElectricityMeter; -import io.openems.edge.meter.api.MeterType; import io.openems.edge.meter.discovergy.jsonrpc.Field; import io.openems.edge.meter.discovergy.jsonrpc.GetFieldNamesRequest; import io.openems.edge.meter.discovergy.jsonrpc.GetFieldNamesResponse; diff --git a/io.openems.edge.meter.discovergy/test/io/openems/edge/meter/discovergy/MeterDiscovergyImplTest.java b/io.openems.edge.meter.discovergy/test/io/openems/edge/meter/discovergy/MeterDiscovergyImplTest.java index 5ae02ad71c1..90dbcfe4cd6 100644 --- a/io.openems.edge.meter.discovergy/test/io/openems/edge/meter/discovergy/MeterDiscovergyImplTest.java +++ b/io.openems.edge.meter.discovergy/test/io/openems/edge/meter/discovergy/MeterDiscovergyImplTest.java @@ -1,20 +1,19 @@ package io.openems.edge.meter.discovergy; +import static io.openems.common.types.MeterType.GRID; + import org.junit.Test; import io.openems.edge.common.test.ComponentTest; -import io.openems.edge.meter.api.MeterType; public class MeterDiscovergyImplTest { - private static final String COMPONENT_ID = "meter0"; - @Test public void test() throws Exception { new ComponentTest(new MeterDiscovergyImpl()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // - .setType(MeterType.GRID) // + .setId("meter0") // + .setType(GRID) // .setPassword("xxx") // .setEmail("x@y.z") // .setSerialNumber("12345678") // diff --git a/io.openems.edge.meter.discovergy/test/io/openems/edge/meter/discovergy/MyConfig.java b/io.openems.edge.meter.discovergy/test/io/openems/edge/meter/discovergy/MyConfig.java index f3010303879..5f0c14b9ee4 100644 --- a/io.openems.edge.meter.discovergy/test/io/openems/edge/meter/discovergy/MyConfig.java +++ b/io.openems.edge.meter.discovergy/test/io/openems/edge/meter/discovergy/MyConfig.java @@ -1,7 +1,7 @@ package io.openems.edge.meter.discovergy; import io.openems.common.test.AbstractComponentConfig; -import io.openems.edge.meter.api.MeterType; +import io.openems.common.types.MeterType; @SuppressWarnings("all") public class MyConfig extends AbstractComponentConfig implements Config { diff --git a/io.openems.edge.meter.eastron/.classpath b/io.openems.edge.meter.eastron/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.meter.eastron/.classpath +++ b/io.openems.edge.meter.eastron/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.meter.eastron/src/io/openems/edge/meter/eastron/sdm120/Config.java b/io.openems.edge.meter.eastron/src/io/openems/edge/meter/eastron/sdm120/Config.java index 1800860d9c5..66b6e54879d 100644 --- a/io.openems.edge.meter.eastron/src/io/openems/edge/meter/eastron/sdm120/Config.java +++ b/io.openems.edge.meter.eastron/src/io/openems/edge/meter/eastron/sdm120/Config.java @@ -3,7 +3,7 @@ import org.osgi.service.metatype.annotations.AttributeDefinition; import org.osgi.service.metatype.annotations.ObjectClassDefinition; -import io.openems.edge.meter.api.MeterType; +import io.openems.common.types.MeterType; import io.openems.edge.meter.api.SinglePhase; @ObjectClassDefinition(name = "Meter Eastron SDM 120", // diff --git a/io.openems.edge.meter.eastron/src/io/openems/edge/meter/eastron/sdm120/MeterEastronSdm120Impl.java b/io.openems.edge.meter.eastron/src/io/openems/edge/meter/eastron/sdm120/MeterEastronSdm120Impl.java index 3667375bb8b..ba24998b1c5 100644 --- a/io.openems.edge.meter.eastron/src/io/openems/edge/meter/eastron/sdm120/MeterEastronSdm120Impl.java +++ b/io.openems.edge.meter.eastron/src/io/openems/edge/meter/eastron/sdm120/MeterEastronSdm120Impl.java @@ -21,6 +21,7 @@ import io.openems.common.channel.AccessMode; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.types.MeterType; import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent; import io.openems.edge.bridge.modbus.api.BridgeModbus; import io.openems.edge.bridge.modbus.api.ModbusComponent; @@ -34,7 +35,6 @@ import io.openems.edge.common.modbusslave.ModbusSlaveTable; import io.openems.edge.common.taskmanager.Priority; import io.openems.edge.meter.api.ElectricityMeter; -import io.openems.edge.meter.api.MeterType; import io.openems.edge.meter.api.SinglePhase; import io.openems.edge.meter.api.SinglePhaseMeter; import io.openems.edge.timedata.api.Timedata; diff --git a/io.openems.edge.meter.eastron/src/io/openems/edge/meter/eastron/sdm630/Config.java b/io.openems.edge.meter.eastron/src/io/openems/edge/meter/eastron/sdm630/Config.java index 072294858f1..371d25772ee 100644 --- a/io.openems.edge.meter.eastron/src/io/openems/edge/meter/eastron/sdm630/Config.java +++ b/io.openems.edge.meter.eastron/src/io/openems/edge/meter/eastron/sdm630/Config.java @@ -3,7 +3,7 @@ import org.osgi.service.metatype.annotations.AttributeDefinition; import org.osgi.service.metatype.annotations.ObjectClassDefinition; -import io.openems.edge.meter.api.MeterType; +import io.openems.common.types.MeterType; @ObjectClassDefinition(name = "Meter Eastron SDM 630", // description = "Implements the Eastron SDM630 meter.") diff --git a/io.openems.edge.meter.eastron/src/io/openems/edge/meter/eastron/sdm630/MeterEastronSdm630Impl.java b/io.openems.edge.meter.eastron/src/io/openems/edge/meter/eastron/sdm630/MeterEastronSdm630Impl.java index b636a7f42da..0c215c37ed3 100644 --- a/io.openems.edge.meter.eastron/src/io/openems/edge/meter/eastron/sdm630/MeterEastronSdm630Impl.java +++ b/io.openems.edge.meter.eastron/src/io/openems/edge/meter/eastron/sdm630/MeterEastronSdm630Impl.java @@ -22,6 +22,7 @@ import io.openems.common.channel.AccessMode; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.types.MeterType; import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent; import io.openems.edge.bridge.modbus.api.BridgeModbus; import io.openems.edge.bridge.modbus.api.ModbusComponent; @@ -36,7 +37,6 @@ import io.openems.edge.common.modbusslave.ModbusSlaveTable; import io.openems.edge.common.taskmanager.Priority; import io.openems.edge.meter.api.ElectricityMeter; -import io.openems.edge.meter.api.MeterType; import io.openems.edge.timedata.api.Timedata; import io.openems.edge.timedata.api.TimedataProvider; import io.openems.edge.timedata.api.utils.CalculateEnergyFromPower; diff --git a/io.openems.edge.meter.eastron/test/io/openems/edge/meter/eastron/sdm120/MeterEastronSdm120ImplTest.java b/io.openems.edge.meter.eastron/test/io/openems/edge/meter/eastron/sdm120/MeterEastronSdm120ImplTest.java index 169778c8b20..050d8ac26b9 100644 --- a/io.openems.edge.meter.eastron/test/io/openems/edge/meter/eastron/sdm120/MeterEastronSdm120ImplTest.java +++ b/io.openems.edge.meter.eastron/test/io/openems/edge/meter/eastron/sdm120/MeterEastronSdm120ImplTest.java @@ -1,28 +1,26 @@ package io.openems.edge.meter.eastron.sdm120; +import static io.openems.common.types.MeterType.GRID; +import static io.openems.edge.meter.api.SinglePhase.L1; + import org.junit.Test; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.meter.api.MeterType; -import io.openems.edge.meter.api.SinglePhase; public class MeterEastronSdm120ImplTest { - private static final String METER_ID = "meter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new MeterEastronSdm120Impl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // - .setType(MeterType.GRID) // - .setPhase(SinglePhase.L1) // + .setId("meter0") // + .setModbusId("modbus0") // + .setType(GRID) // + .setPhase(L1) // .build()) // ; } diff --git a/io.openems.edge.meter.eastron/test/io/openems/edge/meter/eastron/sdm120/MyConfig.java b/io.openems.edge.meter.eastron/test/io/openems/edge/meter/eastron/sdm120/MyConfig.java index f1e83072c87..10cbce9a833 100644 --- a/io.openems.edge.meter.eastron/test/io/openems/edge/meter/eastron/sdm120/MyConfig.java +++ b/io.openems.edge.meter.eastron/test/io/openems/edge/meter/eastron/sdm120/MyConfig.java @@ -1,8 +1,8 @@ package io.openems.edge.meter.eastron.sdm120; import io.openems.common.test.AbstractComponentConfig; +import io.openems.common.types.MeterType; import io.openems.common.utils.ConfigUtils; -import io.openems.edge.meter.api.MeterType; import io.openems.edge.meter.api.SinglePhase; @SuppressWarnings("all") diff --git a/io.openems.edge.meter.eastron/test/io/openems/edge/meter/eastron/sdm630/MeterEastronSdm630ImplTest.java b/io.openems.edge.meter.eastron/test/io/openems/edge/meter/eastron/sdm630/MeterEastronSdm630ImplTest.java index d79ef87a631..e291fe7ebcd 100644 --- a/io.openems.edge.meter.eastron/test/io/openems/edge/meter/eastron/sdm630/MeterEastronSdm630ImplTest.java +++ b/io.openems.edge.meter.eastron/test/io/openems/edge/meter/eastron/sdm630/MeterEastronSdm630ImplTest.java @@ -1,26 +1,24 @@ package io.openems.edge.meter.eastron.sdm630; +import static io.openems.common.types.MeterType.GRID; + import org.junit.Test; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.meter.api.MeterType; public class MeterEastronSdm630ImplTest { - private static final String METER_ID = "meter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new MeterEastronSdm630Impl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // - .setType(MeterType.GRID) // + .setId("meter0") // + .setModbusId("modbus0") // + .setType(GRID) // .build()) // ; } diff --git a/io.openems.edge.meter.eastron/test/io/openems/edge/meter/eastron/sdm630/MyConfig.java b/io.openems.edge.meter.eastron/test/io/openems/edge/meter/eastron/sdm630/MyConfig.java index d686ca017c8..ab2389f95ea 100644 --- a/io.openems.edge.meter.eastron/test/io/openems/edge/meter/eastron/sdm630/MyConfig.java +++ b/io.openems.edge.meter.eastron/test/io/openems/edge/meter/eastron/sdm630/MyConfig.java @@ -1,8 +1,8 @@ package io.openems.edge.meter.eastron.sdm630; import io.openems.common.test.AbstractComponentConfig; +import io.openems.common.types.MeterType; import io.openems.common.utils.ConfigUtils; -import io.openems.edge.meter.api.MeterType; @SuppressWarnings("all") public class MyConfig extends AbstractComponentConfig implements Config { diff --git a/io.openems.edge.meter.janitza/.classpath b/io.openems.edge.meter.janitza/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.meter.janitza/.classpath +++ b/io.openems.edge.meter.janitza/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.meter.janitza/src/io/openems/edge/meter/janitza/umg511/Config.java b/io.openems.edge.meter.janitza/src/io/openems/edge/meter/janitza/umg511/Config.java index 74cffce7ed0..bc652b3732e 100644 --- a/io.openems.edge.meter.janitza/src/io/openems/edge/meter/janitza/umg511/Config.java +++ b/io.openems.edge.meter.janitza/src/io/openems/edge/meter/janitza/umg511/Config.java @@ -3,7 +3,7 @@ import org.osgi.service.metatype.annotations.AttributeDefinition; import org.osgi.service.metatype.annotations.ObjectClassDefinition; -import io.openems.edge.meter.api.MeterType; +import io.openems.common.types.MeterType; @ObjectClassDefinition(// name = "Meter Janitza UMG 511", // diff --git a/io.openems.edge.meter.janitza/src/io/openems/edge/meter/janitza/umg511/MeterJanitzaUmg511Impl.java b/io.openems.edge.meter.janitza/src/io/openems/edge/meter/janitza/umg511/MeterJanitzaUmg511Impl.java index b4ad5712068..6e79361ce31 100644 --- a/io.openems.edge.meter.janitza/src/io/openems/edge/meter/janitza/umg511/MeterJanitzaUmg511Impl.java +++ b/io.openems.edge.meter.janitza/src/io/openems/edge/meter/janitza/umg511/MeterJanitzaUmg511Impl.java @@ -17,6 +17,7 @@ import io.openems.common.channel.AccessMode; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.types.MeterType; import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent; import io.openems.edge.bridge.modbus.api.BridgeModbus; import io.openems.edge.bridge.modbus.api.ModbusComponent; @@ -29,7 +30,6 @@ import io.openems.edge.common.modbusslave.ModbusSlaveTable; import io.openems.edge.common.taskmanager.Priority; import io.openems.edge.meter.api.ElectricityMeter; -import io.openems.edge.meter.api.MeterType; /** * Implements the Janitza UMG 511 power analyzer. diff --git a/io.openems.edge.meter.janitza/src/io/openems/edge/meter/janitza/umg604/Config.java b/io.openems.edge.meter.janitza/src/io/openems/edge/meter/janitza/umg604/Config.java index cc79c8d9742..ef53697d991 100644 --- a/io.openems.edge.meter.janitza/src/io/openems/edge/meter/janitza/umg604/Config.java +++ b/io.openems.edge.meter.janitza/src/io/openems/edge/meter/janitza/umg604/Config.java @@ -3,7 +3,7 @@ import org.osgi.service.metatype.annotations.AttributeDefinition; import org.osgi.service.metatype.annotations.ObjectClassDefinition; -import io.openems.edge.meter.api.MeterType; +import io.openems.common.types.MeterType; @ObjectClassDefinition(// name = "Meter Janitza UMG 604", // diff --git a/io.openems.edge.meter.janitza/src/io/openems/edge/meter/janitza/umg604/MeterJanitzaUmg604Impl.java b/io.openems.edge.meter.janitza/src/io/openems/edge/meter/janitza/umg604/MeterJanitzaUmg604Impl.java index 95fa44bd68f..d2ea696abb4 100644 --- a/io.openems.edge.meter.janitza/src/io/openems/edge/meter/janitza/umg604/MeterJanitzaUmg604Impl.java +++ b/io.openems.edge.meter.janitza/src/io/openems/edge/meter/janitza/umg604/MeterJanitzaUmg604Impl.java @@ -17,6 +17,7 @@ import io.openems.common.channel.AccessMode; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.types.MeterType; import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent; import io.openems.edge.bridge.modbus.api.BridgeModbus; import io.openems.edge.bridge.modbus.api.ModbusComponent; @@ -29,7 +30,6 @@ import io.openems.edge.common.modbusslave.ModbusSlaveTable; import io.openems.edge.common.taskmanager.Priority; import io.openems.edge.meter.api.ElectricityMeter; -import io.openems.edge.meter.api.MeterType; /** * Implements the Janitza UMG 604 power analyzer. diff --git a/io.openems.edge.meter.janitza/src/io/openems/edge/meter/janitza/umg96rme/Config.java b/io.openems.edge.meter.janitza/src/io/openems/edge/meter/janitza/umg96rme/Config.java index aad641796ec..e64b3dd8b6b 100644 --- a/io.openems.edge.meter.janitza/src/io/openems/edge/meter/janitza/umg96rme/Config.java +++ b/io.openems.edge.meter.janitza/src/io/openems/edge/meter/janitza/umg96rme/Config.java @@ -3,7 +3,7 @@ import org.osgi.service.metatype.annotations.AttributeDefinition; import org.osgi.service.metatype.annotations.ObjectClassDefinition; -import io.openems.edge.meter.api.MeterType; +import io.openems.common.types.MeterType; @ObjectClassDefinition(// name = "Meter Janitza UMG 96RM-E", // diff --git a/io.openems.edge.meter.janitza/src/io/openems/edge/meter/janitza/umg96rme/MeterJanitzaUmg96rmeImpl.java b/io.openems.edge.meter.janitza/src/io/openems/edge/meter/janitza/umg96rme/MeterJanitzaUmg96rmeImpl.java index 7b9f91291a4..5af9f905a30 100644 --- a/io.openems.edge.meter.janitza/src/io/openems/edge/meter/janitza/umg96rme/MeterJanitzaUmg96rmeImpl.java +++ b/io.openems.edge.meter.janitza/src/io/openems/edge/meter/janitza/umg96rme/MeterJanitzaUmg96rmeImpl.java @@ -17,6 +17,7 @@ import io.openems.common.channel.AccessMode; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.types.MeterType; import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent; import io.openems.edge.bridge.modbus.api.BridgeModbus; import io.openems.edge.bridge.modbus.api.ModbusComponent; @@ -29,7 +30,6 @@ import io.openems.edge.common.modbusslave.ModbusSlaveTable; import io.openems.edge.common.taskmanager.Priority; import io.openems.edge.meter.api.ElectricityMeter; -import io.openems.edge.meter.api.MeterType; /** * Implements the Janitza UMG 96RM-E power analyzer. diff --git a/io.openems.edge.meter.janitza/test/io/openems/edge/meter/janitza/umg511/MeterJanitzaUmg511ImplTest.java b/io.openems.edge.meter.janitza/test/io/openems/edge/meter/janitza/umg511/MeterJanitzaUmg511ImplTest.java index 852686d87d1..3cc1195ff82 100644 --- a/io.openems.edge.meter.janitza/test/io/openems/edge/meter/janitza/umg511/MeterJanitzaUmg511ImplTest.java +++ b/io.openems.edge.meter.janitza/test/io/openems/edge/meter/janitza/umg511/MeterJanitzaUmg511ImplTest.java @@ -1,26 +1,24 @@ package io.openems.edge.meter.janitza.umg511; +import static io.openems.common.types.MeterType.GRID; + import org.junit.Test; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.meter.api.MeterType; public class MeterJanitzaUmg511ImplTest { - private static final String METER_ID = "meter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new MeterJanitzaUmg511Impl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // - .setType(MeterType.GRID) // + .setId("meter0") // + .setModbusId("modbus0") // + .setType(GRID) // .build()) // ; } diff --git a/io.openems.edge.meter.janitza/test/io/openems/edge/meter/janitza/umg511/MyConfig.java b/io.openems.edge.meter.janitza/test/io/openems/edge/meter/janitza/umg511/MyConfig.java index ded5dd84e28..56ed0452b5b 100644 --- a/io.openems.edge.meter.janitza/test/io/openems/edge/meter/janitza/umg511/MyConfig.java +++ b/io.openems.edge.meter.janitza/test/io/openems/edge/meter/janitza/umg511/MyConfig.java @@ -1,8 +1,8 @@ package io.openems.edge.meter.janitza.umg511; import io.openems.common.test.AbstractComponentConfig; +import io.openems.common.types.MeterType; import io.openems.common.utils.ConfigUtils; -import io.openems.edge.meter.api.MeterType; @SuppressWarnings("all") public class MyConfig extends AbstractComponentConfig implements Config { diff --git a/io.openems.edge.meter.janitza/test/io/openems/edge/meter/janitza/umg604/MeterJanitzaUmg604ImplTest.java b/io.openems.edge.meter.janitza/test/io/openems/edge/meter/janitza/umg604/MeterJanitzaUmg604ImplTest.java index 22fe623eb16..f309c6be3d1 100644 --- a/io.openems.edge.meter.janitza/test/io/openems/edge/meter/janitza/umg604/MeterJanitzaUmg604ImplTest.java +++ b/io.openems.edge.meter.janitza/test/io/openems/edge/meter/janitza/umg604/MeterJanitzaUmg604ImplTest.java @@ -1,26 +1,24 @@ package io.openems.edge.meter.janitza.umg604; +import static io.openems.common.types.MeterType.GRID; + import org.junit.Test; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.meter.api.MeterType; public class MeterJanitzaUmg604ImplTest { - private static final String METER_ID = "meter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new MeterJanitzaUmg604Impl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // - .setType(MeterType.GRID) // + .setId("meter0") // + .setModbusId("modbus0") // + .setType(GRID) // .build()) // ; } diff --git a/io.openems.edge.meter.janitza/test/io/openems/edge/meter/janitza/umg604/MyConfig.java b/io.openems.edge.meter.janitza/test/io/openems/edge/meter/janitza/umg604/MyConfig.java index e1cc866681b..770e25f4663 100644 --- a/io.openems.edge.meter.janitza/test/io/openems/edge/meter/janitza/umg604/MyConfig.java +++ b/io.openems.edge.meter.janitza/test/io/openems/edge/meter/janitza/umg604/MyConfig.java @@ -1,8 +1,8 @@ package io.openems.edge.meter.janitza.umg604; import io.openems.common.test.AbstractComponentConfig; +import io.openems.common.types.MeterType; import io.openems.common.utils.ConfigUtils; -import io.openems.edge.meter.api.MeterType; @SuppressWarnings("all") public class MyConfig extends AbstractComponentConfig implements Config { diff --git a/io.openems.edge.meter.janitza/test/io/openems/edge/meter/janitza/umg96rme/MeterJanitzaUmg96rmeImplTest.java b/io.openems.edge.meter.janitza/test/io/openems/edge/meter/janitza/umg96rme/MeterJanitzaUmg96rmeImplTest.java index ea3f2c9ddc2..7a1c5ed8a1b 100644 --- a/io.openems.edge.meter.janitza/test/io/openems/edge/meter/janitza/umg96rme/MeterJanitzaUmg96rmeImplTest.java +++ b/io.openems.edge.meter.janitza/test/io/openems/edge/meter/janitza/umg96rme/MeterJanitzaUmg96rmeImplTest.java @@ -1,26 +1,24 @@ package io.openems.edge.meter.janitza.umg96rme; +import static io.openems.common.types.MeterType.GRID; + import org.junit.Test; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.meter.api.MeterType; public class MeterJanitzaUmg96rmeImplTest { - private static final String METER_ID = "meter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new MeterJanitzaUmg96rmeImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // - .setType(MeterType.GRID) // + .setId("meter0") // + .setModbusId("modbus0") // + .setType(GRID) // .build()) // ; } diff --git a/io.openems.edge.meter.janitza/test/io/openems/edge/meter/janitza/umg96rme/MyConfig.java b/io.openems.edge.meter.janitza/test/io/openems/edge/meter/janitza/umg96rme/MyConfig.java index 68e0d2c2008..5d68b21c458 100644 --- a/io.openems.edge.meter.janitza/test/io/openems/edge/meter/janitza/umg96rme/MyConfig.java +++ b/io.openems.edge.meter.janitza/test/io/openems/edge/meter/janitza/umg96rme/MyConfig.java @@ -1,8 +1,8 @@ package io.openems.edge.meter.janitza.umg96rme; import io.openems.common.test.AbstractComponentConfig; +import io.openems.common.types.MeterType; import io.openems.common.utils.ConfigUtils; -import io.openems.edge.meter.api.MeterType; @SuppressWarnings("all") public class MyConfig extends AbstractComponentConfig implements Config { diff --git a/io.openems.edge.meter.kdk/.classpath b/io.openems.edge.meter.kdk/.classpath index bbfbdbe40e7..b4cffd0fe60 100755 --- a/io.openems.edge.meter.kdk/.classpath +++ b/io.openems.edge.meter.kdk/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.meter.kdk/src/io/openems/edge/meter/kdk/puct2/Config.java b/io.openems.edge.meter.kdk/src/io/openems/edge/meter/kdk/puct2/Config.java index a9c3415de16..98b9fca80d5 100755 --- a/io.openems.edge.meter.kdk/src/io/openems/edge/meter/kdk/puct2/Config.java +++ b/io.openems.edge.meter.kdk/src/io/openems/edge/meter/kdk/puct2/Config.java @@ -3,7 +3,7 @@ import org.osgi.service.metatype.annotations.AttributeDefinition; import org.osgi.service.metatype.annotations.ObjectClassDefinition; -import io.openems.edge.meter.api.MeterType; +import io.openems.common.types.MeterType; @ObjectClassDefinition(// name = "Meter KDK 2PU CT", // diff --git a/io.openems.edge.meter.kdk/src/io/openems/edge/meter/kdk/puct2/MeterKdk2puctImpl.java b/io.openems.edge.meter.kdk/src/io/openems/edge/meter/kdk/puct2/MeterKdk2puctImpl.java index a8e380226d3..f3f11b84e11 100755 --- a/io.openems.edge.meter.kdk/src/io/openems/edge/meter/kdk/puct2/MeterKdk2puctImpl.java +++ b/io.openems.edge.meter.kdk/src/io/openems/edge/meter/kdk/puct2/MeterKdk2puctImpl.java @@ -19,6 +19,7 @@ import io.openems.common.channel.AccessMode; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.types.MeterType; import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent; import io.openems.edge.bridge.modbus.api.BridgeModbus; import io.openems.edge.bridge.modbus.api.ModbusComponent; @@ -36,7 +37,6 @@ import io.openems.edge.common.taskmanager.Priority; import io.openems.edge.common.type.TypeUtils; import io.openems.edge.meter.api.ElectricityMeter; -import io.openems.edge.meter.api.MeterType; @Designate(ocd = Config.class, factory = true) @Component(// diff --git a/io.openems.edge.meter.kdk/test/io/openems/edge/meter/kdk/puct2/MeterKdk2puctImplTest.java b/io.openems.edge.meter.kdk/test/io/openems/edge/meter/kdk/puct2/MeterKdk2puctImplTest.java index 8cc7f90bdc4..f042c8e572d 100755 --- a/io.openems.edge.meter.kdk/test/io/openems/edge/meter/kdk/puct2/MeterKdk2puctImplTest.java +++ b/io.openems.edge.meter.kdk/test/io/openems/edge/meter/kdk/puct2/MeterKdk2puctImplTest.java @@ -1,28 +1,26 @@ package io.openems.edge.meter.kdk.puct2; +import static io.openems.common.types.MeterType.GRID; + import org.junit.Test; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.meter.api.MeterType; public class MeterKdk2puctImplTest { - private static final String COMPONENT_ID = "component0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new MeterKdk2puctImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // - .setModbusId(MODBUS_ID) // + .setId("component0") // + .setModbusId("modbus0") // .setModbusUnitId(1) // - .setMeterType(MeterType.GRID) // + .setMeterType(GRID) // .setInvert(false) // .build()) .next(new TestCase()); diff --git a/io.openems.edge.meter.kdk/test/io/openems/edge/meter/kdk/puct2/MyConfig.java b/io.openems.edge.meter.kdk/test/io/openems/edge/meter/kdk/puct2/MyConfig.java index 1271760edf0..943b1f5c06f 100755 --- a/io.openems.edge.meter.kdk/test/io/openems/edge/meter/kdk/puct2/MyConfig.java +++ b/io.openems.edge.meter.kdk/test/io/openems/edge/meter/kdk/puct2/MyConfig.java @@ -1,8 +1,8 @@ package io.openems.edge.meter.kdk.puct2; import io.openems.common.test.AbstractComponentConfig; +import io.openems.common.types.MeterType; import io.openems.common.utils.ConfigUtils; -import io.openems.edge.meter.api.MeterType; @SuppressWarnings("all") public class MyConfig extends AbstractComponentConfig implements Config { diff --git a/io.openems.edge.meter.phoenixcontact/.classpath b/io.openems.edge.meter.phoenixcontact/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.meter.phoenixcontact/.classpath +++ b/io.openems.edge.meter.phoenixcontact/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.meter.phoenixcontact/src/io/openems/edge/meter/phoenixcontact/Config.java b/io.openems.edge.meter.phoenixcontact/src/io/openems/edge/meter/phoenixcontact/Config.java index 7797744593a..7d35f93e563 100644 --- a/io.openems.edge.meter.phoenixcontact/src/io/openems/edge/meter/phoenixcontact/Config.java +++ b/io.openems.edge.meter.phoenixcontact/src/io/openems/edge/meter/phoenixcontact/Config.java @@ -3,7 +3,7 @@ import org.osgi.service.metatype.annotations.AttributeDefinition; import org.osgi.service.metatype.annotations.ObjectClassDefinition; -import io.openems.edge.meter.api.MeterType; +import io.openems.common.types.MeterType; @ObjectClassDefinition(// name = "Meter Phoenix Contact", // diff --git a/io.openems.edge.meter.phoenixcontact/src/io/openems/edge/meter/phoenixcontact/PhoenixContactMeterImpl.java b/io.openems.edge.meter.phoenixcontact/src/io/openems/edge/meter/phoenixcontact/PhoenixContactMeterImpl.java index 146005b63ff..37b66d4356c 100644 --- a/io.openems.edge.meter.phoenixcontact/src/io/openems/edge/meter/phoenixcontact/PhoenixContactMeterImpl.java +++ b/io.openems.edge.meter.phoenixcontact/src/io/openems/edge/meter/phoenixcontact/PhoenixContactMeterImpl.java @@ -18,6 +18,7 @@ import io.openems.common.channel.AccessMode; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.types.MeterType; import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent; import io.openems.edge.bridge.modbus.api.BridgeModbus; import io.openems.edge.bridge.modbus.api.ModbusComponent; @@ -31,7 +32,6 @@ import io.openems.edge.common.modbusslave.ModbusSlaveTable; import io.openems.edge.common.taskmanager.Priority; import io.openems.edge.meter.api.ElectricityMeter; -import io.openems.edge.meter.api.MeterType; @Designate(ocd = Config.class, factory = true) @Component(// diff --git a/io.openems.edge.meter.phoenixcontact/test/io/openems/edge/meter/phoenixcontact/MyConfig.java b/io.openems.edge.meter.phoenixcontact/test/io/openems/edge/meter/phoenixcontact/MyConfig.java index f168618fdee..d59d6e3d362 100644 --- a/io.openems.edge.meter.phoenixcontact/test/io/openems/edge/meter/phoenixcontact/MyConfig.java +++ b/io.openems.edge.meter.phoenixcontact/test/io/openems/edge/meter/phoenixcontact/MyConfig.java @@ -1,8 +1,8 @@ package io.openems.edge.meter.phoenixcontact; import io.openems.common.test.AbstractComponentConfig; +import io.openems.common.types.MeterType; import io.openems.common.utils.ConfigUtils; -import io.openems.edge.meter.api.MeterType; @SuppressWarnings("all") public class MyConfig extends AbstractComponentConfig implements Config { diff --git a/io.openems.edge.meter.phoenixcontact/test/io/openems/edge/meter/phoenixcontact/PhoenixContactMeterImplTest.java b/io.openems.edge.meter.phoenixcontact/test/io/openems/edge/meter/phoenixcontact/PhoenixContactMeterImplTest.java index c6801784fb8..38cbacc4542 100644 --- a/io.openems.edge.meter.phoenixcontact/test/io/openems/edge/meter/phoenixcontact/PhoenixContactMeterImplTest.java +++ b/io.openems.edge.meter.phoenixcontact/test/io/openems/edge/meter/phoenixcontact/PhoenixContactMeterImplTest.java @@ -1,27 +1,25 @@ package io.openems.edge.meter.phoenixcontact; +import static io.openems.common.types.MeterType.PRODUCTION; + import org.junit.Test; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.meter.api.MeterType; public class PhoenixContactMeterImplTest { - private static final String COMPONENT_ID = "meter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new PhoenixContactMeterImpl()) // .addReference("cm", new DummyConfigurationAdmin()) - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // - .setModbusId(MODBUS_ID) // - .setMeterType(MeterType.PRODUCTION) // + .setId("meter0") // + .setModbusId("modbus0") // + .setMeterType(PRODUCTION) // .build()) .next(new TestCase()); } diff --git a/io.openems.edge.meter.plexlog/.classpath b/io.openems.edge.meter.plexlog/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.meter.plexlog/.classpath +++ b/io.openems.edge.meter.plexlog/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.meter.plexlog/src/io/openems/edge/meter/plexlog/Config.java b/io.openems.edge.meter.plexlog/src/io/openems/edge/meter/plexlog/Config.java index bd9e1b71ee4..fe27dbe8ab0 100644 --- a/io.openems.edge.meter.plexlog/src/io/openems/edge/meter/plexlog/Config.java +++ b/io.openems.edge.meter.plexlog/src/io/openems/edge/meter/plexlog/Config.java @@ -3,7 +3,7 @@ import org.osgi.service.metatype.annotations.AttributeDefinition; import org.osgi.service.metatype.annotations.ObjectClassDefinition; -import io.openems.edge.meter.api.MeterType; +import io.openems.common.types.MeterType; @ObjectClassDefinition(// name = "Meter Plexlog Datalogger", // diff --git a/io.openems.edge.meter.plexlog/src/io/openems/edge/meter/plexlog/MeterPlexlogDataloggerImpl.java b/io.openems.edge.meter.plexlog/src/io/openems/edge/meter/plexlog/MeterPlexlogDataloggerImpl.java index 50bf5fa08f8..f18568c5ec9 100644 --- a/io.openems.edge.meter.plexlog/src/io/openems/edge/meter/plexlog/MeterPlexlogDataloggerImpl.java +++ b/io.openems.edge.meter.plexlog/src/io/openems/edge/meter/plexlog/MeterPlexlogDataloggerImpl.java @@ -15,6 +15,7 @@ import org.osgi.service.metatype.annotations.Designate; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.types.MeterType; import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent; import io.openems.edge.bridge.modbus.api.BridgeModbus; import io.openems.edge.bridge.modbus.api.ModbusComponent; @@ -27,7 +28,6 @@ import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.taskmanager.Priority; import io.openems.edge.meter.api.ElectricityMeter; -import io.openems.edge.meter.api.MeterType; @Designate(ocd = Config.class, factory = true) @Component(// diff --git a/io.openems.edge.meter.plexlog/test/io/openems/edge/meter/plexlog/MeterPlexlogDataloggerImplTest.java b/io.openems.edge.meter.plexlog/test/io/openems/edge/meter/plexlog/MeterPlexlogDataloggerImplTest.java index b5d53bd93e7..57ce87bcbe0 100644 --- a/io.openems.edge.meter.plexlog/test/io/openems/edge/meter/plexlog/MeterPlexlogDataloggerImplTest.java +++ b/io.openems.edge.meter.plexlog/test/io/openems/edge/meter/plexlog/MeterPlexlogDataloggerImplTest.java @@ -1,27 +1,25 @@ package io.openems.edge.meter.plexlog; +import static io.openems.common.types.MeterType.PRODUCTION; + import org.junit.Test; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.meter.api.MeterType; public class MeterPlexlogDataloggerImplTest { - private static final String COMPONENT_ID = "meter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new MeterPlexlogDataloggerImpl()) // .addReference("cm", new DummyConfigurationAdmin()) - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // - .setMeterType(MeterType.PRODUCTION) // - .setModbusId(MODBUS_ID) // + .setId("meter0") // + .setMeterType(PRODUCTION) // + .setModbusId("modbus0") // .build()) .next(new TestCase()); } diff --git a/io.openems.edge.meter.plexlog/test/io/openems/edge/meter/plexlog/MyConfig.java b/io.openems.edge.meter.plexlog/test/io/openems/edge/meter/plexlog/MyConfig.java index 33027ba6d0f..1de26eb6930 100644 --- a/io.openems.edge.meter.plexlog/test/io/openems/edge/meter/plexlog/MyConfig.java +++ b/io.openems.edge.meter.plexlog/test/io/openems/edge/meter/plexlog/MyConfig.java @@ -1,8 +1,8 @@ package io.openems.edge.meter.plexlog; import io.openems.common.test.AbstractComponentConfig; +import io.openems.common.types.MeterType; import io.openems.common.utils.ConfigUtils; -import io.openems.edge.meter.api.MeterType; @SuppressWarnings("all") public class MyConfig extends AbstractComponentConfig implements Config { diff --git a/io.openems.edge.meter.pqplus/.classpath b/io.openems.edge.meter.pqplus/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.meter.pqplus/.classpath +++ b/io.openems.edge.meter.pqplus/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.meter.pqplus/src/io/openems/edge/meter/pqplus/umd96/Config.java b/io.openems.edge.meter.pqplus/src/io/openems/edge/meter/pqplus/umd96/Config.java index ee9703714c0..ce230fe69b4 100644 --- a/io.openems.edge.meter.pqplus/src/io/openems/edge/meter/pqplus/umd96/Config.java +++ b/io.openems.edge.meter.pqplus/src/io/openems/edge/meter/pqplus/umd96/Config.java @@ -3,7 +3,7 @@ import org.osgi.service.metatype.annotations.AttributeDefinition; import org.osgi.service.metatype.annotations.ObjectClassDefinition; -import io.openems.edge.meter.api.MeterType; +import io.openems.common.types.MeterType; @ObjectClassDefinition(// name = "Meter PQ-Plus UMD 96", // diff --git a/io.openems.edge.meter.pqplus/src/io/openems/edge/meter/pqplus/umd96/MeterPqplusUmd96Impl.java b/io.openems.edge.meter.pqplus/src/io/openems/edge/meter/pqplus/umd96/MeterPqplusUmd96Impl.java index 248c2c30784..6c6202f7841 100644 --- a/io.openems.edge.meter.pqplus/src/io/openems/edge/meter/pqplus/umd96/MeterPqplusUmd96Impl.java +++ b/io.openems.edge.meter.pqplus/src/io/openems/edge/meter/pqplus/umd96/MeterPqplusUmd96Impl.java @@ -15,6 +15,7 @@ import org.osgi.service.metatype.annotations.Designate; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.types.MeterType; import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent; import io.openems.edge.bridge.modbus.api.BridgeModbus; import io.openems.edge.bridge.modbus.api.ModbusComponent; @@ -25,7 +26,6 @@ import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.taskmanager.Priority; import io.openems.edge.meter.api.ElectricityMeter; -import io.openems.edge.meter.api.MeterType; /** * Implements the PQ Plus UMD 96 meter. diff --git a/io.openems.edge.meter.pqplus/src/io/openems/edge/meter/pqplus/umd97/Config.java b/io.openems.edge.meter.pqplus/src/io/openems/edge/meter/pqplus/umd97/Config.java index 6f158db2353..685b59a10bc 100644 --- a/io.openems.edge.meter.pqplus/src/io/openems/edge/meter/pqplus/umd97/Config.java +++ b/io.openems.edge.meter.pqplus/src/io/openems/edge/meter/pqplus/umd97/Config.java @@ -3,7 +3,7 @@ import org.osgi.service.metatype.annotations.AttributeDefinition; import org.osgi.service.metatype.annotations.ObjectClassDefinition; -import io.openems.edge.meter.api.MeterType; +import io.openems.common.types.MeterType; @ObjectClassDefinition(// name = "Meter PQ-Plus UMD 97", // diff --git a/io.openems.edge.meter.pqplus/src/io/openems/edge/meter/pqplus/umd97/MeterPqplusUmd97Impl.java b/io.openems.edge.meter.pqplus/src/io/openems/edge/meter/pqplus/umd97/MeterPqplusUmd97Impl.java index 72accb117fb..e77f309fd9a 100644 --- a/io.openems.edge.meter.pqplus/src/io/openems/edge/meter/pqplus/umd97/MeterPqplusUmd97Impl.java +++ b/io.openems.edge.meter.pqplus/src/io/openems/edge/meter/pqplus/umd97/MeterPqplusUmd97Impl.java @@ -15,6 +15,7 @@ import org.osgi.service.metatype.annotations.Designate; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.types.MeterType; import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent; import io.openems.edge.bridge.modbus.api.BridgeModbus; import io.openems.edge.bridge.modbus.api.ModbusComponent; @@ -25,7 +26,6 @@ import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.taskmanager.Priority; import io.openems.edge.meter.api.ElectricityMeter; -import io.openems.edge.meter.api.MeterType; /** * Implements the PQ Plus UMD 97 meter. diff --git a/io.openems.edge.meter.pqplus/test/io/openems/edge/meter/pqplus/umd96/MeterPqplusUmd96ImplTest.java b/io.openems.edge.meter.pqplus/test/io/openems/edge/meter/pqplus/umd96/MeterPqplusUmd96ImplTest.java index 403893ed299..8599768a21d 100644 --- a/io.openems.edge.meter.pqplus/test/io/openems/edge/meter/pqplus/umd96/MeterPqplusUmd96ImplTest.java +++ b/io.openems.edge.meter.pqplus/test/io/openems/edge/meter/pqplus/umd96/MeterPqplusUmd96ImplTest.java @@ -1,26 +1,24 @@ package io.openems.edge.meter.pqplus.umd96; +import static io.openems.common.types.MeterType.GRID; + import org.junit.Test; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.meter.api.MeterType; public class MeterPqplusUmd96ImplTest { - private static final String METER_ID = "meter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new MeterPqplusUmd96Impl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // - .setType(MeterType.GRID) // + .setId("meter0") // + .setModbusId("modbus0") // + .setType(GRID) // .build()) // ; } diff --git a/io.openems.edge.meter.pqplus/test/io/openems/edge/meter/pqplus/umd96/MyConfig.java b/io.openems.edge.meter.pqplus/test/io/openems/edge/meter/pqplus/umd96/MyConfig.java index bc502d3e9f2..2882de0ec27 100644 --- a/io.openems.edge.meter.pqplus/test/io/openems/edge/meter/pqplus/umd96/MyConfig.java +++ b/io.openems.edge.meter.pqplus/test/io/openems/edge/meter/pqplus/umd96/MyConfig.java @@ -1,8 +1,8 @@ package io.openems.edge.meter.pqplus.umd96; import io.openems.common.test.AbstractComponentConfig; +import io.openems.common.types.MeterType; import io.openems.common.utils.ConfigUtils; -import io.openems.edge.meter.api.MeterType; @SuppressWarnings("all") public class MyConfig extends AbstractComponentConfig implements Config { diff --git a/io.openems.edge.meter.pqplus/test/io/openems/edge/meter/pqplus/umd97/MeterPqplusUmd97ImplTest.java b/io.openems.edge.meter.pqplus/test/io/openems/edge/meter/pqplus/umd97/MeterPqplusUmd97ImplTest.java index 106baa6ae23..2f7c436e66d 100644 --- a/io.openems.edge.meter.pqplus/test/io/openems/edge/meter/pqplus/umd97/MeterPqplusUmd97ImplTest.java +++ b/io.openems.edge.meter.pqplus/test/io/openems/edge/meter/pqplus/umd97/MeterPqplusUmd97ImplTest.java @@ -1,26 +1,24 @@ package io.openems.edge.meter.pqplus.umd97; +import static io.openems.common.types.MeterType.GRID; + import org.junit.Test; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.meter.api.MeterType; public class MeterPqplusUmd97ImplTest { - private static final String METER_ID = "meter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new MeterPqplusUmd97Impl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // - .setType(MeterType.GRID) // + .setId("meter0") // + .setModbusId("modbus0") // + .setType(GRID) // .build()) // ; } diff --git a/io.openems.edge.meter.pqplus/test/io/openems/edge/meter/pqplus/umd97/MyConfig.java b/io.openems.edge.meter.pqplus/test/io/openems/edge/meter/pqplus/umd97/MyConfig.java index a924208764e..d906352df16 100644 --- a/io.openems.edge.meter.pqplus/test/io/openems/edge/meter/pqplus/umd97/MyConfig.java +++ b/io.openems.edge.meter.pqplus/test/io/openems/edge/meter/pqplus/umd97/MyConfig.java @@ -1,8 +1,8 @@ package io.openems.edge.meter.pqplus.umd97; import io.openems.common.test.AbstractComponentConfig; +import io.openems.common.types.MeterType; import io.openems.common.utils.ConfigUtils; -import io.openems.edge.meter.api.MeterType; @SuppressWarnings("all") public class MyConfig extends AbstractComponentConfig implements Config { diff --git a/io.openems.edge.meter.schneider.acti9.smartlink/.classpath b/io.openems.edge.meter.schneider.acti9.smartlink/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.meter.schneider.acti9.smartlink/.classpath +++ b/io.openems.edge.meter.schneider.acti9.smartlink/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.meter.schneider.acti9.smartlink/src/io/openems/edge/meter/schneider/acti9/smartlink/Config.java b/io.openems.edge.meter.schneider.acti9.smartlink/src/io/openems/edge/meter/schneider/acti9/smartlink/Config.java index eff440fdddb..b61d6586f8b 100644 --- a/io.openems.edge.meter.schneider.acti9.smartlink/src/io/openems/edge/meter/schneider/acti9/smartlink/Config.java +++ b/io.openems.edge.meter.schneider.acti9.smartlink/src/io/openems/edge/meter/schneider/acti9/smartlink/Config.java @@ -3,7 +3,7 @@ import org.osgi.service.metatype.annotations.AttributeDefinition; import org.osgi.service.metatype.annotations.ObjectClassDefinition; -import io.openems.edge.meter.api.MeterType; +import io.openems.common.types.MeterType; @ObjectClassDefinition(// name = "Meter Schneider Acti9 Smartlink", // diff --git a/io.openems.edge.meter.schneider.acti9.smartlink/src/io/openems/edge/meter/schneider/acti9/smartlink/MeterSchneiderActi9SmartlinkImpl.java b/io.openems.edge.meter.schneider.acti9.smartlink/src/io/openems/edge/meter/schneider/acti9/smartlink/MeterSchneiderActi9SmartlinkImpl.java index f2d9c28e77c..5c3419cbe6f 100644 --- a/io.openems.edge.meter.schneider.acti9.smartlink/src/io/openems/edge/meter/schneider/acti9/smartlink/MeterSchneiderActi9SmartlinkImpl.java +++ b/io.openems.edge.meter.schneider.acti9.smartlink/src/io/openems/edge/meter/schneider/acti9/smartlink/MeterSchneiderActi9SmartlinkImpl.java @@ -16,6 +16,7 @@ import org.osgi.service.metatype.annotations.Designate; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.types.MeterType; import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent; import io.openems.edge.bridge.modbus.api.BridgeModbus; import io.openems.edge.bridge.modbus.api.ModbusComponent; @@ -27,7 +28,6 @@ import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.taskmanager.Priority; import io.openems.edge.meter.api.ElectricityMeter; -import io.openems.edge.meter.api.MeterType; @Designate(ocd = Config.class, factory = true) @Component(// diff --git a/io.openems.edge.meter.schneider.acti9.smartlink/test/io/openems/edge/meter/schneider/acti9/smartlink/MeterSchneiderActi9SmartlinkImplTest.java b/io.openems.edge.meter.schneider.acti9.smartlink/test/io/openems/edge/meter/schneider/acti9/smartlink/MeterSchneiderActi9SmartlinkImplTest.java index 31b7ac51617..1cb4e0a5112 100644 --- a/io.openems.edge.meter.schneider.acti9.smartlink/test/io/openems/edge/meter/schneider/acti9/smartlink/MeterSchneiderActi9SmartlinkImplTest.java +++ b/io.openems.edge.meter.schneider.acti9.smartlink/test/io/openems/edge/meter/schneider/acti9/smartlink/MeterSchneiderActi9SmartlinkImplTest.java @@ -1,26 +1,24 @@ package io.openems.edge.meter.schneider.acti9.smartlink; +import static io.openems.common.types.MeterType.GRID; + import org.junit.Test; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.meter.api.MeterType; public class MeterSchneiderActi9SmartlinkImplTest { - private static final String METER_ID = "meter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new MeterSchneiderActi9SmartlinkImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // - .setType(MeterType.GRID) // + .setId("meter0") // + .setModbusId("modbus0") // + .setType(GRID) // .setInvert(false) // .build()) // ; diff --git a/io.openems.edge.meter.schneider.acti9.smartlink/test/io/openems/edge/meter/schneider/acti9/smartlink/MyConfig.java b/io.openems.edge.meter.schneider.acti9.smartlink/test/io/openems/edge/meter/schneider/acti9/smartlink/MyConfig.java index 6be2798bdbd..72fee02b36c 100644 --- a/io.openems.edge.meter.schneider.acti9.smartlink/test/io/openems/edge/meter/schneider/acti9/smartlink/MyConfig.java +++ b/io.openems.edge.meter.schneider.acti9.smartlink/test/io/openems/edge/meter/schneider/acti9/smartlink/MyConfig.java @@ -1,8 +1,8 @@ package io.openems.edge.meter.schneider.acti9.smartlink; import io.openems.common.test.AbstractComponentConfig; +import io.openems.common.types.MeterType; import io.openems.common.utils.ConfigUtils; -import io.openems.edge.meter.api.MeterType; @SuppressWarnings("all") public class MyConfig extends AbstractComponentConfig implements Config { diff --git a/io.openems.edge.meter.siemens/.classpath b/io.openems.edge.meter.siemens/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.meter.siemens/.classpath +++ b/io.openems.edge.meter.siemens/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.meter.siemens/doc/sicamT7KG966/SICAM_T_7KG966_DE.pdf b/io.openems.edge.meter.siemens/doc/sicamT7KG966/SICAM_T_7KG966_DE.pdf new file mode 100644 index 00000000000..ae23903fb12 Binary files /dev/null and b/io.openems.edge.meter.siemens/doc/sicamT7KG966/SICAM_T_7KG966_DE.pdf differ diff --git a/io.openems.edge.meter.siemens/doc/sicamT7KG966/SICAM_T_7KG966_US.pdf b/io.openems.edge.meter.siemens/doc/sicamT7KG966/SICAM_T_7KG966_US.pdf new file mode 100644 index 00000000000..d10923fc94b Binary files /dev/null and b/io.openems.edge.meter.siemens/doc/sicamT7KG966/SICAM_T_7KG966_US.pdf differ diff --git a/io.openems.edge.meter.siemens/src/io/openems/edge/meter/siemens/pac1600/Config.java b/io.openems.edge.meter.siemens/src/io/openems/edge/meter/siemens/pac1600/Config.java index 0143777c75b..8b3b7e76c0c 100644 --- a/io.openems.edge.meter.siemens/src/io/openems/edge/meter/siemens/pac1600/Config.java +++ b/io.openems.edge.meter.siemens/src/io/openems/edge/meter/siemens/pac1600/Config.java @@ -3,7 +3,7 @@ import org.osgi.service.metatype.annotations.AttributeDefinition; import org.osgi.service.metatype.annotations.ObjectClassDefinition; -import io.openems.edge.meter.api.MeterType; +import io.openems.common.types.MeterType; @ObjectClassDefinition(// name = "Meter Siemens PAC1600", // diff --git a/io.openems.edge.meter.siemens/src/io/openems/edge/meter/siemens/pac1600/MeterSiemensPac1600Impl.java b/io.openems.edge.meter.siemens/src/io/openems/edge/meter/siemens/pac1600/MeterSiemensPac1600Impl.java index dbe1c463ce1..8adfba56bce 100644 --- a/io.openems.edge.meter.siemens/src/io/openems/edge/meter/siemens/pac1600/MeterSiemensPac1600Impl.java +++ b/io.openems.edge.meter.siemens/src/io/openems/edge/meter/siemens/pac1600/MeterSiemensPac1600Impl.java @@ -13,6 +13,7 @@ import io.openems.common.channel.AccessMode; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.types.MeterType; import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent; import io.openems.edge.bridge.modbus.api.BridgeModbus; import io.openems.edge.bridge.modbus.api.ElementToChannelConverter; @@ -27,7 +28,6 @@ import io.openems.edge.common.modbusslave.ModbusSlaveTable; import io.openems.edge.common.taskmanager.Priority; import io.openems.edge.meter.api.ElectricityMeter; -import io.openems.edge.meter.api.MeterType; @Designate(ocd = Config.class, factory = true) @Component(// diff --git a/io.openems.edge.meter.siemens/src/io/openems/edge/meter/siemens/pac2200/Config.java b/io.openems.edge.meter.siemens/src/io/openems/edge/meter/siemens/pac2200/Config.java index c100374f8d1..1033011652e 100644 --- a/io.openems.edge.meter.siemens/src/io/openems/edge/meter/siemens/pac2200/Config.java +++ b/io.openems.edge.meter.siemens/src/io/openems/edge/meter/siemens/pac2200/Config.java @@ -3,7 +3,7 @@ import org.osgi.service.metatype.annotations.AttributeDefinition; import org.osgi.service.metatype.annotations.ObjectClassDefinition; -import io.openems.edge.meter.api.MeterType; +import io.openems.common.types.MeterType; @ObjectClassDefinition(// name = "Meter Siemens PAC2200/PAC3200/PAC4200", // diff --git a/io.openems.edge.meter.siemens/src/io/openems/edge/meter/siemens/pac2200/MeterSiemensPac2200Impl.java b/io.openems.edge.meter.siemens/src/io/openems/edge/meter/siemens/pac2200/MeterSiemensPac2200Impl.java index 3c2e8f5d7fa..a70b44e5e59 100644 --- a/io.openems.edge.meter.siemens/src/io/openems/edge/meter/siemens/pac2200/MeterSiemensPac2200Impl.java +++ b/io.openems.edge.meter.siemens/src/io/openems/edge/meter/siemens/pac2200/MeterSiemensPac2200Impl.java @@ -17,6 +17,7 @@ import io.openems.common.channel.AccessMode; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.types.MeterType; import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent; import io.openems.edge.bridge.modbus.api.BridgeModbus; import io.openems.edge.bridge.modbus.api.ModbusComponent; @@ -30,7 +31,6 @@ import io.openems.edge.common.modbusslave.ModbusSlaveTable; import io.openems.edge.common.taskmanager.Priority; import io.openems.edge.meter.api.ElectricityMeter; -import io.openems.edge.meter.api.MeterType; /** * Implements the Siemens PAC2200/PAC3200/PAC4200 power meter. diff --git a/io.openems.edge.meter.siemens/src/io/openems/edge/meter/siemens/sicam/Config.java b/io.openems.edge.meter.siemens/src/io/openems/edge/meter/siemens/sicam/Config.java new file mode 100644 index 00000000000..bb364f1c51b --- /dev/null +++ b/io.openems.edge.meter.siemens/src/io/openems/edge/meter/siemens/sicam/Config.java @@ -0,0 +1,36 @@ +package io.openems.edge.meter.siemens.sicam; + +import org.osgi.service.metatype.annotations.AttributeDefinition; +import org.osgi.service.metatype.annotations.ObjectClassDefinition; + +import io.openems.common.types.MeterType; + +@ObjectClassDefinition(// + name = "Meter Siemens SICAMT7KG966", // + description = "Implements the Siemens Multifunctional Transducer SICAM T 7KG966.") +@interface Config { + + @AttributeDefinition(name = "Component-ID", description = "Unique ID of this Component") + String id() default "meter0"; + + @AttributeDefinition(name = "Alias", description = "Human-readable name of this Component; defaults to Component-ID") + String alias() default ""; + + @AttributeDefinition(name = "Is enabled?", description = "Is this Component enabled?") + boolean enabled() default true; + + @AttributeDefinition(name = "Meter-Type", description = "What is measured by this Meter?") + MeterType type() default MeterType.GRID; + + @AttributeDefinition(name = "Modbus-ID", description = "ID of Modbus bridge.") + String modbus_id() default "modbus0"; + + @AttributeDefinition(name = "Modbus Unit-ID", description = "The Unit-ID of the Modbus device.") + int modbusUnitId() default 1; + + @AttributeDefinition(name = "Modbus target filter", description = "This is auto-generated by 'Modbus-ID'.") + String Modbus_target() default "(enabled=true)"; + + String webconsole_configurationFactory_nameHint() default "Meter Siemens SICAMT7KG966 [{id}]"; + +} \ No newline at end of file diff --git a/io.openems.edge.meter.siemens/src/io/openems/edge/meter/siemens/sicam/MeterSiemensSicamt7kg966.java b/io.openems.edge.meter.siemens/src/io/openems/edge/meter/siemens/sicam/MeterSiemensSicamt7kg966.java new file mode 100644 index 00000000000..39ad1b43e28 --- /dev/null +++ b/io.openems.edge.meter.siemens/src/io/openems/edge/meter/siemens/sicam/MeterSiemensSicamt7kg966.java @@ -0,0 +1,25 @@ +package io.openems.edge.meter.siemens.sicam; + +import io.openems.edge.bridge.modbus.api.ModbusComponent; +import io.openems.edge.common.channel.Doc; +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.common.modbusslave.ModbusSlave; +import io.openems.edge.meter.api.ElectricityMeter; + +public interface MeterSiemensSicamt7kg966 extends ElectricityMeter, ModbusComponent, OpenemsComponent, ModbusSlave { + public enum ChannelId implements io.openems.edge.common.channel.ChannelId { + ; + + private final Doc doc; + + private ChannelId(Doc doc) { + this.doc = doc; + } + + @Override + public Doc doc() { + return this.doc; + } + } + +} \ No newline at end of file diff --git a/io.openems.edge.meter.siemens/src/io/openems/edge/meter/siemens/sicam/MeterSiemensSicamt7kg966Impl.java b/io.openems.edge.meter.siemens/src/io/openems/edge/meter/siemens/sicam/MeterSiemensSicamt7kg966Impl.java new file mode 100644 index 00000000000..8be4a946ba3 --- /dev/null +++ b/io.openems.edge.meter.siemens/src/io/openems/edge/meter/siemens/sicam/MeterSiemensSicamt7kg966Impl.java @@ -0,0 +1,129 @@ +package io.openems.edge.meter.siemens.sicam; + +import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SCALE_FACTOR_3; + +import org.osgi.service.cm.ConfigurationAdmin; +import org.osgi.service.component.ComponentContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.ConfigurationPolicy; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; +import org.osgi.service.component.annotations.ReferencePolicyOption; +import org.osgi.service.metatype.annotations.Designate; + +import io.openems.common.channel.AccessMode; +import io.openems.common.exceptions.OpenemsException; +import io.openems.common.types.MeterType; +import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent; +import io.openems.edge.bridge.modbus.api.BridgeModbus; +import io.openems.edge.bridge.modbus.api.ModbusComponent; +import io.openems.edge.bridge.modbus.api.ModbusProtocol; +import io.openems.edge.bridge.modbus.api.element.DummyRegisterElement; +import io.openems.edge.bridge.modbus.api.element.FloatDoublewordElement; +import io.openems.edge.bridge.modbus.api.task.FC3ReadRegistersTask; +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.common.modbusslave.ModbusSlave; +import io.openems.edge.common.modbusslave.ModbusSlaveTable; +import io.openems.edge.common.taskmanager.Priority; +import io.openems.edge.meter.api.ElectricityMeter; + +/** + * Implements the Siemens SICAM T 7KG966 meter. + */ +@Designate(ocd = Config.class, factory = true) +@Component(// + name = "Meter.Siemens.SICAMT7KG966", // + immediate = true, // + configurationPolicy = ConfigurationPolicy.REQUIRE // +) +public class MeterSiemensSicamt7kg966Impl extends AbstractOpenemsModbusComponent + implements MeterSiemensSicamt7kg966, ElectricityMeter, ModbusComponent, OpenemsComponent, ModbusSlave { + + private MeterType meterType = MeterType.GRID; + + @Reference + protected ConfigurationAdmin cm; + + protected MeterSiemensSicamt7kg966Impl() { + super(// + OpenemsComponent.ChannelId.values(), // + ModbusComponent.ChannelId.values(), // + ElectricityMeter.ChannelId.values(), // + MeterSiemensSicamt7kg966.ChannelId.values() // + ); + } + + @Reference(policy = ReferencePolicy.STATIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.MANDATORY) + protected void setModbus(BridgeModbus modbus) { + super.setModbus(modbus); + } + + @Activate + private void activate(ComponentContext context, Config config) throws OpenemsException { + this.meterType = config.type(); + + if (super.activate(context, config.id(), config.alias(), config.enabled(), config.modbusUnitId(), this.cm, + "Modbus", config.modbus_id())) { + return; + } + } + + @Override + protected void deactivate() { + super.deactivate(); + } + + @Override + public MeterType getMeterType() { + return this.meterType; + } + + @Override + protected ModbusProtocol defineModbusProtocol() { + final var offset = -1; + return new ModbusProtocol(this, // + new FC3ReadRegistersTask(offset + 201, Priority.HIGH, // + m(ElectricityMeter.ChannelId.VOLTAGE_L1, new FloatDoublewordElement(offset + 201), + SCALE_FACTOR_3), + m(ElectricityMeter.ChannelId.VOLTAGE_L2, new FloatDoublewordElement(offset + 203), + SCALE_FACTOR_3), + m(ElectricityMeter.ChannelId.VOLTAGE_L3, new FloatDoublewordElement(offset + 205), + SCALE_FACTOR_3), + new DummyRegisterElement(offset + 207, offset + 208), + m(ElectricityMeter.ChannelId.CURRENT_L1, new FloatDoublewordElement(offset + 209), + SCALE_FACTOR_3), + m(ElectricityMeter.ChannelId.CURRENT_L2, new FloatDoublewordElement(offset + 211), + SCALE_FACTOR_3), + m(ElectricityMeter.ChannelId.CURRENT_L3, new FloatDoublewordElement(offset + 213), + SCALE_FACTOR_3), + new DummyRegisterElement(offset + 215, offset + 222), + m(ElectricityMeter.ChannelId.VOLTAGE, new FloatDoublewordElement(offset + 223), SCALE_FACTOR_3), + m(ElectricityMeter.ChannelId.CURRENT, new FloatDoublewordElement(offset + 225), SCALE_FACTOR_3), + m(ElectricityMeter.ChannelId.ACTIVE_POWER_L1, new FloatDoublewordElement(offset + 227)), + m(ElectricityMeter.ChannelId.ACTIVE_POWER_L2, new FloatDoublewordElement(offset + 229)), + m(ElectricityMeter.ChannelId.ACTIVE_POWER_L3, new FloatDoublewordElement(offset + 231)), + m(ElectricityMeter.ChannelId.ACTIVE_POWER, new FloatDoublewordElement(offset + 233)), + m(ElectricityMeter.ChannelId.REACTIVE_POWER_L1, new FloatDoublewordElement(offset + 235)), + m(ElectricityMeter.ChannelId.REACTIVE_POWER_L2, new FloatDoublewordElement(offset + 237)), + m(ElectricityMeter.ChannelId.REACTIVE_POWER_L3, new FloatDoublewordElement(offset + 239)), + m(ElectricityMeter.ChannelId.REACTIVE_POWER, new FloatDoublewordElement(offset + 241)), + new DummyRegisterElement(offset + 243, offset + 274), // + m(ElectricityMeter.ChannelId.FREQUENCY, new FloatDoublewordElement(offset + 275), + SCALE_FACTOR_3))); + } + + @Override + public String debugLog() { + return "L:" + this.getActivePower().asString(); + } + + @Override + public ModbusSlaveTable getModbusSlaveTable(AccessMode accessMode) { + return new ModbusSlaveTable(// + OpenemsComponent.getModbusSlaveNatureTable(accessMode), // + ElectricityMeter.getModbusSlaveNatureTable(accessMode)); + } + +} diff --git a/io.openems.edge.meter.siemens/test/io/openems/edge/meter/siemens/pac1600/MeterSiemensPac1600ImplTest.java b/io.openems.edge.meter.siemens/test/io/openems/edge/meter/siemens/pac1600/MeterSiemensPac1600ImplTest.java index 98707507163..51a3d0b5c7f 100644 --- a/io.openems.edge.meter.siemens/test/io/openems/edge/meter/siemens/pac1600/MeterSiemensPac1600ImplTest.java +++ b/io.openems.edge.meter.siemens/test/io/openems/edge/meter/siemens/pac1600/MeterSiemensPac1600ImplTest.java @@ -1,26 +1,24 @@ package io.openems.edge.meter.siemens.pac1600; +import static io.openems.common.types.MeterType.GRID; + import org.junit.Test; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.meter.api.MeterType; public class MeterSiemensPac1600ImplTest { - private static final String METER_ID = "meter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new MeterSiemensPac1600Impl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // - .setType(MeterType.GRID) // + .setId("meter0") // + .setModbusId("modbus0") // + .setType(GRID) // .build()) // ; } diff --git a/io.openems.edge.meter.siemens/test/io/openems/edge/meter/siemens/pac1600/MyConfig.java b/io.openems.edge.meter.siemens/test/io/openems/edge/meter/siemens/pac1600/MyConfig.java index 52cc1aa53da..c969615d9fc 100644 --- a/io.openems.edge.meter.siemens/test/io/openems/edge/meter/siemens/pac1600/MyConfig.java +++ b/io.openems.edge.meter.siemens/test/io/openems/edge/meter/siemens/pac1600/MyConfig.java @@ -1,8 +1,8 @@ package io.openems.edge.meter.siemens.pac1600; import io.openems.common.test.AbstractComponentConfig; +import io.openems.common.types.MeterType; import io.openems.common.utils.ConfigUtils; -import io.openems.edge.meter.api.MeterType; @SuppressWarnings("all") public class MyConfig extends AbstractComponentConfig implements Config { diff --git a/io.openems.edge.meter.siemens/test/io/openems/edge/meter/siemens/pac2200/MeterSiemensPac2200ImplTest.java b/io.openems.edge.meter.siemens/test/io/openems/edge/meter/siemens/pac2200/MeterSiemensPac2200ImplTest.java index 51a0989cde4..a75b0c69dce 100644 --- a/io.openems.edge.meter.siemens/test/io/openems/edge/meter/siemens/pac2200/MeterSiemensPac2200ImplTest.java +++ b/io.openems.edge.meter.siemens/test/io/openems/edge/meter/siemens/pac2200/MeterSiemensPac2200ImplTest.java @@ -1,26 +1,24 @@ package io.openems.edge.meter.siemens.pac2200; +import static io.openems.common.types.MeterType.GRID; + import org.junit.Test; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.meter.api.MeterType; public class MeterSiemensPac2200ImplTest { - private static final String METER_ID = "meter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new MeterSiemensPac2200Impl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // - .setType(MeterType.GRID) // + .setId("meter0") // + .setModbusId("modbus0") // + .setType(GRID) // .setInvert(false) // .build()) // ; diff --git a/io.openems.edge.meter.siemens/test/io/openems/edge/meter/siemens/pac2200/MyConfig.java b/io.openems.edge.meter.siemens/test/io/openems/edge/meter/siemens/pac2200/MyConfig.java index e652b511f87..be3116bd633 100644 --- a/io.openems.edge.meter.siemens/test/io/openems/edge/meter/siemens/pac2200/MyConfig.java +++ b/io.openems.edge.meter.siemens/test/io/openems/edge/meter/siemens/pac2200/MyConfig.java @@ -1,8 +1,8 @@ package io.openems.edge.meter.siemens.pac2200; import io.openems.common.test.AbstractComponentConfig; +import io.openems.common.types.MeterType; import io.openems.common.utils.ConfigUtils; -import io.openems.edge.meter.api.MeterType; @SuppressWarnings("all") public class MyConfig extends AbstractComponentConfig implements Config { diff --git a/io.openems.edge.meter.siemens/test/io/openems/edge/meter/siemens/sicam/MeterSiemensSicamt7kg966ImplTest.java b/io.openems.edge.meter.siemens/test/io/openems/edge/meter/siemens/sicam/MeterSiemensSicamt7kg966ImplTest.java new file mode 100644 index 00000000000..61d8d0be378 --- /dev/null +++ b/io.openems.edge.meter.siemens/test/io/openems/edge/meter/siemens/sicam/MeterSiemensSicamt7kg966ImplTest.java @@ -0,0 +1,26 @@ +package io.openems.edge.meter.siemens.sicam; + +import static io.openems.common.types.MeterType.GRID; + +import org.junit.Test; + +import io.openems.edge.bridge.modbus.test.DummyModbusBridge; +import io.openems.edge.common.test.ComponentTest; +import io.openems.edge.common.test.DummyConfigurationAdmin; + +public class MeterSiemensSicamt7kg966ImplTest { + + @Test + public void test() throws Exception { + new ComponentTest(new MeterSiemensSicamt7kg966Impl()) // + .addReference("cm", new DummyConfigurationAdmin()) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // + .activate(MyConfig.create() // + .setId("meter0") // + .setModbusId("modbus0") // + .setType(GRID) // + .build()) // + ; + } + +} \ No newline at end of file diff --git a/io.openems.edge.meter.siemens/test/io/openems/edge/meter/siemens/sicam/MyConfig.java b/io.openems.edge.meter.siemens/test/io/openems/edge/meter/siemens/sicam/MyConfig.java new file mode 100644 index 00000000000..8dda59090a1 --- /dev/null +++ b/io.openems.edge.meter.siemens/test/io/openems/edge/meter/siemens/sicam/MyConfig.java @@ -0,0 +1,75 @@ +package io.openems.edge.meter.siemens.sicam; + +import io.openems.common.test.AbstractComponentConfig; +import io.openems.common.types.MeterType; +import io.openems.common.utils.ConfigUtils; + +@SuppressWarnings("all") +public class MyConfig extends AbstractComponentConfig implements Config { + + protected static class Builder { + private String id; + private String modbusId; + private int modbusUnitId; + private MeterType type; + + private Builder() { + } + + public Builder setId(String id) { + this.id = id; + return this; + } + + public Builder setModbusId(String modbusId) { + this.modbusId = modbusId; + return this; + } + + public Builder setType(MeterType type) { + this.type = type; + return this; + } + + public MyConfig build() { + return new MyConfig(this); + } + } + + /** + * Create a Config builder. + * + * @return a {@link Builder} + */ + public static Builder create() { + return new Builder(); + } + + private final Builder builder; + + private MyConfig(Builder builder) { + super(Config.class, builder.id); + this.builder = builder; + } + + @Override + public String modbus_id() { + return this.builder.modbusId; + } + + @Override + public String Modbus_target() { + return ConfigUtils.generateReferenceTargetFilter(this.id(), this.modbus_id()); + } + + @Override + public int modbusUnitId() { + return this.builder.modbusUnitId; + } + + @Override + public MeterType type() { + return this.builder.type; + } + +} diff --git a/io.openems.edge.meter.sma.shm20/.classpath b/io.openems.edge.meter.sma.shm20/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.meter.sma.shm20/.classpath +++ b/io.openems.edge.meter.sma.shm20/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.meter.sma.shm20/src/io/openems/edge/meter/sma/shm20/Config.java b/io.openems.edge.meter.sma.shm20/src/io/openems/edge/meter/sma/shm20/Config.java index 9c477ae2878..8564caf5a70 100644 --- a/io.openems.edge.meter.sma.shm20/src/io/openems/edge/meter/sma/shm20/Config.java +++ b/io.openems.edge.meter.sma.shm20/src/io/openems/edge/meter/sma/shm20/Config.java @@ -3,7 +3,7 @@ import org.osgi.service.metatype.annotations.AttributeDefinition; import org.osgi.service.metatype.annotations.ObjectClassDefinition; -import io.openems.edge.meter.api.MeterType; +import io.openems.common.types.MeterType; @ObjectClassDefinition(name = "Meter SMA Sunny Home Manager 2.0", // description = "Implements the SMA Sunny Home Manager 2.0 integrated meter.") diff --git a/io.openems.edge.meter.sma.shm20/src/io/openems/edge/meter/sma/shm20/MeterSmaShm20Impl.java b/io.openems.edge.meter.sma.shm20/src/io/openems/edge/meter/sma/shm20/MeterSmaShm20Impl.java index 7e31503d6e9..9fb292f460f 100644 --- a/io.openems.edge.meter.sma.shm20/src/io/openems/edge/meter/sma/shm20/MeterSmaShm20Impl.java +++ b/io.openems.edge.meter.sma.shm20/src/io/openems/edge/meter/sma/shm20/MeterSmaShm20Impl.java @@ -17,6 +17,7 @@ import org.osgi.service.metatype.annotations.Designate; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.types.MeterType; import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent; import io.openems.edge.bridge.modbus.api.BridgeModbus; import io.openems.edge.bridge.modbus.api.ModbusComponent; @@ -29,7 +30,6 @@ import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.taskmanager.Priority; import io.openems.edge.meter.api.ElectricityMeter; -import io.openems.edge.meter.api.MeterType; @Designate(ocd = Config.class, factory = true) @Component(// diff --git a/io.openems.edge.meter.sma.shm20/test/io/openems/edge/meter/sma/shm20/MeterSmaShm20ImplTest.java b/io.openems.edge.meter.sma.shm20/test/io/openems/edge/meter/sma/shm20/MeterSmaShm20ImplTest.java index 2d72d2aa8ee..3afb34d95ad 100644 --- a/io.openems.edge.meter.sma.shm20/test/io/openems/edge/meter/sma/shm20/MeterSmaShm20ImplTest.java +++ b/io.openems.edge.meter.sma.shm20/test/io/openems/edge/meter/sma/shm20/MeterSmaShm20ImplTest.java @@ -1,26 +1,24 @@ package io.openems.edge.meter.sma.shm20; +import static io.openems.common.types.MeterType.GRID; + import org.junit.Test; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.meter.api.MeterType; public class MeterSmaShm20ImplTest { - private static final String METER_ID = "meter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new MeterSmaShm20Impl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // - .setType(MeterType.GRID) // + .setId("meter0") // + .setModbusId("modbus0") // + .setType(GRID) // .build()) // ; } diff --git a/io.openems.edge.meter.sma.shm20/test/io/openems/edge/meter/sma/shm20/MyConfig.java b/io.openems.edge.meter.sma.shm20/test/io/openems/edge/meter/sma/shm20/MyConfig.java index 97a925d683c..442a28eacfd 100644 --- a/io.openems.edge.meter.sma.shm20/test/io/openems/edge/meter/sma/shm20/MyConfig.java +++ b/io.openems.edge.meter.sma.shm20/test/io/openems/edge/meter/sma/shm20/MyConfig.java @@ -1,8 +1,8 @@ package io.openems.edge.meter.sma.shm20; import io.openems.common.test.AbstractComponentConfig; +import io.openems.common.types.MeterType; import io.openems.common.utils.ConfigUtils; -import io.openems.edge.meter.api.MeterType; @SuppressWarnings("all") public class MyConfig extends AbstractComponentConfig implements Config { diff --git a/io.openems.edge.meter.socomec/.classpath b/io.openems.edge.meter.socomec/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.meter.socomec/.classpath +++ b/io.openems.edge.meter.socomec/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.meter.socomec/src/io/openems/edge/meter/socomec/AbstractSocomecMeter.java b/io.openems.edge.meter.socomec/src/io/openems/edge/meter/socomec/AbstractSocomecMeter.java index eaa80b7399f..8dc64080350 100644 --- a/io.openems.edge.meter.socomec/src/io/openems/edge/meter/socomec/AbstractSocomecMeter.java +++ b/io.openems.edge.meter.socomec/src/io/openems/edge/meter/socomec/AbstractSocomecMeter.java @@ -1,6 +1,7 @@ package io.openems.edge.meter.socomec; import static io.openems.edge.bridge.modbus.api.ModbusUtils.readElementOnce; +import static io.openems.edge.bridge.modbus.api.ModbusUtils.FunctionCode.FC3; import java.util.concurrent.CompletableFuture; @@ -147,14 +148,15 @@ private CompletableFuture getSocomecIdentifier() { final var result = new CompletableFuture(); // Search for Socomec identifier register. Needs to be "SOCO". - readElementOnce(this.modbusProtocol, ModbusUtils::retryOnNull, new UnsignedQuadruplewordElement(0xC350)) + readElementOnce(FC3, this.modbusProtocol, ModbusUtils::retryOnNull, new UnsignedQuadruplewordElement(0xC350)) .thenAccept(value -> { if (value != 0x0053004F0043004FL /* SOCO */) { this.channel(SocomecMeter.ChannelId.NO_SOCOMEC_METER).setNextValue(true); // Complete result with Long value result.complete(String.valueOf(value)); } - readElementOnce(this.modbusProtocol, ModbusUtils::retryOnNull, new StringWordElement(0xC38A, 8)) + readElementOnce(FC3, this.modbusProtocol, ModbusUtils::retryOnNull, + new StringWordElement(0xC38A, 8)) // .thenAccept(name -> { result.complete(name.toLowerCase()); }); diff --git a/io.openems.edge.meter.socomec/src/io/openems/edge/meter/socomec/singlephase/Config.java b/io.openems.edge.meter.socomec/src/io/openems/edge/meter/socomec/singlephase/Config.java index e7f2f875d2e..b8eb095a2f3 100644 --- a/io.openems.edge.meter.socomec/src/io/openems/edge/meter/socomec/singlephase/Config.java +++ b/io.openems.edge.meter.socomec/src/io/openems/edge/meter/socomec/singlephase/Config.java @@ -3,7 +3,7 @@ import org.osgi.service.metatype.annotations.AttributeDefinition; import org.osgi.service.metatype.annotations.ObjectClassDefinition; -import io.openems.edge.meter.api.MeterType; +import io.openems.common.types.MeterType; import io.openems.edge.meter.api.SinglePhase; @ObjectClassDefinition(// diff --git a/io.openems.edge.meter.socomec/src/io/openems/edge/meter/socomec/singlephase/MeterSocomecSinglephaseImpl.java b/io.openems.edge.meter.socomec/src/io/openems/edge/meter/socomec/singlephase/MeterSocomecSinglephaseImpl.java index 04317f76ce8..5db8dbdd32a 100644 --- a/io.openems.edge.meter.socomec/src/io/openems/edge/meter/socomec/singlephase/MeterSocomecSinglephaseImpl.java +++ b/io.openems.edge.meter.socomec/src/io/openems/edge/meter/socomec/singlephase/MeterSocomecSinglephaseImpl.java @@ -21,6 +21,7 @@ import io.openems.common.channel.AccessMode; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.types.MeterType; import io.openems.edge.bridge.modbus.api.BridgeModbus; import io.openems.edge.bridge.modbus.api.ModbusComponent; import io.openems.edge.bridge.modbus.api.element.DummyRegisterElement; @@ -32,7 +33,6 @@ import io.openems.edge.common.modbusslave.ModbusSlaveTable; import io.openems.edge.common.taskmanager.Priority; import io.openems.edge.meter.api.ElectricityMeter; -import io.openems.edge.meter.api.MeterType; import io.openems.edge.meter.api.SinglePhase; import io.openems.edge.meter.api.SinglePhaseMeter; import io.openems.edge.meter.socomec.AbstractSocomecMeter; diff --git a/io.openems.edge.meter.socomec/src/io/openems/edge/meter/socomec/threephase/Config.java b/io.openems.edge.meter.socomec/src/io/openems/edge/meter/socomec/threephase/Config.java index e5188cf84c6..8cf48d76e88 100644 --- a/io.openems.edge.meter.socomec/src/io/openems/edge/meter/socomec/threephase/Config.java +++ b/io.openems.edge.meter.socomec/src/io/openems/edge/meter/socomec/threephase/Config.java @@ -3,7 +3,7 @@ import org.osgi.service.metatype.annotations.AttributeDefinition; import org.osgi.service.metatype.annotations.ObjectClassDefinition; -import io.openems.edge.meter.api.MeterType; +import io.openems.common.types.MeterType; @ObjectClassDefinition(// name = "Meter Socomec Threephase", // diff --git a/io.openems.edge.meter.socomec/src/io/openems/edge/meter/socomec/threephase/MeterSocomecThreephaseImpl.java b/io.openems.edge.meter.socomec/src/io/openems/edge/meter/socomec/threephase/MeterSocomecThreephaseImpl.java index 5152fbde0a0..9e82f5e5132 100644 --- a/io.openems.edge.meter.socomec/src/io/openems/edge/meter/socomec/threephase/MeterSocomecThreephaseImpl.java +++ b/io.openems.edge.meter.socomec/src/io/openems/edge/meter/socomec/threephase/MeterSocomecThreephaseImpl.java @@ -21,6 +21,7 @@ import io.openems.common.channel.AccessMode; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.types.MeterType; import io.openems.edge.bridge.modbus.api.BridgeModbus; import io.openems.edge.bridge.modbus.api.ModbusComponent; import io.openems.edge.bridge.modbus.api.element.DummyRegisterElement; @@ -32,7 +33,6 @@ import io.openems.edge.common.modbusslave.ModbusSlaveTable; import io.openems.edge.common.taskmanager.Priority; import io.openems.edge.meter.api.ElectricityMeter; -import io.openems.edge.meter.api.MeterType; import io.openems.edge.meter.socomec.AbstractSocomecMeter; import io.openems.edge.meter.socomec.SocomecMeter; diff --git a/io.openems.edge.meter.socomec/test/io/openems/edge/meter/socomec/singlephase/MeterSocomecSinglephaseImplTest.java b/io.openems.edge.meter.socomec/test/io/openems/edge/meter/socomec/singlephase/MeterSocomecSinglephaseImplTest.java index 7b232645b1f..9565b4bb274 100644 --- a/io.openems.edge.meter.socomec/test/io/openems/edge/meter/socomec/singlephase/MeterSocomecSinglephaseImplTest.java +++ b/io.openems.edge.meter.socomec/test/io/openems/edge/meter/socomec/singlephase/MeterSocomecSinglephaseImplTest.java @@ -1,19 +1,17 @@ package io.openems.edge.meter.socomec.singlephase; +import static io.openems.common.types.MeterType.GRID; +import static io.openems.edge.meter.api.SinglePhase.L1; + import org.junit.Before; import org.junit.Test; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.meter.api.MeterType; -import io.openems.edge.meter.api.SinglePhase; public class MeterSocomecSinglephaseImplTest { - private static final String METER_ID = "meter0"; - private static final String MODBUS_ID = "modbus0"; - private static MeterSocomecSinglephaseImpl meter; @Before @@ -21,13 +19,13 @@ public void setup() throws Exception { meter = new MeterSocomecSinglephaseImpl(); new ComponentTest(meter) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // - .setType(MeterType.GRID) // + .setId("meter0") // + .setModbusId("modbus0") // + .setType(GRID) // .setInvert(false) // - .setPhase(SinglePhase.L1) // + .setPhase(L1) // .build()); // } diff --git a/io.openems.edge.meter.socomec/test/io/openems/edge/meter/socomec/singlephase/MyConfig.java b/io.openems.edge.meter.socomec/test/io/openems/edge/meter/socomec/singlephase/MyConfig.java index d17afa4603c..23f79f2a771 100644 --- a/io.openems.edge.meter.socomec/test/io/openems/edge/meter/socomec/singlephase/MyConfig.java +++ b/io.openems.edge.meter.socomec/test/io/openems/edge/meter/socomec/singlephase/MyConfig.java @@ -1,8 +1,8 @@ package io.openems.edge.meter.socomec.singlephase; import io.openems.common.test.AbstractComponentConfig; +import io.openems.common.types.MeterType; import io.openems.common.utils.ConfigUtils; -import io.openems.edge.meter.api.MeterType; import io.openems.edge.meter.api.SinglePhase; @SuppressWarnings("all") diff --git a/io.openems.edge.meter.socomec/test/io/openems/edge/meter/socomec/threephase/MeterSocomecThreephaseImplTest.java b/io.openems.edge.meter.socomec/test/io/openems/edge/meter/socomec/threephase/MeterSocomecThreephaseImplTest.java index 399894b186f..5d3ff68ab51 100644 --- a/io.openems.edge.meter.socomec/test/io/openems/edge/meter/socomec/threephase/MeterSocomecThreephaseImplTest.java +++ b/io.openems.edge.meter.socomec/test/io/openems/edge/meter/socomec/threephase/MeterSocomecThreephaseImplTest.java @@ -1,18 +1,16 @@ package io.openems.edge.meter.socomec.threephase; +import static io.openems.common.types.MeterType.GRID; + import org.junit.Before; import org.junit.Test; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.meter.api.MeterType; public class MeterSocomecThreephaseImplTest { - private static final String METER_ID = "meter0"; - private static final String MODBUS_ID = "modbus0"; - private static MeterSocomecThreephaseImpl meter; @Before @@ -20,11 +18,11 @@ public void setup() throws Exception { meter = new MeterSocomecThreephaseImpl(); new ComponentTest(meter) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // - .setType(MeterType.GRID) // + .setId("meter0") // + .setModbusId("modbus0") // + .setType(GRID) // .setInvert(false) // .build()); // } diff --git a/io.openems.edge.meter.socomec/test/io/openems/edge/meter/socomec/threephase/MyConfig.java b/io.openems.edge.meter.socomec/test/io/openems/edge/meter/socomec/threephase/MyConfig.java index 23923e567d1..1aaef059a46 100644 --- a/io.openems.edge.meter.socomec/test/io/openems/edge/meter/socomec/threephase/MyConfig.java +++ b/io.openems.edge.meter.socomec/test/io/openems/edge/meter/socomec/threephase/MyConfig.java @@ -1,8 +1,8 @@ package io.openems.edge.meter.socomec.threephase; import io.openems.common.test.AbstractComponentConfig; +import io.openems.common.types.MeterType; import io.openems.common.utils.ConfigUtils; -import io.openems.edge.meter.api.MeterType; @SuppressWarnings("all") public class MyConfig extends AbstractComponentConfig implements Config { diff --git a/io.openems.edge.meter.sunspec/.classpath b/io.openems.edge.meter.sunspec/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.meter.sunspec/.classpath +++ b/io.openems.edge.meter.sunspec/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.meter.virtual/.classpath b/io.openems.edge.meter.virtual/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.meter.virtual/.classpath +++ b/io.openems.edge.meter.virtual/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.meter.virtual/src/io/openems/edge/meter/virtual/add/Config.java b/io.openems.edge.meter.virtual/src/io/openems/edge/meter/virtual/add/Config.java index c36a906d9b8..4fc95f884ca 100644 --- a/io.openems.edge.meter.virtual/src/io/openems/edge/meter/virtual/add/Config.java +++ b/io.openems.edge.meter.virtual/src/io/openems/edge/meter/virtual/add/Config.java @@ -3,7 +3,7 @@ import org.osgi.service.metatype.annotations.AttributeDefinition; import org.osgi.service.metatype.annotations.ObjectClassDefinition; -import io.openems.edge.meter.api.MeterType; +import io.openems.common.types.MeterType; @ObjectClassDefinition(// name = "Meter Virtual Add", // diff --git a/io.openems.edge.meter.virtual/src/io/openems/edge/meter/virtual/add/MeterVirtualAddImpl.java b/io.openems.edge.meter.virtual/src/io/openems/edge/meter/virtual/add/MeterVirtualAddImpl.java index b31e3c66280..6f325670232 100644 --- a/io.openems.edge.meter.virtual/src/io/openems/edge/meter/virtual/add/MeterVirtualAddImpl.java +++ b/io.openems.edge.meter.virtual/src/io/openems/edge/meter/virtual/add/MeterVirtualAddImpl.java @@ -17,12 +17,12 @@ import io.openems.common.channel.AccessMode; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.types.MeterType; import io.openems.edge.common.component.AbstractOpenemsComponent; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.modbusslave.ModbusSlave; import io.openems.edge.common.modbusslave.ModbusSlaveTable; import io.openems.edge.meter.api.ElectricityMeter; -import io.openems.edge.meter.api.MeterType; import io.openems.edge.meter.api.VirtualMeter; @Designate(ocd = Config.class, factory = true) diff --git a/io.openems.edge.meter.virtual/src/io/openems/edge/meter/virtual/subtract/Config.java b/io.openems.edge.meter.virtual/src/io/openems/edge/meter/virtual/subtract/Config.java index 7ece79b26e9..4366e8af241 100644 --- a/io.openems.edge.meter.virtual/src/io/openems/edge/meter/virtual/subtract/Config.java +++ b/io.openems.edge.meter.virtual/src/io/openems/edge/meter/virtual/subtract/Config.java @@ -4,7 +4,7 @@ import org.osgi.service.metatype.annotations.AttributeDefinition; import org.osgi.service.metatype.annotations.ObjectClassDefinition; -import io.openems.edge.meter.api.MeterType; +import io.openems.common.types.MeterType; @ObjectClassDefinition(// name = "Meter Virtual Subtract", // diff --git a/io.openems.edge.meter.virtual/src/io/openems/edge/meter/virtual/subtract/VirtualSubtractMeterImpl.java b/io.openems.edge.meter.virtual/src/io/openems/edge/meter/virtual/subtract/VirtualSubtractMeterImpl.java index 0f11e68eb9b..31e60176923 100644 --- a/io.openems.edge.meter.virtual/src/io/openems/edge/meter/virtual/subtract/VirtualSubtractMeterImpl.java +++ b/io.openems.edge.meter.virtual/src/io/openems/edge/meter/virtual/subtract/VirtualSubtractMeterImpl.java @@ -16,12 +16,12 @@ import io.openems.common.channel.AccessMode; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.types.MeterType; import io.openems.edge.common.component.AbstractOpenemsComponent; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.modbusslave.ModbusSlave; import io.openems.edge.common.modbusslave.ModbusSlaveTable; import io.openems.edge.meter.api.ElectricityMeter; -import io.openems.edge.meter.api.MeterType; import io.openems.edge.meter.api.VirtualMeter; @Designate(ocd = Config.class, factory = true) diff --git a/io.openems.edge.meter.virtual/test/io/openems/edge/meter/virtual/add/MeterVirtualAddImplTest.java b/io.openems.edge.meter.virtual/test/io/openems/edge/meter/virtual/add/MeterVirtualAddImplTest.java index 69dd6d1c616..9ca132e2e74 100644 --- a/io.openems.edge.meter.virtual/test/io/openems/edge/meter/virtual/add/MeterVirtualAddImplTest.java +++ b/io.openems.edge.meter.virtual/test/io/openems/edge/meter/virtual/add/MeterVirtualAddImplTest.java @@ -1,89 +1,59 @@ package io.openems.edge.meter.virtual.add; +import static io.openems.common.types.MeterType.GRID; +import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.ACTIVE_POWER; +import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.ACTIVE_POWER_L1; +import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.ACTIVE_POWER_L2; +import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.ACTIVE_POWER_L3; +import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.FREQUENCY; +import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.VOLTAGE; + import org.junit.Test; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.meter.api.MeterType; import io.openems.edge.meter.test.DummyElectricityMeter; public class MeterVirtualAddImplTest { - private static final String METER_ID = "meter0"; - private static final ChannelAddress METER_POWER = new ChannelAddress(METER_ID, "ActivePower"); - private static final ChannelAddress METER_VOLTAGE = new ChannelAddress(METER_ID, "Voltage"); - private static final ChannelAddress METER_FREQ = new ChannelAddress(METER_ID, "Frequency"); - - private static final ChannelAddress METER_POWER_L1 = new ChannelAddress(METER_ID, "ActivePowerL1"); - private static final ChannelAddress METER_POWER_L2 = new ChannelAddress(METER_ID, "ActivePowerL2"); - private static final ChannelAddress METER_POWER_L3 = new ChannelAddress(METER_ID, "ActivePowerL3"); - - private static final String METER_ID_1 = "meter1"; - private static final ChannelAddress METER_ID_1_ACTIVEPOWER = new ChannelAddress(METER_ID_1, "ActivePower"); - private static final ChannelAddress METER_ID_1_VOLTAGE = new ChannelAddress(METER_ID_1, "Voltage"); - private static final ChannelAddress METER_ID_1_FREQUENCY = new ChannelAddress(METER_ID_1, "Frequency"); - - private static final ChannelAddress METER_ID_1_ACTIVEPOWER_L1 = new ChannelAddress(METER_ID_1, "ActivePowerL1"); - private static final ChannelAddress METER_ID_1_ACTIVEPOWER_L2 = new ChannelAddress(METER_ID_1, "ActivePowerL2"); - private static final ChannelAddress METER_ID_1_ACTIVEPOWER_L3 = new ChannelAddress(METER_ID_1, "ActivePowerL3"); - - private static final String METER_ID_2 = "meter2"; - private static final ChannelAddress METER_ID_2_ACTIVEPOWER = new ChannelAddress(METER_ID_2, "ActivePower"); - private static final ChannelAddress METER_ID_2_VOLTAGE = new ChannelAddress(METER_ID_2, "Voltage"); - private static final ChannelAddress METER_ID_2_FREQUENCY = new ChannelAddress(METER_ID_2, "Frequency"); - - private static final ChannelAddress METER_ID_2_ACTIVEPOWER_L1 = new ChannelAddress(METER_ID_2, "ActivePowerL1"); - private static final ChannelAddress METER_ID_2_ACTIVEPOWER_L2 = new ChannelAddress(METER_ID_2, "ActivePowerL2"); - private static final ChannelAddress METER_ID_2_ACTIVEPOWER_L3 = new ChannelAddress(METER_ID_2, "ActivePowerL3"); - - private static final String METER_ID_3 = "meter3"; - private static final ChannelAddress METER_ID_3_ACTIVEPOWER = new ChannelAddress(METER_ID_3, "ActivePower"); - private static final ChannelAddress METER_ID_3_VOLTAGE = new ChannelAddress(METER_ID_3, "Voltage"); - private static final ChannelAddress METER_ID_3_FREQUENCY = new ChannelAddress(METER_ID_3, "Frequency"); - - private static final ChannelAddress METER_ID_3_ACTIVEPOWER_L1 = new ChannelAddress(METER_ID_3, "ActivePowerL1"); - private static final ChannelAddress METER_ID_3_ACTIVEPOWER_L2 = new ChannelAddress(METER_ID_3, "ActivePowerL2"); - private static final ChannelAddress METER_ID_3_ACTIVEPOWER_L3 = new ChannelAddress(METER_ID_3, "ActivePowerL3"); - @Test public void test() throws Exception { new ComponentTest(new MeterVirtualAddImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("addMeter", new DummyElectricityMeter(METER_ID_1)) - .addReference("addMeter", new DummyElectricityMeter(METER_ID_2)) // - .addReference("addMeter", new DummyElectricityMeter(METER_ID_3)) // + .addReference("addMeter", new DummyElectricityMeter("meter1")) + .addReference("addMeter", new DummyElectricityMeter("meter2")) // + .addReference("addMeter", new DummyElectricityMeter("meter3")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setMeterIds(METER_ID_1, METER_ID_2, METER_ID_3) // - .setType(MeterType.GRID) // + .setId("meter0") // + .setMeterIds("meter1", "meter2", "meter3") // + .setType(GRID) // .build()) .next(new TestCase("one") // - .input(METER_ID_1_ACTIVEPOWER, 6_000) // - .input(METER_ID_1_ACTIVEPOWER_L1, 2_000) // - .input(METER_ID_1_ACTIVEPOWER_L2, 2_000) // - .input(METER_ID_1_ACTIVEPOWER_L3, 2_000) // - .input(METER_ID_2_ACTIVEPOWER, 7_500) // - .input(METER_ID_2_ACTIVEPOWER_L1, 2_500) // - .input(METER_ID_2_ACTIVEPOWER_L2, 2_500) // - .input(METER_ID_2_ACTIVEPOWER_L3, 2_500) // - .input(METER_ID_3_ACTIVEPOWER, 9_000) // - .input(METER_ID_3_ACTIVEPOWER_L1, 3_000) // - .input(METER_ID_3_ACTIVEPOWER_L2, 3_000) // - .input(METER_ID_3_ACTIVEPOWER_L3, 3_000) // - .input(METER_ID_1_VOLTAGE, 10) // - .input(METER_ID_2_VOLTAGE, 20) // - .input(METER_ID_3_VOLTAGE, 30) // - .input(METER_ID_1_FREQUENCY, 49) // - .input(METER_ID_2_FREQUENCY, 51) // - .input(METER_ID_3_FREQUENCY, 56)) // + .input("meter1", ACTIVE_POWER, 6_000) // + .input("meter1", ACTIVE_POWER_L1, 2_000) // + .input("meter1", ACTIVE_POWER_L2, 2_000) // + .input("meter1", ACTIVE_POWER_L3, 2_000) // + .input("meter2", ACTIVE_POWER, 7_500) // + .input("meter2", ACTIVE_POWER_L1, 2_500) // + .input("meter2", ACTIVE_POWER_L2, 2_500) // + .input("meter2", ACTIVE_POWER_L3, 2_500) // + .input("meter3", ACTIVE_POWER, 9_000) // + .input("meter3", ACTIVE_POWER_L1, 3_000) // + .input("meter3", ACTIVE_POWER_L2, 3_000) // + .input("meter3", ACTIVE_POWER_L3, 3_000) // + .input("meter1", VOLTAGE, 10) // + .input("meter2", VOLTAGE, 20) // + .input("meter3", VOLTAGE, 30) // + .input("meter1", FREQUENCY, 49) // + .input("meter2", FREQUENCY, 51) // + .input("meter3", FREQUENCY, 56)) // .next(new TestCase("two") // - .output(METER_POWER, 22_500) // - .output(METER_POWER_L1, 7_500) // - .output(METER_POWER_L2, 7_500) // - .output(METER_POWER_L3, 7_500) // - .output(METER_VOLTAGE, 20) // - .output(METER_FREQ, 52)); + .output(ACTIVE_POWER, 22_500) // + .output(ACTIVE_POWER_L1, 7_500) // + .output(ACTIVE_POWER_L2, 7_500) // + .output(ACTIVE_POWER_L3, 7_500) // + .output(VOLTAGE, 20) // + .output(FREQUENCY, 52)); } } diff --git a/io.openems.edge.meter.virtual/test/io/openems/edge/meter/virtual/add/MyConfig.java b/io.openems.edge.meter.virtual/test/io/openems/edge/meter/virtual/add/MyConfig.java index d51dda118d9..8e38a4efcba 100644 --- a/io.openems.edge.meter.virtual/test/io/openems/edge/meter/virtual/add/MyConfig.java +++ b/io.openems.edge.meter.virtual/test/io/openems/edge/meter/virtual/add/MyConfig.java @@ -1,7 +1,7 @@ package io.openems.edge.meter.virtual.add; import io.openems.common.test.AbstractComponentConfig; -import io.openems.edge.meter.api.MeterType; +import io.openems.common.types.MeterType; @SuppressWarnings("all") public class MyConfig extends AbstractComponentConfig implements Config { diff --git a/io.openems.edge.meter.virtual/test/io/openems/edge/meter/virtual/subtract/MyConfig.java b/io.openems.edge.meter.virtual/test/io/openems/edge/meter/virtual/subtract/MyConfig.java index 10eca8d640c..2dfa372ebf0 100644 --- a/io.openems.edge.meter.virtual/test/io/openems/edge/meter/virtual/subtract/MyConfig.java +++ b/io.openems.edge.meter.virtual/test/io/openems/edge/meter/virtual/subtract/MyConfig.java @@ -1,8 +1,8 @@ package io.openems.edge.meter.virtual.subtract; import io.openems.common.test.AbstractComponentConfig; +import io.openems.common.types.MeterType; import io.openems.common.utils.ConfigUtils; -import io.openems.edge.meter.api.MeterType; @SuppressWarnings("all") public class MyConfig extends AbstractComponentConfig implements Config { diff --git a/io.openems.edge.meter.virtual/test/io/openems/edge/meter/virtual/subtract/VirtualSubtractMeterImplTest.java b/io.openems.edge.meter.virtual/test/io/openems/edge/meter/virtual/subtract/VirtualSubtractMeterImplTest.java index d92470e3068..a5c4772190a 100644 --- a/io.openems.edge.meter.virtual/test/io/openems/edge/meter/virtual/subtract/VirtualSubtractMeterImplTest.java +++ b/io.openems.edge.meter.virtual/test/io/openems/edge/meter/virtual/subtract/VirtualSubtractMeterImplTest.java @@ -1,60 +1,50 @@ package io.openems.edge.meter.virtual.subtract; -import org.junit.Test; +import static io.openems.common.types.MeterType.GRID; + +import java.util.List; -import com.google.common.collect.Lists; +import org.junit.Test; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; +import io.openems.edge.ess.api.SymmetricEss; import io.openems.edge.ess.test.DummyManagedSymmetricEss; -import io.openems.edge.meter.api.MeterType; +import io.openems.edge.meter.api.ElectricityMeter; import io.openems.edge.meter.test.DummyElectricityMeter; public class VirtualSubtractMeterImplTest { - private static final String METER_ID = "meter0"; - private static final ChannelAddress METER_POWER = new ChannelAddress(METER_ID, "ActivePower"); - - private static final String MINUEND_ID = "meter1"; - private static final ChannelAddress MINUEND_POWER = new ChannelAddress(MINUEND_ID, "ActivePower"); - - private static final String SUBTRAHEND1_ID = "meter2"; - private static final ChannelAddress SUBTRAHEND1_POWER = new ChannelAddress(SUBTRAHEND1_ID, "ActivePower"); - - private static final String SUBTRAHEND2_ID = "ess0"; - private static final ChannelAddress SUBTRAHEND2_POWER = new ChannelAddress(SUBTRAHEND2_ID, "ActivePower"); - @Test public void test() throws Exception { new ComponentTest(new VirtualSubtractMeterImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("minuend", new DummyElectricityMeter(MINUEND_ID)) // - .addReference("subtrahends", Lists.newArrayList(// - new DummyElectricityMeter(SUBTRAHEND1_ID), // - new DummyManagedSymmetricEss(SUBTRAHEND2_ID))) // + .addReference("minuend", new DummyElectricityMeter("meter1")) // + .addReference("subtrahends", List.of(// + new DummyElectricityMeter("meter2"), // + new DummyManagedSymmetricEss("ess0"))) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setType(MeterType.GRID) // + .setId("meter0") // + .setType(GRID) // .setAddToSum(true) // - .setMinuendId(MINUEND_ID) // - .setSubtrahendsIds(SUBTRAHEND1_ID, SUBTRAHEND2_ID) // + .setMinuendId("meter1") // + .setSubtrahendsIds("meter2", "ess0") // .build()) // .next(new TestCase() // - .input(MINUEND_POWER, 5_000) // - .input(SUBTRAHEND1_POWER, 2_000) // - .input(SUBTRAHEND2_POWER, 4_000) // - .output(METER_POWER, -1000)) // + .input("meter1", ElectricityMeter.ChannelId.ACTIVE_POWER, 5_000) // + .input("meter2", ElectricityMeter.ChannelId.ACTIVE_POWER, 2_000) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 4_000) // + .output("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -1000)) // .next(new TestCase() // - .input(MINUEND_POWER, null) // - .input(SUBTRAHEND1_POWER, 2_000) // - .input(SUBTRAHEND2_POWER, 4_000) // - .output(METER_POWER, null)) // + .input("meter1", ElectricityMeter.ChannelId.ACTIVE_POWER, null) // + .input("meter2", ElectricityMeter.ChannelId.ACTIVE_POWER, 2_000) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 4_000) // + .output("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, null)) // .next(new TestCase() // - .input(MINUEND_POWER, 5_000) // - .input(SUBTRAHEND1_POWER, null) // - .input(SUBTRAHEND2_POWER, 4_000) // - .output(METER_POWER, 1000)); + .input("meter1", ElectricityMeter.ChannelId.ACTIVE_POWER, 5_000) // + .input("meter2", ElectricityMeter.ChannelId.ACTIVE_POWER, null) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 4_000) // + .output("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 1000)); } } \ No newline at end of file diff --git a/io.openems.edge.meter.weidmueller/.classpath b/io.openems.edge.meter.weidmueller/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.meter.weidmueller/.classpath +++ b/io.openems.edge.meter.weidmueller/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.meter.weidmueller/src/io/openems/edge/meter/weidmueller/Config.java b/io.openems.edge.meter.weidmueller/src/io/openems/edge/meter/weidmueller/Config.java index 8f1c7107db9..6821bd4a123 100644 --- a/io.openems.edge.meter.weidmueller/src/io/openems/edge/meter/weidmueller/Config.java +++ b/io.openems.edge.meter.weidmueller/src/io/openems/edge/meter/weidmueller/Config.java @@ -3,7 +3,7 @@ import org.osgi.service.metatype.annotations.AttributeDefinition; import org.osgi.service.metatype.annotations.ObjectClassDefinition; -import io.openems.edge.meter.api.MeterType; +import io.openems.common.types.MeterType; @ObjectClassDefinition(// name = "Meter Weidmueller 525", // diff --git a/io.openems.edge.meter.weidmueller/src/io/openems/edge/meter/weidmueller/MeterWeidmueller525Impl.java b/io.openems.edge.meter.weidmueller/src/io/openems/edge/meter/weidmueller/MeterWeidmueller525Impl.java index 4dd38b5e0a3..245a476f35d 100644 --- a/io.openems.edge.meter.weidmueller/src/io/openems/edge/meter/weidmueller/MeterWeidmueller525Impl.java +++ b/io.openems.edge.meter.weidmueller/src/io/openems/edge/meter/weidmueller/MeterWeidmueller525Impl.java @@ -14,6 +14,7 @@ import io.openems.common.channel.AccessMode; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.types.MeterType; import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent; import io.openems.edge.bridge.modbus.api.BridgeModbus; import io.openems.edge.bridge.modbus.api.ModbusComponent; @@ -25,7 +26,6 @@ import io.openems.edge.common.modbusslave.ModbusSlaveTable; import io.openems.edge.common.taskmanager.Priority; import io.openems.edge.meter.api.ElectricityMeter; -import io.openems.edge.meter.api.MeterType; @Designate(ocd = Config.class, factory = true) @Component(// diff --git a/io.openems.edge.meter.weidmueller/test/io/openems/edge/meter/weidmueller/MeterWeidmueller525ImplTest.java b/io.openems.edge.meter.weidmueller/test/io/openems/edge/meter/weidmueller/MeterWeidmueller525ImplTest.java index cb447a0c407..47f6a43085b 100644 --- a/io.openems.edge.meter.weidmueller/test/io/openems/edge/meter/weidmueller/MeterWeidmueller525ImplTest.java +++ b/io.openems.edge.meter.weidmueller/test/io/openems/edge/meter/weidmueller/MeterWeidmueller525ImplTest.java @@ -1,26 +1,24 @@ package io.openems.edge.meter.weidmueller; +import static io.openems.common.types.MeterType.GRID; + import org.junit.Test; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.meter.api.MeterType; public class MeterWeidmueller525ImplTest { - private static final String METER_ID = "meter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new MeterWeidmueller525Impl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // - .setType(MeterType.GRID) // + .setId("meter0") // + .setModbusId("modbus0") // + .setType(GRID) // .build()) // ; } diff --git a/io.openems.edge.meter.weidmueller/test/io/openems/edge/meter/weidmueller/MyConfig.java b/io.openems.edge.meter.weidmueller/test/io/openems/edge/meter/weidmueller/MyConfig.java index cee040fdd54..78b45a398fc 100644 --- a/io.openems.edge.meter.weidmueller/test/io/openems/edge/meter/weidmueller/MyConfig.java +++ b/io.openems.edge.meter.weidmueller/test/io/openems/edge/meter/weidmueller/MyConfig.java @@ -1,8 +1,8 @@ package io.openems.edge.meter.weidmueller; import io.openems.common.test.AbstractComponentConfig; +import io.openems.common.types.MeterType; import io.openems.common.utils.ConfigUtils; -import io.openems.edge.meter.api.MeterType; @SuppressWarnings("all") public class MyConfig extends AbstractComponentConfig implements Config { diff --git a/io.openems.edge.meter.ziehl/.classpath b/io.openems.edge.meter.ziehl/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.meter.ziehl/.classpath +++ b/io.openems.edge.meter.ziehl/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.meter.ziehl/src/io/openems/edge/meter/ziehl/efr4001ip/Config.java b/io.openems.edge.meter.ziehl/src/io/openems/edge/meter/ziehl/efr4001ip/Config.java index 15188ec783f..81fae48a3dc 100644 --- a/io.openems.edge.meter.ziehl/src/io/openems/edge/meter/ziehl/efr4001ip/Config.java +++ b/io.openems.edge.meter.ziehl/src/io/openems/edge/meter/ziehl/efr4001ip/Config.java @@ -3,7 +3,7 @@ import org.osgi.service.metatype.annotations.AttributeDefinition; import org.osgi.service.metatype.annotations.ObjectClassDefinition; -import io.openems.edge.meter.api.MeterType; +import io.openems.common.types.MeterType; @ObjectClassDefinition(// name = "Meter Ziehl EFR4001IP", // diff --git a/io.openems.edge.meter.ziehl/src/io/openems/edge/meter/ziehl/efr4001ip/MeterZiehlEfr4001IpImpl.java b/io.openems.edge.meter.ziehl/src/io/openems/edge/meter/ziehl/efr4001ip/MeterZiehlEfr4001IpImpl.java index e6d852fcd0d..bfedc41e858 100644 --- a/io.openems.edge.meter.ziehl/src/io/openems/edge/meter/ziehl/efr4001ip/MeterZiehlEfr4001IpImpl.java +++ b/io.openems.edge.meter.ziehl/src/io/openems/edge/meter/ziehl/efr4001ip/MeterZiehlEfr4001IpImpl.java @@ -19,6 +19,7 @@ import io.openems.common.channel.AccessMode; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.types.MeterType; import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent; import io.openems.edge.bridge.modbus.api.BridgeModbus; import io.openems.edge.bridge.modbus.api.ModbusComponent; @@ -32,7 +33,6 @@ import io.openems.edge.common.modbusslave.ModbusSlaveTable; import io.openems.edge.common.taskmanager.Priority; import io.openems.edge.meter.api.ElectricityMeter; -import io.openems.edge.meter.api.MeterType; @Designate(ocd = Config.class, factory = true) @Component(// diff --git a/io.openems.edge.meter.ziehl/test/io/openems/edge/meter/ziehl/efr4001ip/MeterZiehlEfr4001IpImplTest.java b/io.openems.edge.meter.ziehl/test/io/openems/edge/meter/ziehl/efr4001ip/MeterZiehlEfr4001IpImplTest.java index 11397a94eee..7306a4697c7 100644 --- a/io.openems.edge.meter.ziehl/test/io/openems/edge/meter/ziehl/efr4001ip/MeterZiehlEfr4001IpImplTest.java +++ b/io.openems.edge.meter.ziehl/test/io/openems/edge/meter/ziehl/efr4001ip/MeterZiehlEfr4001IpImplTest.java @@ -1,28 +1,26 @@ package io.openems.edge.meter.ziehl.efr4001ip; +import static io.openems.common.types.MeterType.GRID; + import org.junit.Test; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.meter.api.MeterType; public class MeterZiehlEfr4001IpImplTest { - private static final String COMPONENT_ID = "component0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new MeterZiehlEfr4001IpImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // - .setModbusId(MODBUS_ID) // + .setId("component0") // + .setModbusId("modbus0") // .setModbusUnitId(1) // - .setMeterType(MeterType.GRID) // + .setMeterType(GRID) // .setInvert(false) // .build()) .next(new TestCase()); diff --git a/io.openems.edge.meter.ziehl/test/io/openems/edge/meter/ziehl/efr4001ip/MyConfig.java b/io.openems.edge.meter.ziehl/test/io/openems/edge/meter/ziehl/efr4001ip/MyConfig.java index c46214848ca..b5d0c49f635 100644 --- a/io.openems.edge.meter.ziehl/test/io/openems/edge/meter/ziehl/efr4001ip/MyConfig.java +++ b/io.openems.edge.meter.ziehl/test/io/openems/edge/meter/ziehl/efr4001ip/MyConfig.java @@ -1,8 +1,8 @@ package io.openems.edge.meter.ziehl.efr4001ip; import io.openems.common.test.AbstractComponentConfig; +import io.openems.common.types.MeterType; import io.openems.common.utils.ConfigUtils; -import io.openems.edge.meter.api.MeterType; @SuppressWarnings("all") public class MyConfig extends AbstractComponentConfig implements Config { diff --git a/io.openems.edge.onewire.thermometer/.classpath b/io.openems.edge.onewire.thermometer/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.onewire.thermometer/.classpath +++ b/io.openems.edge.onewire.thermometer/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.predictor.api/.classpath b/io.openems.edge.predictor.api/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.predictor.api/.classpath +++ b/io.openems.edge.predictor.api/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.predictor.api/src/io/openems/edge/predictor/api/prediction/AbstractPredictor.java b/io.openems.edge.predictor.api/src/io/openems/edge/predictor/api/prediction/AbstractPredictor.java index 88144c3f004..2f58d242fe8 100644 --- a/io.openems.edge.predictor.api/src/io/openems/edge/predictor/api/prediction/AbstractPredictor.java +++ b/io.openems.edge.predictor.api/src/io/openems/edge/predictor/api/prediction/AbstractPredictor.java @@ -6,6 +6,7 @@ import java.time.ZonedDateTime; import java.util.HashMap; import java.util.Map; +import java.util.Optional; import org.osgi.service.component.ComponentContext; import org.slf4j.Logger; @@ -59,7 +60,10 @@ public ChannelAddress[] getChannelAddresses() { public Prediction getPrediction(ChannelAddress channelAddress) { var now = roundDownToQuarter(ZonedDateTime.now(this.getClockProvider().getClock())); var prediction = this.predictions.get(channelAddress); - if (prediction == null || prediction.isEmpty() || now.isAfter(prediction.valuePerQuarter.firstKey())) { + if (Optional.ofNullable(prediction) // + .map(p -> p.getFirstTime()) // + .map(t -> now.isAfter(t)) // + .orElse(true /* any null? */)) { // Create new prediction prediction = this.createNewPrediction(channelAddress); this.predictions.put(channelAddress, prediction); diff --git a/io.openems.edge.predictor.api/src/io/openems/edge/predictor/api/prediction/Prediction.java b/io.openems.edge.predictor.api/src/io/openems/edge/predictor/api/prediction/Prediction.java index d632a34995e..4c7e406716b 100644 --- a/io.openems.edge.predictor.api/src/io/openems/edge/predictor/api/prediction/Prediction.java +++ b/io.openems.edge.predictor.api/src/io/openems/edge/predictor/api/prediction/Prediction.java @@ -1,21 +1,17 @@ package io.openems.edge.predictor.api.prediction; import static io.openems.common.utils.DateUtils.roundDownToQuarter; -import static io.openems.edge.common.type.TypeUtils.max; -import static io.openems.edge.common.type.TypeUtils.min; -import static java.util.Collections.emptySortedMap; -import static java.util.Collections.unmodifiableSortedMap; import java.time.ZonedDateTime; -import java.util.ArrayList; import java.util.Objects; import java.util.SortedMap; -import java.util.TreeMap; -import java.util.stream.IntStream; import java.util.stream.Stream; +import com.google.common.collect.ImmutableSortedMap; + import io.openems.common.types.ChannelAddress; import io.openems.edge.common.sum.Sum; +import io.openems.edge.common.type.QuarterlyValues; /** * Holds a prediction - one value per 15 minutes. @@ -25,7 +21,7 @@ * to _sum/ProductionGridActivePower, the value is in unit Watt and represents * the average Watt within a 15 minutes period. */ -public class Prediction { +public class Prediction extends QuarterlyValues { /** * Holds an 'empty' {@link Prediction} object, i.e. `valuePerQuarter` map is @@ -33,16 +29,17 @@ public class Prediction { * * @return an 'empty' {@link TimeOfUsePrices} object */ - public static final Prediction EMPTY_PREDICTION = new Prediction(emptySortedMap()); + public static final Prediction EMPTY_PREDICTION = new Prediction(ImmutableSortedMap.of()); /** * Sums up the given {@link Prediction}s. If any source value is null, the - * result value is also null. + * result value is null/removed. * * @param predictions the given {@link Prediction} * @return a {@link Prediction} holding the sum of all predictions. */ public static Prediction sum(Prediction... predictions) { + // Evaluate the minimum common time of all source predictions final var minTime = Stream.of(predictions) // .map(p -> p.valuePerQuarter) // .filter(m -> !m.isEmpty()) // @@ -51,13 +48,17 @@ public static Prediction sum(Prediction... predictions) { if (minTime.isEmpty()) { return EMPTY_PREDICTION; } + + // Evaluate the maximium common of all source predictions final var maxTime = Stream.of(predictions) // + .filter(m -> !m.isEmpty()) // .map(p -> p.valuePerQuarter.lastKey()) // .min(ZonedDateTime::compareTo); if (maxTime.isEmpty()) { return EMPTY_PREDICTION; } - final var result = new TreeMap(); + + final var result = ImmutableSortedMap.naturalOrder(); for (var time = minTime.get(); !time.isAfter(maxTime.get()); time.plusMinutes(15)) { var values = Stream.of(predictions) // .map(p -> p.valuePerQuarter.get(time)) // @@ -70,10 +71,22 @@ public static Prediction sum(Prediction... predictions) { } result.put(time, sum); } - return Prediction.from(UNLIMITED, result); + return Prediction.from(result.build()); } - private static record ValueRange(Integer min, Integer max) { + protected static record ValueRange(Integer min, Integer max) { + public Integer fitWithin(Integer value) { + if (value == null) { + return null; + } + if (this.max != null && value > this.max) { + value = this.max; + } + if (this.min != null && value < this.min) { + value = this.min; + } + return value; + } } private static final ValueRange UNLIMITED = new ValueRange(null, null); @@ -109,7 +122,7 @@ public static ValueRange getValueRange(Sum sum, ChannelAddress channelAddress) { * @return a {@link Prediction} object */ public static Prediction from(ZonedDateTime time, Integer... values) { - return from(UNLIMITED, time, values); + return new Prediction(time, values); } /** @@ -134,36 +147,12 @@ public static Prediction from(Sum sum, ChannelAddress channelAddress, ZonedDateT * Constructs a {@link Prediction} object. * *

    - * Trailing `nulls` are cut out. - * - * @param valueRange the {@link ValueRange} - * @param time the base time of the prediction values, rounded down to 15 - * minutes - * @param values the quarterly prediction values. - * @return a {@link Prediction} object - */ - public static Prediction from(ValueRange valueRange, ZonedDateTime time, Integer... values) { - return from(valueRange, buildMap(time, values)); - } - - /** - * Constructs a {@link Prediction} object. + * Map keys must be full quarters (15 minutes) * - *

    - * Postprocessing is applied: - * - *

      - *
    • Map keys are rounded down to full quarters (15 minutes) - *
    • Gaps in keys are filled (value = null) - *
    • Trailing null values are removed - *
    - * - * @param valueRange the {@link ValueRange} - * @param map a {@link SortedMap} of times and prices + * @param map a {@link SortedMap} of times and prices * @return a {@link Prediction} object */ - public static Prediction from(ValueRange valueRange, SortedMap map) { - map = postprocessMap(valueRange, map); + public static Prediction from(ImmutableSortedMap map) { if (map.isEmpty()) { return EMPTY_PREDICTION; } @@ -200,82 +189,29 @@ public static Prediction from(ZonedDateTime time, Prediction prediction) { return new Prediction(newMap); } - /** - * Unmodifiable Map of Quarters (rounded to 15 minutes) and their respective - * predicted values. Values can be null. - */ - public final SortedMap valuePerQuarter; + private static Prediction from(ValueRange valueRange, ZonedDateTime time, Integer... values) { + if (values.length == 0) { + return EMPTY_PREDICTION; + } + return new Prediction(time, Stream.of(values) // + .map(valueRange::fitWithin) // apply ValueRange + .toArray(Integer[]::new)); + } - private Prediction(SortedMap valuePerQuarter) { - // We use unmodifiableSortedMap instead of the more expressive Guava - // ImmutableSortedMap, because we require `null` values. - this.valuePerQuarter = unmodifiableSortedMap(valuePerQuarter); + private Prediction(ImmutableSortedMap valuePerQuarter) { + super(valuePerQuarter); } - /** - * Returns {@code true} if this map contains no predictions. - * - * @return {@code true} if this map contains no predictions - */ - public boolean isEmpty() { - return this.valuePerQuarter.isEmpty(); + private Prediction(ZonedDateTime time, Integer... values) { + super(time, values); } /** - * Gets the prediction values as an array of {@link Integer}s. + * Gets the prediction as an array of {@link Integer}s. * - * @return values array + * @return prices array */ public Integer[] asArray() { - return this.valuePerQuarter.values().toArray(Integer[]::new); - } - - @Override - public String toString() { - StringBuilder b = new StringBuilder("Prediction="); - if (this.isEmpty()) { - b.append("EMPTY"); - } else { - b.append(this.valuePerQuarter.firstKey().toString()); // - b.append("="); - this.valuePerQuarter.values().forEach(v -> b.append(v).append(",")); - } - return b.toString(); - } - - protected static SortedMap buildMap(ZonedDateTime time, Integer... values) { - time = roundDownToQuarter(time); - var result = new TreeMap(); - for (var value : values) { - result.put(time, value); - time = time.plusMinutes(15); - } - return result; - } - - protected static SortedMap postprocessMap(ValueRange valueRange, - SortedMap map) { - var values = new ArrayList<>(map.values()); - var lastNonNullIndex = IntStream.range(0, values.size()) // - .filter(i -> values.get(i) != null) // - .reduce((first, second) -> second) // - .orElse(-1); - - var result = map.entrySet().stream() // - .limit(lastNonNullIndex + 1) // remove trailing nulls - .collect(TreeMap::new, // - (m, e) -> m.put(// - roundDownToQuarter(e.getKey()), // - e.getValue() == null ? null : min(valueRange.max, max(valueRange.min, e.getValue()))), // - TreeMap::putAll); - - if (!result.isEmpty()) { - // Fill gaps - for (var time = result.firstKey(); time.isBefore(result.lastKey()); time = time.plusMinutes(15)) { - result.putIfAbsent(time, null); - } - } - - return result; + return super.asArray(Integer[]::new); } } diff --git a/io.openems.edge.predictor.api/test/io/openems/edge/predictor/api/oneday/PredictionTest.java b/io.openems.edge.predictor.api/test/io/openems/edge/predictor/api/oneday/PredictionTest.java deleted file mode 100644 index c42a7d2b3a7..00000000000 --- a/io.openems.edge.predictor.api/test/io/openems/edge/predictor/api/oneday/PredictionTest.java +++ /dev/null @@ -1,36 +0,0 @@ -package io.openems.edge.predictor.api.oneday; - -import static org.junit.Assert.assertArrayEquals; - -import java.time.ZonedDateTime; -import java.util.Arrays; - -import org.junit.Test; - -import io.openems.common.types.ChannelAddress; -import io.openems.edge.common.sum.DummySum; -import io.openems.edge.predictor.api.prediction.Prediction; - -public class PredictionTest { - - @Test - public void testOf() { - var now = ZonedDateTime.now(); - var sum = new DummySum(); - - assertArrayEquals(new Integer[] { 1, 5, 7, 0 /* ValueRange positive */, 9, null, null }, // - Arrays.copyOfRange(// - Prediction.from(sum, // - new ChannelAddress("_sum", "ProductionActivePower"), now, // - 1, 5, 7, -1, 9 // - ).asArray(), 0, 7)); - - assertArrayEquals(new Integer[] { 1, 5, 7, -1 /* NONE */, 9, null, null }, // - Arrays.copyOfRange(// - Prediction.from(sum, // - new ChannelAddress("foo", "bar"), now, // - 1, 5, 7, -1, 9 // - ).asArray(), 0, 7)); - } - -} diff --git a/io.openems.edge.predictor.api/test/io/openems/edge/predictor/api/prediction/PredictionTest.java b/io.openems.edge.predictor.api/test/io/openems/edge/predictor/api/prediction/PredictionTest.java new file mode 100644 index 00000000000..0938f36e55a --- /dev/null +++ b/io.openems.edge.predictor.api/test/io/openems/edge/predictor/api/prediction/PredictionTest.java @@ -0,0 +1,64 @@ +package io.openems.edge.predictor.api.prediction; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import java.time.ZonedDateTime; +import java.util.Arrays; + +import org.junit.Test; + +import io.openems.common.types.ChannelAddress; +import io.openems.edge.common.sum.DummySum; +import io.openems.edge.predictor.api.prediction.Prediction.ValueRange; + +public class PredictionTest { + + @Test + public void testOf() { + var now = ZonedDateTime.now(); + var sum = new DummySum(); + + assertArrayEquals(new Integer[] { 1, 5, 7, 0 /* ValueRange positive */, 9, null, null }, // + Arrays.copyOfRange(// + Prediction.from(sum, // + new ChannelAddress("_sum", "ProductionActivePower"), now, // + 1, 5, 7, -1, 9 // + ).asArray(), 0, 7)); + + assertArrayEquals(new Integer[] { 1, 5, 7, -1 /* NONE */, 9, null, null }, // + Arrays.copyOfRange(// + Prediction.from(sum, // + new ChannelAddress("foo", "bar"), now, // + 1, 5, 7, -1, 9 // + ).asArray(), 0, 7)); + } + + @Test + public void testValueRange() { + { + var vr = new ValueRange(null, null); + assertNull(vr.fitWithin(null)); + assertEquals(100, vr.fitWithin(100).intValue()); + } + { + var vr = new ValueRange(-100, null); + assertNull(vr.fitWithin(null)); + assertEquals(-100, vr.fitWithin(-150).intValue()); + assertEquals(150, vr.fitWithin(150).intValue()); + } + { + var vr = new ValueRange(null, 100); + assertNull(vr.fitWithin(null)); + assertEquals(-150, vr.fitWithin(-150).intValue()); + assertEquals(100, vr.fitWithin(150).intValue()); + } + { + var vr = new ValueRange(-100, 100); + assertNull(vr.fitWithin(null)); + assertEquals(-100, vr.fitWithin(-150).intValue()); + assertEquals(100, vr.fitWithin(150).intValue()); + } + } +} diff --git a/io.openems.edge.predictor.api/test/io/openems/edge/predictor/api/test/DummyPredictorManagerTest.java b/io.openems.edge.predictor.api/test/io/openems/edge/predictor/api/test/DummyPredictorManagerTest.java index 4166129be64..7dfd0039849 100644 --- a/io.openems.edge.predictor.api/test/io/openems/edge/predictor/api/test/DummyPredictorManagerTest.java +++ b/io.openems.edge.predictor.api/test/io/openems/edge/predictor/api/test/DummyPredictorManagerTest.java @@ -1,16 +1,15 @@ package io.openems.edge.predictor.api.test; +import static io.openems.common.test.TestUtils.createDummyClock; import static io.openems.edge.predictor.api.prediction.Prediction.EMPTY_PREDICTION; import static org.junit.Assert.assertEquals; -import java.time.Instant; -import java.time.ZoneOffset; +import java.time.ZoneId; import java.time.ZonedDateTime; import org.junit.Test; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.common.test.TimeLeapClock; import io.openems.common.types.ChannelAddress; import io.openems.edge.common.sum.DummySum; import io.openems.edge.common.test.DummyComponentManager; @@ -18,14 +17,14 @@ public class DummyPredictorManagerTest { - private static final ChannelAddress SUM_PRODUCTION_ACTIVE_POWER = new ChannelAddress("_sum", + protected static final ChannelAddress SUM_PRODUCTION_ACTIVE_POWER = new ChannelAddress("_sum", "ProductionActivePower"); - private static final ChannelAddress SUM_CONSUMPTION_ACTIVE_POWER = new ChannelAddress("_sum", + protected static final ChannelAddress SUM_CONSUMPTION_ACTIVE_POWER = new ChannelAddress("_sum", "ConsumptionActivePower"); @Test public void test() throws OpenemsNamedException { - final var clock = new TimeLeapClock(Instant.parse("2020-01-01T20:00:00.00Z"), ZoneOffset.UTC); + final var clock = createDummyClock(); final var now = ZonedDateTime.now(clock); final var cm = new DummyComponentManager(clock); final var sum = new DummySum(); @@ -41,6 +40,10 @@ public void test() throws OpenemsNamedException { assertEquals(EMPTY_PREDICTION, sut.getPrediction(new ChannelAddress("foo", "bar"))); assertEquals(sut, sut.self()); + + assertEquals(9, sut.getPrediction(SUM_CONSUMPTION_ACTIVE_POWER).getAt(now).intValue()); + assertEquals(9, sut.getPrediction(SUM_CONSUMPTION_ACTIVE_POWER) + .getAt(now.withZoneSameInstant(ZoneId.of("Europe/Berlin"))).intValue()); } } diff --git a/io.openems.edge.predictor.api/test/io/openems/edge/predictor/api/test/DummyPredictorTest.java b/io.openems.edge.predictor.api/test/io/openems/edge/predictor/api/test/DummyPredictorTest.java new file mode 100644 index 00000000000..c7f317dd22b --- /dev/null +++ b/io.openems.edge.predictor.api/test/io/openems/edge/predictor/api/test/DummyPredictorTest.java @@ -0,0 +1,50 @@ +package io.openems.edge.predictor.api.test; + +import static io.openems.common.test.TestUtils.createDummyClock; +import static io.openems.edge.predictor.api.test.DummyPredictorManagerTest.SUM_PRODUCTION_ACTIVE_POWER; +import static java.time.temporal.ChronoUnit.MINUTES; +import static org.junit.Assert.assertEquals; + +import java.time.ZonedDateTime; + +import org.junit.Test; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.edge.common.sum.DummySum; +import io.openems.edge.common.test.DummyComponentManager; +import io.openems.edge.predictor.api.prediction.Prediction; + +public class DummyPredictorTest { + + @Test + public void test() throws OpenemsNamedException { + final var clock = createDummyClock(); + final var now = ZonedDateTime.now(clock); + final var cm = new DummyComponentManager(clock); + final var sum = new DummySum(); + var sut = new DummyPredictor("predictor0", cm, + Prediction.from(sum, SUM_PRODUCTION_ACTIVE_POWER, now, new Integer[] { 1, 2, 3, 4 }), + SUM_PRODUCTION_ACTIVE_POWER); + + assertEquals(4, sut.getPrediction(SUM_PRODUCTION_ACTIVE_POWER).asArray().length); + clock.leap(15, MINUTES); + assertEquals(3, sut.getPrediction(SUM_PRODUCTION_ACTIVE_POWER).asArray().length); + clock.leap(1, MINUTES); + assertEquals(3, sut.getPrediction(SUM_PRODUCTION_ACTIVE_POWER).asArray().length); + } + + @Test + public void testEmpty() throws OpenemsNamedException { + final var clock = createDummyClock(); + final var now = ZonedDateTime.now(clock); + final var cm = new DummyComponentManager(clock); + final var sum = new DummySum(); + var sut = new DummyPredictor("predictor0", cm, + Prediction.from(sum, SUM_PRODUCTION_ACTIVE_POWER, now, new Integer[0]), SUM_PRODUCTION_ACTIVE_POWER); + + assertEquals(0, sut.getPrediction(SUM_PRODUCTION_ACTIVE_POWER).asArray().length); + clock.leap(15, MINUTES); + assertEquals(0, sut.getPrediction(SUM_PRODUCTION_ACTIVE_POWER).asArray().length); + } + +} diff --git a/io.openems.edge.predictor.lstm/.classpath b/io.openems.edge.predictor.lstm/.classpath new file mode 100644 index 00000000000..b4cffd0fe60 --- /dev/null +++ b/io.openems.edge.predictor.lstm/.classpath @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/io.openems.edge.predictor.lstm/.gitignore b/io.openems.edge.predictor.lstm/.gitignore new file mode 100644 index 00000000000..c2b941a96de --- /dev/null +++ b/io.openems.edge.predictor.lstm/.gitignore @@ -0,0 +1,2 @@ +/bin_test/ +/generated/ diff --git a/io.openems.edge.predictor.lstm/.project b/io.openems.edge.predictor.lstm/.project new file mode 100644 index 00000000000..c7d8eddc465 --- /dev/null +++ b/io.openems.edge.predictor.lstm/.project @@ -0,0 +1,23 @@ + + + io.openems.edge.predictor.lstm + + + + + + org.eclipse.jdt.core.javabuilder + + + + + bndtools.core.bndbuilder + + + + + + org.eclipse.jdt.core.javanature + bndtools.core.bndnature + + diff --git a/io.openems.edge.predictor.lstm/.settings/org.eclipse.core.resources.prefs b/io.openems.edge.predictor.lstm/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 00000000000..99f26c0203a --- /dev/null +++ b/io.openems.edge.predictor.lstm/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding/=UTF-8 diff --git a/io.openems.edge.predictor.lstm/bnd.bnd b/io.openems.edge.predictor.lstm/bnd.bnd new file mode 100644 index 00000000000..6b77ac1cff0 --- /dev/null +++ b/io.openems.edge.predictor.lstm/bnd.bnd @@ -0,0 +1,16 @@ +Bundle-Name: OpenEMS Edge Predictor LSTM +Bundle-Vendor: FENECON GmbH +Bundle-License: https://opensource.org/licenses/EPL-2.0 +Bundle-Version: 1.0.0.${tstamp} + +-buildpath: \ + ${buildpath},\ + io.openems.common,\ + io.openems.edge.common,\ + io.openems.edge.controller.api,\ + io.openems.edge.predictor.api,\ + io.openems.edge.timedata.api,\ + org.apache.commons.math3,\ + +-testpath: \ + ${testpath} \ No newline at end of file diff --git a/io.openems.edge.predictor.lstm/readme.adoc b/io.openems.edge.predictor.lstm/readme.adoc new file mode 100644 index 00000000000..cc07f8b1bd6 --- /dev/null +++ b/io.openems.edge.predictor.lstm/readme.adoc @@ -0,0 +1,28 @@ += Long Short-Term Memory (LSTM) predictor + +The Long Short-Term Memory (LSTM) model is a type of recurrent neural network (RNN) that is particularly well-suited for time series prediction tasks, including consumption and production power predictions, due to its ability to capture dependencies and patterns over time. https://en.wikipedia.org/wiki/Long_short-term_memory[More details on LSTM] + +This application is used for predicting consumption and production power values. +Here, for power prediction, LSTM models can analyze historical power data to learn patterns and trends that occur over time, such as: + +* Daily and seasonal variations: Consumption power often follows cyclic patterns (e.g. higher usage during the day, lower at night). Solar production power is high during the day and zero during the nights. +* External Factors: LSTM can incorporate external factors like weather, day of the week, or holidays to improve prediction accuracy. + +== Training LSTM for power predictions: + +* Input Data (channels address `_sum/UnmanagedConsumptionActivePower`): Time series data of past consumptions. +* Pre-processing: Data needs to be scaled and sometimes transformed to remove seasonality or noise. +* Training: The LSTM is trained on historical data using techniques like backpropagation through time (BPTT), where it learns to minimize the error between predicted and actual consumption. +* Prediction: Once trained, the model can predict future power consumption for various time steps ahead (e.g., hours, days, or even weeks). + +In practice, LSTMs are favored for their ability to learn complex time-related patterns, making them effective in forecasting energy demand patterns that can inform Energy Management System (EMS) for optimized energy distribution and cost strategies. + +== Note for activating the predictor + +This predictor creates a folder named "lstm" in the OpenEMS data directory (defined by `openems.data.dir` in `EdgeApp.bndrun`). + +Initially, a generic model will be used for predictions, which may not yield optimal results. However, a training process is scheduled to occur every 45 days, during which the models in this directory will be updated. The 45-day interval consists of 30 days for training and 15 days for validation. + +As a result of this process, a new model will be trained and will automatically replace the previous one. + +https://github.com/OpenEMS/openems/tree/develop/io.openems.edge.predictor.lstm[Source Code icon:github[]] diff --git a/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/Config.java b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/Config.java new file mode 100644 index 00000000000..00031ca085b --- /dev/null +++ b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/Config.java @@ -0,0 +1,32 @@ +package io.openems.edge.predictor.lstm; + +import org.osgi.service.metatype.annotations.AttributeDefinition; +import org.osgi.service.metatype.annotations.ObjectClassDefinition; + +import io.openems.edge.predictor.api.prediction.LogVerbosity; + +@ObjectClassDefinition(// + name = "Predictor LSTM", // + description = "Implements Long Short-Term Memory (LSTM), which is a type of recurrent neural network (RNN) " // + + "designed to capture long-range dependencies in sequential data, such as time series. This makes " // + + "LSTMs particularly effective for time series prediction, as they can learn patterns and trends " // + + "over time, handling long-term dependencies while filtering out irrelevant information.") +@interface Config { + + @AttributeDefinition(name = "Component-ID", description = "Unique ID of this Component") + String id() default "predictor0"; + + @AttributeDefinition(name = "Alias", description = "Human-readable name of this Component; defaults to Component-ID") + String alias() default ""; + + @AttributeDefinition(name = "Is enabled?", description = "Is this Component enabled?") + boolean enabled() default true; + + @AttributeDefinition(name = "Channel-Address", description = "Channel-Address this Predictor is used for, e.g. '_sum/UnmanagedConsumptionActivePower'") + String channelAddress(); + + @AttributeDefinition(name = "Log-Verbosity", description = "The log verbosity.") + LogVerbosity logVerbosity() default LogVerbosity.NONE; + + String webconsole_configurationFactory_nameHint() default "Predictor LSTM [{id}]"; +} \ No newline at end of file diff --git a/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/PredictorLstm.java b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/PredictorLstm.java new file mode 100644 index 00000000000..52bd0beec61 --- /dev/null +++ b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/PredictorLstm.java @@ -0,0 +1,92 @@ +package io.openems.edge.predictor.lstm; + +import static io.openems.common.channel.Level.INFO; +import static io.openems.common.types.OpenemsType.DOUBLE; +import static io.openems.common.types.OpenemsType.LONG; + +import io.openems.edge.common.channel.Doc; +import io.openems.edge.common.channel.DoubleReadChannel; +import io.openems.edge.common.channel.LongReadChannel; +import io.openems.edge.common.channel.StateChannel; +import io.openems.edge.common.component.OpenemsComponent; + +public interface PredictorLstm extends OpenemsComponent { + + public enum ChannelId implements io.openems.edge.common.channel.ChannelId { + LAST_TRAINED_TIME(Doc.of(LONG) // + .text("Last trained time as Unixtimestamp [ms]")), // + MODEL_ERROR(Doc.of(DOUBLE) // + .text("Error in the Model")), // + CANNOT_TRAIN_CONDITON(Doc.of(INFO) // + .text("When the data set is empty, entirely null, or contains 50% null values.")); + + private final Doc doc; + + private ChannelId(Doc doc) { + this.doc = doc; + } + + @Override + public Doc doc() { + return this.doc; + } + } + + /** + * Gets the Channel for {@link ChannelId#CANNOT_TRAIN_CONDITON}. + * + * @return the Channel + */ + public default StateChannel getCannotTrainConditionChannel() { + return this.channel(ChannelId.CANNOT_TRAIN_CONDITON); + } + + /** + * Internal method to set the 'nextValue' on + * {@link ChannelId#CANNOT_TRAIN_CONDITON} Channel. + * + * @param value the next value + */ + public default void _setCannotTrainCondition(boolean value) { + this.getCannotTrainConditionChannel().setNextValue(value); + } + + /** + * Gets the Channel for {@link ChannelId#LAST_TRAINED_TIME}. + * + * @return the Channel + */ + public default LongReadChannel getLastTrainedTimeChannel() { + return this.channel(ChannelId.LAST_TRAINED_TIME); + } + + /** + * Internal method to set the 'nextValue' on {@link ChannelId#LAST_TRAINED_TIME} + * Channel. + * + * @param value the next value + */ + public default void _setLastTrainedTime(long value) { + this.getLastTrainedTimeChannel().setNextValue(value); + } + + /** + * Gets the Channel for {@link ChannelId#MODEL_ERROR}. + * + * @return the Channel + */ + public default DoubleReadChannel getModelErrorChannel() { + return this.channel(ChannelId.MODEL_ERROR); + } + + /** + * Internal method to set the 'nextValue' on {@link ChannelId#LAST_TRAINED_TIME} + * Channel. + * + * @param value the next value + */ + public default void _setModelError(Double value) { + this.getModelErrorChannel().setNextValue(value); + } + +} diff --git a/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/PredictorLstmImpl.java b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/PredictorLstmImpl.java new file mode 100644 index 00000000000..1ae7b5f61ed --- /dev/null +++ b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/PredictorLstmImpl.java @@ -0,0 +1,293 @@ +package io.openems.edge.predictor.lstm; + +import static io.openems.common.utils.ThreadPoolUtils.shutdownAndAwaitTermination; +import static io.openems.edge.common.jsonapi.EdgeGuards.roleIsAtleast; +import static io.openems.edge.predictor.lstm.jsonrpc.PredictionRequestHandler.handlerGetPredictionRequest; +import static io.openems.edge.predictor.lstm.preprocessing.DataModification.removeNegatives; +import static io.openems.edge.predictor.lstm.utilities.DataUtility.combine; +import static io.openems.edge.predictor.lstm.utilities.DataUtility.concatenateList; +import static io.openems.edge.predictor.lstm.utilities.DataUtility.getData; +import static io.openems.edge.predictor.lstm.utilities.DataUtility.getDate; +import static io.openems.edge.predictor.lstm.utilities.DataUtility.getMinute; +import static java.lang.Math.min; + +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.SortedMap; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.stream.IntStream; + +import org.osgi.service.component.ComponentContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.ConfigurationPolicy; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.metatype.annotations.Designate; + +import com.google.common.collect.Sets; +import com.google.gson.JsonElement; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.session.Role; +import io.openems.common.timedata.Resolution; +import io.openems.common.types.ChannelAddress; +import io.openems.edge.common.component.ClockProvider; +import io.openems.edge.common.component.ComponentManager; +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.common.jsonapi.ComponentJsonApi; +import io.openems.edge.common.jsonapi.JsonApiBuilder; +import io.openems.edge.common.sum.Sum; +import io.openems.edge.controller.api.Controller; +import io.openems.edge.predictor.api.manager.PredictorManager; +import io.openems.edge.predictor.api.prediction.AbstractPredictor; +import io.openems.edge.predictor.api.prediction.Prediction; +import io.openems.edge.predictor.api.prediction.Predictor; +import io.openems.edge.predictor.lstm.common.HyperParameters; +import io.openems.edge.predictor.lstm.common.LstmPredictor; +import io.openems.edge.predictor.lstm.common.ReadAndSaveModels; +import io.openems.edge.predictor.lstm.jsonrpc.GetPredictionRequest; +import io.openems.edge.predictor.lstm.train.LstmTrain; +import io.openems.edge.timedata.api.Timedata; + +@Designate(ocd = Config.class, factory = true) +@Component(// + name = "Predictor.LSTM", // + immediate = true, // + configurationPolicy = ConfigurationPolicy.REQUIRE // +) +public class PredictorLstmImpl extends AbstractPredictor + implements Predictor, OpenemsComponent, ComponentJsonApi, PredictorLstm { + + /** 45 days. */ + private static final long DAYS_45 = 45; + + /** 45 days in minutes. */ + private static final long PERIOD = DAYS_45 * 24 * 60; + + @Reference + private Sum sum; + + @Reference + private Timedata timedata; + + @Reference + private ComponentManager componentManager; + + @Reference + private PredictorManager predictorManager; + + @Override + protected ClockProvider getClockProvider() { + return this.componentManager; + } + + public PredictorLstmImpl() throws OpenemsNamedException { + super(// + OpenemsComponent.ChannelId.values(), // + Controller.ChannelId.values(), // + PredictorLstm.ChannelId.values()// + ); + } + + private ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); + private ChannelAddress channelForPrediction; + + @Activate + private void activate(ComponentContext context, Config config) throws OpenemsNamedException { + super.activate(context, config.id(), config.alias(), config.enabled(), // + new String[] { config.channelAddress() }, config.logVerbosity()); + + var channelAddress = ChannelAddress.fromString(config.channelAddress()); + this.channelForPrediction = channelAddress; + + /* + * Avoid training for the newly setup Edges due to lack of data. Set a fixed + * 45-day period: 30 days for training and 15 days for validation. + */ + this.scheduler.scheduleAtFixedRate(// + new LstmTrain(this.timedata, channelAddress, this, DAYS_45), // + 0, // + PERIOD, // + TimeUnit.MINUTES// + ); + } + + @Override + @Deactivate + protected void deactivate() { + shutdownAndAwaitTermination(this.scheduler, 0); + super.deactivate(); + } + + @Override + protected Prediction createNewPrediction(ChannelAddress channelAddress) { + var hyperParameters = ReadAndSaveModels.read(channelAddress.getChannelId()); + var now = ZonedDateTime.now(); + + var seasonalityFuture = CompletableFuture + .supplyAsync(() -> this.predictSeasonality(channelAddress, now, hyperParameters)); + + var trendFuture = CompletableFuture.supplyAsync(() -> this.predictTrend(channelAddress, now, hyperParameters)); + + var dayPlus1SeasonalityFuture = CompletableFuture + .supplyAsync(() -> this.predictSeasonality(channelAddress, now.plusDays(1), hyperParameters)); + + // var combinePrerequisites = CompletableFuture.allOf(seasonalityFuture, + // trendFuture); + + try { + // TODO combinePrerequisites.get(); + + // Current day prediction + var currentDayPredicted = combine(trendFuture.get(), seasonalityFuture.get()); + + // Next Day prediction + var plus1DaySeasonalityPrediction = dayPlus1SeasonalityFuture.get(); + + // Concat current and Nextday + var actualPredicted = concatenateList(currentDayPredicted, plus1DaySeasonalityPrediction); + + var baseTimeOfPrediction = now.withMinute(getMinute(now, hyperParameters)).withSecond(0).withNano(0); + + return Prediction.from(this.sum, channelAddress, // + baseTimeOfPrediction, // + averageInChunks(actualPredicted)); + + } catch (Exception e) { + throw new RuntimeException("Error in getting prediction execution", e); + } + } + + /** + * Averages the elements of an integer array in chunks of a specified size. + * + *

    + * This method takes an input array of integers and divides it into chunks of a + * fixed size. For each chunk, it calculates the average of the integers and + * stores the result in a new array. The size of the result array is determined + * by the total number of elements in the input array divided by the chunk size. + *

    + * + * @param inputList an arrayList of Doubles to be processed. The array length + * must be a multiple of the chunk size for correct processing. + * @return an array of integers containing the averages of each chunk. + * + */ + private static Integer[] averageInChunks(ArrayList inputList) { + final int chunkSize = 3; + var resultSize = inputList.size() / chunkSize; + var result = new Integer[resultSize]; + + for (int i = 0; i < inputList.size(); i += chunkSize) { + var sum = IntStream.range(i, min(i + chunkSize, inputList.size())) // + .mapToDouble(j -> inputList.get(j))// + .sum(); + result[i / chunkSize] = (int) (sum / chunkSize); + } + return result; + } + + /** + * Queries historic data for a specified time range and channel address with + * given {@link ChannelAddress}. + * + * @param from the start of the time range + * @param until the end of the time range + * @param channelAddress the {@link ChannelAddress} for the query + * @param hyperParameters the {@link HyperParameters} that include the interval + * for data resolution + * @return a SortedMap where the key is a ZonedDateTime representing the + * timestamp of the data point, and the value is another SortedMap where + * the key is the ChannelAddress and the value is the data point as a + * JsonElement. and null if error + */ + private SortedMap> queryHistoricData(ZonedDateTime from, + ZonedDateTime until, ChannelAddress channelAddress, HyperParameters hyperParameters) { + try { + return this.timedata.queryHistoricData(null, from, until, Sets.newHashSet(channelAddress), + new Resolution(hyperParameters.getInterval(), ChronoUnit.MINUTES)); + } catch (OpenemsNamedException e) { + e.printStackTrace(); + } + return null; + } + + /** + * Predicts trend values for a specified channel at the current date using LSTM + * models. + * + * @param channelAddress The {@link ChannelAddress} for which trend values are + * predicted. + * @param nowDate The current date and time for which trend values are + * predicted. + * @param hyperParameters The {@link HyperParameters} for the prediction model. + * @return A list of predicted trend values for the specified channel at the + * current date. + * @throws SomeException If there's any specific exception that might be thrown + * during the process. + */ + public ArrayList predictTrend(ChannelAddress channelAddress, ZonedDateTime nowDate, + HyperParameters hyperParameters) { + var till = nowDate.withMinute(getMinute(nowDate, hyperParameters)).withSecond(0).withNano(0); + var from = till.minusMinutes(hyperParameters.getInterval() * hyperParameters.getWindowSizeTrend()); + + var trendQueryResult = this.queryHistoricData(// + from, // + till, // + channelAddress, // + hyperParameters); + + return LstmPredictor.predictTrend(// + getData(trendQueryResult), // + getDate(trendQueryResult), // + till, // + hyperParameters); + } + + /** + * Predicts Seasonality values for a specified channel at the current date using + * LSTM models. + * + * @param channelAddress The address of the channel for which seasonality + * values are predicted. + * @param nowDate The current date and time for which seasonality values + * are predicted. + * @param hyperParameters The {@link ChannelAddress} for the prediction model. + * @return A list of predicted seasonality values for the specified channel at + * the current date. + * @throws SomeException If there's any specific exception that might be thrown + * during the process. + */ + public ArrayList predictSeasonality(ChannelAddress channelAddress, ZonedDateTime nowDate, + HyperParameters hyperParameters) { + var till = nowDate.withMinute(getMinute(nowDate, hyperParameters)).withSecond(0).withNano(0); + var temp = till.minusDays(hyperParameters.getWindowSizeSeasonality() - 1); + + var from = temp// + .withMinute(getMinute(nowDate, hyperParameters))// + .withSecond(0)// + .withNano(0); + + var targetFrom = till.plusMinutes(hyperParameters.getInterval()); + var queryResult = this.queryHistoricData(from, till, channelAddress, hyperParameters); + + return LstmPredictor.getArranged( + LstmPredictor.getIndex(targetFrom.getHour(), targetFrom.getMinute(), hyperParameters), // + LstmPredictor.predictSeasonality(removeNegatives(getData(queryResult)), getDate(queryResult), // + hyperParameters)); + } + + @Override + public void buildJsonApiRoutes(JsonApiBuilder builder) { + builder.handleRequest(GetPredictionRequest.METHOD, endpoint -> { + endpoint.setGuards(roleIsAtleast(Role.OWNER)); + }, call -> { + return handlerGetPredictionRequest(call.getRequest().id, this.predictorManager, this.channelForPrediction); + }); + } +} \ No newline at end of file diff --git a/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/common/DataStatistics.java b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/common/DataStatistics.java new file mode 100644 index 00000000000..ec4ef9fd5a5 --- /dev/null +++ b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/common/DataStatistics.java @@ -0,0 +1,156 @@ +package io.openems.edge.predictor.lstm.common; + +import static java.util.Arrays.stream; +import static java.util.stream.IntStream.range; + +import java.util.Collection; +import java.util.stream.DoubleStream; + +public class DataStatistics { + + /** + * Get the mean of the array. + * + * @param data the data + * @return mean value + */ + public static double getMean(Collection data) { + return data.stream() // + .mapToDouble(Number::doubleValue) // + .average() // + .orElse(0.0); + } + + /** + * Calculates the mean (average) of each row in a 2D array of doubles and + * returns an ArrayList containing the means of each row. + * + * @param data a 2D array of doubles containing the data from which to calculate + * means + * @return an ArrayList of Double containing the means of each row + */ + public static double[] getMean(double[][] data) { + return stream(data) // + .mapToDouble(row -> stream(row) // + .average() // + .orElse(0.0)) // + .toArray(); + } + + /** + * Computes the mean (average) of an array of double values. + * + *

    + * This method calculates the mean by summing all the elements in the input + * array and dividing by the number of elements. If the array is empty, it + * throws a NoSuchElementException. + *

    + * + * @param data the array of double values for which the mean is to be computed + * @return the mean of the input array + * @throws java.util.NoSuchElementException if the array is empty + */ + public static double getMean(double[] data) { + return stream(data) // + .parallel() // + .average() // + .getAsDouble(); + } + + /** + * Calculates the standard deviation of a list of double values. This method + * computes the standard deviation of the provided list of double values. + * Standard deviation measures the amount of variation or dispersion in the + * data. It is calculated as the square root of the variance, which is the + * average of the squared differences between each data point and the mean. When + * stander deviation is 0, the method returns a value close to zero to avoid + * divisible by 0 error + * + * @param data An ArrayList of double values for which to calculate the standard + * deviation. + * @return The standard deviation of the provided data as a double value. + * @throws IllegalArgumentException if the input list is empty. + */ + public static double getStandardDeviation(Collection data) { + var mean = getMean(data); + return getStandardDeviation(data, mean); + } + + /** + * Calculates the deviation of the data from the expected error. This method + * computes the average deviation from the expected error. + * + * @param data the data of type numbers + * @param expectedError the expected error + * @return stdDeviation the standard deviation + */ + public static double getStandardDeviation(Collection data, double expectedError) { + return getStandardDeviation(data.stream().mapToDouble(Number::doubleValue), data.size(), expectedError); + } + + /** + * Computes the standard deviation of an array of double values. + * + *

    + * This method calculates the mean of the input array, then computes the + * variance by finding the average of the squared differences from the mean. + * Finally, it returns the square root of the variance as the standard + * deviation. If the standard deviation is zero, a very small positive number + * (1e-15) is returned to avoid returning zero. + *

    + * + * @param data the array of double values for which the standard deviation is to + * be computed + * @return the standard deviation of the input array + */ + public static double getStandardDeviation(double[] data) { + var mean = stream(data) // + .average() // + .getAsDouble(); + return getStandardDeviation(stream(data), data.length, mean); + } + + /** + * Calculates the standard deviation of each row in a 2D array of doubles and + * returns an ArrayList containing the standard deviations of each row. + * + * @param data a 2D array of doubles containing the data from which to calculate + * standard deviations + * @return an ArrayList of Double containing the standard deviations of each row + */ + public static double[] getStandardDeviation(double[][] data) { + return stream(data)// + .mapToDouble(row -> getStandardDeviation(row))// + .toArray(); + } + + private static double getStandardDeviation(DoubleStream data, int length, double expectedError) { + double sumSquaredDeviations = data // + .map(x -> Math.pow(x - expectedError, 2))// + .sum(); + double variance = sumSquaredDeviations / length; + double stdDeviation = Math.sqrt(variance); + return (stdDeviation == 0) ? 0.000000000000001 : stdDeviation; + } + + /** + * Computes the root mean square (RMS) error between two arrays of double + * values. + * + * @param original the original array of double values + * @param computed the computed array of double values + * @return the RMS error between the original and computed arrays + * @throws IllegalArgumentException if the arrays have different lengths + */ + public static double computeRms(double[] original, double[] computed) { + if (original.length != computed.length) { + throw new IllegalArgumentException("Arrays must have the same length"); + } + + var sumOfSquaredDifferences = range(0, original.length) // + .mapToDouble(i -> Math.pow(original[i] - computed[i], 2)) // + .average(); + + return Math.sqrt(sumOfSquaredDifferences.getAsDouble()); + } +} diff --git a/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/common/DynamicItterationValue.java b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/common/DynamicItterationValue.java new file mode 100644 index 00000000000..6314a1372da --- /dev/null +++ b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/common/DynamicItterationValue.java @@ -0,0 +1,24 @@ +package io.openems.edge.predictor.lstm.common; + +import java.util.ArrayList; +import java.util.Collections; + +public class DynamicItterationValue { + + public static int setIteration(ArrayList errors, int errorIndex, HyperParameters hyperParameters) { + if (errors.isEmpty()) { + return 10; + } + + var minError = Collections.min(errors); + var maxError = Collections.max(errors); + var minIteration = 1; + var maxIteration = 10 * hyperParameters.getEpochTrack() + 1; + + var errorValue = errors.get(errorIndex); + var normalizedError = (errorValue - minError) / (maxError - minError); + var iterationValue = minIteration + (normalizedError * (maxIteration - minIteration)); + + return (int) Math.round(iterationValue); + } +} diff --git a/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/common/HyperParameters.java b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/common/HyperParameters.java new file mode 100644 index 00000000000..d6d0e7a8f98 --- /dev/null +++ b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/common/HyperParameters.java @@ -0,0 +1,885 @@ +package io.openems.edge.predictor.lstm.common; + +import java.io.Serializable; +import java.time.OffsetDateTime; +import java.util.ArrayList; +import java.util.Collections; + +public class HyperParameters implements Serializable { + + private OffsetDateTime lastTrainedDate; + + public OffsetDateTime getLastTrainedDate() { + return this.lastTrainedDate; + } + + /** + * Serializable class version number for ensuring compatibility during + * serialization. + */ + private static final long serialVersionUID = 1L; + + /** + * Maximum iteration factor. + * + *

    + * This value is used by DynamicItterationValue class to set the + * gdItterationValue DynamicItterationValue class changes the classes such that + * the gdItteration value is in between 1 and maxItterFactor*current Epoch value + * +1 When epoch increase, learning rate decreases and the gdItteration value + * increases. Set the value always to 10. + */ + private final int maxItterFactor = 10; + + /** + * Upper limit for the learning rate. + * + *

    + * This value is used by the ADGRAD optimizer as the initial learning rate. The + * optimizer dynamically adjusts the learning rate over epochs, starting with + * the value of learningRateUpperLimit. The adjustment is typically aimed at + * improving convergence by starting with a higher learning rate and gradually + * decreasing it. + * + *

    + * This variable can be set to any value between 0 and 1. It is important to + * ensure that the value of learningRateUpperLimit is always greater than + * learnignRateLowerLimit to allow proper functioning of the dynamic learning + * rate setup. Default value: 0.01 + */ + private double learningRateUpperLimit = 0.01; + + /** + * Lower limit for the learning rate. + * + *

    + * This value is used by the ADGRAD optimizer as the minimum learning rate. As + * the training progresses, the optimizer adjusts the learning rate and it + * converges to the value of learnignRateLowerLimit by the final epoch. This + * helps in fine-tuning the model parameters and achieving better accuracy by + * the end of the training. + * + *

    + * This variable can be set to any value between 0 and 1. It is crucial that the + * value of learnignRateLowerLimit is always less than learningRateUpperLimit to + * enable the proper decreasing trend of the learning rate throughout the + * training process. Default value: 0.0001 + */ + private double learnignRateLowerLimit = 0.0001; + + /** + * Proportion of data to be used for training. + * + *

    + * This variable determines the fraction of the entire dataset that will be + * allocated for training purposes. The remaining portion of the dataset will be + * used for validation. The value of this variable should be within the range of + * 0 to 1, where: + *

      + *
    • 0 means 0% of the dataset is used for training (i.e., no training data). + *
    • 1 means 100% of the dataset is used for training (i.e., no validation + * data). + *
    + * + *

    + * The program utilizes this variable to split the input dataset vector into two + * separate vectors. One vector contains the training data, and the other vector + * contains the validation data. The split is essential for assessing the + * performance of the model on unseen data, helping to prevent overfitting and + * to ensure the model's generalizability. + */ + private double dataSplitTrain = 0.7; + + /** + * Proportion of data to be used for validation. + */ + private double dataSplitValidate = 1 - this.dataSplitTrain; + + private double wiInit = 0.2; + private double woInit = 0.2; + private double wzInit = 0.2; + private double riInit = 0.2; + private double roInit = 0.2; + private double rzInit = 0.2; + private double ytInit = 0.2; + private double ctInit = 0.2; + + /** + * Interval for logging or updating parameters. + */ + private int interval = 5; + + /** + * Size of each batch for training. + * + *

    + * To manage the computational load on the CPU during training, the training + * data is divided into smaller subsets called batches. + * + *

    + * For our LSTM (Long Short-Term Memory) model, a general rule of thumb is that + * datasets consisting of 30 days of data with 5-minute intervals should not be + * divided into batches greater than 2. This helps to balance the computational + * load and the memory usage during training. + * + *

    + * Considerations for setting the batch size: + *

      + *
    • If the training data size is large, more batches should be created to + * avoid excessive memory usage, which could lead to heap memory errors. + *
    • If the training data size is small, fewer batches should be created to + * ensure each batch contains a sufficient number of samples for meaningful + * updates. Creating too many batches with too few samples can lead to index out + * of range errors during training. + *
    + */ + private int batchSize = 10; + + /** + * Counter for tracking batches. + * + *

    + * This counter keeps track of the number of batches that have passed through + * the training process. + * + *

      + *
    • It updates after each batch completes its training. + *
    • In case the training is interrupted, this counter allows the process to + * resume from the last completed batch, ensuring continuity and efficiency in + * the training process. + *
    + * + *

    + * This mechanism is crucial for maintaining the state of the training process, + * especially in scenarios where interruptions may occur. + */ + private int batchTrack = 0; + + /** + * Number of epochs for training. + * + *

    + * An epoch refers to one complete pass through the entire training dataset. + * During each epoch, the model processes all the training data in batches, + * updating the model parameters iteratively. After each epoch, the learning + * rate can be adjusted, and the training process continues on the same dataset. + * + *

    + * The number of epochs is a crucial hyperparameter in training neural networks. + * More epochs generally mean that the model has more opportunities to learn + * from the data, potentially improving its performance. However, more epochs + * also mean longer training times and a higher risk of overfitting, where the + * model learns the training data too well and performs poorly on new, unseen + * data. + * + *

    + * It is recommended to keep the number of epochs in the range of 30 to 50 for a + * balanced approach between training time and model performance. Adjusting the + * number of epochs can be necessary based on the specific characteristics of + * the dataset and the complexity of the model. + */ + private int epoch = 10; + + /** + * Counter for tracking epochs. The counter updates after every time all batches + * undergoes training. This value is searilized along with the weights. in case + * training stops, this record is used to resme the training from the last stop + * point. + */ + private int epochTrack = 0; + + /** + * Number of predictions using trend weights. + * + *

    + * This parameter determines the number of predictions made based on the trend + * weights derived from the most recent trend window data. The trend window is a + * specific period used to analyze the trend patterns of the data. + * + *

    + * By default, one prediction is made using the last trend window data if this + * value is set to 1. This means that the system will use the data from the last + * trend window to make a single prediction. + * + *

    + * It is advisable to set this value to 12 if the interval between data points + * is 5 minutes Similarly, set this value to 8 if the interval between data + * points is 15 minutes . The interval represents the time or sequence gap + * between consecutive data points being analyzed. + * + *

    + * Setting a higher value than recommended can lead to inaccuracies in the + * prediction. This is because too many trend points may cause the model + * misinterpret the trend patterns, resulting in errors. + */ + private int trendPoints = 12; + + /** + * Window size for analyzing seasonality. + * + *

    + * This parameter defines the window size used for analyzing seasonal patterns + * in the data. A window size of 7 means that the model will use data from the + * last 7 days to train at one instance. Additionally, it will utilize the data + * from the last 7 days to predict data points for the next 24 hours. + * + *

    + * The window size can be adjusted up to a maximum of 14. While increasing the + * window size can potentially provide more accurate seasonal insights, it also + * increases the computational load. + * + *

    + * Key points: - Set to 7 to use the last 7 days of data for training and for + * predicting the next 24 hours. - The value can be adjusted up to 14. - Be + * aware that higher values may be computationally intensive. + */ + private int windowSizeSeasonality = 7; + + /** + * Window size for analyzing trend. + * + *

    + * This parameter specifies the window size used for analyzing trend patterns in + * the data. A window size of 5 means that the model will consider data from the + * last 5 time intervals to analyze the trend. This helps in identifying the + * direction and strength of the trend over recent time periods. Keep the value + * in between 5 to 7 + */ + private int windowSizeTrend = 5; + + /** + * Number of iterations for gradient descent. + * + *

    + * This parameter defines the number of iterations to be performed during the + * gradient descent optimization process. Gradient descent is used to minimize + * the cost function by iteratively updating the model parameters. + * + *

    + * The number of iterations can be set between 1 and 100. A higher number of + * iterations can potentially lead to models with improved accuracy as the + * optimization process has more opportunities to converge to a minimum. + * However, increasing the number of iterations also increases the computation + * time required for training the model. + * + *

    + * Key points: - Set to 10 to perform 10 iterations of gradient descent. - Can + * be adjusted between 1 and 100 based on the trade-off between accuracy and + * computation time. - Higher values may improve model accuracy but will also + * increase computation time. + */ + private int gdIterration = 10; + + /** + * Counter for general tracking purposes. + * + *

    + * This counter is used to determine whether the training process is being + * executed for the first time. + *

      + *
    • If the count is 0, the algorithm will use the initial weights and start a + * new training process. + *
    • If the count value is greater than 0, the algorithm will continue + * training the existing models. + *
    + * + *

    + * This mechanism ensures that the model can distinguish between initializing + * new training sessions and performing subsequent training iterations. + * + *

    + * Note: Just like in programming, remember that if you start counting from 0, + * you're a true computer scientist! + */ + private int count = 0; + + /** + * Threshold error value. + * + *

    + * This value represents the threshold error, typically measured in the same + * units as the training data. It can also be considered as the allowed error + * margin. The Root Mean Square (RMS) error computed during the model evaluation + * reflects the average deviation from this threshold value. + * + *

    + * Key points: - Measured in the same units as the training data. - Represents + * the acceptable error margin. - RMS error indicates the average deviation from + * this threshold. + */ + private double targetError = 0; + + /** + * Minimum value for scaling data. + * + *

    + * This value defines the minimum threshold for scaling the data. It should + * always be less than the `scalingMax` value. The unit of this value is the + * same as that of the training data. this valve can be negative and positive it + * id + * + *

    + * Once set, it is important not to change this value, as it could affect the + * consistency of the scaling process. + * + *

    + * Note: value once set should not be changed, as changing it is as risky as + * debugging a program on a Friday afternoon! + */ + private double scalingMin = 0; + + /** + * Maximum value for scaling data. + * + *

    + * This value defines the maximum threshold for scaling the data. It should + * always be greater than the `scalingMin` value. The unit of this value is the + * same as that of the training data. This value can be positive or negative, + * depending on the data range. + * + *

    + * Once set, it is important not to change this value, as it could affect the + * consistency of the scaling process. + * + *

    + * Note: Setting this value high is like aiming for the stars with your data! + * Just remember, changing it later could be as risky as giving a programmer, a + * cup of coffee after midnight! + */ + private double scalingMax = 20000; + + /** + * Model data structure for trend analysis. + * + *

    + * This is the brain of the model, responsible for storing updated weights and + * biases during the training process for trend analysis. + * + *

    + * The structure comprises nested arrays to store weights and biases: + * + *

    +	 * [ [wi1,wi2, wi3, ..., wik], 
    +	 *   [wo1, wo2, wo3, ..., wok], 
    +	 *   [wz1, wz2, wz3, ..., wzk],
    +	 *   [Ri1, Ri2, Ri3, ..., Rik], 
    +	 *   [Ro1, Ro2, Ro3, ..., Rok], 
    +	 *   [Rz1, Rz2, Rz3, ..., Rzk], 
    +	 *   [Yt1, Yt2, Yt3, ..., Ytk], 
    +	 *   [Ct1, Ct2, Ct3, ..., Ctk]
    +	 * ]
    +	 * 
    + * + *

    + * Where Wi, Wo, Wz, Ri, Ro, Rz, Yt, and Ct are the weights and biases of the + * LSTM cells, and 1, 2, 3, ..., k represent the window size. + * + *

    + * The first two nested arrays ensure that the second nested array is available + * for every time depending on the interval. first element of second nested + * array is used for the prediction of the trend point for 00:05 (if the + * interval is 5) + * + *

    + * Fun Fact: This data structure holds the keys to predicting trends better than + * a psychic octopus predicting World Cup winners! + */ + private ArrayList>>> modelTrend = new ArrayList>>>(); + + /** + * Model data structure for seasonality analysis. + * + *

    + * This data structure serves as the backbone of the model, specifically + * designed to store updated weights and biases during the training process for + * seasonality analysis. + * + *

    + * The structure consists of nested ArrayLists to accommodate the weights and + * biases. + * + *

    +	 * [ [wi1,wi2, wi3, ..., wik], 
    +	 *   [wo1, wo2, wo3, ..., wok], 
    +	 *   [wz1, wz2, wz3, ..., wzk],
    +	 *   [Ri1, Ri2, Ri3, ..., Rik], 
    +	 *   [Ro1, Ro2, Ro3, ..., Rok], 
    +	 *   [Rz1, Rz2, Rz3, ..., Rzk], 
    +	 *   [Yt1, Yt2, Yt3, ..., Ytk], 
    +	 *   [Ct1, Ct2, Ct3, ..., Ctk]
    +	 * ]
    +	 * 
    + * + *

    + * Where Wi, Wo, Wz are the weights of the LSTM cells, and 1, 2, 3, ..., k + * represent the window size. + * + *

    + * The first two nested arrays ensure that the second nested array is available + * for every time depending on the interval. first element of second nested + * array is used for the prediction of the trend point for 00:00 (if the + * interval is 5) + * + *

    + * Fun Fact: With this data structure, our model can predict seasonal pattern + * more accurately than a fortune-teller! + */ + private ArrayList>>> modelSeasonality = new ArrayList>>>(); + + /** + * List of all model errors related to trend analysis. + * + *

    + * This vector holds the Root Mean Square (RMS) errors of different models + * recorded during multiple training steps in modelTrend. + * + *

    + * Fun Fact: These errors are like the turn signals on a BMW - sometimes they're + * there, sometimes they're not, but they always keep us guessing and learning + * along the way! + */ + private ArrayList allModelErrorTrend = new ArrayList(); + + /** + * List of all model errors related to seasonality analysis. + * + *

    + * This vector contains the Root Mean Square (RMS) errors of different models + * recorded during multiple training steps in modelSeasonality. + * + *

    + * Fun Fact: These errors are like the various recipes for currywurst - some may + * be a bit spicier than others, but they all add flavor to our models, just + * like currywurst adds flavor to German cuisine! + */ + private ArrayList allModelErrorSeasonality = new ArrayList(); + + /** + * Mean value for normalization or scaling purposes. + * + *

    + * This value is crucial for ensuring proper normalization or scaling of the + * data. It acts as the central point around which the data is normalized or + * scaled. + * + *

    + * It's important to set this value to 0, just like it's important to feed your + * girlfriend when she's hungry, because, trust me, she can be mean when hungry! + */ + private double mean = 0; + + /** + * Standard deviation for normalization or scaling purposes. + * + *

    + * This value plays a crucial role in determining the spread or dispersion of + * the data during normalization or scaling. + */ + private double standardDeviation = 1; + + /** + * Root Mean Square Error (RMSE) for trend analysis. + * + *

    + * This list contains RMSE values for trend analysis. Unlike + * 'allModelErrorTrend', this list is limited in size to accommodate 60 divided + * by the interval multiplied by 24, and each value represents the RMSE of the + * model predicting for a specific time interval. + * + *

    + * The error at index 0 corresponds to the model predicting for 00:05, with + * subsequent indices representing subsequent time intervals. + */ + private ArrayList rmsErrorTrend = new ArrayList(); + + /** + * Root Mean Square Error (RMSE) for seasonality analysis. + * + *

    + * This list contains RMSE values for seasonality analysis. Each value + * represents the RMSE of the model's predictions related to seasonality. + */ + private ArrayList rmsErrorSeasonality = new ArrayList(); + + /** + * Counter for outer loop iterations, possibly for nested loops. Note: only used + * in unit test case + */ + private int outerLoopCount = 0; + + /** + * Name of the model. + */ + private String modelName = ""; + + public HyperParameters() { + } + + public void setLearningRateUpperLimit(double rate) { + this.learningRateUpperLimit = rate; + } + + public double getLearningRateUpperLimit() { + return this.learningRateUpperLimit; + } + + public void setLearningRateLowerLimit(double val) { + this.learnignRateLowerLimit = val; + } + + public double getLearningRateLowerLimit() { + return this.learnignRateLowerLimit; + } + + public void setWiInit(double val) { + this.wiInit = val; + } + + public double getWiInit() { + return this.wiInit; + } + + public void setWoInit(double val) { + this.woInit = val; + } + + public double getWoInit() { + return this.woInit; + } + + public void setWzInit(double val) { + this.wzInit = val; + } + + public double getWzInit() { + return this.wzInit; + } + + public void setriInit(double rate) { + this.riInit = rate; + } + + public double getRiInit() { + return this.riInit; + } + + public void setRoInit(double val) { + this.roInit = val; + } + + public double getRoInit() { + return this.roInit; + } + + public void setRzInit(double val) { + this.rzInit = val; + } + + public double getRzInit() { + return this.rzInit; + } + + public void setYtInit(double val) { + this.ytInit = val; + } + + public double getYtInit() { + return this.ytInit; + } + + public void setCtInit(double val) { + this.ctInit = val; + } + + public double getCtInit() { + return this.ctInit; + } + + public int getWindowSizeSeasonality() { + return this.windowSizeSeasonality; + } + + public int getGdIterration() { + return this.gdIterration; + } + + public void setGdIterration(int val) { + this.gdIterration = val; + } + + public int getWindowSizeTrend() { + return this.windowSizeTrend; + } + + public double getScalingMin() { + return this.scalingMin; + } + + public double getScalingMax() { + return this.scalingMax; + } + + public void setCount(int val) { + this.count = val; + } + + public int getCount() { + return this.count; + } + + public void setDatasplitTrain(double val) { + this.dataSplitTrain = val; + } + + public double getDataSplitTrain() { + return this.dataSplitTrain; + } + + public void setDatasplitValidate(double val) { + this.dataSplitValidate = val; + } + + public double getDataSplitValidate() { + return this.dataSplitValidate; + } + + public int getTrendPoint() { + return this.trendPoints; + } + + public int getEpoch() { + + return this.epoch; + } + + public int getInterval() { + return this.interval; + } + + public void setRmsErrorTrend(double val) { + this.rmsErrorTrend.add(val); + } + + public void setRmsErrorSeasonality(double val) { + this.rmsErrorSeasonality.add(val); + } + + public ArrayList getRmsErrorSeasonality() { + return this.rmsErrorSeasonality; + } + + public ArrayList getRmsErrorTrend() { + return this.rmsErrorTrend; + } + + public void setEpochTrack(int val) { + this.epochTrack = val; + } + + public int getEpochTrack() { + return this.epochTrack; + } + + public int getMinimumErrorModelSeasonality() { + return this.rmsErrorSeasonality.indexOf(Collections.min(this.rmsErrorSeasonality)); + } + + public int getMinimumErrorModelTrend() { + return this.rmsErrorTrend.indexOf(Collections.min(this.rmsErrorTrend)); + } + + public int getOuterLoopCount() { + return this.outerLoopCount; + } + + public void setOuterLoopCount(int val) { + this.outerLoopCount = val; + } + + public int getBatchSize() { + return this.batchSize; + } + + public int getBatchTrack() { + return this.batchTrack; + } + + public void setBatchTrack(int val) { + this.batchTrack = val; + } + + public void setModelName(String val) { + this.modelName = val; + } + + public String getModelName() { + return this.modelName; + } + + public double getMean() { + return this.mean; + + } + + public double getStandardDeviation() { + return this.standardDeviation; + } + + public double getTargetError() { + return this.targetError; + } + + public void setTargetError(double val) { + this.targetError = val; + } + + public int getMaxItter() { + return this.maxItterFactor; + } + + /** + * Updates the model trend with new values. + * + * @param val ArrayList of ArrayLists of ArrayLists of Double containing the new + * values to add to the model trend + */ + public void updatModelTrend(ArrayList>> val) { + this.modelTrend.add(val); + } + + /** + * Retrieves the most recently recorded model trend from the list of model + * trends. + * + * @return The most recently recorded model trend, represented as an ArrayList + * of ArrayLists of ArrayLists of Double. + */ + public ArrayList>> getlastModelTrend() { + return this.modelTrend.get(this.modelTrend.size() - 1); + } + + public ArrayList>> getBestModelTrend() { + return this.modelTrend.get(this.getMinimumErrorModelTrend()); + } + + public ArrayList>> getBestModelSeasonality() { + return this.modelSeasonality.get(this.getMinimumErrorModelSeasonality()); + } + + public ArrayList>>> getAllModelsTrend() { + return this.modelTrend; + } + + public ArrayList>>> getAllModelSeasonality() { + return this.modelSeasonality; + } + + public void setAllModelErrorTrend(ArrayList val) { + this.allModelErrorTrend = val; + } + + public void setAllModelErrorSeason(ArrayList val) { + this.allModelErrorSeasonality = val; + } + + public ArrayList getAllModelErrorTrend() { + return this.allModelErrorTrend; + } + + public ArrayList getAllModelErrorSeason() { + return this.allModelErrorSeasonality; + } + + /** + * Retrieves the last model trend from the list of model trends. + * + * @return ArrayList of ArrayLists of ArrayLists of Double representing the last + * model trend + */ + public ArrayList>> getlastModelSeasonality() { + return this.modelSeasonality.get(this.modelSeasonality.size() - 1); + } + + /** + * reset the error in the model. + */ + public void resetModelErrorValue() { + this.rmsErrorSeasonality = new ArrayList(); + this.rmsErrorTrend = new ArrayList(); + } + + /** + * Updates the model seasonality with new values. + * + * @param val The new model seasonality values to add, represented as an + * ArrayList of ArrayLists of ArrayLists of Double. + */ + public void updateModelSeasonality(ArrayList>> val) { + this.modelSeasonality.add(val); + } + + /** + * Prints the current values of hyperparameters and related attributes to the + * console. + */ + public void printHyperParameters() { + var string = new StringBuilder() // + .append("learningRateUpperLimit=").append(this.learningRateUpperLimit).append("\n") // + .append("learnignRateLowerLimit=").append(this.learnignRateLowerLimit).append("\n") // + .append("wiInit=").append(this.wiInit).append("\n") // + .append("woInit=").append(this.woInit).append("\n") // + .append("wzInit=").append(this.wzInit).append("\n") // + .append("riInit=").append(this.riInit).append("\n") // + .append("roInit=").append(this.roInit).append("\n") // + .append("rzInit=").append(this.rzInit).append("\n") // + .append("ytInit=").append(this.ytInit).append("\n") // + .append("ctInit=").append(this.ctInit).append("\n") // + .append("Epoch=").append(this.epoch).append("\n") // + .append("windowSizeSeasonality=").append(this.windowSizeSeasonality).append("\n") // + .append("windowSizeTrend=").append(this.windowSizeTrend).append("\n") // + .append("scalingMin=").append(this.scalingMin).append("\n") // + .append("scalingMax=").append(this.scalingMax).append("\n") // + .append("RMS error trend=").append(this.getRmsErrorTrend()).append("\n") // + .append("RMS error seasonality=").append(this.getRmsErrorSeasonality()).append("\n") // + .append("Count value=").append(this.count).append("\n") // + .append("Outer loop Count=").append(this.outerLoopCount).append("\n") // + .append("Epoch track=").append(this.epochTrack).append("\n") // + .toString(); + + System.out.println(string); + } + + /** + * Updates the models and their corresponding error indices based on the minimum + * error values obtained from model trends and model seasonality. This method + * first retrieves the indices of models with minimum errors for both trends and + * seasonality. Then it retrieves the corresponding models and clears the + * existing model trends, model seasonality, RMS errors for trend, and RMS + * errors for seasonality. After that, it adds the retrieved models to the + * respective model lists and updates the RMS errors with the minimum error + * values. + */ + public void update() { + int minErrorIndTrend = this.getMinimumErrorModelTrend(); + int minErrorIndSeasonlity = this.getMinimumErrorModelSeasonality(); + + // uipdating models + var modelTrendTemp = this.modelTrend.get(minErrorIndTrend); + final var modelTempSeasonality = this.modelSeasonality.get(minErrorIndSeasonlity); + this.modelTrend.clear(); + this.modelSeasonality.clear(); + this.modelTrend.add(modelTrendTemp); + this.modelSeasonality.add(modelTempSeasonality); + + // updating index + double minErrorTrend = this.rmsErrorTrend.get(minErrorIndTrend); + final double minErrorSeasonality = this.rmsErrorSeasonality.get(minErrorIndSeasonlity); + this.rmsErrorTrend.clear(); + this.rmsErrorSeasonality.clear(); + this.rmsErrorTrend.add(minErrorTrend); + this.rmsErrorSeasonality.add(minErrorSeasonality); + this.count = 1; + this.lastTrainedDate = OffsetDateTime.now(); + } + +} diff --git a/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/common/LstmPredictor.java b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/common/LstmPredictor.java new file mode 100644 index 00000000000..0e87c7252e0 --- /dev/null +++ b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/common/LstmPredictor.java @@ -0,0 +1,411 @@ +package io.openems.edge.predictor.lstm.common; + +import static io.openems.edge.predictor.lstm.utilities.UtilityConversion.to1DArray; +import static io.openems.edge.predictor.lstm.utilities.UtilityConversion.to1DArrayList; +import static io.openems.edge.predictor.lstm.utilities.UtilityConversion.to2DArrayList; +import static io.openems.edge.predictor.lstm.utilities.UtilityConversion.to2DList; + +import java.time.OffsetDateTime; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.List; + +import io.openems.edge.predictor.lstm.preprocessingpipeline.PreprocessingPipeImpl; +import io.openems.edge.predictor.lstm.utilities.MathUtils; + +public class LstmPredictor { + + /** + * Predicts seasonality based on the provided data and models. + * + * @param data The input data to predict seasonality for. + * @param date The corresponding date and time information for the + * data points. + * @param hyperParameters The hyperparameters for the prediction model. + * @return A list of predicted values for the seasonality. + * @throws SomeException If there's any specific exception that might be thrown + * during the process. + */ + public static ArrayList predictSeasonality(ArrayList data, ArrayList date, + HyperParameters hyperParameters) { + var preprocessing = new PreprocessingPipeImpl(hyperParameters); + preprocessing.setData(to1DArray(data)).setDates(date); + var resized = to2DList((double[][][]) preprocessing.interpolate()// + .scale()// + .filterOutliers() // + .groupByHoursAndMinutes()// + .execute()); + preprocessing.setData(resized); + var normalized = (double[][]) preprocessing// + .normalize()// + .execute(); + var allModel = hyperParameters.getBestModelSeasonality(); + var predicted = predictPre(to2DArrayList(normalized), allModel, hyperParameters); + preprocessing.setData(to1DArray(predicted))// + .setMean(DataStatistics.getMean(resized)) + .setStandardDeviation(DataStatistics.getStandardDeviation(resized)); + var seasonalityPrediction = (double[]) preprocessing.reverseNormalize()// + .reverseScale()// + .execute(); + return to1DArrayList(seasonalityPrediction); + } + + /** + * Predicts trend values for a given time period using LSTM models. + * + * @param data The historical data for trend prediction. + * @param date The corresponding date and time information for the + * historical data points. + * @param until The target time until which trend values will be + * predicted. + * @param hyperParameters The hyperparameters for the prediction model. + * @return A list of predicted trend values. + * @throws SomeException If there's any specific exception that might be thrown + * during the process. + */ + public static ArrayList predictTrend(ArrayList data, ArrayList date, + ZonedDateTime until, HyperParameters hyperParameters) { + var preprocessing = new PreprocessingPipeImpl(hyperParameters); + preprocessing.setData(to1DArray(data)).setDates(date); + + var scaled = (double[]) preprocessing// + .interpolate()// + .scale()// + .execute(); + + // normalize + var trendPrediction = new double[hyperParameters.getTrendPoint()]; + var mean = DataStatistics.getMean(scaled); + var standerDev = DataStatistics.getStandardDeviation(scaled); + preprocessing.setData(scaled); + var normData = to1DArrayList((double[]) preprocessing// + .normalize()// + .execute()); + + var predictionFor = until.plusMinutes(hyperParameters.getInterval()); + var val = hyperParameters.getBestModelTrend(); + for (int i = 0; i < hyperParameters.getTrendPoint(); i++) { + var temp = predictionFor.plusMinutes(i * hyperParameters.getInterval()); + + var modlelindex = (int) decodeDateToColumnIndex(temp, hyperParameters); + double predTemp = LstmPredictor.predict(// + normData, // + val.get(modlelindex).get(0), val.get(modlelindex).get(1), // + val.get(modlelindex).get(2), val.get(modlelindex).get(3), // + val.get(modlelindex).get(4), val.get(modlelindex).get(5), // + val.get(modlelindex).get(7), val.get(modlelindex).get(6), // + hyperParameters); + normData.add(predTemp); + normData.remove(0); + trendPrediction[i] = (predTemp); + } + + preprocessing.setData(trendPrediction).setMean(mean).setStandardDeviation(standerDev); + + return to1DArrayList((double[]) preprocessing// + .reverseNormalize()// + .reverseScale()// + .execute()); + } + + /** + * Decodes a ZonedDateTime to its corresponding column index based on prediction + * interval and window size. + * + * @param predictionFor The ZonedDateTime for which the column index is to be + * decoded. + * @param hyperParameters The hyperparameters for the prediction model. + * @return The decoded column index for the given ZonedDateTime. If the index is + * negative, it is adjusted to the corresponding positive index for a + * 24-hour period. + */ + public static double decodeDateToColumnIndex(ZonedDateTime predictionFor, HyperParameters hyperParameters) { + var hour = predictionFor.getHour(); + var minute = predictionFor.getMinute(); + var index = (Integer) hour * (60 / hyperParameters.getInterval()) + minute / hyperParameters.getInterval(); + var modifiedIndex = index - hyperParameters.getWindowSizeTrend(); + if (modifiedIndex >= 0) { + return modifiedIndex; + } else { + return modifiedIndex + 60 / hyperParameters.getInterval() * 24; + } + } + + /** + * Re-arranges an ArrayList of Double values by splitting it at the specified + * index and moving the second part to the front. + * + * @param splitIndex The index at which the ArrayList will be split. + * @param singleArray An ArrayList of Double values to be re-arranged. + * @return A new ArrayList containing the Double values after re-arrangement. + */ + public static ArrayList getArranged(int splitIndex, ArrayList singleArray) { + var arranged = new ArrayList(); + var firstGroup = new ArrayList(); + var secondGroup = new ArrayList(); + + for (var i = 0; i < singleArray.size(); i++) { + if (i < splitIndex) { + firstGroup.add(singleArray.get(i)); + } else { + secondGroup.add(singleArray.get(i)); + } + } + + arranged.addAll(secondGroup); + arranged.addAll(firstGroup); + + return arranged; + } + + /** + * Calculates the index of a specific hour and minute combination within a + * 24-hour period, divided into 15-minute intervals. + * + * @param hour The hour component (0-23) to be used for the + * calculation. + * @param minute The minute component (0, 5, 10, ..., 55) to be used + * for the + * @param hyperParameters is the object of class HyperParameters, calculation. + * @return The index representing the specified hour and minute combination. + */ + public static Integer getIndex(Integer hour, Integer minute, HyperParameters hyperParameters) { + var k = 0; + for (var i = 0; i < 24; i++) { + for (var j = 0; j < (int) 60 / hyperParameters.getInterval(); j++) { + var h = i; + var m = j * hyperParameters.getInterval(); + if (hour == h && minute == m) { + return k; + } else { + k = k + 1; + } + } + } + return k; + } + + /** + * Predict output values based on input data and a list of model parameters for + * multiple instances. This method takes a list of input data instances and a + * list of model parameters and predicts output values for each instance using + * the model. + * + * @param inputData An ArrayList of ArrayLists of Doubles, where each + * inner ArrayList represents input data for one + * instance. + * @param val An ArrayList of ArrayLists of ArrayLists of Doubles + * representing the model parameters for each instance. + * Each innermost ArrayList should contain model + * parameters in the following order: 0: Input weight + * vector (wi) 1: Output weight vector (wo) 2: Recurrent + * weight vector (wz) 3: Recurrent input activations (rI) + * 4: Recurrent output activations (rO) 5: Recurrent + * update activations (rZ) 6: Current cell state (ct) 7: + * Current output (yt) + * @param hyperParameters instance of class HyperParamters data + * @return An ArrayList of Double values representing the predicted output for + * each input data instance. + */ + public static ArrayList predictPre(ArrayList> inputData, + ArrayList>> val, HyperParameters hyperParameters) { + var result = new ArrayList(); + for (var i = 0; i < inputData.size(); i++) { + + var wi = val.get(i).get(0); + var wo = val.get(i).get(1); + var wz = val.get(i).get(2); + var rI = val.get(i).get(3); + var rO = val.get(i).get(4); + var rZ = val.get(i).get(5); + var ct = val.get(i).get(7); + var yt = val.get(i).get(6); + + result.add(predict(inputData.get(i), wi, wo, wz, rI, rO, rZ, ct, yt, hyperParameters)); + } + return result; + } + + /** + * Predict the output values based on input data and model parameters. This + * method takes input data and a set of model parameters and predicts output + * values for each data point using the model. + * + * @param data A 2D array representing the input data where each row + * is a data point. + * @param val An ArrayList containing model parameters, including + * weight vectors and activation values. The ArrayList + * should contain the following sublists in this order: + * 0: Input weight vector (wi) 1: Output weight vector + * (wo) 2: Recurrent weight vector (wz) 3: Recurrent + * input activations (rI) 4: Recurrent output activations + * (rO) 5: Recurrent update activations (rZ) 6: Current + * output (yt) 7: Current cell state (ct) + * + * @param hyperParameters instance of class HyperParamters data + * + * @return An ArrayList of Double values representing the predicted output for + * each input data point. + * + */ + public static ArrayList predictPre(double[][] data, List> val, + HyperParameters hyperParameters) { + var result = new ArrayList(); + + var wi = val.get(0); + var wo = val.get(1); + var wz = val.get(2); + var rI = val.get(3); + var rO = val.get(4); + var rZ = val.get(5); + var yt = val.get(6); + var ct = val.get(7); + + for (var i = 0; i < data.length; i++) { + result.add(predict(data[i], wi, wo, wz, rI, rO, rZ, yt, ct, hyperParameters)); + } + return result; + } + + /** + * Predict an output value based on input data and model parameters. This method + * predicts a single output value based on input data and a set of model + * parameters for a LSTM model. + * + * @param inputData An ArrayList of Doubles representing the input data + * for prediction. + * @param wi An ArrayList of Doubles representing the input weight + * vector (wi) for the RNN model. + * @param wo An ArrayList of Doubles representing the output weight + * vector (wo) for the RNN model. + * @param wz An ArrayList of Doubles representing the recurrent + * weight vector (wz) for the RNN model. + * @param rI An ArrayList of Doubles representing the recurrent + * input activations (rI) for the RNN model. + * @param rO An ArrayList of Doubles representing the recurrent + * output activations (rO) for the RNN model. + * @param rZ An ArrayList of Doubles representing the recurrent + * update activations (rZ) for the RNN model. + * @param cta An ArrayList of Doubles representing the current cell + * state (ct) for the RNN model. + * @param yta An ArrayList of Doubles representing the current + * output (yt) for the RNN model. + * @param hyperParameters instance of class HyperParamters data + * @return A double representing the predicted output value based on the input + * data and model parameters. + */ + public static double predict(ArrayList inputData, ArrayList wi, ArrayList wo, + ArrayList wz, ArrayList rI, ArrayList rO, ArrayList rZ, + ArrayList cta, ArrayList yta, HyperParameters hyperParameters) { + var ct = hyperParameters.getCtInit(); + var yt = hyperParameters.getYtInit(); + var standData = inputData;// DataModification.standardize(inputData, hyperParameters); + + for (var i = 0; i < standData.size(); i++) { + var ctMinusOne = ct; + var yTMinusOne = yt; + var xt = standData.get(i); + var it = MathUtils.sigmoid(wi.get(i) * xt + rI.get(i) * yTMinusOne); + var ot = MathUtils.sigmoid(wo.get(i) * xt + rO.get(i) * yTMinusOne); + var zt = MathUtils.tanh(wz.get(i) * xt + rZ.get(i) * yTMinusOne); + ct = ctMinusOne + it * zt; + yt = ot * MathUtils.tanh(ct); + } + return yt; + } + + /** + * Predict an output value based on input data and model parameters. This method + * predicts a single output value based on input data and a set of model + * parameters for a LSTM model. + * + * @param inputData An ArrayList of Doubles representing the input data + * for prediction. + * @param wi An ArrayList of Doubles representing the input weight + * vector (wi) for the RNN model. + * @param wo An ArrayList of Doubles representing the output weight + * vector (wo) for the RNN model. + * @param wz An ArrayList of Doubles representing the recurrent + * weight vector (wz) for the RNN model. + * @param rI An ArrayList of Doubles representing the recurrent + * input activations (rI) for the RNN model. + * @param rO An ArrayList of Doubles representing the recurrent + * output activations (rO) for the RNN model. + * @param rZ An ArrayList of Doubles representing the recurrent + * update activations (rZ) for the RNN model. + * @param cta An ArrayList of Doubles representing the current cell + * state (ct) for the RNN model. + * @param yta An ArrayList of Doubles representing the current + * output (yt) for the RNN model. + * @param hyperParameters instance of class HyperParamters data + * @return A double representing the predicted output value based on the input + * data and model parameters. + */ + public static double predict(double[] inputData, ArrayList wi, ArrayList wo, ArrayList wz, + ArrayList rI, ArrayList rO, ArrayList rZ, ArrayList cta, + ArrayList yta, HyperParameters hyperParameters) { + var ct = hyperParameters.getCtInit(); + var yt = hyperParameters.getYtInit(); + var standData = inputData;// DataModification.standardize(inputData, hyperParameters); + + for (var i = 0; i < standData.length; i++) { + var ctMinusOne = ct; + var yTMinusOne = yt; + var xt = standData.length; + var it = MathUtils.sigmoid(wi.get(i) * xt + rI.get(i) * yTMinusOne); + var ot = MathUtils.sigmoid(wo.get(i) * xt + rO.get(i) * yTMinusOne); + var zt = MathUtils.tanh(wz.get(i) * xt + rZ.get(i) * yTMinusOne); + ct = ctMinusOne + it * zt; + yt = ot * MathUtils.tanh(ct); + } + return yt; + } + + /** + * Predict a focused output value based on input data and model parameters. This + * method predicts a single focused output value based on input data and a set + * of model parameters for a LSTM model with a focus on specific activations. + * + * @param inputData An ArrayList of Doubles representing the input data + * for prediction. + * @param wi An ArrayList of Doubles representing the input weight + * vector (wi) for the RNN model. + * @param wo An ArrayList of Doubles representing the output weight + * vector (wo) for the RNN model. + * @param wz An ArrayList of Doubles representing the recurrent + * weight vector (wz) for the RNN model. + * @param rI An ArrayList of Doubles representing the recurrent + * input activations (rI) for the RNN model. + * @param rO An ArrayList of Doubles representing the recurrent + * output activations (rO) for the RNN model. + * @param rZ An ArrayList of Doubles representing the recurrent + * update activations (rZ) for the RNN model. + * @param cta An ArrayList of Doubles representing the current cell + * state (ct) for the RNN model. + * @param yta An ArrayList of Doubles representing the current + * output (yt) for the RNN model. + * @param hyperParameters instance of class HyperParamters data + * @return A double representing the predicted focused output value based on the + * input data and model parameters. + */ + public static double predictFocoused(ArrayList inputData, ArrayList wi, ArrayList wo, + ArrayList wz, ArrayList rI, ArrayList rO, ArrayList rZ, + ArrayList cta, ArrayList yta, HyperParameters hyperParameters) { + var ct = hyperParameters.getCtInit(); + var yt = hyperParameters.getYtInit(); + + var standData = inputData; + + for (var i = 0; i < standData.size(); i++) { + var ctMinusOne = ct; + var ytMinusOne = yt; + var xt = standData.get(i); + var it = MathUtils.sigmoid(rI.get(i) * ytMinusOne); + var ot = MathUtils.sigmoid(rO.get(i) * ytMinusOne); + var zt = MathUtils.tanh(wz.get(i) * xt); + ct = ctMinusOne + it * zt; + yt = ot * MathUtils.tanh(ct); + } + return yt; + } +} diff --git a/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/common/OffsetDateTimeAdapter.java b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/common/OffsetDateTimeAdapter.java new file mode 100644 index 00000000000..bf35fc6d06b --- /dev/null +++ b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/common/OffsetDateTimeAdapter.java @@ -0,0 +1,29 @@ +package io.openems.edge.predictor.lstm.common; + +import java.lang.reflect.Type; +import java.time.OffsetDateTime; +import java.time.format.DateTimeFormatter; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; + +public class OffsetDateTimeAdapter implements JsonSerializer, JsonDeserializer { + + private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ISO_OFFSET_DATE_TIME; + + @Override + public JsonElement serialize(OffsetDateTime src, Type typeOfSrc, JsonSerializationContext context) { + return new JsonPrimitive(src.format(FORMATTER)); + } + + @Override + public OffsetDateTime deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) + throws JsonParseException { + return OffsetDateTime.parse(json.getAsString(), FORMATTER); + } +} diff --git a/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/common/ReadAndSaveModels.java b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/common/ReadAndSaveModels.java new file mode 100644 index 00000000000..68e71d8ede2 --- /dev/null +++ b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/common/ReadAndSaveModels.java @@ -0,0 +1,157 @@ +package io.openems.edge.predictor.lstm.common; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Reader; +import java.nio.file.Paths; +import java.time.OffsetDateTime; +import java.util.ArrayList; +import java.util.Base64; +import java.util.zip.DeflaterOutputStream; +import java.util.zip.InflaterInputStream; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import io.openems.common.OpenemsConstants; +import io.openems.edge.predictor.lstm.validator.ValidationSeasonalityModel; +import io.openems.edge.predictor.lstm.validator.ValidationTrendModel; + +public class ReadAndSaveModels { + + protected static final String MODEL_FOLDER = File.separator + "lstm" + File.separator; + + private static final String MODEL_DIRECTORY = Paths.get(OpenemsConstants.getOpenemsDataDir())// + .toFile()// + .getAbsolutePath(); + + /** + * Saves the {@link HyperParameters} object to a file in JSON format. This + * method serializes the provided {@link HyperParameters} object into JSON + * format and saves it to a file with the specified name in the "lstm" + * directory. The serialization process utilizes a custom Gson instance + * configured to handle the serialization of OffsetDateTime objects. The file is + * saved in the directory specified by the OpenEMS data directory. + * + * @param hyperParameters The {@link HyperParameters} object to be saved. + */ + public static void save(HyperParameters hyperParameters) { + String modelName = hyperParameters.getModelName(); + String filePath = Paths.get(MODEL_DIRECTORY, MODEL_FOLDER, modelName)// + .toString(); + + Gson gson = new GsonBuilder()// + .registerTypeAdapter(OffsetDateTime.class, new OffsetDateTimeAdapter())// + .create(); + + try { + var compressedData = compress(hyperParameters); + var compressedDataString = Base64.getEncoder().encodeToString(compressedData); + var json = gson.toJson(compressedDataString); + + try (FileWriter writer = new FileWriter(filePath)) { + writer.write(json); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * Reads and de-serializes a {@link HyperParameters} object from a JSON file. + * This method reads a HyperParameters object from the specified JSON file, + * de-serializing it into a {@link HyperParameters} instance. The + * de-serialization process utilizes a custom Gson instance configured to handle + * the de-serialization of {@link OffsetDateTime} objects. The file is expected + * to be located in the "lstm" directory within the OpenEMS data directory. + * + * @param fileName The name of the JSON file to read the HyperParameters from. + * @return The {@link HyperParameters} object read from the file. + * @throws FileNotFoundException If the specified file is not found. + * @throws IOException If an I/O error occurs while reading the file. + */ + public static HyperParameters read(String fileName) { + String filePath = Paths.get(MODEL_DIRECTORY, MODEL_FOLDER, fileName)// + .toString(); + + try (Reader reader = new FileReader(filePath)) { + Gson gson = new GsonBuilder()// + .registerTypeAdapter(OffsetDateTime.class, new OffsetDateTimeAdapter())// + .create(); + var json = gson.fromJson(reader, String.class); + var deserializedData = Base64.getDecoder().decode(json); + return decompress(deserializedData); + } catch (IOException e) { + var hyperParameters = new HyperParameters(); + hyperParameters.setModelName(fileName); + return hyperParameters; + } + } + + /** + * Compress the data. + * + * @param hyp the Hyper parameter object + * @return compressend byte array + */ + public static byte[] compress(HyperParameters hyp) { + try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); + DeflaterOutputStream dos = new DeflaterOutputStream(baos); + ObjectOutputStream oos = new ObjectOutputStream(dos)) { + + oos.writeObject(hyp); + dos.finish(); + return baos.toByteArray(); + + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + + /** + * DeCompress the data. + * + * @param value the value array to decompress + * @return Hyper parameter + */ + public static HyperParameters decompress(byte[] value) { + HyperParameters hyperParameters = null; + try (ByteArrayInputStream bais = new ByteArrayInputStream(value); + InflaterInputStream iis = new InflaterInputStream(bais); + ObjectInputStream ois = new ObjectInputStream(iis)) { + hyperParameters = (HyperParameters) ois.readObject(); + } catch (IOException | ClassNotFoundException e) { + e.printStackTrace(); + } + return hyperParameters; + } + + /** + * Adapt it. + * + * @param hyperParameters the Hyperparameter + * @param data the data + * @param dates the dates + */ + public static void adapt(HyperParameters hyperParameters, ArrayList data, ArrayList dates) { + if (hyperParameters.getCount() == 0) { + return; + } + + var valSeas = new ValidationSeasonalityModel(); + var valTrend = new ValidationTrendModel(); + + hyperParameters.resetModelErrorValue(); + + valSeas.validateSeasonality(data, dates, hyperParameters.getAllModelSeasonality(), hyperParameters); + valTrend.validateTrend(data, dates, hyperParameters.getAllModelsTrend(), hyperParameters); + } +} diff --git a/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/common/ReadCsv.java b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/common/ReadCsv.java new file mode 100644 index 00000000000..d72fba5d9b4 --- /dev/null +++ b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/common/ReadCsv.java @@ -0,0 +1,74 @@ +package io.openems.edge.predictor.lstm.common; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.nio.file.Paths; +import java.time.OffsetDateTime; +import java.util.ArrayList; + +import io.openems.common.OpenemsConstants; + +public class ReadCsv { + + private static final String MODEL_DIRECTORY = Paths.get(OpenemsConstants.getOpenemsDataDir())// + .toFile().getAbsolutePath(); + private static final String MODEL_FOLDER = File.separator + "models" + File.separator; + + private ArrayList data = new ArrayList(); + private ArrayList dates = new ArrayList(); + + public ReadCsv(String path) { + this.getDataFromCsv(path); + } + + /** + * Reads data from a CSV file and populates class fields with the data. This + * method reads data from a CSV file specified by the provided file name. Each + * line in the CSV file is expected to contain timestamped data points, where + * the first column represents timestamps in the ISO-8601 format and subsequent + * columns represent numeric data. The data is parsed, and the timestamps and + * numeric values are stored in class fields for further processing. + * + * @param fileName The name of the CSV file to read data from. + * @throws IOException if there are issues reading the file. + */ + public void getDataFromCsv(String fileName) { + try { + var path = Paths.get(MODEL_DIRECTORY, MODEL_FOLDER, fileName)// + .toString(); + + var reader = new BufferedReader(new FileReader(path)); + var line = reader.readLine(); + + while (line != null) { + var parts = line.split(","); + var date = OffsetDateTime.parse(parts[0]); + var temp2 = 0.0; + + for (int i = 1; i < parts.length; i++) { + if (parts[i].equals("") || parts[i].equals("nan")) { + temp2 = Double.NaN; + } else { + temp2 = (Double.parseDouble(parts[i])); + } + } + this.dates.add(date); + this.data.add(temp2); + line = reader.readLine(); + } + reader.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public ArrayList getData() { + return this.data; + } + + public ArrayList getDates() { + return this.dates; + } +} diff --git a/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/interpolation/CubicalInterpolation.java b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/interpolation/CubicalInterpolation.java new file mode 100644 index 00000000000..083dac99ac0 --- /dev/null +++ b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/interpolation/CubicalInterpolation.java @@ -0,0 +1,117 @@ +package io.openems.edge.predictor.lstm.interpolation; + +import java.util.ArrayList; +import java.util.stream.IntStream; + +import org.apache.commons.math3.analysis.interpolation.SplineInterpolator; +import org.apache.commons.math3.analysis.polynomials.PolynomialSplineFunction; + +public class CubicalInterpolation extends SplineInterpolator { + + private ArrayList data; + + public CubicalInterpolation(ArrayList data) { + this.data = data; + } + + public CubicalInterpolation() { + } + + /** + * Compute Cubical interpolation. + * + * @return interpolated results + */ + public ArrayList compute() { + var interpolation = new ArrayList>(); + var function = this.getFunctionForAllInterval(this.data); + var differences = this.firstOrderDiff(function); + + for (int i = 0; i < differences.length; i++) { + if (differences[i] != 1) { + int requiredPoints = (int) (differences[i] - 1); + interpolation.add(this.calculate(function.getPolynomials()[i].getCoefficients(), requiredPoints)); + } + } + this.generateCombineInstruction(interpolation, differences); + return this.data; + } + + private PolynomialSplineFunction getFunctionForAllInterval(ArrayList data) { + var nonNaNCount = data.stream().filter(d -> !Double.isNaN(d)).count(); + + var dataNew = new double[(int) nonNaNCount]; + var xVal = new double[(int) nonNaNCount]; + + int[] index = { 0 }; + IntStream.range(0, data.size())// + .filter(i -> !Double.isNaN(data.get(i)))// + .forEach(i -> { + dataNew[index[0]] = data.get(i); + xVal[index[0]] = i + 1; + index[0]++; + }); + + return interpolate(xVal, dataNew); + } + + private double[] firstOrderDiff(PolynomialSplineFunction function) { + double[] knots = function.getKnots(); + return IntStream.range(0, knots.length - 1)// + .mapToDouble(i -> knots[i + 1] - knots[i])// + .toArray(); + } + + private ArrayList calculate(double[] weight, int requiredPoints) { + ArrayList result = new ArrayList<>(); + for (int j = 0; j < requiredPoints; j++) { + double sum = 0; + for (int i = 0; i < weight.length; i++) { + sum += weight[i] * Math.pow(j + 1, i); + } + result.add(sum); + } + return result; + } + + private void generateCombineInstruction(ArrayList> interPolatedValue, double[] firstOrderDiff) { + int count = 0; + int startingPoint = 0; + int addedData = 0; + + for (int i = 0; i < firstOrderDiff.length; i++) { + + if (firstOrderDiff[i] != 1) { + startingPoint = i + 1 + addedData; + this.combineToData(startingPoint, (int) firstOrderDiff[i] - 1, interPolatedValue.get(count)); + addedData = (int) (firstOrderDiff[i] - 1 + addedData); + count = count + 1; + } + } + } + + private void combineToData(int startingPoint, int totalpointsRequired, ArrayList dataToAdd) { + for (int i = 0; i < totalpointsRequired; i++) { + this.data.set(i + startingPoint, dataToAdd.get(i)); + } + } + + /** + * Can interpolate ?. + * + * @return boolean yes or no. + */ + public boolean canInterpolate() { + var nonNaNCount = this.data.stream().filter(d -> d != null && !Double.isNaN(d)).count(); + return this.data.size() > 4 && nonNaNCount > 2; + } + + public void setData(ArrayList val) { + this.data = val; + } + + public ArrayList getInterPolatedData() { + return this.data; + } + +} diff --git a/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/interpolation/InterpolationManager.java b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/interpolation/InterpolationManager.java new file mode 100644 index 00000000000..19864647a72 --- /dev/null +++ b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/interpolation/InterpolationManager.java @@ -0,0 +1,148 @@ +package io.openems.edge.predictor.lstm.interpolation; + +import java.time.OffsetDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.OptionalDouble; +import java.util.stream.Collectors; + +import io.openems.edge.predictor.lstm.common.HyperParameters; +import io.openems.edge.predictor.lstm.utilities.UtilityConversion; + +public class InterpolationManager { + + private ArrayList interpolated = new ArrayList(); + private ArrayList newDates = new ArrayList(); + + public ArrayList getInterpolatedData() { + return this.interpolated; + } + + public ArrayList getNewDates() { + return this.newDates; + } + + public InterpolationManager(double[] data, HyperParameters hyperParameters) { + var dataList = UtilityConversion.to1DArrayList(data); + this.makeInterpolation(dataList); + } + + public InterpolationManager(ArrayList data, HyperParameters hyperParameters) { + this.makeInterpolation(data); + } + + private void makeInterpolation(ArrayList data) { + ArrayList dataDouble = replaceNullWithNaN(data); + double mean = calculateMean(dataDouble); + + // TODO why 96 + int groupSize = 96; + + List> groupedData = group(dataDouble, groupSize); + + CubicalInterpolation inter = new CubicalInterpolation(); + + List> interpolatedGroupedData = groupedData.stream()// + .map(currentGroup -> { + if (this.interpolationDecision(currentGroup)) { + this.handleFirstAndLastDataPoint(currentGroup, mean); + inter.setData(currentGroup); + return inter.canInterpolate() ? inter.compute() : LinearInterpolation.interpolate(currentGroup); + } else { + return currentGroup; + } + }).collect(Collectors.toList()); + + this.interpolated = unGroup(interpolatedGroupedData); + } + + private void handleFirstAndLastDataPoint(ArrayList currentGroup, double mean) { + int firstIndex = 0; + int lastIndex = currentGroup.size() - 1; + + if (Double.isNaN(currentGroup.get(firstIndex))) { + currentGroup.set(firstIndex, mean); + } + if (Double.isNaN(currentGroup.get(lastIndex))) { + currentGroup.set(lastIndex, mean); + } + } + + /** + * Checks whether interpolation is needed based on the presence of NaN values in + * the provided list. + * + * @param data The list of Double values to be checked. + * @return true if interpolation is needed (contains at least one NaN value), + * false otherwise. + */ + private boolean interpolationDecision(ArrayList data) { + return data.stream().anyMatch(value -> Double.isNaN(value)); + } + + /** + * Replaces null values with Double.NaN in the given ArrayList. + * + * @param data The ArrayList to be processed. + * @return A new ArrayList with null values replaced by Double.NaN. + */ + public static ArrayList replaceNullWithNaN(ArrayList data) { + return data.stream()// + .map(value -> (value == null) ? Double.NaN : value)// + .collect(Collectors.toCollection(ArrayList::new)); + } + + /** + * Calculates the mean (average) of a list of numeric values, excluding NaN + * values. + * + * @param data The list of numeric values from which to calculate the mean. + * @return The mean of the non-NaN numeric values in the input list. + */ + public static double calculateMean(ArrayList data) { + if (data.isEmpty()) { + return Double.NaN; + } + + OptionalDouble meanOptional = data.stream()// + .filter(value -> !Double.isNaN(value))// + .mapToDouble(Double::doubleValue)// + .average(); + + return meanOptional.orElse(Double.NaN); + } + + /** + * Ungroups a list of sublists into a single list. + * + * @param data The list of sublists to be ungrouped. + * @return A single list containing all elements from the sublists. + */ + public static ArrayList unGroup(List> data) { + return data.stream()// + .flatMap(List::stream)// + .collect(Collectors.toCollection(ArrayList::new)); + } + + /** + * Groups a list of data into sublists of a specified size. This method takes a + * list of data and groups it into sublists of a specified size. Each sublist + * will contain up to {@code groupSize} elements, except for the last sublist, + * which may contain fewer elements if the total number of elements is not a + * multiple of {@code groupSize}. + * + * @param data The list of data to be grouped. + * @param groupSize The maximum number of elements in each sublist. + * @return A list of sublists, each containing up to {@code groupSize} elements. + */ + public static ArrayList> group(ArrayList data, int groupSize) { + ArrayList> groupedData = new ArrayList<>(); + + for (int i = 0; i < data.size(); i += groupSize) { + ArrayList sublist = new ArrayList<>(data.subList(i, Math.min(i + groupSize, data.size()))); + groupedData.add(sublist); + } + return groupedData; + } + +} diff --git a/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/interpolation/LinearInterpolation.java b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/interpolation/LinearInterpolation.java new file mode 100644 index 00000000000..e570476bde4 --- /dev/null +++ b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/interpolation/LinearInterpolation.java @@ -0,0 +1,99 @@ +package io.openems.edge.predictor.lstm.interpolation; + +import java.util.ArrayList; + +public class LinearInterpolation { + + /** + * Interpolates NaN values in the provided data set. + * + * @param data The input data set with NaN values. + * @return The data set with NaN values replaced by interpolated values. + */ + public static ArrayList interpolate(ArrayList data) { + var coordinate = determineInterpolatingPoints(data); + for (int i = 0; i < coordinate.size(); i++) { + var xVal1 = coordinate.get(i).get(0); + var xVal2 = coordinate.get(i).get(1); + + var ineterPolationResult = computeInterpolation(xVal1, xVal2, data.get(xVal1), data.get((int) xVal2)); + data = combine(data, ineterPolationResult, xVal1, xVal2); + + } + return data; + } + + /** + * Determines the indices where NaN values are sandwiched between non-NaN values + * in a given data set. + * + * @param data The input data set. + * @return A list of coordinate pairs representing the indices where NaN values + * are sandwiched. + */ + public static ArrayList> determineInterpolatingPoints(ArrayList data) { + var coordinates = new ArrayList>(); + + var inNaNSequence = false; + var xVal1 = -1; + + for (int i = 0; i < data.size(); i++) { + var currentValue = data.get(i); + + if (Double.isNaN(currentValue)) { + if (!inNaNSequence) { + xVal1 = i - 1; + inNaNSequence = true; + } + } else { + if (inNaNSequence) { + var xVal2 = i; + var temp = new ArrayList(); + temp.add(xVal1); + temp.add(xVal2); + coordinates.add(temp); + inNaNSequence = false; + } + } + } + return coordinates; + } + + /** + * Computes linear interpolation between two values. + * + * @param xValue1 The x-value corresponding to the first data point. + * @param xValue2 The x-value corresponding to the second data point. + * @param yValue1 The y-value corresponding to the first data point. + * @param yValue2 The y-value corresponding to the second data point. + * @return A list of interpolated y-values between xValue1 and xValue2. + */ + public static ArrayList computeInterpolation(int xValue1, int xValue2, double yValue1, double yValue2) { + var interPolatedResults = new ArrayList(); + var xVal1 = (double) xValue1; + var xVal2 = (double) xValue2; + + for (int i = 1; i < (xValue2 - xValue1); i++) { + interPolatedResults + .add((yValue1 * ((xVal2 - (i + xVal1)) / (xVal2 - xVal1)) + yValue2 * ((i) / (xVal2 - xVal1)))); + } + return interPolatedResults; + } + + /** + * Combines the original data set with the interpolation result. + * + * @param orginalData The original data set. + * @param interpolatedResult The result of linear interpolation. + * @param xValue1 The first index used for interpolation. + * @param xValue2 The second index used for interpolation. + * @return The combined data set with interpolated values. + */ + public static ArrayList combine(ArrayList orginalData, ArrayList interpolatedResult, + int xValue1, int xValue2) { + for (int i = 0; i < (interpolatedResult.size()); i++) { + orginalData.set((i + xValue1 + 1), interpolatedResult.get(i)); + } + return orginalData; + } +} diff --git a/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/jsonrpc/GetPredictionRequest.java b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/jsonrpc/GetPredictionRequest.java new file mode 100644 index 00000000000..7cb108cc6fe --- /dev/null +++ b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/jsonrpc/GetPredictionRequest.java @@ -0,0 +1,50 @@ +package io.openems.edge.predictor.lstm.jsonrpc; + +import com.google.gson.JsonObject; + +import io.openems.common.exceptions.OpenemsException; +import io.openems.common.jsonrpc.base.JsonrpcRequest; + +/* + * url = http://localhost:8084/jsonrpc + * { + * "method": "componentJsonApi", + * "params": { + * "componentId": "predictor0", + * "payload": { + * "method": "getPrediction", + * "params": { + * "id": "edge0" + * } + * } + * } +*} + */ +public class GetPredictionRequest extends JsonrpcRequest { + + public static final String METHOD = "getPrediction"; + + /** + * Get prediction. + * + * @param r the request + * @return new prediction + * @throws on error + */ + public static GetPredictionRequest from(JsonrpcRequest r) throws OpenemsException { + return new GetPredictionRequest(r); + } + + public GetPredictionRequest() { + super(METHOD); + } + + private GetPredictionRequest(JsonrpcRequest request) { + super(request, METHOD); + } + + @Override + public JsonObject getParams() { + return new JsonObject(); + } +} diff --git a/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/jsonrpc/GetPredictionResponse.java b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/jsonrpc/GetPredictionResponse.java new file mode 100644 index 00000000000..1d5350ce8cb --- /dev/null +++ b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/jsonrpc/GetPredictionResponse.java @@ -0,0 +1,56 @@ +package io.openems.edge.predictor.lstm.jsonrpc; + +import java.time.ZonedDateTime; +import java.util.NavigableMap; +import java.util.SortedMap; +import java.util.UUID; + +import com.google.gson.JsonArray; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; + +import io.openems.common.jsonrpc.base.JsonrpcResponseSuccess; +import io.openems.common.utils.JsonUtils; +import io.openems.common.utils.JsonUtils.JsonObjectBuilder; + +public class GetPredictionResponse extends JsonrpcResponseSuccess { + + private final JsonArray prediction; + private final SortedMap predictionResult; + + public GetPredictionResponse(JsonArray prediction) { + this(UUID.randomUUID(), prediction); + } + + public GetPredictionResponse(UUID id, JsonArray prediction) { + super(id); + this.prediction = prediction != null ? prediction : new JsonArray(); + this.predictionResult = null; + } + + public GetPredictionResponse(UUID id, NavigableMap predictionResult) { + super(id); + this.predictionResult = predictionResult; + this.prediction = new JsonArray(); + if (predictionResult != null) { + predictionResult.values().forEach(value -> { + this.prediction.add(value != null ? new JsonPrimitive(value) : JsonNull.INSTANCE); + }); + } + } + + @Override + public JsonObject getResult() { + JsonObjectBuilder result = JsonUtils.buildJsonObject() // + .add("prediction", this.prediction) // + .add("size", new JsonPrimitive(this.prediction.size())); + + if (this.predictionResult != null) { + result.add("TimeValueMap", new JsonPrimitive(this.predictionResult.toString())); + } else { + result.add("timeValueMap", JsonNull.INSTANCE); + } + return result.build(); + } +} diff --git a/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/jsonrpc/PredictionRequestHandler.java b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/jsonrpc/PredictionRequestHandler.java new file mode 100644 index 00000000000..f0e96b7a44c --- /dev/null +++ b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/jsonrpc/PredictionRequestHandler.java @@ -0,0 +1,23 @@ +package io.openems.edge.predictor.lstm.jsonrpc; + +import java.util.UUID; + +import io.openems.common.types.ChannelAddress; +import io.openems.edge.predictor.api.manager.PredictorManager; + +public class PredictionRequestHandler { + + /** + * Handle {@link GetPredictionRequest}; return {@link GetPredictionResponse}. + * + * @param requestId the id + * @param predictionManager the {@link PredictorManager} + * @param channelAddress the {@link ChannelAddress} + * @return the new prediction + */ + public static GetPredictionResponse handlerGetPredictionRequest(UUID requestId, PredictorManager predictionManager, + ChannelAddress channelAddress) { + var sortedMap = predictionManager.getPrediction(channelAddress).toMapWithAllQuarters(); + return new GetPredictionResponse(requestId, sortedMap); + } +} diff --git a/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/performance/PerformanceMatrix.java b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/performance/PerformanceMatrix.java new file mode 100644 index 00000000000..37fc4e2d60b --- /dev/null +++ b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/performance/PerformanceMatrix.java @@ -0,0 +1,272 @@ +package io.openems.edge.predictor.lstm.performance; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.math3.distribution.TDistribution; +import org.apache.commons.math3.stat.StatUtils; + +import io.openems.edge.predictor.lstm.common.DataStatistics; + +public class PerformanceMatrix { + private ArrayList target = new ArrayList(); + private ArrayList predicted = new ArrayList(); + private double allowedError = 0.0; + + public PerformanceMatrix(ArrayList tar, ArrayList predict, double allowedErr) { + this.target = tar; + this.predicted = predict; + this.allowedError = allowedErr; + } + + /** + * Calculates the mean absolute error between the target and predicted values. + * Mean absolute error (MAE) is a metric that measures the average absolute + * difference between corresponding elements of two lists. + * + * @param target The list of target values. + * @param predicted The list of predicted values. + * @return The mean absolute error between the target and predicted values. + * @throws IllegalArgumentException If the input lists have different sizes. + */ + public static double meanAbsoluteError(ArrayList target, ArrayList predicted) { + if (predicted.size() != target.size()) { + throw new IllegalArgumentException("Input lists must have the same size"); + } + + double sumError = 0.0; + for (int i = 0; i < predicted.size(); i++) { + double error = Math.abs(predicted.get(i) - target.get(i)); + sumError += error; + } + + return sumError / predicted.size(); + } + + /** + * Calculates the Root Mean Square (RMS) error between the target and predicted + * values. RMS error is a measure of the average magnitude of the differences + * between corresponding elements of two lists. + * + * @param target The list of target values. + * @param predicted The list of predicted values. + * @return The root mean square error between the target and predicted values. + * @throws IllegalArgumentException If the input lists have different sizes. + */ + public static double rmsError(ArrayList target, ArrayList predicted) { + if (predicted.size() != target.size()) { + throw new IllegalArgumentException("Input lists must have the same size"); + } + + double sumSquaredError = 0.0; + for (int i = 0; i < predicted.size(); i++) { + double error = predicted.get(i) - target.get(i); + sumSquaredError += error * error; + } + + double meanSquaredError = sumSquaredError / predicted.size(); + return Math.sqrt(meanSquaredError); + } + + /** + * Calculate the RmsError of two arrays. + * + * @param target double array of target + * @param predicted double array of predicted + * @return rms Error + */ + public static double rmsError(double[] target, double[] predicted) { + if (predicted.length != target.length) { + throw new IllegalArgumentException("Input lists must have the same size"); + } + + double sumSquaredError = 0.0; + for (int i = 0; i < predicted.length; i++) { + double error = predicted[i] - target[i]; + sumSquaredError += error * error; + } + + double meanSquaredError = sumSquaredError / predicted.length; + return Math.sqrt(meanSquaredError); + } + + /** + * Calculates the Mean Squared Error (MSE) between the target and predicted + * values. MSE is a measure of the average squared differences between + * corresponding elements of two lists. + * + * @param target The list of target values. + * @param predicted The list of predicted values. + * @return The mean squared error between the target and predicted values. + * @throws IllegalArgumentException If the input lists have different sizes. + */ + public static double meanSquaredError(ArrayList target, ArrayList predicted) { + if (predicted.size() != target.size()) { + throw new IllegalArgumentException("Input lists must have the same size"); + } + + double sumSquaredError = 0.0; + for (int i = 0; i < predicted.size(); i++) { + double error = predicted.get(i) - target.get(i); + sumSquaredError += error * error; + } + + return sumSquaredError / predicted.size(); + } + + /** + * Calculates the accuracy between the target and predicted values within a + * specified allowed percentage difference. + * + * @param target The list of target values. + * @param predicted The list of predicted values. + * @param allowedPercentage The maximum allowed percentage difference for + * accuracy. + * @return The accuracy between the target and predicted values. + */ + public static double accuracy(ArrayList target, ArrayList predicted, double allowedPercentage) { + double count = 0; + + for (int i = 0; i < predicted.size(); i++) { + double diff = Math.abs(predicted.get(i) - target.get(i)) // + / Math.max(predicted.get(i), target.get(i)); + if (diff <= allowedPercentage) { + count++; + } + } + return (double) count / predicted.size(); + } + + /** + * Calculate the Accuracy of the predicted compared to target. + * + * @param target double array of target + * @param predicted double array of predicted + * @param allowedPercentage allowed percentage error + * @return accuracy + */ + public static double accuracy(double[] target, double[] predicted, double allowedPercentage) { + double count = 0; + + for (int i = 0; i < predicted.length; i++) { + double diff = Math.abs(predicted[i] - target[i]) // + / Math.max(predicted[i], target[i]); + if (diff <= allowedPercentage) { + count++; + } + } + return (double) count / predicted.length; + } + + /** + * Calculates the Mean Absolute Percentage Error (MAPE) between the target and + * predicted values. MAPE is a measure of the average percentage difference + * between corresponding elements of two lists. + * + * @param target The list of target values. + * @param predicted The list of predicted values. + * @return The mean absolute percentage error between the target and predicted + * values. + * @throws IllegalArgumentException If the input lists have different sizes. + */ + public static double meanAbslutePercentage(ArrayList target, ArrayList predicted) { + if (predicted.size() != target.size()) { + throw new IllegalArgumentException("Input lists must have the same size"); + } + + double sumPercentageError = 0.0; + for (int i = 0; i < predicted.size(); i++) { + double absoluteError = Math.abs(predicted.get(i) - target.get(i)); + double percentageError = absoluteError / target.get(i) * 100.0; + sumPercentageError += percentageError; + } + + return sumPercentageError / predicted.size(); + } + + /** + * Calculates the two-tailed p-value using the t-statistic for the differences + * between predicted and actual values. + * + * @param target The list of target values. + * @param predicted The list of predicted values. + * @return The two-tailed p-value for the differences between predicted and + * actual values. + * @throws IllegalArgumentException If the input lists have different sizes. + */ + public static double pvalue(ArrayList target, ArrayList predicted) { + if (predicted.size() != target.size()) { + throw new IllegalArgumentException("Input lists must have the same size."); + } + + List differences = new ArrayList<>(); + for (int i = 0; i < predicted.size(); i++) { + differences.add(predicted.get(i) - target.get(i)); + } + + double[] differencesArray = differences.stream()// + .mapToDouble(Double::doubleValue).toArray(); + double mean = StatUtils.mean(differencesArray); + double stdDev = Math.sqrt(StatUtils.variance(differencesArray)); + + // Calculate the t-statistic + double tStat = mean / (stdDev / Math.sqrt(predicted.size())); + + // Degrees of freedom + int degreesOfFreedom = predicted.size() - 1; + + // Create a T-distribution with the appropriate degrees of freedom + TDistribution tDistribution = new TDistribution(degreesOfFreedom); + + // Calculate the two-tailed p-value + double pValue = 2 * (1.0 - tDistribution.cumulativeProbability(Math.abs(tStat))); + + return pValue; + } + + /** + * Generates and prints a performance report containing various statistical + * metrics and error measures between the actual and predicted data. The report + * includes average, standard deviation, mean absolute error, RMS error, mean + * squared error, mean absolute percentage error, and accuracy with a specified + * error margin. Note: This method assumes that the necessary statistical + * methods (e.g., meanAbsoluteError, rmsError, meanSquaredError, + * meanAbslutePercentage, accuracy) are implemented in the same class. The + * p-value calculation is not included in the report by default. + */ + public void statusReport() { + System.out.println("\n.................. Performance Report ............................."); + + // Calculate and display statistics for actual data + double averageActual = DataStatistics.getMean(this.target); + double stdDevActual = DataStatistics.getStandardDeviation(this.target); + System.out.println("Average of actual data = " + averageActual); + System.out.println("Standard deviation of actual data = " + stdDevActual); + + // Calculate and display statistics for predicted data + double averagePredicted = DataStatistics.getMean(this.predicted); + double stdDevPredicted = DataStatistics.getStandardDeviation(this.predicted); + System.out.println("Average of prediction data = " + averagePredicted); + System.out.println("Standard deviation of predicted data = " + stdDevPredicted); + + // Display various error metrics + System.out.println("Mean absolute error = " + meanAbsoluteError(this.target, this.predicted) + + " (average absolute difference between predicted and actual values)"); + + System.out.println("RMS error = " + rmsError(this.target, this.predicted) + " (square root of the MSE)"); + + System.out.println("Mean squared error = " + meanSquaredError(this.target, this.predicted) + + " (average of the squared differences between predicted and actual values)"); + + System.out.println("Mean absolute percentage error = " + meanAbslutePercentage(this.target, this.predicted) + + " (measures the average percentage difference between predicted and actual values)"); + + // Display accuracy with the specified error margin + double accuracyPercentage = accuracy(this.target, this.predicted, this.allowedError) * 100; + + System.out.println("Accuracy for " + this.allowedError * 100 + "% error margin = " + accuracyPercentage + "%"); + + System.out.println(""); + } + +} diff --git a/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessing/DataModification.java b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessing/DataModification.java new file mode 100644 index 00000000000..47d107b9843 --- /dev/null +++ b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessing/DataModification.java @@ -0,0 +1,725 @@ +package io.openems.edge.predictor.lstm.preprocessing; + +import static io.openems.edge.predictor.lstm.common.DataStatistics.getMean; +import static io.openems.edge.predictor.lstm.common.DataStatistics.getStandardDeviation; +import static java.util.Arrays.stream; +import static java.util.stream.Collectors.toCollection; +import static java.util.stream.IntStream.range; + +import java.time.OffsetDateTime; +import java.util.ArrayList; +import java.util.List; + +import io.openems.edge.predictor.lstm.common.HyperParameters; + +public class DataModification { + + private static final double MIN_SCALED = 0.2; + private static final double MAX_SCALED = 0.8; + + /** + * Scales a list of numeric data values to a specified range. This method scales + * a list of numeric data values to a specified range defined by the minimum + * (min) and maximum (max) values. The scaled data will be within the range + * defined by the minimumScaled (minScaled) and maximumScaled (maxScaled) + * values. + * + * @param data The list of numeric data values to be scaled. + * @param min The original minimum value in the data. + * @param max The original maximum value in the data. + * @return A new list containing the scaled data within the specified range. + */ + public static ArrayList scale(ArrayList data, double min, double max) { + return data.stream()// + .map(value -> MIN_SCALED + ((value - min) / (max - min)) * (MAX_SCALED - MIN_SCALED)) + .collect(toCollection(ArrayList::new)); + } + + /** + * * Scales a list of numeric data values to a specified range. This method + * scales a list of numeric data values to a specified range defined by the + * minimum (min) and maximum (max) values. The scaled data will be within the + * range defined by the minimumScaled (minScaled) and maximumScaled (maxScaled) + * values. + * + * @param data The array of numeric data values to be scaled. + * @param min The original minimum value in the data. + * @param max The original maximum value in the data. + * @return A new list containing the scaled data within the specified range. + */ + public static double[] scale(double[] data, double min, double max) { + return stream(data)// + .map(value -> MIN_SCALED + ((value - min) / (max - min)) * (MAX_SCALED - MIN_SCALED))// + .toArray(); + } + + /** + * Re-scales a single data point from the scaled range to the original range. + * This method re-scales a single data point from the scaled range (defined by + * 'minScaled' and 'maxScaled') back to the original range, which is specified + * by 'minOriginal' and 'maxOriginal'. It performs the reverse scaling operation + * for a single data value. + * + * @param scaledData The data point to be rescaled from the scaled range to the + * original range. + * @param minOriginal The minimum value of the training dataset (original data + * range). + * @param maxOriginal The maximum value of the training dataset (original data + * range). + * @return The rescaled data point in the original range. + */ + public static double scaleBack(double scaledData, double minOriginal, double maxOriginal) { + return calculateScale(scaledData, MIN_SCALED, MAX_SCALED, minOriginal, maxOriginal); + } + + /** + * Scales back a list of double values from a scaled range to the original + * range. This method takes a list of scaled values and scales them back to + * their original range based on the specified minimum and maximum values of the + * original range. + * + * @param data The list of double values to be scaled back. + * @param minOriginal The minimum value of the original range. + * @param maxOriginal The maximum value of the original range. + * @return A new ArrayList containing the scaled back values. + */ + public static ArrayList scaleBack(ArrayList data, double minOriginal, double maxOriginal) { + return data.stream()// + .map(value -> calculateScale(value, MIN_SCALED, MAX_SCALED, minOriginal, maxOriginal))// + .collect(toCollection(ArrayList::new)); + } + + /** + * * Scales back a list of double values from a scaled range to the original + * range. This method takes a list of scaled values and scales them back to + * their original range based on the specified minimum and maximum values of the + * original range. + * + * @param data The list of double values to be scaled back. + * @param minOriginal The minimum value of the original range. + * @param maxOriginal The maximum value of the original range. + * @return A new ArrayList containing the scaled back values. + */ + public static double[] scaleBack(double[] data, double minOriginal, double maxOriginal) { + return stream(data)// + .map(value -> calculateScale(value, MIN_SCALED, MAX_SCALED, minOriginal, maxOriginal))// + .toArray(); + } + + /** + * Scales a value from a scaled range back to the original range. + * + * @param valScaled The value in the scaled range to be converted back to the + * original range. + * @param minScaled The minimum value of the scaled range. + * @param maxScaled The maximum value of the scaled range. + * @param minOriginal The minimum value of the original range. + * @param maxOriginal The maximum value of the original range. + * @return The value converted back to the original range. + */ + private static double calculateScale(double valScaled, double minScaled, double maxScaled, double minOriginal, + double maxOriginal) { + return ((valScaled - minScaled) * (maxOriginal - minOriginal) / (maxScaled - minScaled)// + ) + minOriginal; + } + + /** + * Normalize a 2D array of data using standardization (z-score normalization). + * This method normalizes a 2D array of data by applying standardization + * (z-score normalization) to each row independently. The result is a new 2D + * array of normalized data. + * + * @param data The 2D array of data to be normalized. + * @param hyperParameters instance of class HyperParameters + * @return A new 2D array containing the standardized (normalized) data. + */ + public static double[][] normalizeData(double[][] data, HyperParameters hyperParameters) { + var standData = new double[data.length][data[0].length];// Here error + for (int i = 0; i < data.length; i++) { + standData[i] = standardize(data[i], hyperParameters); + } + return standData; + } + + /** + * Normalizes the data based on the given target values, using standardization. + * This method calculates the standardization of each data point in the input + * data array with respect to the corresponding target value. It utilizes the + * mean and standard deviation of the input data array to perform the + * standardization. + * + * @param data The input data array containing the features to be + * normalized. + * @param target The target values to which the data will be + * standardized. + * @param hyperParameters The {@link HyperParameters} required for + * normalization. + * @return A double array containing the normalized data. + */ + + public static double[] normalizeData(double[][] data, double[] target, HyperParameters hyperParameters) { + var standData = new double[target.length]; + for (int i = 0; i < data.length; i++) { + standData[i] = standardize(target[i], getMean(data[i]), getStandardDeviation(data[i]), hyperParameters); + } + return standData; + } + + /** + * Standardizes a 1D array of data using Z-score normalization. This method + * standardizes a 1D array of data by applying Z-score normalization. It + * calculates the mean and standard deviation of the input data and then + * standardizes each data point. + * + * @param inputData The 1D array of data to be standardized. + * @param hyperParameters instance of {@link HyperParameters} + * @return A new 1D array containing the standardized (normalized) data. + */ + public static double[] standardize(double[] inputData, HyperParameters hyperParameters) { + double meanCurrent = getMean(inputData); + + double stdDeviationCurrent = getStandardDeviation(inputData); + double meanTarget = hyperParameters.getMean(); + double stdDeviationTarget = hyperParameters.getStandardDeviation(); + + double[] standardizedData = new double[inputData.length]; + for (int i = 0; i < inputData.length; i++) { + standardizedData[i] = meanTarget + + ((inputData[i] - meanCurrent) * (stdDeviationTarget / stdDeviationCurrent)); + } + return standardizedData; + } + + /** + * Standardizes a given input data point using mean and standard deviation. This + * method standardizes the input data point based on the provided mean and + * standard deviation of the current data and the target mean and standard + * deviation specified in the {@link HyperParameters}. + * + * @param inputData The input data point to be standardized. + * @param mean The mean of the current data. + * @param standerdDev The standard deviation of the current data. + * @param hyperParameters The {@link HyperParameters} containing the target mean + * and standard deviation. + * @return The standardized value of the input data point. + */ + public static double standardize(double inputData, double mean, double standerdDev, + HyperParameters hyperParameters) { + double meanCurrent = mean; + + double stdDeviationCurrent = standerdDev; + double meanTarget = hyperParameters.getMean(); + double stdDeviationTarget = hyperParameters.getStandardDeviation(); + return meanTarget + ((inputData - meanCurrent) * (stdDeviationTarget / stdDeviationCurrent)); + } + + /** + * Reverse standardizes a data point that was previously standardized using + * Z-score normalization. This method reverses the standardization process for a + * single data point that was previously standardized using Z-score + * normalization. It requires the mean and standard deviation of the original + * data along with the Z-score value (zvalue) to perform the reverse + * standardization. + * + * @param mean The mean of the original data. + * @param standardDeviation The standard deviation of the original data. + * @param zvalue The Z-score value of the standardized data point. + * @param hyperParameters instance of {@link HyperParameters} + * @return The reverse standardized value in the original data's scale. + */ + public static double reverseStandrize(double zvalue, double mean, double standardDeviation, + HyperParameters hyperParameters) { + double reverseStand = 0; + double meanTarget = hyperParameters.getMean(); + double standardDeviationTarget = hyperParameters.getStandardDeviation(); + + reverseStand = ((zvalue - meanTarget) * (standardDeviation / standardDeviationTarget) + mean); + return reverseStand; + } + + /** + * Reverse standardizes a list of data points based on given mean, standard + * deviation, and {@link HyperParameters}. This method reverse standardizes each + * data point in the input list based on the provided mean, standard deviation, + * and {@link HyperParameters}. It returns a new Array containing the reverse + * standardized values. + * + * @param data The list of data points to be reverse standardized. + * @param mean The list of means corresponding to the data points. + * @param standDeviation The list of standard deviations corresponding to the + * data points. + * @param hyperParameters The {@link HyperParameters} containing the target mean + * and standard deviation. + * @return A new list containing the reverse standardized values. + */ + public static double[] reverseStandrize(ArrayList data, ArrayList mean, + ArrayList standDeviation, HyperParameters hyperParameters) { + var revNorm = new double[data.size()]; + for (int i = 0; i < data.size(); i++) { + revNorm[i] = (reverseStandrize(data.get(i), mean.get(i), standDeviation.get(i), hyperParameters)); + } + return revNorm; + } + + /** + * Reverse standardizes a list of data points based on given mean, standard + * deviation, and {@link HyperParameters}. This method reverse standardizes each + * data point in the input list based on the provided mean, standard deviation, + * and {@link HyperParameters}. It returns a new list containing the reverse + * standardized values. + * + * @param data The Array of data points to be reverse standardized. + * @param mean The Array of means corresponding to the data points. + * @param standDeviation The Array of standard deviations corresponding to the + * data points. + * @param hyperParameters The {@link HyperParameters} containing the target mean + * and standard deviation. + * @return A new Array containing the reverse standardized values. + */ + public static double[] reverseStandrize(double[] data, double[] mean, double[] standDeviation, + HyperParameters hyperParameters) { + var revNorm = new double[data.length]; + for (int i = 0; i < data.length; i++) { + revNorm[i] = (reverseStandrize(data[i], mean[i], standDeviation[i], hyperParameters)); + } + return revNorm; + } + + /** + * Reverse standardizes a list of data points based on given mean, standard + * deviation, and {@link HyperParameters}. This method reverse standardizes each + * data point in the input list based on the provided mean, standard deviation, + * and {@link HyperParameters}. It returns a new Array containing the reverse + * standardized values. + * + * @param data The Array of data points to be reverse standardized. + * @param mean The mean corresponding to the data points. + * @param standDeviation The standard deviation corresponding to the data + * points. + * @param hyperParameters The {@link HyperParameters} containing the target mean + * and standard deviation. + * @return A new Array containing the reverse standardized values. + */ + public static double[] reverseStandrize(ArrayList data, double mean, double standDeviation, + HyperParameters hyperParameters) { + var revNorm = new double[data.size()]; + for (int i = 0; i < data.size(); i++) { + revNorm[i] = (reverseStandrize(data.get(i), mean, standDeviation, hyperParameters)); + } + return revNorm; + } + + /** + * Reverse standardizes a list of data points based on given mean, standard + * deviation, and {@link HyperParameters}. This method reverse standardizes each + * data point in the input list based on the provided mean, standard deviation, + * and {@link HyperParameters}. It returns a new list containing the reverse + * standardized values. + * + * @param data The list of data points to be reverse standardized. + * @param mean The mean corresponding to the data points. + * @param standDeviation The standard deviation corresponding to the data + * points. + * @param hyperParameters The {@link HyperParameters} containing the target mean + * and standard deviation. + * @return A new list containing the reverse standardized values. + */ + public static double[] reverseStandrize(double[] data, double mean, double standDeviation, + HyperParameters hyperParameters) { + var revNorm = new double[data.length]; + for (int i = 0; i < data.length; i++) { + revNorm[i] = (reverseStandrize(data[i], mean, standDeviation, hyperParameters)); + } + return revNorm; + } + + /** + * Modifies the given time-series data for long-term prediction by grouping it + * based on hours and minutes. + * + * @param data The {@link ArrayList} of Double values representing the + * time-series data. + * @param date The {@link ArrayList} of OffsetDateTime objects corresponding to + * the timestamps of the data. + * @return An {@link ArrayList} of {@link ArrayList} of {@link ArrayList}, + * representing the modified data grouped by hours and minutes. + */ + + public static ArrayList>> groupDataByHourAndMinute(ArrayList data, + ArrayList date) { + var dataGroupedByMinute = new ArrayList>>(); + var dateGroupedByMinute = new ArrayList>>(); + + var groupByHour = new GroupBy(data, date); + groupByHour.hour(); + + for (int i = 0; i < groupByHour.getGroupedDataByHour().size(); i++) { + GroupBy groupByMinute = new GroupBy(groupByHour.getGroupedDataByHour().get(i), + groupByHour.getGroupedDateByHour().get(i)); + + groupByMinute.minute(); + dataGroupedByMinute.add(groupByMinute.getGroupedDataByMinute()); + dateGroupedByMinute.add(groupByMinute.getGroupedDateByMinute()); + } + return dataGroupedByMinute; + } + + /** + * Modify the data for trend term prediction. + * + * @param data The ArrayList of Double values data. + * @param date The ArrayList of Double values date. + * @param hyperParameters The {@link HyperParameters} + * @return The ArrayList of modified values + */ + public static ArrayList> modifyFortrendPrediction(ArrayList data, + ArrayList date, HyperParameters hyperParameters) { + var firstModification = groupDataByHourAndMinute(data, date); + + // Flatten the structure of the first modification + var secondModification = flatten3dto2d(firstModification); + + // Apply windowing to create the third modification + var thirdModification = applyWindowing(secondModification, hyperParameters); + + return thirdModification; + } + + private static ArrayList> flatten3dto2d(// + ArrayList>> data) { + return data.stream()// + .flatMap(twoDList -> twoDList.stream())// + .collect(toCollection(ArrayList::new)); + } + + /** + * Decreases the dimensionality of a 4D ArrayList to a 3D ArrayList. This method + * flattens the input 4D ArrayList to a 3D ArrayList by merging the innermost + * ArrayLists into one. It returns the resulting 3D ArrayList. + * + * @param model The 4D ArrayList to decrease in dimensionality. + * @return The resulting 3D ArrayList after decreasing the dimensionality. + */ + public static ArrayList>> flattern4dto3d( + ArrayList>>> model) { + return model.stream()// + .flatMap(threeDList -> threeDList.stream())// + .collect(toCollection(ArrayList::new)); + } + + private static ArrayList> applyWindowing(ArrayList> data, + HyperParameters hyperParameters) { + ArrayList> windowedData = new ArrayList<>(); + int windowSize = hyperParameters.getWindowSizeTrend(); + + for (int i = 0; i < data.size(); i++) { + ArrayList> toCombine = new ArrayList<>(); + + for (int j = 0; j <= windowSize; j++) { + int index = (j + i) % data.size(); + toCombine.add(data.get(index)); + } + windowedData.add(combinedArray(toCombine)); + } + return windowedData; + } + + /** + * Flatten the array by combining. + * + * @param values The ArrayList of Double values. + * @return reGroupedsecond Teh Flattened ArrayList + */ + public static ArrayList combinedArray(ArrayList> values) { + var minSize = values.stream()// + .mapToInt(ArrayList::size)// + .min()// + .orElse(0); + var reGroupedsecond = new ArrayList(); + + for (int i = 0; i < minSize; i++) { + for (ArrayList innerList : values) { + reGroupedsecond.add(innerList.get(i)); + } + } + return reGroupedsecond; + } + + /** + * Splits a list of Double values into multiple batches and returns the batches. + * The method divides the original list into a specified number of groups, + * ensuring that each group has an approximately equal number of elements. It + * handles any remainder by distributing the extra elements among the first few + * groups. + * + * @param originalList The original list of Double values to be split into + * batches. + * @param numberOfGroups The desired number of groups to split the list into. + * @return An ArrayList of ArrayLists, where each inner ArrayList represents a + * batch of Double values. + */ + public static ArrayList> getDataInBatch(ArrayList originalList, int numberOfGroups) { + ArrayList> splitGroups = new ArrayList<>(); + var originalSize = originalList.size(); + var groupSize = originalSize / numberOfGroups; + var remainder = originalSize % numberOfGroups; + + var currentIndex = 0; + for (int i = 0; i < numberOfGroups; i++) { + var groupCount = groupSize + (i < remainder ? 1 : 0); + var group = new ArrayList<>(originalList.subList(currentIndex, currentIndex + groupCount)); + splitGroups.add(group); + currentIndex += groupCount; + } + return splitGroups; + } + + /** + * Splits a list of OffsetDateTime into multiple batches and returns the + * batches. The method divides the original list into a specified number of + * groups, ensuring that each group has an approximately equal number of + * elements. It handles any remainder by distributing the extra elements among + * the first few groups. + * + * @param originalList The original list of OffsetDateTime to be split into + * batches. + * @param numberOfGroups The desired number of groups to split the list into. + * @return An ArrayList of ArrayLists, where each inner ArrayList represents a + * batch of OffsetDateTime objects. + */ + public static ArrayList> getDateInBatch(ArrayList originalList, + int numberOfGroups) { + var splitGroups = new ArrayList>(); + var originalSize = originalList.size(); + var groupSize = originalSize / numberOfGroups; + var remainder = originalSize % numberOfGroups; + + var currentIndex = 0; + for (int i = 0; i < numberOfGroups; i++) { + var groupCount = groupSize + (i < remainder ? 1 : 0); + var group = new ArrayList(originalList.subList(currentIndex, currentIndex + groupCount)); + splitGroups.add(group); + currentIndex += groupCount; + } + + return splitGroups; + } + + /** + * Removes negative values from the given ArrayList of Doubles by replacing them + * with 0. + * + * @param data The ArrayList of Doubles containing numeric values. + * @return ArrayList<Double> A new ArrayList<Double> with negative + * values replaced by zero. + */ + public static ArrayList removeNegatives(ArrayList data) { + return data.stream()// + // Replace negative values with 0 + .map(value -> value == null || Double.isNaN(value) ? Double.NaN : Math.max(value, 0)) + .collect(toCollection(ArrayList::new)); + + } + + /** + * Replaces all negative values in the input array with 0. NaN values in the + * array remain unchanged. + * + * @param data the input array of doubles + * @return a new array with negative values replaced by 0 + */ + public static double[] removeNegatives(double[] data) { + return stream(data)// + .map(value -> Double.isNaN(value) ? Double.NaN : Math.max(value, 0))// + .toArray(); + } + + /** + * Scales each element in the input ArrayList by a specified scaling factor. + * + * @param data The ArrayList of Double values to be scaled. + * @param scalingFactor The factor by which each element in the data ArrayList + * will be multiplied. + * @return A new ArrayList containing the scaled values. + */ + public static ArrayList constantScaling(ArrayList data, double scalingFactor) { + return data.stream() // + .map(val -> val * scalingFactor) // + .collect(toCollection(ArrayList::new)); + } + + /** + * Scales each element in the input ArrayList by a specified scaling factor. + * + * @param data The Array of Double values to be scaled. + * @param scalingFactor The factor by which each element in the data Array will + * be multiplied. + * @return A new Array containing the scaled values. + */ + public static double[] constantScaling(double[] data, double scalingFactor) { + return stream(data) // + .map(val -> val * scalingFactor) // + .toArray(); + } + + /** + * Reshapes a 3D ArrayList into a 4D ArrayList structure. This method takes a + * three-dimensional ArrayList of data and reshapes it into a four-dimensional + * ArrayList structure. The reshaping is performed by dividing the original data + * into blocks of size 4x24. The resulting four-dimensional ArrayList contains + * these blocks. + * + * + * @param dataList The 3D list to be reshaped. + * @param hyperParameters The hyperparameters containing the interval used to + * reshape the list. + * @return A reshaped 4D list. + */ + public static ArrayList>>> reshape( + ArrayList>> dataList, HyperParameters hyperParameters) { + // Calculate the dimensions for reshaping + var rowsPerDay = 60 / hyperParameters.getInterval() * 24; + var numDays = dataList.size() / rowsPerDay; + + // Initialize the reshaped 4D list + var reshapedData = new ArrayList>>>(); + + var dataIndex = 0; + for (int day = 0; day < numDays; day++) { + var dailyData = new ArrayList>>(); + for (int row = 0; row < rowsPerDay; row++) { + dailyData.add(dataList.get(dataIndex)); + dataIndex++; + } + reshapedData.add(dailyData); + } + + return reshapedData; + } + + /** + * Updates the model with the specified weights based on the given indices and + * model type. This method extracts the optimum weights from the provided 4D + * ArrayList of models using the given indices and model type. It updates the + * hyperparameters with the extracted weights based on the model type. + * + * @param allModel The 4D ArrayList containing all models. + * @param indices The list of indices specifying the location of optimum + * weights in the models. + * @param fileName The name of the file to save the final model. + * @param modelType The type of the model ("trend.txt" or + * "seasonality.txt"). + * @param hyperParameters The hyperparameters to update with the extracted + * weights. + */ + public static void updateModel(ArrayList>>> allModel, // + List> indices, // + String fileName, // + String modelType, // + HyperParameters hyperParameters) { + var optimumWeights = new ArrayList>>(); + + for (List idx : indices) { + var tempWeights = allModel // + .get(idx.get(0)) // + .get(idx.get(1)); + optimumWeights.add(tempWeights); + } + + switch (modelType.toLowerCase()) { + case "trend": + hyperParameters.updatModelTrend(optimumWeights); + break; + case "seasonality": + hyperParameters.updateModelSeasonality(optimumWeights); + break; + default: + throw new IllegalArgumentException("Invalid model type: " + modelType); + } + } + + /** + * Performs element-wise multiplication of two arrays. + * + * @param featureA the first array + * @param featureB the second array + * @return a new array where each element is the product of the corresponding + * elements of featureA and featureB + * @throws IllegalArgumentException if the input arrays are of different lengths + */ + public static double[] elementWiseMultiplication(double[] featureA, double[] featureB) { + if (featureA.length != featureB.length) { + throw new IllegalArgumentException("The input arrays must have the same length."); + } + return range(0, featureA.length)// + .mapToDouble(i -> featureA[i] * featureB[i])// + .toArray(); + } + + /** + * Performs element-wise multiplication of two ArrayLists. + * + * @param featureA the first ArrayList + * @param featureB the second ArrayList + * @return a new ArrayList where each element is the result of multiplying the + * corresponding elements of featureA and featureB + * @throws IllegalArgumentException if the input ArrayLists are of different + * lengths + */ + public static ArrayList elementWiseMultiplication(ArrayList featureA, ArrayList featureB) { + if (featureA.size() != featureB.size()) { + throw new IllegalArgumentException("The input ArrayLists must have the same length."); + } + var result = new ArrayList(); + range(0, featureA.size()) // + .forEach(i -> result.add(featureA.get(i) * featureB.get(i))); + return result; + } + + /** + * Performs element-wise division of two ArrayLists. If an element in featureB + * is zero, the corresponding element in the result will be zero. + * + * @param featureA the first ArrayList + * @param featureB the second ArrayList + * @return a new ArrayList where each element is the result of dividing the + * corresponding elements of featureA by featureB or zero if the element + * in featureB is zero + * @throws IllegalArgumentException if the input ArrayLists are of different + * lengths + */ + public static ArrayList elementWiseDiv(ArrayList featureA, ArrayList featureB) { + if (featureA.size() != featureB.size()) { + throw new IllegalArgumentException("The input ArrayLists must have the same length."); + } + var result = new ArrayList(); + range(0, featureA.size()) // + .forEach(i -> result.add(featureB.get(i) == 0 // + ? featureA.get(i) // + : featureA.get(i) / featureB.get(i))); + return result; + } + + /** + * Performs element-wise division of two arrays. If an element in featureB is + * zero, the corresponding element in the result will be zero. + * + * @param featureA the first array + * @param featureB the second array + * @return a new array where each element is the result of dividing the + * corresponding elements of featureA by featureB or zero if the element + * in featureB is zero + * @throws IllegalArgumentException if the input arrays are of different lengths + */ + public static double[] elementWiseDiv(double[] featureA, double[] featureB) { + if (featureA.length != featureB.length) { + throw new IllegalArgumentException("The input arrays must have the same length."); + } + return range(0, featureA.length)// + .mapToDouble(i -> featureB[i] == 0 // + ? featureA[i] // + : featureA[i] / featureB[i])// + .toArray(); + } + +} diff --git a/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessing/Differencing.java b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessing/Differencing.java new file mode 100644 index 00000000000..e774cb42763 --- /dev/null +++ b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessing/Differencing.java @@ -0,0 +1,54 @@ +package io.openems.edge.predictor.lstm.preprocessing; + +import static java.util.stream.IntStream.range; + +public class Differencing { + + /** + * First order Differencing. + * + * @param data data for Differencing + * @return the first order Differencing + */ + public static double[] firstOrderDifferencing(double[] data) { + if (data.length < 2) { + throw new IllegalArgumentException("Data array must contain at least two elements."); + } + + return range(0, data.length - 1)// + .mapToDouble(i -> data[i] - data[i + 1])// + .toArray(); + } + + /** + * first Order Accumulating. + * + * @param data data for Differencing + * @param init data for init + * @return the first order Differencing + */ + public static double[] firstOrderAccumulating(double[] data, double init) { + if (data.length == 0) { + throw new IllegalArgumentException("Data array must not be empty."); + } + + var accumulating = new double[data.length]; + accumulating[0] = data[0] + init; + + range(1, data.length)// + .forEach(i -> accumulating[i] = accumulating[i - 1] + data[i]); + + return accumulating; + } + + /** + * first Order Accumulating. + * + * @param data data for Differencing + * @param init data for init + * @return the first order Differencing + */ + public static double firstOrderAccumulating(double data, double init) { + return data + init; + } +} diff --git a/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessing/FilterOutliers.java b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessing/FilterOutliers.java new file mode 100644 index 00000000000..20b5f80f9c5 --- /dev/null +++ b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessing/FilterOutliers.java @@ -0,0 +1,96 @@ +package io.openems.edge.predictor.lstm.preprocessing; + +import static java.util.stream.IntStream.range; + +import java.util.ArrayList; +import java.util.Arrays; + +import org.apache.commons.math3.stat.descriptive.rank.Percentile; + +import io.openems.edge.predictor.lstm.utilities.MathUtils; + +public class FilterOutliers { + + /** + * Filters out outliers from the dataset until no outliers are detected. + * + * @param data the input dataset + * @return the filtered dataset with outliers removed + */ + public static double[] filterOutlier(double[] data) { + if (data == null || data.length == 0) { + throw new IllegalArgumentException("Input data must not be null or empty."); + } + + var filteredData = Arrays.copyOf(data, data.length); + var iterationCount = 0; + var hasOutliers = true; + + while (hasOutliers && iterationCount <= 100) { + var outlierIndices = detect(filteredData); + + if (outlierIndices.isEmpty()) { + hasOutliers = false; + } else { + filteredData = filter(filteredData, outlierIndices); + } + + iterationCount++; + } + + return filteredData; + } + + /** + * Applies the hyperbolic tangent function to data points at the specified + * indices. + * + * @param data the input dataset + * @param indices the indices of data points to be transformed + * @return the transformed dataset + */ + public static double[] filter(double[] data, ArrayList indices) { + if (data == null || indices == null) { + throw new IllegalArgumentException("Input data and indices must not be null."); + } + + if (indices.isEmpty()) { + return data; + } + + double[] result = data.clone(); + for (int index : indices) { + if (index >= 0 && index < result.length) { + result[index] = MathUtils.tanh(result[index]); + } else { + throw new IllegalArgumentException("Index out of bounds: " + index); + } + } + return result; + } + + /** + * Detects outliers in the dataset using the interquartile range (IQR) method. + * + * @param data the input dataset + * @return a list of indices of the detected outliers + */ + public static ArrayList detect(double[] data) { + if (data == null || data.length == 0) { + throw new IllegalArgumentException("Input data must not be null or empty."); + } + + var perc = new Percentile(); + var q1 = perc.evaluate(data, 25); // 25th percentile (Q1) (First percentile) + var q3 = perc.evaluate(data, 75); // 75th percentile (Q3) (Third percentile) + var iqr = q3 - q1; + var upperLimit = q3 + 1.5 * iqr; + var lowerLimit = q1 - 1.5 * iqr; + + // Detect outliers + return range(0, data.length)// + .filter(i -> data[i] < lowerLimit || data[i] > upperLimit) + .collect(ArrayList::new, ArrayList::add, ArrayList::addAll); + } + +} diff --git a/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessing/GroupBy.java b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessing/GroupBy.java new file mode 100644 index 00000000000..91d6ec2403a --- /dev/null +++ b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessing/GroupBy.java @@ -0,0 +1,99 @@ +package io.openems.edge.predictor.lstm.preprocessing; + +import static java.util.stream.Collectors.toList; +import static java.util.stream.IntStream.range; + +import java.time.OffsetDateTime; +import java.time.temporal.ChronoField; +import java.util.ArrayList; +import java.util.List; + +public class GroupBy { + + private final ArrayList data; + private final ArrayList date; + + private final ArrayList> groupedDateByMin = new ArrayList<>(); + private final ArrayList> groupedDataByMin = new ArrayList<>(); + private final ArrayList> groupedDateByHour = new ArrayList<>(); + private final ArrayList> groupedDataByHour = new ArrayList<>(); + + /** + * Group by Temporal filed. + * + * @param chronoField {@link ChronoField} + * @param groupedDateList The list of groupedDateList. + * @param groupedDataList The list of groupedDataList. + */ + public void groupByTemporalField(ChronoField chronoField, List> groupedDateList, + List> groupedDataList) { + var uniqueList = this.extractUniqueAndSortedValues(chronoField); + + for (var uniqueValue : uniqueList) { + var groupedDateTemp = this.groupDatesByUniqueValue(uniqueValue, chronoField); + var groupedDataTemp = this.groupDataByUniqueValue(uniqueValue, chronoField); + + groupedDateList.add(new ArrayList<>(groupedDateTemp)); + groupedDataList.add(new ArrayList<>(groupedDataTemp)); + } + } + + private List extractUniqueAndSortedValues(ChronoField chronoField) { + return this.date.stream()// + .map(date -> date.get(chronoField)) // + .distinct() // + .sorted() // + .collect(toList()); + } + + private List groupDatesByUniqueValue(Integer uniqueValue, ChronoField chronoField) { + return this.date.stream()// + .filter(date -> uniqueValue.equals(date.get(chronoField)))// + .collect(toList()); + } + + private List groupDataByUniqueValue(Integer uniqueValue, ChronoField chronoField) { + return range(0, this.data.size())// + .filter(i -> { + double dateValue = this.date.get(i).get(chronoField); + return Double.compare(dateValue, uniqueValue.doubleValue()) == 0; + }) // + .mapToObj(i -> this.data.get(i)) // + .collect(toList()); + } + + /** + * grouping by hour. + */ + public void hour() { + this.groupByTemporalField(ChronoField.HOUR_OF_DAY, this.groupedDateByHour, this.groupedDataByHour); + } + + /** + * grouping by minute. + */ + public void minute() { + this.groupByTemporalField(ChronoField.MINUTE_OF_HOUR, this.groupedDateByMin, this.groupedDataByMin); + } + + public ArrayList> getGroupedDataByHour() { + return this.groupedDataByHour; + } + + public ArrayList> getGroupedDateByHour() { + return this.groupedDateByHour; + } + + public ArrayList> getGroupedDataByMinute() { + return this.groupedDataByMin; + } + + public ArrayList> getGroupedDateByMinute() { + return this.groupedDateByMin; + } + + public GroupBy(ArrayList data, List date) { + this.data = new ArrayList<>(data); + this.date = new ArrayList<>(date); + } +} \ No newline at end of file diff --git a/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessing/MovingAverage.java b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessing/MovingAverage.java new file mode 100644 index 00000000000..16929491761 --- /dev/null +++ b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessing/MovingAverage.java @@ -0,0 +1,29 @@ +package io.openems.edge.predictor.lstm.preprocessing; + +public class MovingAverage { + + public static final int WINDOW_SIZE = 3; + + /** + * Compute the Moving average for the data array. + * + * @param data the data for calculating the Moving average + * @return the moving average + */ + public static double[] movingAverage(double[] data) { + var paddedInputData = new double[data.length + WINDOW_SIZE - 1]; + System.arraycopy(data, 0, paddedInputData, WINDOW_SIZE / 2, data.length); + + var movingAverages = new double[data.length]; + + for (int i = 0; i < data.length; i++) { + double sum = 0; + for (int j = 0; j < WINDOW_SIZE; j++) { + sum += paddedInputData[i + j]; + } + movingAverages[i] = sum / WINDOW_SIZE; + } + + return movingAverages; + } +} diff --git a/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessing/Shuffle.java b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessing/Shuffle.java new file mode 100644 index 00000000000..70addcbbb31 --- /dev/null +++ b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessing/Shuffle.java @@ -0,0 +1,69 @@ +package io.openems.edge.predictor.lstm.preprocessing; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Random; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +public class Shuffle { + + private double[][] data; + private double[] target; + + public Shuffle(double[][] data, double[] target) { + this.data = this.copy2DArray(data); + this.target = Arrays.copyOf(target, target.length); + this.shuffleIt(); + } + + /** + * Shuffles the data and target arrays to randomize the order of elements. This + * method shuffles the data and target arrays simultaneously, ensuring that the + * corresponding data and target values remain aligned. + */ + public void shuffleIt() { + List indices = IntStream.range(0, this.data.length)// + .boxed()// + .collect(Collectors.toList()); + + Collections.shuffle(indices, new Random(100)); + + CompletableFuture dataFuture = CompletableFuture + .runAsync(() -> this.shuffleData(new ArrayList<>(indices))); + CompletableFuture targetFuture = CompletableFuture + .runAsync(() -> this.shuffleTarget(new ArrayList<>(indices))); + + CompletableFuture combinedFuture = CompletableFuture.allOf(dataFuture, targetFuture); + combinedFuture.join(); + } + + private void shuffleData(List indices) { + this.data = indices.stream()// + .map(i -> Arrays.copyOf(this.data[i], this.data[i].length))// + .toArray(double[][]::new); + } + + private void shuffleTarget(List indices) { + this.target = indices.stream()// + .mapToDouble(i -> this.target[i])// + .toArray(); + } + + public double[] getTarget() { + return this.target; + } + + public double[][] getData() { + return this.data; + } + + private double[][] copy2DArray(double[][] array) { + return Arrays.stream(array)// + .map(row -> Arrays.copyOf(row, row.length))// + .toArray(double[][]::new); + } +} diff --git a/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessingpipeline/ConstantScalingPipe.java b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessingpipeline/ConstantScalingPipe.java new file mode 100644 index 00000000000..49e6b6147a4 --- /dev/null +++ b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessingpipeline/ConstantScalingPipe.java @@ -0,0 +1,19 @@ +package io.openems.edge.predictor.lstm.preprocessingpipeline; + +import static io.openems.edge.predictor.lstm.preprocessing.DataModification.constantScaling; + +public class ConstantScalingPipe implements Stage { + + private double scalingFactor; + + public ConstantScalingPipe(double factor) { + this.scalingFactor = factor; + } + + @Override + public Object execute(Object input) { + return (input instanceof double[] in) // + ? constantScaling(in, this.scalingFactor) // + : new IllegalArgumentException("Input must be an instance of double[]"); + } +} \ No newline at end of file diff --git a/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessingpipeline/DifferencingPipe.java b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessingpipeline/DifferencingPipe.java new file mode 100644 index 00000000000..eda1d0963cf --- /dev/null +++ b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessingpipeline/DifferencingPipe.java @@ -0,0 +1,13 @@ +package io.openems.edge.predictor.lstm.preprocessingpipeline; + +import io.openems.edge.predictor.lstm.preprocessing.Differencing; + +public class DifferencingPipe implements Stage { + + @Override + public Object execute(Object input) { + return (input instanceof double[] in) // + ? Differencing.firstOrderDifferencing(in) // + : new IllegalArgumentException("Input must be an instance of double[]"); + } +} diff --git a/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessingpipeline/FilterOutliersPipe.java b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessingpipeline/FilterOutliersPipe.java new file mode 100644 index 00000000000..e6ec17f578a --- /dev/null +++ b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessingpipeline/FilterOutliersPipe.java @@ -0,0 +1,13 @@ +package io.openems.edge.predictor.lstm.preprocessingpipeline; + +import io.openems.edge.predictor.lstm.preprocessing.FilterOutliers; + +public class FilterOutliersPipe implements Stage { + + @Override + public Object execute(Object input) { + return (input instanceof double[] in) // + ? FilterOutliers.filterOutlier(in) // + : new IllegalArgumentException("Input must be an instance of double[]"); + } +} diff --git a/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessingpipeline/GroupToStiffWindowPipe.java b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessingpipeline/GroupToStiffWindowPipe.java new file mode 100644 index 00000000000..2c026921590 --- /dev/null +++ b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessingpipeline/GroupToStiffWindowPipe.java @@ -0,0 +1,118 @@ +package io.openems.edge.predictor.lstm.preprocessingpipeline; + +import static io.openems.edge.predictor.lstm.utilities.UtilityConversion.to1DArray; +import static io.openems.edge.predictor.lstm.utilities.UtilityConversion.to1DArrayList; +import static io.openems.edge.predictor.lstm.utilities.UtilityConversion.to2DArray; +import static java.util.stream.IntStream.range; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public class GroupToStiffWindowPipe implements Stage { + private int window; + + public GroupToStiffWindowPipe(int windowSize) { + this.window = windowSize; + + } + + @Override + public Object execute(Object input) { + if (input instanceof double[] inputData) { + var inputDataList = to1DArrayList(inputData); + + var resultArray = new double[2][][]; + var stiffedTargetGroup = new double[1][]; + + stiffedTargetGroup[0] = groupToStiffedTarget(inputDataList, this.window); + var stiffedWindowGroup = groupToStiffedWindow(inputDataList, this.window); + + resultArray[0] = stiffedWindowGroup; + resultArray[1] = stiffedTargetGroup; + + return resultArray; + + } else { + throw new IllegalArgumentException("Input must be an instance of double[]"); + } + } + + /** + * Groups the values in the input ArrayList into windows of a specified size and + * converts the grouped data into a 2D array representing the stiffed windowed + * structure. + * + * @param values The input ArrayList of Double values to be grouped into + * windows. + * @param windowSize The size of each window for grouping the values. + * @return A 2D array representing the stiffed windowed structure of the grouped + * values. + */ + public static double[][] groupToStiffedWindow(ArrayList values, int windowSize) { + if (windowSize < 1 || windowSize > values.size()) { + throw new IllegalArgumentException("Invalid window size"); + } + + List indices = range(0, values.size() - windowSize + 1) // + .filter(i -> i % (windowSize + 1) == 0) // + .boxed() // + .collect(Collectors.toList()); // + + List> windowedData = indices.stream() // + .map(i -> values.subList(i, i + windowSize)) // + .map(ArrayList::new) // + .collect(Collectors.toList()); // + + return to2DArray(windowedData); + } + + /** + * Groups the values in the input ArrayList into a stiffed target structure, + * extracting every nth element to form windows of a specified size and converts + * the grouped data into a 1D array. + * + * @param val The input ArrayList of Double values from which the stiffed + * target structure is created. + * @param windowSize The size of each window, representing the step size for + * selecting elements. + * @return A 1D array representing the stiffed target structure of the grouped + * values. + */ + public static double[] groupToStiffedTarget(ArrayList val, int windowSize) { + if (windowSize < 1 || windowSize > val.size()) { + throw new IllegalArgumentException("Invalid window size"); + } + + var windowedData = range(0, val.size())// + .filter(j -> j % (windowSize + 1) == windowSize)// + .mapToObj(val::get)// + .collect(Collectors.toList()); + + return to1DArray(windowedData); + } + + /** + * Groups the values in the input Array into a stiffed target structure, + * extracting every nth element to form windows of a specified size and converts + * the grouped data into a 1D array. + * + * @param val The input Array of Double values from which the stiffed + * target structure is created. + * @param windowSize The size of each window, representing the step size for + * selecting elements. + * @return A 1D array representing the stiffed target structure of the grouped + * values. + */ + public static double[] groupToStiffedTarget(double[] val, int windowSize) { + if (windowSize < 1 || windowSize > val.length) { + throw new IllegalArgumentException("Invalid window size"); + } + + return range(0, val.length)// + .filter(j -> j % (windowSize + 1) == windowSize)// + .mapToDouble(j -> val[j]) // + .toArray(); + } + +} diff --git a/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessingpipeline/GroupbyPipe.java b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessingpipeline/GroupbyPipe.java new file mode 100644 index 00000000000..1a107b62607 --- /dev/null +++ b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessingpipeline/GroupbyPipe.java @@ -0,0 +1,30 @@ +package io.openems.edge.predictor.lstm.preprocessingpipeline; + +import static io.openems.edge.predictor.lstm.preprocessing.DataModification.groupDataByHourAndMinute; +import static io.openems.edge.predictor.lstm.utilities.UtilityConversion.to1DArrayList; +import static io.openems.edge.predictor.lstm.utilities.UtilityConversion.to3DArray; + +import java.time.OffsetDateTime; +import java.util.ArrayList; + +import io.openems.edge.predictor.lstm.common.HyperParameters; + +public class GroupbyPipe implements Stage { + + private ArrayList dates; + + public GroupbyPipe(HyperParameters hype, ArrayList date) { + this.dates = date; + } + + @Override + public Object execute(Object input) { + if (input instanceof double[] in) { + var inList = to1DArrayList(in); + var groupedByHourAndMinuteList = groupDataByHourAndMinute(inList, this.dates); + return to3DArray(groupedByHourAndMinuteList); + } else { + throw new IllegalArgumentException("Input must be an instance of double[]"); + } + } +} \ No newline at end of file diff --git a/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessingpipeline/GrouptoWindowpipe.java b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessingpipeline/GrouptoWindowpipe.java new file mode 100644 index 00000000000..944cf8248bd --- /dev/null +++ b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessingpipeline/GrouptoWindowpipe.java @@ -0,0 +1,75 @@ +package io.openems.edge.predictor.lstm.preprocessingpipeline; + +import static io.openems.edge.predictor.lstm.utilities.SlidingWindowSpliterator.windowed; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import io.openems.edge.predictor.lstm.utilities.UtilityConversion; + +public class GrouptoWindowpipe implements Stage { + + public static final Function>, double[][]> twoDListToTwoDArray = UtilityConversion::to2DArray; + + private int window; + + public GrouptoWindowpipe(int windowSize) { + this.window = windowSize; + } + + @Override + public Object execute(Object input) { + if (input instanceof double[] inputData) { + try { + var windowedTarget = this.getTargetData(inputData); + var windowedData = this.getWindowDataTrain(inputData); + + return new double[][][] { windowedData, new double[][] { windowedTarget } }; + + } catch (Exception e) { + throw new RuntimeException("Error processing input data", e); + } + } else { + throw new IllegalArgumentException("Input must be an instance of double[]"); + } + } + + private double[][] getWindowDataTrain(double[] data) { + var lower = 0; + var upper = data.length - 1; + + var subList = IntStream.range(lower, upper)// + .mapToObj(index -> data[index]) // + .collect(Collectors.toCollection(ArrayList::new)); + + var res = windowed(subList, this.window) // + .map(s -> s.collect(Collectors.toList())) // + .collect(Collectors.toList()); + + return twoDListToTwoDArray.apply(res); + } + + /** + * Retrieves the target data from a list of scaled data points. + * + * @param data The list containing scaled data points. + * @return An array containing the target data. + * @throws Exception If the provided list of scaled data is empty. + */ + public double[] getTargetData(double[] data) throws Exception { + if (data.length == 0) { + throw new Exception("Scaled data is empty"); + } + var lower = 0; + var upper = data.length - 1; + + var subArr = IntStream.range(lower + this.window, upper + 1) // + .mapToDouble(index -> data[index]) // + .toArray(); + + return subArr; + } +} diff --git a/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessingpipeline/InterpolationPipe.java b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessingpipeline/InterpolationPipe.java new file mode 100644 index 00000000000..28e4a35106a --- /dev/null +++ b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessingpipeline/InterpolationPipe.java @@ -0,0 +1,29 @@ +package io.openems.edge.predictor.lstm.preprocessingpipeline; + +import static io.openems.edge.predictor.lstm.utilities.UtilityConversion.to1DArray; +import static io.openems.edge.predictor.lstm.utilities.UtilityConversion.to1DArrayList; + +import java.time.OffsetDateTime; +import java.util.ArrayList; + +import io.openems.edge.predictor.lstm.common.HyperParameters; +import io.openems.edge.predictor.lstm.interpolation.InterpolationManager; + +public class InterpolationPipe implements Stage { + private HyperParameters hyperParameters; + + public InterpolationPipe(HyperParameters hype, ArrayList dates) { + this.hyperParameters = hype; + } + + @Override + public Object execute(Object input) { + if (input instanceof double[] in) { + var inList = to1DArrayList(in); + var inter = new InterpolationManager(inList, this.hyperParameters); + return to1DArray(inter.getInterpolatedData()); + } else { + throw new IllegalArgumentException("Input must be an instance of double[]"); + } + } +} diff --git a/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessingpipeline/ModifyDataForTrend.java b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessingpipeline/ModifyDataForTrend.java new file mode 100644 index 00000000000..965dd023e0f --- /dev/null +++ b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessingpipeline/ModifyDataForTrend.java @@ -0,0 +1,42 @@ +package io.openems.edge.predictor.lstm.preprocessingpipeline; + +import static io.openems.edge.predictor.lstm.preprocessing.DataModification.modifyFortrendPrediction; +import static io.openems.edge.predictor.lstm.utilities.UtilityConversion.to1DArrayList; +import static io.openems.edge.predictor.lstm.utilities.UtilityConversion.to2DArray; + +import java.time.OffsetDateTime; +import java.util.ArrayList; + +import io.openems.edge.predictor.lstm.common.HyperParameters; + +public class ModifyDataForTrend implements Stage { + + private HyperParameters hyperparameters; + + private ArrayList dates; + + public ModifyDataForTrend(ArrayList date, HyperParameters hype) { + this.dates = date; + this.hyperparameters = hype; + } + + @Override + public Object execute(Object input) { + + if (input instanceof double[] inputData) { + try { + var inList = to1DArrayList(inputData); + var modified = modifyFortrendPrediction(inList, this.dates, this.hyperparameters); + return to2DArray(modified); + } catch (Exception e) { + throw new RuntimeException("Error processing input data", e); + } + } else { + throw new IllegalArgumentException("Input must be an instance of double[]"); + } + } + + public void setDates(ArrayList date) { + this.dates = date; + } +} diff --git a/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessingpipeline/MovingAveragePipe.java b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessingpipeline/MovingAveragePipe.java new file mode 100644 index 00000000000..4ac0ba16f18 --- /dev/null +++ b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessingpipeline/MovingAveragePipe.java @@ -0,0 +1,13 @@ +package io.openems.edge.predictor.lstm.preprocessingpipeline; + +import io.openems.edge.predictor.lstm.preprocessing.MovingAverage; + +public class MovingAveragePipe implements Stage { + + @Override + public Object execute(Object input) { + return (input instanceof double[] in) // + ? MovingAverage.movingAverage(in) // + : new IllegalArgumentException("Input must be an instance of double[]"); + } +} diff --git a/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessingpipeline/NormalizePipe.java b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessingpipeline/NormalizePipe.java new file mode 100644 index 00000000000..6ee14e728bd --- /dev/null +++ b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessingpipeline/NormalizePipe.java @@ -0,0 +1,42 @@ +package io.openems.edge.predictor.lstm.preprocessingpipeline; + +import static io.openems.edge.predictor.lstm.preprocessing.DataModification.normalizeData; +import static io.openems.edge.predictor.lstm.preprocessing.DataModification.standardize; + +import io.openems.edge.predictor.lstm.common.HyperParameters; + +public class NormalizePipe implements Stage { + + private HyperParameters hyperParameters; + + public NormalizePipe(HyperParameters hyper) { + this.hyperParameters = hyper; + } + + @Override + public Object execute(Object input) { + try { + if (input instanceof double[][][] inputArray) { + + double[][] trainData = inputArray[0]; + double[] targetData = inputArray[1][0]; + + double[][] normalizedTrainData = normalizeData(trainData, this.hyperParameters); + double[] normalizedTargetData = normalizeData(trainData, targetData, this.hyperParameters); + + return new double[][][] { normalizedTrainData, { normalizedTargetData } }; + + } else if (input instanceof double[][] inputArray) { + return normalizeData(inputArray, this.hyperParameters); + + } else if (input instanceof double[] inputArray) { + return standardize(inputArray, this.hyperParameters); + + } else { + throw new IllegalArgumentException("Illegal Argument encountered during normalization"); + } + } catch (Exception e) { + throw new RuntimeException("Illegal Argument encountered during normalization"); + } + } +} diff --git a/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessingpipeline/PiplineInterface.java b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessingpipeline/PiplineInterface.java new file mode 100644 index 00000000000..8d24d634e55 --- /dev/null +++ b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessingpipeline/PiplineInterface.java @@ -0,0 +1,24 @@ +package io.openems.edge.predictor.lstm.preprocessingpipeline; + +/** + * Represents a pipeline that processes data through a series of stages. + * + * @param The type of the output produced by the pipeline. + * @param The type of the input consumed by the pipeline. + */ +public interface PiplineInterface { + + /** + * Adds a stage to the pipeline. + * + * @param stage The stage to be added to the pipeline. + */ + public void add(Stage stage); + + /** + * Executes the pipeline, processing the data through the added stages. + * + * @return The result of executing the pipeline. + */ + public Object execute(); +} diff --git a/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessingpipeline/PreprocessingPipeImpl.java b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessingpipeline/PreprocessingPipeImpl.java new file mode 100644 index 00000000000..7081d8c790e --- /dev/null +++ b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessingpipeline/PreprocessingPipeImpl.java @@ -0,0 +1,230 @@ +package io.openems.edge.predictor.lstm.preprocessingpipeline; + +import java.time.OffsetDateTime; +import java.util.ArrayList; +import java.util.List; + +import io.openems.edge.predictor.lstm.common.HyperParameters; + +public class PreprocessingPipeImpl implements PiplineInterface { + + private Object inputData; + private Object outputData; + private Object mean; + private Object standardDeviation; + private HyperParameters hyperParameter; + private double scalingFactor = 0.001; + private ArrayList dates; + private List> stages = new ArrayList<>(); + + public PreprocessingPipeImpl(ArrayList data, ArrayList dates, + HyperParameters hyperParameters) { + this.inputData = data; + this.dates = dates; + this.hyperParameter = hyperParameters; + } + + public PreprocessingPipeImpl(HyperParameters hyperParameters) { + this.hyperParameter = hyperParameters; + } + + private PreprocessingPipeImpl addStage(Stage stage) { + this.stages.add(stage); + return this; + } + + /** + * Add moving average stage. + * + * @return this + */ + public PreprocessingPipeImpl movingAverage() { + return this.addStage(new MovingAveragePipe()); + } + + /** + * Add scale stage. + * + * @return this + */ + public PreprocessingPipeImpl scale() { + return this.addStage(new ScalingPipe(this.hyperParameter)); + } + + /** + * Add constant scale stage. + * + * @return this + */ + public PreprocessingPipeImpl constantscale() { + return this.addStage(new ConstantScalingPipe(this.scalingFactor)); + } + + /** + * Add trainTestSplit stage. + * + * @return this + */ + public PreprocessingPipeImpl trainTestSplit() { + return this.addStage(new TrainAndTestSplitPipe(this.hyperParameter)); + } + + /** + * Add filterOutliers stage. + * + * @return this + */ + public PreprocessingPipeImpl filterOutliers() { + return this.addStage(new FilterOutliersPipe()); + } + + /** + * Add normalize stage. + * + * @return this + */ + public PreprocessingPipeImpl normalize() { + return this.addStage(new NormalizePipe(this.hyperParameter)); + } + + /** + * Add groupToWIndowTrend stage. + * + * @return this + */ + public PreprocessingPipeImpl groupToWIndowTrend() { + return this.addStage(new GrouptoWindowpipe(this.hyperParameter.getWindowSizeTrend())); + } + + /** + * Add groupToWIndowSeasonality stage. + * + * @return this + */ + public PreprocessingPipeImpl groupToWIndowSeasonality() { + return this.addStage(new GrouptoWindowpipe(this.hyperParameter.getWindowSizeSeasonality())); + } + + /** + * Add shuffle stage. + * + * @return this + */ + public PreprocessingPipeImpl shuffle() { + return this.addStage(new ShufflePipe()); + } + + /** + * Add groupByHoursAndMinutes stage. + * + * @return this + */ + public PreprocessingPipeImpl groupByHoursAndMinutes() { + return this.addStage(new GroupbyPipe(this.hyperParameter, this.dates)); + } + + /** + * Add Remove Negatives. + * + * @return this + */ + + public PreprocessingPipeImpl removeNegatives() { + return this.addStage(new RemoveNegativesPipe()); + } + + /** + * Add groupToStiffedWindow stage. + * + * @return this + */ + public PreprocessingPipeImpl groupToStiffedWindow() { + return this.addStage(new GroupToStiffWindowPipe(this.hyperParameter.getWindowSizeTrend())); + } + + /** + * Add interpolate stage. + * + * @return this + */ + public PreprocessingPipeImpl interpolate() { + return this.addStage(new InterpolationPipe(this.hyperParameter, this.dates)); + } + + /** + * Add modifyForShortTermPrediction stage. + * + * @return this + */ + public PreprocessingPipeImpl modifyForTrendPrediction() { + return this.addStage(new ModifyDataForTrend(this.dates, this.hyperParameter)); + } + + /** + * Add differencing stage. + * + * @return this + */ + public PreprocessingPipeImpl differencing() { + return this.addStage(new DifferencingPipe()); + } + + /** + * Add reverseScale stage. + * + * @return this + */ + public PreprocessingPipeImpl reverseScale() { + return this.addStage(new ReverseScalingPipe(this.hyperParameter)); + } + + /** + * Add reverseNormalize stage. + * + * @return this + */ + public PreprocessingPipeImpl reverseNormalize() { + return this.addStage(new ReverseNormalizationPipe(this.mean, this.standardDeviation, this.hyperParameter)); + } + + @Override + public Object execute() { + Object preprocessingInput = this.inputData; + for (Stage i : this.stages) { + preprocessingInput = i.execute(preprocessingInput); + } + this.outputData = preprocessingInput; + this.stages = new ArrayList>(); + return this.outputData; + } + + @Override + public void add(Stage stage) { + this.stages.add(stage); + } + + public PreprocessingPipeImpl setData(Object val) { + this.inputData = val; + return this; + } + + public PreprocessingPipeImpl setMean(Object val) { + this.mean = val; + return this; + } + + public PreprocessingPipeImpl setStandardDeviation(Object val) { + this.standardDeviation = val; + return this; + } + + public PreprocessingPipeImpl setDates(ArrayList date) { + this.dates = date; + return this; + } + + public PreprocessingPipeImpl setScalingFactor(double val) { + this.scalingFactor = val; + return this; + } +} diff --git a/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessingpipeline/RemoveNegativesPipe.java b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessingpipeline/RemoveNegativesPipe.java new file mode 100644 index 00000000000..10f9d492f27 --- /dev/null +++ b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessingpipeline/RemoveNegativesPipe.java @@ -0,0 +1,13 @@ +package io.openems.edge.predictor.lstm.preprocessingpipeline; + +import static io.openems.edge.predictor.lstm.preprocessing.DataModification.removeNegatives; + +public class RemoveNegativesPipe implements Stage { + + @Override + public Object execute(Object input) { + return (input instanceof double[] in) // + ? removeNegatives(in) // + : new IllegalArgumentException("Input must be an instance of double[]"); + } +} \ No newline at end of file diff --git a/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessingpipeline/ReverseNormalizationPipe.java b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessingpipeline/ReverseNormalizationPipe.java new file mode 100644 index 00000000000..210774461c1 --- /dev/null +++ b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessingpipeline/ReverseNormalizationPipe.java @@ -0,0 +1,46 @@ +package io.openems.edge.predictor.lstm.preprocessingpipeline; + +import io.openems.edge.predictor.lstm.common.HyperParameters; +import io.openems.edge.predictor.lstm.preprocessing.DataModification; + +public class ReverseNormalizationPipe implements Stage { + private Object mean; + private Object stdDeviation; + private HyperParameters hyperParameters; + + public ReverseNormalizationPipe(Object average, Object std, HyperParameters hyp) { + this.mean = average; + this.stdDeviation = std; + this.hyperParameters = hyp; + } + + @Override + public Object execute(Object input) { + try { + if (input instanceof double[] inputArray) { + if (this.mean instanceof double[] meanArray // + && this.stdDeviation instanceof double[] sdArray) { + return DataModification.reverseStandrize(inputArray, meanArray, sdArray, this.hyperParameters); + + } else if (this.mean instanceof Double meanValue // + && this.stdDeviation instanceof Double sdValue) { + return DataModification.reverseStandrize(inputArray, meanValue, sdValue, this.hyperParameters); + + } else { + throw new IllegalArgumentException("Input must be an instance of double[]"); + } + + } else if (input instanceof Double inputArray) { + double mean = (double) this.mean; + double std = (double) this.stdDeviation; + return DataModification.reverseStandrize(inputArray, mean, std, this.hyperParameters); + + } else { + throw new IllegalArgumentException("Input must be an instance of double[]"); + } + + } catch (Exception e) { + throw new RuntimeException("Error processing input data", e); + } + } +} diff --git a/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessingpipeline/ReverseScalingPipe.java b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessingpipeline/ReverseScalingPipe.java new file mode 100644 index 00000000000..065562807c8 --- /dev/null +++ b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessingpipeline/ReverseScalingPipe.java @@ -0,0 +1,20 @@ +package io.openems.edge.predictor.lstm.preprocessingpipeline; + +import static io.openems.edge.predictor.lstm.preprocessing.DataModification.scaleBack; + +import io.openems.edge.predictor.lstm.common.HyperParameters; + +public class ReverseScalingPipe implements Stage { + private HyperParameters hype; + + public ReverseScalingPipe(HyperParameters hyperParameters) { + this.hype = hyperParameters; + } + + @Override + public Object execute(Object input) { + return (input instanceof double[] inputArray) // + ? scaleBack(inputArray, this.hype.getScalingMin(), this.hype.getScalingMax()) // + : new IllegalArgumentException("Input must be an instance of double[]"); + } +} diff --git a/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessingpipeline/ScalingPipe.java b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessingpipeline/ScalingPipe.java new file mode 100644 index 00000000000..7f958983bc1 --- /dev/null +++ b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessingpipeline/ScalingPipe.java @@ -0,0 +1,67 @@ +package io.openems.edge.predictor.lstm.preprocessingpipeline; + +import java.util.Arrays; + +import io.openems.edge.predictor.lstm.common.HyperParameters; + +public class ScalingPipe implements Stage { + + private static final double MIN_SCALED = 0.2; + private static final double MAX_SCALED = 0.8; + + private HyperParameters hyperParameter; + + public ScalingPipe(HyperParameters hyperParameters) { + this.hyperParameter = hyperParameters; + } + + @Override + public Object execute(Object value) { + if (value instanceof double[][] v) { + return this.scaleSecondCase(v); + + } else if (value instanceof double[] v) { + return (this.scaleFirstCase(v)); + + } else { + throw new IllegalArgumentException("Input must be an instance of double[]"); + } + } + + /** + * Scales the data in the second case of a two-dimensional array using the + * preprocessing pipeline. + * + * @param value The two-dimensional array containing data to be scaled, where + * each row represents a separate case. + * @return A two-dimensional array containing the scaled data, where each row + * corresponds to the scaled data of the respective case. + */ + public double[][] scaleSecondCase(double[][] value) { + if (value == null || value.length != 2 || value[0] == null || value[1] == null) { + throw new IllegalArgumentException("Input must be a non-null 2xN array."); + } + + var result = new double[2][]; + result[0] = this.scaleFirstCase(value[0]); + result[1] = this.scaleFirstCase(value[1]); + + return result; + } + + /** + * Scales the data in the first case of a one-dimensional array using the + * provided scaling range. + * + * @param value The one-dimensional array containing data to be scaled. + * @return An array containing the scaled data. + */ + public double[] scaleFirstCase(double[] value) { + var min = this.hyperParameter.getScalingMin(); + var max = this.hyperParameter.getScalingMax(); + + return Arrays.stream(value)// + .map(v -> MIN_SCALED + ((v - min) / (max - min)) * (MAX_SCALED - MIN_SCALED))// + .toArray(); + } +} diff --git a/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessingpipeline/ShufflePipe.java b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessingpipeline/ShufflePipe.java new file mode 100644 index 00000000000..a38afdbc4fa --- /dev/null +++ b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessingpipeline/ShufflePipe.java @@ -0,0 +1,23 @@ +package io.openems.edge.predictor.lstm.preprocessingpipeline; + +import io.openems.edge.predictor.lstm.preprocessing.Shuffle; + +public class ShufflePipe implements Stage { + + @Override + public Object execute(Object input) { + if (!(input instanceof double[][][] data)) { + throw new IllegalArgumentException("Input must be a 3-dimensional double array."); + } + + double[][] trainData = data[0]; + double[] targetData = data[1][0]; + + var shuffle = new Shuffle(trainData, targetData); + + double[][] shuffledData = shuffle.getData(); + double[] shuffledTarget = shuffle.getTarget(); + + return new double[][][] { shuffledData, { shuffledTarget } }; + } +} diff --git a/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessingpipeline/Stage.java b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessingpipeline/Stage.java new file mode 100644 index 00000000000..38015272d19 --- /dev/null +++ b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessingpipeline/Stage.java @@ -0,0 +1,12 @@ +package io.openems.edge.predictor.lstm.preprocessingpipeline; + +public interface Stage { + + /** + * Executes the stage's processing logic on the provided input. + * + * @param input The input data to be processed by the stage. + * @return The result of the processing, typically of type O. + */ + public O execute(final I input); +} diff --git a/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessingpipeline/TrainAndTestSplitPipe.java b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessingpipeline/TrainAndTestSplitPipe.java new file mode 100644 index 00000000000..54c976f2e8e --- /dev/null +++ b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/preprocessingpipeline/TrainAndTestSplitPipe.java @@ -0,0 +1,50 @@ +package io.openems.edge.predictor.lstm.preprocessingpipeline; + +import java.util.stream.IntStream; + +import io.openems.edge.predictor.lstm.common.HyperParameters; + +public class TrainAndTestSplitPipe implements Stage { + + private HyperParameters hyp; + + public TrainAndTestSplitPipe(HyperParameters hyperParameters) { + this.hyp = hyperParameters; + } + + /** + * Splits the provided data into training and validation datasets based on the + * configured split percentage. + * + * @param value The array of data to be split. + * @return A 2D array where the first row contains the training data and the + * second row contains the validation data. + */ + @Override + public Object execute(Object value) { + if (value instanceof double[] valueTemp) { + double splitPercentage = this.hyp.getDataSplitTrain(); + int dataSize = valueTemp.length - 1; + + int trainLowerIndex = 0; + int trainUpperIndex = (int) (splitPercentage * dataSize); + + int testLowerIndex = trainUpperIndex; + int testUpperIndex = dataSize + 1; + + double[][] combinedData = { // train data + IntStream.range(trainLowerIndex, trainUpperIndex) // + .mapToDouble(index -> valueTemp[index]) // + .toArray(), // target data + IntStream.range(testLowerIndex, testUpperIndex) // + .mapToDouble(index -> valueTemp[index]) // + .toArray() // + }; + + return combinedData; + } else { + throw new IllegalArgumentException("Input must be an instance of double[]"); + } + + } +} diff --git a/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/train/LstmTrain.java b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/train/LstmTrain.java new file mode 100644 index 00000000000..67e1e3053db --- /dev/null +++ b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/train/LstmTrain.java @@ -0,0 +1,171 @@ +package io.openems.edge.predictor.lstm.train; + +import static io.openems.edge.predictor.lstm.preprocessing.DataModification.constantScaling; +import static io.openems.edge.predictor.lstm.preprocessing.DataModification.removeNegatives; +import static java.util.stream.Collectors.toCollection; + +import java.time.OffsetDateTime; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Objects; +import java.util.SortedMap; +import java.util.TreeMap; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.Sets; +import com.google.gson.JsonElement; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.timedata.Resolution; +import io.openems.common.types.ChannelAddress; +import io.openems.edge.predictor.lstm.PredictorLstm; +import io.openems.edge.predictor.lstm.common.ReadAndSaveModels; +import io.openems.edge.timedata.api.Timedata; + +public class LstmTrain implements Runnable { + + private final Logger log = LoggerFactory.getLogger(LstmTrain.class); + + private final Timedata timedata; + private final ChannelAddress channelAddress; + private final PredictorLstm parent; + private final long days; + + public LstmTrain(Timedata timedata, ChannelAddress channelAddress, PredictorLstm parent, long days) { + this.timedata = timedata; + this.channelAddress = channelAddress; + this.parent = parent; + this.days = days; + } + + @Override + public void run() { + var now = ZonedDateTime.now(); + var until = now.minusDays(1).withHour(23).withMinute(45).withSecond(0).withNano(0); + var fromDate = until.minusDays(this.days).withHour(0).withMinute(0).withSecond(0).withNano(0); + + final SortedMap> queryResult; + var hyperParameters = ReadAndSaveModels.read(this.channelAddress.getChannelId()); + + var trainMap = new TreeMap>(); + var validateMap = new TreeMap>(); + + try { + queryResult = this.timedata.queryHistoricData(null, fromDate, until, Sets.newHashSet(this.channelAddress), + new Resolution(hyperParameters.getInterval(), ChronoUnit.MINUTES)); + + int totalItems = queryResult.size(); + int trainSize = (int) (totalItems * 0.66); // 66% train and 33% validation + + int count = 0; + for (Map.Entry> entry : queryResult.entrySet()) { + if (count < trainSize) { + trainMap.put(entry.getKey(), entry.getValue()); + } else { + validateMap.put(entry.getKey(), entry.getValue()); + } + count++; + } + + } catch (OpenemsNamedException e) { + e.printStackTrace(); + } + + // Get the training data + var trainingData = this.getData(trainMap); + + if (this.cannotTrainConditions(trainingData)) { + this.parent._setCannotTrainCondition(true); + this.log.info("Cannot proceed with training: Data is all null or insufficient data."); + return; + } + + // Get the training Date + var trainingDate = this.getDate(trainMap); + // Get the training data + var validationData = this.getData(validateMap); + // Get the validationDate + var validationDate = this.getDate(validateMap); + + /** + * TODO Read an save model.adapt method ReadAndSaveModels.adapt(hyperParameters, + * validateBatchData, validateBatchDate); + */ + new TrainAndValidateBatch(// + constantScaling(removeNegatives(trainingData), 1), trainingDate, // + constantScaling(removeNegatives(validationData), 1), validationDate, // + hyperParameters); + + this.parent._setLastTrainedTime(hyperParameters.getLastTrainedDate().toInstant().toEpochMilli()); + this.parent._setModelError(Collections.min(hyperParameters.getRmsErrorSeasonality())); + this.parent._setCannotTrainCondition(false); + + } + + /** + * Extracts data values. + * + * @param queryResult The SortedMap queryResult. + * @return An ArrayList of Double values extracted from non-null JsonElement + * values. + */ + public ArrayList getData(SortedMap> queryResult) { + ArrayList data = new ArrayList<>(); + + queryResult.values().stream()// + .map(SortedMap::values)// + .flatMap(Collection::stream)// + .map(v -> { + if (v.isJsonNull()) { + return null; + } + return v.getAsDouble(); + }).forEach(value -> data.add(value)); + + return data; + } + + /** + * Extracts OffsetDateTime objects from the keys of a SortedMap containing + * ZonedDateTime keys. + * + * @param queryResult The SortedMap containing ZonedDateTime keys and associated + * data. + * @return An ArrayList of OffsetDateTime objects extracted from the + * ZonedDateTime keys. + */ + public ArrayList getDate( + SortedMap> queryResult) { + return queryResult.keySet().stream()// + .map(ZonedDateTime::toOffsetDateTime)// + .collect(toCollection(ArrayList::new)); + } + + /** + * Checks if all elements in an ArrayList are null. + * + * @param array The ArrayList to be checked. + * @return true if all elements in the ArrayList are null, false otherwise. + */ + private boolean cannotTrainConditions(ArrayList array) { + if (array.isEmpty()) { + return true; // Cannot train with no data + } + + boolean allNulls = array.stream() // + .allMatch(Objects::isNull); + if (allNulls) { + return true; // Cannot train with all null data + } + + var nonNanCount = array.stream().filter(d -> d != null && !Double.isNaN(d)).count(); + var validProportion = (double) nonNanCount / array.size(); + return validProportion <= 0.5; // Cannot train with 50% or more invalid data + } +} diff --git a/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/train/MakeModel.java b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/train/MakeModel.java new file mode 100644 index 00000000000..9ec4280d289 --- /dev/null +++ b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/train/MakeModel.java @@ -0,0 +1,181 @@ +package io.openems.edge.predictor.lstm.train; + +import static io.openems.edge.predictor.lstm.utilities.UtilityConversion.to1DArray; + +import java.time.OffsetDateTime; +import java.util.ArrayList; + +import io.openems.edge.predictor.lstm.common.DynamicItterationValue; +import io.openems.edge.predictor.lstm.common.HyperParameters; +import io.openems.edge.predictor.lstm.preprocessingpipeline.PreprocessingPipeImpl; +import io.openems.edge.predictor.lstm.util.Engine.EngineBuilder; + +public class MakeModel { + + public static final String SEASONALITY = "seasonality"; + public static final String TREND = "trend"; + + /** + * Trains the trend model using the specified data, timestamps, and + * hyperparameters. The training process involves preprocessing the data for + * short-term prediction, generating initial weights, and fitting the model for + * each modified data segment. The trained weights are saved to the model file. + * + * @param data The ArrayList of Double values representing the + * time-series data. + * @param date The ArrayList of OffsetDateTime objects corresponding + * to the timestamps of the data. + * @param hyperParameters The hyperparameters configuration for training the + * trend model. + * @return weightMatrix Trained models. + */ + public synchronized ArrayList>>> trainTrend(ArrayList data, + ArrayList date, HyperParameters hyperParameters) { + var weightMatrix = new ArrayList>>>(); + var weightTrend = new ArrayList>(); + PreprocessingPipeImpl preProcessing = new PreprocessingPipeImpl(hyperParameters); + preProcessing.setData(to1DArray(data)); + preProcessing.setDates(date); + + var modifiedData = (double[][]) preProcessing// + .interpolate()// + .movingAverage()// + .scale()// + .filterOutliers()// + .modifyForTrendPrediction()// + .execute(); + + for (int i = 0; i < modifiedData.length; i++) { + weightTrend = (hyperParameters.getCount() == 0) // + ? generateInitialWeightMatrix(hyperParameters.getWindowSizeTrend(), hyperParameters)// + : hyperParameters.getlastModelTrend().get(i); + + preProcessing.setData(modifiedData[i]); + + var preProcessed = (double[][][]) preProcessing// + .groupToStiffedWindow()// + .normalize()// + .shuffle()// + .execute(); + + var model = new EngineBuilder() // + .setInputMatrix(preProcessed[0])// + .setTargetVector(preProcessed[1][0]) // + .build(); + model.fit(hyperParameters.getGdIterration(), weightTrend, hyperParameters); + weightMatrix.add(model.getWeights()); + + } + + return weightMatrix; + } + + /** + * Trains the seasonality model using the specified data, timestamps, and + * hyperparameters. The training process involves preprocessing the data, + * grouping it by hour and minute, and fitting the model for each group. The + * trained weights are saved to the model file. + * + * @param data The ArrayList of Double values representing the + * time-series data. + * @param date The ArrayList of OffsetDateTime objects corresponding + * to the timestamps of the data. + * @param hyperParameters The hyperparameters configuration for training the + * seasonality model. + * @return weightMatrix Trained seasonality models. + */ + + public synchronized ArrayList>>> trainSeasonality(ArrayList data, + ArrayList date, HyperParameters hyperParameters) { + var weightMatrix = new ArrayList>>>(); + var weightSeasonality = new ArrayList>(); + int windowsSize = hyperParameters.getWindowSizeSeasonality(); + + var preprocessing = new PreprocessingPipeImpl(hyperParameters); + preprocessing.setData(to1DArray(data));// + preprocessing.setDates(date);// + + var dataGroupedByMinute = (double[][][]) preprocessing// + .interpolate()// + .movingAverage()// + .scale()// + .filterOutliers()// + .groupByHoursAndMinutes()// + .execute(); + int k = 0; + + for (int i = 0; i < dataGroupedByMinute.length; i++) { + for (int j = 0; j < dataGroupedByMinute[i].length; j++) { + + hyperParameters.setGdIterration(DynamicItterationValue + .setIteration(hyperParameters.getAllModelErrorSeason(), k, hyperParameters)); + + if (hyperParameters.getCount() == 0) { + weightSeasonality = generateInitialWeightMatrix(windowsSize, hyperParameters); + + } else { + weightSeasonality = hyperParameters.getlastModelSeasonality().get(k); + } + + preprocessing.setData(dataGroupedByMinute[i][j]); + + var preProcessedSeason = (double[][][]) preprocessing// + // .differencing()// + .groupToWIndowSeasonality() // + .normalize() // + .shuffle() // + .execute(); + + var model = new EngineBuilder()// + .setInputMatrix(preProcessedSeason[0]) // + .setTargetVector(preProcessedSeason[1][0]) // + .build(); + + model.fit(hyperParameters.getGdIterration(), weightSeasonality, hyperParameters); + weightMatrix.add(model.getWeights()); + + k = k + 1; + } + } + + return weightMatrix; + + } + + /** + * Generates the initial weight matrix for the LSTM model based on the specified + * window size and hyperparameters. + * + * @param windowSize The size of the window for the initial weight matrix. + * @param hyperParameters The hyperparameters used for generating the initial + * weight matrix. + * @return The initial weight matrix as an ArrayList of ArrayList of Double + * values. + */ + public static ArrayList> generateInitialWeightMatrix(int windowSize, + HyperParameters hyperParameters) { + var initialWeight = new ArrayList>(); + var parameterTypes = new String[] { "wi", "wo", "wz", "ri", "ro", "rz", "yt", "ct" }; + + for (var type : parameterTypes) { + var temp = new ArrayList(); + for (int i = 1; i <= windowSize; i++) { + var value = switch (type) { + case "wi" -> hyperParameters.getWiInit(); + case "wo" -> hyperParameters.getWoInit(); + case "wz" -> hyperParameters.getWzInit(); + case "ri" -> hyperParameters.getRiInit(); + case "ro" -> hyperParameters.getRoInit(); + case "rz" -> hyperParameters.getRzInit(); + case "yt" -> hyperParameters.getYtInit(); + case "ct" -> hyperParameters.getCtInit(); + default -> throw new IllegalArgumentException("Invalid parameter type"); + }; + temp.add(value); + } + initialWeight.add(temp); + } + return initialWeight; + } + +} diff --git a/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/train/TrainAndValidateBatch.java b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/train/TrainAndValidateBatch.java new file mode 100644 index 00000000000..dde5e3d92d6 --- /dev/null +++ b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/train/TrainAndValidateBatch.java @@ -0,0 +1,88 @@ +package io.openems.edge.predictor.lstm.train; + +import java.time.OffsetDateTime; +import java.util.ArrayList; +import java.util.concurrent.CompletableFuture; + +import io.openems.edge.predictor.lstm.common.HyperParameters; +import io.openems.edge.predictor.lstm.common.ReadAndSaveModels; +import io.openems.edge.predictor.lstm.preprocessing.DataModification; +import io.openems.edge.predictor.lstm.validator.ValidationSeasonalityModel; +import io.openems.edge.predictor.lstm.validator.ValidationTrendModel; + +public class TrainAndValidateBatch { + + public TrainAndValidateBatch(// + ArrayList trainData, // + ArrayList trainDate, // + ArrayList validateData, // + ArrayList validateDate, // + HyperParameters hyperParameter) { + + /* + * var checkTrain = trainData.size() / hyperParameter.getBatchSize() + * + * if ( checkTrain <= hyperParameter.getWindowSizeSeasonality() || checkTrain <= + * hyperParameter.getWindowSizeTrend() ) { throw new Exception; } + */ + + var batchedData = DataModification.getDataInBatch(// + trainData, hyperParameter.getBatchSize()); + var batchedDate = DataModification.getDateInBatch(// + trainDate, hyperParameter.getBatchSize()); + + for (int epoch = hyperParameter.getEpochTrack(); epoch < hyperParameter.getEpoch(); epoch++) { + + int k = hyperParameter.getCount(); + + for (int batch = hyperParameter.getBatchTrack(); batch < hyperParameter.getBatchSize(); batch++) { + + hyperParameter.setCount(k); + System.out.println("=====> Batch = " + hyperParameter.getBatchTrack() // + + "/" + hyperParameter.getBatchSize()); + System.out.println("=====> Epoch= " + epoch // + + "/" + hyperParameter.getEpoch()); + + MakeModel makeModels = new MakeModel(); + + var trainDataTemp = batchedData.get(batch); + var trainDateTemp = batchedDate.get(batch); + + CompletableFuture firstTaskFuture = CompletableFuture + // Train the Seasonality model + .supplyAsync(() -> makeModels.trainSeasonality(trainDataTemp, trainDateTemp, hyperParameter)) + + // Validate this Seasonality model + .thenAccept(untestedSeasonalityMoadels -> new ValidationSeasonalityModel().validateSeasonality( + validateData, validateDate, untestedSeasonalityMoadels, hyperParameter)); + + CompletableFuture secondTaskFuture = CompletableFuture + // Train the trend model + .supplyAsync(() -> makeModels.trainTrend(trainDataTemp, trainDateTemp, hyperParameter)) + + // validate the trend model + .thenAccept(untestedSeasonalityMoadels -> new ValidationTrendModel().validateTrend(validateData, + validateDate, untestedSeasonalityMoadels, hyperParameter)); + + k = k + 1; + try { + CompletableFuture.allOf(firstTaskFuture, secondTaskFuture).get(); + } catch (Exception e) { + e.printStackTrace(); + } + + hyperParameter.setBatchTrack(batch + 1); + hyperParameter.setCount(k); + ReadAndSaveModels.save(hyperParameter); + } + + hyperParameter.setBatchTrack(0); + hyperParameter.setEpochTrack(hyperParameter.getEpochTrack() + 1); + hyperParameter.update(); + ReadAndSaveModels.save(hyperParameter); + } + + hyperParameter.setEpochTrack(0); + ReadAndSaveModels.save(hyperParameter); + } +} diff --git a/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/util/AdaptiveLearningRate.java b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/util/AdaptiveLearningRate.java new file mode 100644 index 00000000000..1ddd8325749 --- /dev/null +++ b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/util/AdaptiveLearningRate.java @@ -0,0 +1,47 @@ +package io.openems.edge.predictor.lstm.util; + +import io.openems.edge.predictor.lstm.common.HyperParameters; + +public class AdaptiveLearningRate { + + /** + * Adjusts the learning rate based on the given percentage. * the total + * iterations. + * + * @param hyperParameters An instance of class HyperParameter + * @return The adapted learning rate calculated using a cosine annealing + * strategy. + */ + public double scheduler(HyperParameters hyperParameters) { + var maximum = hyperParameters.getLearningRateUpperLimit(); + var minimum = hyperParameters.getLearningRateLowerLimit(); + var tCurByTmax = (double) hyperParameters.getEpochTrack() / hyperParameters.getEpoch(); + var cosineValue = Math.cos(tCurByTmax * Math.PI); + return (minimum + 0.5 * (maximum - minimum) * (1 + cosineValue)); + } + + /** + * Performs the Adagrad optimization step to adjust the learning rate based on + * the gradient information. + * + * @param globalLearningRate The global learning rate for the optimization + * process. + * @param localLearningRate The local learning rate, which is dynamically + * adjusted during the optimization. + * @param gradient The gradient value computed during the + * optimization. + * @param iteration The iteration number, used to determine if this is + * the first iteration. + * @return The adapted learning rate based on the Adagrad optimization strategy. + */ + public double adagradOptimizer(double globalLearningRate, double localLearningRate, double gradient, + int iteration) { + if (iteration == 0 || localLearningRate == 0 || (globalLearningRate == 0 && gradient == 0)) { + return globalLearningRate; + } + + double adjustedRate = Math.pow(globalLearningRate / localLearningRate, 2) // + + Math.pow(gradient, 2); + return globalLearningRate / Math.sqrt(adjustedRate); + } +} diff --git a/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/util/Cell.java b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/util/Cell.java new file mode 100644 index 00000000000..14da87f1a42 --- /dev/null +++ b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/util/Cell.java @@ -0,0 +1,315 @@ +package io.openems.edge.predictor.lstm.util; + +import java.util.Random; + +import io.openems.edge.predictor.lstm.utilities.MathUtils; + +public class Cell { + + private double error; + private double wI; + private double wO; + private double wZ; + + private double rI; + private double rO; + private double rZ; + + private double yT; + private double ytMinusOne; + + private double cT; + private double ctMinusOne; + private double oT; + private double zT; + + private double iT; + private double dlByDy; + private double dlByDo; + private double dlByDc; + private double dlByDi; + private double dlByDz; + private double delI; + private double delO; + private double delZ; + + private double xT; + private double outputDataLoc; + + private double delF; + + public Cell(double xt, double outputData) { + this(xt, outputData, 1, 1, 1, 1, 1, 1, 0); + } + + public Cell(double xt, double outputData, double wI, double wO, double wZ, double rI, double rO, double rZ, + double yT) { + this.dlByDc = 0; + this.error = 0; + this.wI = wI; + this.wO = wO; + this.wZ = wZ; + this.rI = rI; + this.rO = rO; + this.rZ = rZ; + this.cT = 0; + this.oT = 0; + this.zT = 0; + this.yT = 0; + this.ytMinusOne = 0; + this.ctMinusOne = 0; + this.ytMinusOne = this.yT; + this.dlByDy = 0; + this.dlByDo = 0; + this.dlByDc = 0; + this.dlByDi = 0; + this.dlByDz = 0; + this.delI = 0; + this.delO = 0; + this.delZ = 0; + this.iT = 0; + this.xT = xt; + this.outputDataLoc = outputData; + } + + /** + * Forward propagation. + */ + public void forwardPropogation() { + double dropOutProb; + boolean decissionFlag = this.decisionDropout(); + if (decissionFlag) { + dropOutProb = 0.0; + this.iT = MathUtils.sigmoid(this.wI * this.xT + this.rI * this.ytMinusOne); + this.oT = MathUtils.sigmoid(this.wO * this.xT + this.rO * this.ytMinusOne); + this.zT = MathUtils.tanh(this.wZ * this.xT + this.rZ * this.ytMinusOne); + this.cT = this.ctMinusOne + this.iT * this.zT * dropOutProb; + this.yT = this.ytMinusOne * (1 - dropOutProb) + this.oT * MathUtils.tanh(this.cT) * dropOutProb; + this.error = Math.abs(this.yT - this.outputDataLoc) / Math.sqrt(2); + } else { + this.iT = MathUtils.sigmoid(this.wI * this.xT + this.rI * this.ytMinusOne); + this.oT = MathUtils.sigmoid(this.wO * this.xT + this.rO * this.ytMinusOne); + this.zT = MathUtils.tanh(this.wZ * this.xT + this.rZ * this.ytMinusOne); + this.cT = this.ctMinusOne + this.iT * this.zT; + this.yT = this.oT * MathUtils.tanh(this.cT); + this.error = Math.abs(this.yT - this.outputDataLoc) / Math.sqrt(2); + } + } + + /** + * Backward propagation. + */ + public void backwardPropogation() { + this.dlByDy = this.error; + this.dlByDo = this.dlByDy * MathUtils.tanh(this.cT); + this.dlByDc = this.dlByDy * this.oT * MathUtils.tanhDerivative(this.cT) + this.dlByDc; + this.dlByDi = this.dlByDc * this.zT; + this.dlByDz = this.dlByDc * this.iT; + this.delI = this.dlByDi * MathUtils.sigmoidDerivative(this.wI * this.xT + this.rI * this.ytMinusOne); + this.delO = this.dlByDo * MathUtils.sigmoidDerivative(this.wO * this.xT + this.rO * this.ytMinusOne); + this.delZ = this.dlByDz * MathUtils.tanhDerivative(this.wZ * this.xT + this.rZ * this.ytMinusOne); + } + + /** + * Generates a random decision with dropout probability. This method generates a + * random boolean decision with a dropout probability of 10%. It uses a random + * number generator to determine whether the decision is true or false. The + * probability of returning true is 10%, and the probability of returning false + * is 90%. + * + * @return true with a 10% probability, false with a 90% probability. + */ + public boolean decisionDropout() { + Random random = new Random(); + int randomNumber = random.nextInt(10) + 1; + if (randomNumber > 7) { + return true; + } + return false; + } + + public double getError() { + return this.error; + } + + public void setError(double error) { + this.error = error; + } + + public double getWi() { + return this.wI; + } + + public void setWi(double wi) { + this.wI = wi; + } + + public double getWo() { + return this.wO; + } + + public void setWo(double wo) { + this.wO = wo; + } + + public double getWz() { + return this.wZ; + } + + public void setWz(double wz) { + this.wZ = wz; + } + + public double getRi() { + return this.rI; + } + + public void setRi(double ri) { + this.rI = ri; + } + + public double getRo() { + return this.rO; + } + + public void setRo(double ro) { + this.rO = ro; + } + + public double getRz() { + return this.rZ; + } + + public void setRz(double rz) { + this.rZ = rz; + } + + public double getCt() { + return this.cT; + } + + public void setCt(double ct) { + this.cT = ct; + } + + public double getCtMinusOne() { + return this.ctMinusOne; + } + + public void setCtMinusOne(double ct) { + this.ctMinusOne = ct; + } + + public double getYtMinusOne() { + return this.ytMinusOne; + } + + public void setYtMinusOne(double yt) { + this.ytMinusOne = yt; + } + + public double getYt() { + return this.yT; + } + + public void setYt(double yt) { + this.yT = yt; + } + + public void setIt(double iT) { + this.iT = iT; + } + + public double getIt() { + return this.iT; + } + + public double getOt() { + return this.oT; + } + + public double getZt() { + return this.zT; + } + + public void setDlByDy(double dlByDy) { + this.dlByDy = dlByDy; + } + + public double getDlByDy() { + return this.dlByDy; + } + + public void setDlByDo(double dlByDo) { + this.dlByDo = dlByDo; + } + + public double getDlByDo() { + return this.dlByDo; + } + + public void setDlByDc(double dlByDc) { + this.dlByDc = dlByDc; + } + + public double getDlByDc() { + + return this.dlByDc; + } + + public void setDlByDi(double dlByDi) { + this.dlByDi = dlByDi; + } + + public double getDlByDi() { + return this.dlByDi; + } + + public void setDlByDz(double dlByDz) { + this.dlByDz = dlByDz; + } + + public double getDlByDz() { + return this.dlByDz; + } + + public void setDelI(double delI) { + this.delI = delI; + } + + public double getDelI() { + return this.delI; + } + + public void setDelO(double delO) { + this.delO = delO; + } + + public double getDelF() { + return this.delF; + } + + public void setDelF(double delF) { + this.delF = delF; + } + + public double getDelO() { + return this.delO; + } + + public void setDelZ(double delZ) { + this.delZ = delZ; + } + + public double getDelZ() { + return this.delZ; + } + + public void setXt(double xt) { + this.xT = xt; + } + + public double getXt() { + return this.xT; + } +} diff --git a/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/util/Engine.java b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/util/Engine.java new file mode 100644 index 00000000000..937e0b6d02a --- /dev/null +++ b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/util/Engine.java @@ -0,0 +1,258 @@ +package io.openems.edge.predictor.lstm.util; + +import static io.openems.edge.predictor.lstm.preprocessing.DataModification.scaleBack; +import static io.openems.edge.predictor.lstm.utilities.MathUtils.sigmoid; +import static io.openems.edge.predictor.lstm.utilities.MathUtils.tanh; +import static io.openems.edge.predictor.lstm.utilities.UtilityConversion.getMinIndex; + +import java.util.ArrayList; + +import io.openems.edge.predictor.lstm.common.DataStatistics; +import io.openems.edge.predictor.lstm.common.HyperParameters; +import io.openems.edge.predictor.lstm.util.Lstm.LstmBuilder; + +public class Engine { + + private double[][] inputMatrix; + private double[] targetVector; + private double[][] validateData; + private double[] validateTarget; + private double learningRate; + + private final ArrayList>> weights = new ArrayList>>(); + private final ArrayList> finalWeights = new ArrayList>(); + + /** + * This method train the LSTM network. and Update the finalWeight matrix. + * + * @param epochs Number of times the forward and backward propagation. + * @param val are the weights. + * @param hyperParameters An instance of class HyperParameter + * + */ + public void fit(int epochs, ArrayList> val, HyperParameters hyperParameters) { + var rate = new AdaptiveLearningRate(); + + this.learningRate = rate.scheduler(hyperParameters); + + // First Time default LSTM object + var ls = new LstmBuilder(this.inputMatrix[0], this.targetVector[0])// + .setLearningRate(this.learningRate) // + .setEpoch(epochs)// + .build(); + + ls.initilizeCells(); + + ls.setWi(val); + ls.setWo(val); + ls.setWz(val); + ls.setRi(val); + ls.setRo(val); + ls.setRz(val); + ls.setCt(val); + ls.setYt(val); + + var wieghtMatrix = ls.train(); + + this.weights.add(wieghtMatrix); + + for (int i = 1; i < this.inputMatrix.length; i++) { + + this.learningRate = rate.scheduler(hyperParameters); + // Update the Lstm + ls = new LstmBuilder(this.inputMatrix[i], this.targetVector[i])// + .setLearningRate(this.learningRate) // + .setEpoch(epochs) // + .build(); + + ls.initilizeCells(); + + for (int j = 0; j < ls.cells.size(); j++) { + + ls.cells.get(j).setWi((wieghtMatrix.get(0)).get(j)); + ls.cells.get(j).setWo((wieghtMatrix.get(1)).get(j)); + ls.cells.get(j).setWz((wieghtMatrix.get(2)).get(j)); + ls.cells.get(j).setRi((wieghtMatrix.get(3)).get(j)); + ls.cells.get(j).setRo((wieghtMatrix.get(4)).get(j)); + ls.cells.get(j).setRz((wieghtMatrix.get(5)).get(j)); + ls.cells.get(j).setYtMinusOne(wieghtMatrix.get(6).get(j)); + ls.cells.get(j).setCtMinusOne(wieghtMatrix.get(7).get(j)); + } + + wieghtMatrix = ls.train(); + this.weights.add(wieghtMatrix); + + } + + } + + /** + * Predict using the model and the input data. + * + * @param inputData input data for the prediction. + * @param hyperParameter is the object of class HyperParameter + * @return result + */ + public double[] predict(double[][] inputData, HyperParameters hyperParameter) { + var result = new double[inputData.length]; + for (int i = 0; i < inputData.length; i++) { + + result[i] = this.singleValuePredict(inputData[i], // + this.finalWeights.get(0), // + this.finalWeights.get(1), // + this.finalWeights.get(2), // + this.finalWeights.get(3), // + this.finalWeights.get(4), // + this.finalWeights.get(5), // + this.finalWeights.get(6), // + this.finalWeights.get(7), // + hyperParameter); + } + return result; + } + + /** + * Validate to get the best model. + * + * @param inputData double array + * @param target double array + * @param val weight matrix + * @param hyperParameter An instance of class HyperParameter + * @return The resulted weight matrix + */ + public double[] validate(double[][] inputData, double[] target, ArrayList> val, + HyperParameters hyperParameter) { + var result = new double[inputData.length]; + for (int i = 0; i < inputData.length; i++) { + + result[i] = this.singleValuePredict(inputData[i], // + val.get(0), // + val.get(1), // + val.get(2), // + val.get(3), // + val.get(4), // + val.get(5), // + val.get(6), // + val.get(7), // + hyperParameter); + } + + return result; + } + + /** + * Takes in an array of inputData and predicts single value. + * + * @param inputData double array + * @param wi weight wi + * @param wo weight wo + * @param wz weight wz + * @param Ri weight Ri + * @param Ro weight Ro + * @param Rz weight Rz + * @param ctV vector containing cell state + * @param ytV vector containing cell output + * + * @param hyperParameter An instance of class HyperParameter + * @return The predicted single double value + */ + private double singleValuePredict(double[] inputData, // + ArrayList wi, // + ArrayList wo, // + ArrayList wz, // + ArrayList Ri, // + ArrayList Ro, // + ArrayList Rz, // + ArrayList ytV, // + ArrayList ctV, // + HyperParameters hyperParameter) { + var ct = 0.; + var ctMinusOne = 0.; + var yt = 0.; + var standData = inputData; + + for (int i = 0; i < wi.size(); i++) { + ctMinusOne = ctV.get(i); + double it = sigmoid(wi.get(i) * standData[i] + Ri.get(i) * yt); + double ot = sigmoid(wo.get(i) * standData[i] + Ro.get(i) * yt); + double zt = tanh(wz.get(i) * standData[i] + Rz.get(i) * yt); + ct = ctMinusOne + it * zt; + yt = ot * tanh(ct); + } + return scaleBack(yt, hyperParameter.getScalingMin(), hyperParameter.getScalingMax()); + } + + /** + * Select Best Weight out of all the Weights. + * + * @param wightMatrix All the matrices of the weight. + * @param hyperParameter is the object of class HyperParameter + * @return index index of the best matrix. + */ + public int selectWeight(ArrayList>> wightMatrix, HyperParameters hyperParameter) { + var rms = new double[wightMatrix.size()]; + + for (int k = 0; k < wightMatrix.size(); k++) { + var val = wightMatrix.get(k); + var pre = this.validate(this.validateData, this.validateTarget, val, hyperParameter); + rms[k] = DataStatistics.computeRms(this.validateTarget, pre); + } + var minInd = getMinIndex(rms); + return minInd; + } + + public Engine(EngineBuilder builder) { + this.inputMatrix = builder.inputMatrix; + this.targetVector = builder.targetVector; + this.validateData = builder.validateData; + this.validateTarget = builder.validateTarget; + + } + + public static class EngineBuilder { + private double[][] inputMatrix; + private double[] targetVector; + private double[][] validateData; + private double[] validateTarget; + + public EngineBuilder(double[][] inputMatrix, double[] targetVector, double[][] validateData, + double[] validateTarget, int validatorCounter) { + this.inputMatrix = inputMatrix; + this.targetVector = targetVector; + this.validateData = validateData; + this.validateTarget = validateTarget; + } + + public EngineBuilder() { + } + + public EngineBuilder setInputMatrix(double[][] inputMatrix) { + this.inputMatrix = inputMatrix; + return this; + } + + public EngineBuilder setTargetVector(double[] targetVector) { + this.targetVector = targetVector; + return this; + } + + public EngineBuilder setValidateData(double[][] validateData) { + this.validateData = validateData; + return this; + } + + public EngineBuilder setValidateTarget(double[] validateTarget) { + this.validateTarget = validateTarget; + return this; + } + + public Engine build() { + return new Engine(this); + } + } + + public ArrayList>> getWeights() { + return this.weights; + } + +} diff --git a/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/util/Lstm.java b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/util/Lstm.java new file mode 100644 index 00000000000..7c495d750db --- /dev/null +++ b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/util/Lstm.java @@ -0,0 +1,356 @@ +package io.openems.edge.predictor.lstm.util; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.stream.IntStream; + +public class Lstm { + + private double[] inputData; + private double outputData; + private double derivativeLWrtRi = 0; + private double derivativeLWrtRo = 0; + private double derivativeLWrtRz = 0; + private double derivativeLWrtWi = 0; + private double derivativeLWrtWo = 0; + private double derivativeLWrtWz = 0; + private double learningRate; + private int epoch = 100; + + protected ArrayList cells; + + public Lstm(LstmBuilder builder) { + this.inputData = builder.inputData; + this.outputData = builder.outputData; + this.learningRate = builder.learningRate; + this.epoch = builder.epoch; + } + + /** + * Forward propagation. + */ + public void forwardprop() { + try { + for (int i = 0; i < this.cells.size(); i++) { + this.cells.get(i).forwardPropogation(); + if (i < this.cells.size() - 1) { + this.cells.get(i + 1).setYtMinusOne(this.cells.get(i).getYt()); + this.cells.get(i + 1).setCtMinusOne(this.cells.get(i).getCt()); + this.cells.get(i).setError( + (Math.abs(this.cells.get(i).getYt() - this.cells.get(i + 1).getXt()) / Math.sqrt(2))); + } + } + } catch (IndexOutOfBoundsException e) { + e.printStackTrace(); + } + } + + /** + * Backward propagation. + */ + public void backwardprop() { + var gradients = new ArrayList(); + + for (int i = this.cells.size() - 1; i >= 0; i--) { + if (i < this.cells.size() - 1) { + this.cells.get(i).setDlByDc(this.cells.get(i + 1).getDlByDc()); + } + this.cells.get(i).backwardPropogation(); + } + + for (int i = 0; i < this.cells.size(); i++) { + this.derivativeLWrtRi += this.cells.get(i).getYtMinusOne() * this.cells.get(i).getDelI(); + this.derivativeLWrtRo += this.cells.get(i).getYtMinusOne() * this.cells.get(i).getDelO(); + this.derivativeLWrtRz += this.cells.get(i).getYtMinusOne() * this.cells.get(i).getDelZ(); + + this.derivativeLWrtWi += this.cells.get(i).getXt() * this.cells.get(i).getDelI(); + this.derivativeLWrtWo += this.cells.get(i).getXt() * this.cells.get(i).getDelO(); + this.derivativeLWrtWz += this.cells.get(i).getXt() * this.cells.get(i).getDelZ(); + + // localLearningRate1 = rate.adagradOptimizer(this.learningRate, + // localLearningRate1, this.derivativeLWrtWi, i); + // localLearningRate2 = rate.adagradOptimizer(this.learningRate, + // localLearningRate2, this.derivativeLWrtWo, i); + // localLearningRate3 = rate.adagradOptimizer(this.learningRate, + // localLearningRate3, this.derivativeLWrtWz, i); + // localLearningRate4 = rate.adagradOptimizer(this.learningRate, + // localLearningRate4, this.derivativeLWrtRi, i); + // localLearningRate5 = rate.adagradOptimizer(this.learningRate, + // localLearningRate5, this.derivativeLWrtRo, i); + // localLearningRate6 = rate.adagradOptimizer(this.learningRate, + // localLearningRate6, this.derivativeLWrtRz, i); + + // this.cells.get(i).setWi(this.cells.get(i).getWi() - localLearningRate1 * + // this.derivativeLWrtWi); + // this.cells.get(i).setWo(this.cells.get(i).getWo() - localLearningRate2 * + // this.derivativeLWrtWo); + // this.cells.get(i).setWz(this.cells.get(i).getWz() - localLearningRate3 * + // this.derivativeLWrtWz); + // this.cells.get(i).setRi(this.cells.get(i).getRi() - localLearningRate4 * + // this.derivativeLWrtRi); + // this.cells.get(i).setRo(this.cells.get(i).getRo() - localLearningRate5 * + // this.derivativeLWrtRo); + // this.cells.get(i).setRz(this.cells.get(i).getRz() - localLearningRate6 * + // this.derivativeLWrtRz); + } + for (int i = 0; i < this.cells.size(); i++) { + gradients.add(this.derivativeLWrtWi / this.cells.size()); + gradients.add(this.derivativeLWrtWo / this.cells.size()); + gradients.add(this.derivativeLWrtWz / this.cells.size()); + gradients.add(this.derivativeLWrtRi / this.cells.size()); + gradients.add(this.derivativeLWrtRo / this.cells.size()); + gradients.add(this.derivativeLWrtRz / this.cells.size()); + + } + + this.updateweights(gradients); + } + + protected void updateweights(ArrayList gradients) { + var rate = new AdaptiveLearningRate(); + + var localLearningRate1 = 0.; + var localLearningRate2 = 0.; + var localLearningRate3 = 0.; + var localLearningRate4 = 0.; + var localLearningRate5 = 0.; + var localLearningRate6 = 0.; + + for (int i = 0; i < this.cells.size(); i++) { + + localLearningRate1 = rate.adagradOptimizer(this.learningRate, localLearningRate1, gradients.get(0), i); + localLearningRate2 = rate.adagradOptimizer(this.learningRate, localLearningRate2, gradients.get(1), i); + localLearningRate3 = rate.adagradOptimizer(this.learningRate, localLearningRate3, gradients.get(2), i); + localLearningRate4 = rate.adagradOptimizer(this.learningRate, localLearningRate4, gradients.get(3), i); + localLearningRate5 = rate.adagradOptimizer(this.learningRate, localLearningRate5, gradients.get(4), i); + localLearningRate6 = rate.adagradOptimizer(this.learningRate, localLearningRate6, gradients.get(5), i); + + this.cells.get(i).setWi(this.cells.get(i).getWi() - localLearningRate1 * gradients.get(0)); + this.cells.get(i).setWo(this.cells.get(i).getWo() - localLearningRate2 * gradients.get(1)); + this.cells.get(i).setWz(this.cells.get(i).getWz() - localLearningRate3 * gradients.get(2)); + this.cells.get(i).setRi(this.cells.get(i).getRi() - localLearningRate4 * gradients.get(3)); + this.cells.get(i).setRo(this.cells.get(i).getRo() - localLearningRate5 * gradients.get(4)); + this.cells.get(i).setRz(this.cells.get(i).getRz() - localLearningRate6 * gradients.get(5)); + } + + } + + /** + * Train to get the weight matrix. + * + * @return weight matrix trained weight matrix + */ + public ArrayList> train() { + var mW = new MatrixWeight(); + for (int i = 0; i < this.epoch; i++) { + + this.forwardprop(); + this.backwardprop(); + + var wiList = new ArrayList(); + var woList = new ArrayList(); + var wzList = new ArrayList(); + var riList = new ArrayList(); + var roList = new ArrayList(); + var rzList = new ArrayList(); + var ytList = new ArrayList(); + var ctList = new ArrayList(); + + for (int j = 0; j < this.cells.size(); j++) { + wiList.add(this.cells.get(j).getWi()); // + woList.add(this.cells.get(j).getWo()); // + wzList.add(this.cells.get(j).getWz()); // + riList.add(this.cells.get(j).getRi()); // + roList.add(this.cells.get(j).getRo()); // + rzList.add(this.cells.get(j).getRz()); // + ytList.add(this.cells.get(j).getYt()); // + ctList.add(this.cells.get(j).getCt()); // + } + + mW.getErrorList().add(this.cells.get(this.cells.size() - 1).getError()); + mW.getWi().add(wiList); + mW.getWo().add(woList); + mW.getWz().add(wzList); + mW.getRi().add(riList); + mW.getRo().add(roList); + mW.getRz().add(rzList); + mW.getOut().add(ytList); + mW.getCt().add(ctList); + } + + int globalMinimaIndex = findGlobalMinima(mW.getErrorList()); + + var returnArray = new ArrayList>(); + + returnArray.add(mW.getWi().get(globalMinimaIndex)); + returnArray.add(mW.getWo().get(globalMinimaIndex)); + returnArray.add(mW.getWz().get(globalMinimaIndex)); + returnArray.add(mW.getRi().get(globalMinimaIndex)); + returnArray.add(mW.getRo().get(globalMinimaIndex)); + returnArray.add(mW.getRz().get(globalMinimaIndex)); + returnArray.add(mW.getOut().get(globalMinimaIndex)); + returnArray.add(mW.getCt().get(globalMinimaIndex)); + + return returnArray; + } + + /** + * Get the index of the Global minima. element arr.get(index x) is a local + * minimum if it is less than both its neighbors and an arr can have multiple + * local minima. + * + * @param data {@link java.util.ArrayList} of double + * @return index index of the global minima in the data + */ + public static int findGlobalMinima(ArrayList data) { + return IntStream.range(0, data.size())// + .boxed()// + .min(Comparator.comparingDouble(i -> Math.abs(data.get(i))))// + .orElse(-1); + } + + public double[] getInputData() { + return this.inputData; + } + + public double getOutputData() { + return this.outputData; + } + + public double getDerivativeLWrtRi() { + return this.derivativeLWrtRi; + } + + public double getDerivativeLWrtRo() { + return this.derivativeLWrtRo; + } + + public double getDerivativeLWrtRz() { + return this.derivativeLWrtRz; + } + + public double getDerivativeLWrtWi() { + return this.derivativeLWrtWi; + } + + public double getDerivativeLWrtWo() { + return this.derivativeLWrtWo; + } + + public double getDerivativeLWrtWz() { + return this.derivativeLWrtWz; + } + + public double getLearningRate() { + return this.learningRate; + } + + public ArrayList getCells() { + return this.cells; + } + + public synchronized void setWi(ArrayList> val) { + for (int i = 0; i < val.get(0).size(); i++) { + try { + this.cells.get(i).setWi(val.get(0).get(i)); + } catch (ArithmeticException | IndexOutOfBoundsException e) { + e.printStackTrace(); + } + } + } + + public synchronized void setWo(ArrayList> val) { + for (int i = 0; i < val.get(1).size(); i++) { + this.cells.get(i).setWo(val.get(1).get(i)); + } + } + + public synchronized void setWz(ArrayList> val) { + for (int i = 0; i < val.get(2).size(); i++) { + this.cells.get(i).setWz(val.get(2).get(i)); + } + } + + public synchronized void setRi(ArrayList> val) { + for (int i = 0; i < val.get(3).size(); i++) { + this.cells.get(i).setRi(val.get(3).get(i)); + } + } + + public synchronized void setRo(ArrayList> val) { + for (int i = 0; i < val.get(4).size(); i++) { + this.cells.get(i).setRo(val.get(4).get(i)); + } + } + + public synchronized void setRz(ArrayList> val) { + for (int i = 0; i < val.get(5).size(); i++) { + this.cells.get(i).setRz(val.get(5).get(i)); + } + } + + public synchronized void setYt(ArrayList> val) { + for (int i = 0; i < val.get(6).size(); i++) { + this.cells.get(i).setYt(val.get(6).get(i)); + } + } + + public synchronized void setCt(ArrayList> val) { + for (int i = 0; i < val.get(7).size(); i++) { + this.cells.get(i).setCt(val.get(7).get(i)); + } + } + + /** + * Please build the model with input and target. + * + */ + public static class LstmBuilder { + + protected double[] inputData; + protected double outputData; + + protected double learningRate; // + protected int epoch = 100; // + + public LstmBuilder(double[] inputData, double outputData) { + this.inputData = inputData; + this.outputData = outputData; + } + + public LstmBuilder setInputData(double[] inputData) { + this.inputData = inputData; + return this; + } + + public LstmBuilder setOutputData(double outputData) { + this.outputData = outputData; + return this; + } + + public LstmBuilder setLearningRate(double learningRate) { + this.learningRate = learningRate; + return this; + } + + public LstmBuilder setEpoch(int epoch) { + this.epoch = epoch; + return this; + } + + public Lstm build() { + return new Lstm(this); + } + } + + /** + * Initializes the cell with the default data. + */ + public synchronized void initilizeCells() { + this.cells = new ArrayList<>(); + for (int i = 0; i < this.inputData.length; i++) { + Cell cell = new Cell(this.inputData[i], this.outputData); + this.cells.add(cell); + } + } +} diff --git a/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/util/MatrixWeight.java b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/util/MatrixWeight.java new file mode 100644 index 00000000000..df542345861 --- /dev/null +++ b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/util/MatrixWeight.java @@ -0,0 +1,63 @@ +package io.openems.edge.predictor.lstm.util; + +import java.util.ArrayList; + +public class MatrixWeight { + + private final ArrayList> wI = new ArrayList>(); + private final ArrayList> wO = new ArrayList>(); + private final ArrayList> wZ = new ArrayList>(); + private final ArrayList> wF = new ArrayList>(); + private final ArrayList> rI = new ArrayList>(); + private final ArrayList> rO = new ArrayList>(); + private final ArrayList> rZ = new ArrayList>(); + private final ArrayList> rF = new ArrayList>(); + + private final ArrayList> out = new ArrayList>(); + private final ArrayList> cT = new ArrayList>(); + private final ArrayList errorList = new ArrayList(); + + public ArrayList> getWi() { + return this.wI; + } + + public ArrayList> getWo() { + return this.wO; + } + + public ArrayList> getWz() { + return this.wZ; + } + + public ArrayList> getRi() { + return this.rI; + } + + public ArrayList> getRo() { + return this.rO; + } + + public ArrayList> getRz() { + return this.rZ; + } + + public ArrayList> getOut() { + return this.out; + } + + public ArrayList> getCt() { + return this.cT; + } + + public ArrayList getErrorList() { + return this.errorList; + } + + public ArrayList> getWf() { + return this.wF; + } + + public ArrayList> getRf() { + return this.rF; + } +} diff --git a/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/utilities/DataUtility.java b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/utilities/DataUtility.java new file mode 100644 index 00000000000..844cfe07632 --- /dev/null +++ b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/utilities/DataUtility.java @@ -0,0 +1,124 @@ +package io.openems.edge.predictor.lstm.utilities; + +import java.time.OffsetDateTime; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Objects; +import java.util.SortedMap; +import java.util.stream.Collectors; + +import com.google.gson.JsonElement; + +import io.openems.common.types.ChannelAddress; +import io.openems.edge.predictor.lstm.common.HyperParameters; + +public class DataUtility { + + /** + * Extracts data values. + * + * @param queryResult The SortedMap queryResult. + * @return An ArrayList of Double values extracted from non-null JsonElement + * values. + */ + public static ArrayList getData( + SortedMap> queryResult) { + var data = new ArrayList(); + + queryResult.values().stream()// + .map(SortedMap::values)// + .flatMap(Collection::stream)// + .map(v -> { + if (v.isJsonNull()) { + return null; + } + return v.getAsDouble(); + }).forEach(value -> data.add(value)); + + // TODO remove this later + if (isAllNulls(data)) { + System.out.println("Data is all null, use a different predictor"); + } + + return data; + } + + /** + * Checks if all elements in an ArrayList are null. + * + * @param array The ArrayList to be checked. + * @return true if all elements in the ArrayList are null, false otherwise. + */ + private static boolean isAllNulls(ArrayList array) { + return array.stream() // + .allMatch(Objects::isNull); + } + + /** + * Combines trend and seasonality predictions into a single list of values. + * + * @param trendPrediction The list of predicted trend values. + * @param seasonalityPrediction The list of predicted seasonality values. + * @return A combined list containing both trend and seasonality predictions. + */ + public static ArrayList combine(ArrayList trendPrediction, + ArrayList seasonalityPrediction) { + for (int l = 0; l < trendPrediction.size(); l++) { + seasonalityPrediction.set(l, trendPrediction.get(l)); + } + return seasonalityPrediction; + } + + /** + * Concatenates two lists of {@code Double} values into a single list. + * + * + * @param list1 the first list of {@code Double} values, may be {@code null} + * @param list2 the second list of {@code Double} values, may be {@code null} + * @return a new {@link ArrayList} containing all elements from both input + * lists, or an empty list if both inputs are {@code null} + */ + public static ArrayList concatenateList(ArrayList list1, ArrayList list2) { + ArrayList result = new ArrayList<>(); + if (list1 != null) { + result.addAll(list1); + } + + if (list2 != null) { + result.addAll(list2); + } + + return result; + } + + /** + * Extracts OffsetDateTime objects from the keys of a SortedMap containing + * ZonedDateTime keys. + * + * @param queryResult The SortedMap containing ZonedDateTime keys and associated + * data. + * @return An ArrayList of OffsetDateTime objects extracted from the + * ZonedDateTime keys. + */ + public static ArrayList getDate( + SortedMap> queryResult) { + return queryResult.keySet().stream()// + .map(ZonedDateTime::toOffsetDateTime)// + .collect(Collectors.toCollection(ArrayList::new)); + } + + /** + * Get minute. + * + * @param nowDate the now date + * @param hyperParameters the hyperparameter + * @return int minute + */ + public static Integer getMinute(ZonedDateTime nowDate, HyperParameters hyperParameters) { + int interval = hyperParameters.getInterval(); + int minute = nowDate.getMinute(); + return (int) (minute / interval) * interval; + } + +} diff --git a/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/utilities/MathUtils.java b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/utilities/MathUtils.java new file mode 100644 index 00000000000..27e7a860799 --- /dev/null +++ b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/utilities/MathUtils.java @@ -0,0 +1,45 @@ +package io.openems.edge.predictor.lstm.utilities; + +public class MathUtils { + + /** + * Returns the hyperbolic tangent of a double value. + * + * @param val double value + * @return The hyperbolic tangent of double value + */ + public static double tanh(double val) { + return Math.tanh(val); + } + + /** + * Returns the sigmoid of a double value. + * + * @param val double value + * @return The sigmoid of a double value + */ + public static double sigmoid(double val) { + return 1 / (1 + Math.pow(Math.E, -val)); + } + + /** + * Returns the sigmoid derivative of a double value. + * + * @param val double value + * @return The sigmoid derivative of a double value + */ + public static double sigmoidDerivative(double val) { + return sigmoid(val) * (1 - sigmoid(val)); + } + + /** + * Returns the tanh derivative of a double value. + * + * @param val double value + * @return The tanh derivative of a double value + */ + public static double tanhDerivative(double val) { + return 1 - Math.pow(tanh(val), 2); + } + +} diff --git a/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/utilities/SlidingWindowSpliterator.java b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/utilities/SlidingWindowSpliterator.java new file mode 100644 index 00000000000..cd145dbc428 --- /dev/null +++ b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/utilities/SlidingWindowSpliterator.java @@ -0,0 +1,79 @@ +package io.openems.edge.predictor.lstm.utilities; + +import java.util.ArrayDeque; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.Objects; +import java.util.Queue; +import java.util.Spliterator; +import java.util.function.Consumer; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +public class SlidingWindowSpliterator implements Spliterator> { + + /** + * Creates windows. + * + * @param generic data type + * @param stream Collection + * @param windowSize size of the window + * @return result List of List + */ + public static Stream> windowed(Collection stream, int windowSize) { + return StreamSupport.stream(new SlidingWindowSpliterator<>(stream, windowSize), false); + } + + private final Queue buffer; + private final Iterator sourceIterator; + private final int windowSize; + private final int size; + + private SlidingWindowSpliterator(Collection source, int windowSize) { + this.buffer = new ArrayDeque<>(windowSize); + this.sourceIterator = Objects.requireNonNull(source).iterator(); + this.windowSize = windowSize; + this.size = calculateSize(source, windowSize); + } + + @SuppressWarnings("unchecked") + @Override + public boolean tryAdvance(Consumer> action) { + if (this.windowSize < 1) { + return false; + } + + while (this.sourceIterator.hasNext()) { + this.buffer.add(this.sourceIterator.next()); + + if (this.buffer.size() == this.windowSize) { + action.accept(Arrays.stream((T[]) this.buffer.toArray(new Object[0]))); + this.buffer.poll(); + return true; + } + } + + return false; + } + + @Override + public Spliterator> trySplit() { + return null; + } + + @Override + public long estimateSize() { + return this.size; + } + + @Override + public int characteristics() { + return ORDERED | NONNULL | SIZED; + } + + private static int calculateSize(Collection source, int windowSize) { + return source.size() < windowSize ? 0 : source.size() - windowSize + 1; + } + +} diff --git a/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/utilities/UtilityConversion.java b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/utilities/UtilityConversion.java new file mode 100644 index 00000000000..2c7d3cc0d7a --- /dev/null +++ b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/utilities/UtilityConversion.java @@ -0,0 +1,232 @@ +package io.openems.edge.predictor.lstm.utilities; + +import static java.util.stream.Collectors.toCollection; +import static java.util.stream.Collectors.toList; +import static java.util.stream.IntStream.range; + +import java.time.OffsetDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.stream.DoubleStream; + +public class UtilityConversion { + + /** + * Convert {@link java.util.ArrayList} to double[][]. + * + * @param data {@link java.util.ArrayList} double + * @return result converted double [][] + */ + public static double[][] to2DArray(ArrayList> data) { + return data.stream() // + .map(UtilityConversion::to1DArray) // + .toArray(double[][]::new); + } + + /** + * Convert {@link java.util.List} of double. + * + * @param data {@link java.util.List} of Double + * @return result converted double [][] + */ + public static double[][] to2DArray(List> data) { + return data.stream() // + .map(UtilityConversion::to1DArray) // + .toArray(double[][]::new); + } + + /** + * Convert {@link java.util.List} of double to double[]. + * + * @param data {@link java.util.List} of double + * @return result converted double [] + */ + public static double[] to1DArray(List data) { + return data.stream() // + .mapToDouble(d -> { + if (d == null || d.isNaN() || Double.isNaN(d)) { + return Double.NaN; + } + return d.doubleValue(); + }).toArray(); + } + + /** + * Convert {@link java.util.List} of {@link OffsetDateTime} to + * {@link OffsetDateTime}[]. + * + * @param data {@link java.util.List} of {@link OffsetDateTime} + * @return result converted {@link OffsetDateTime} [] + */ + public static OffsetDateTime[] to1DArray(ArrayList data) { + return data.stream() // + .toArray(OffsetDateTime[]::new); + } + + /** + * Converts an ArrayList of Double values to an array of Integer values. + * + * @param data The ArrayList of Double values to be converted. + * @return An array of Integer values where each element represents the + * converted value from the input ArrayList. + */ + public static Integer[] toInteger1DArray(ArrayList data) { + return data.stream() // + .mapToInt(d -> d.intValue())// + .boxed()// + .toArray(Integer[]::new); + } + + /** + * Converts a three-dimensional ArrayList of Double values into a + * three-dimensional array. + * + * @param data The three-dimensional ArrayList to be converted. + * @return A three-dimensional array containing the elements of the input + * ArrayList. + */ + public static double[][][] to3DArray(ArrayList>> data) { + double[][][] returnArray = new double[data.size()][][]; + for (int i = 0; i < data.size(); i++) { + returnArray[i] = to2DArray(data.get(i)); + } + return returnArray; + } + + /** + * Converts a three-dimensional array of Double values into a two-dimensional + * array. This method converts the input three-dimensional array into a + * three-dimensional ArrayList, then into a two-dimensional ArrayList, and + * finally into a two-dimensional array. + * + * @param data The three-dimensional array to be converted.s + * @return A two-dimensional array containing the elements of the input array. + */ + public static double[][] to2DList(double[][][] data) { + return to2DArray(to2DArrayList(to3DArrayList(data))); + } + + /** + * Converts a two-dimensional array of double values to a two-dimensional + * ArrayList of Double values. + * + * @param data The two-dimensional array of double values to be converted. + * @return A two-dimensional ArrayList of Double values representing the + * converted data. + */ + public static ArrayList> to2DArrayList(double[][] data) { + var result = new ArrayList>(); + for (int i = 0; i < data.length; i++) { + var temp = new ArrayList(); + for (int j = 0; j < data[i].length; j++) { + temp.add(data[i][j]); + + } + result.add(temp); + } + return result; + } + + /** + * Converts a three-dimensional ArrayList of Double values into a + * two-dimensional ArrayList. + * + * @param data The three-dimensional ArrayList to be converted. + * @return A two-dimensional ArrayList containing the elements of the input + * ArrayList. + */ + public static ArrayList> to2DArrayList(ArrayList>> data) { + var resized = new ArrayList>(); + + for (int i = 0; i < data.size(); i++) { + for (int j = 0; j < data.get(i).size(); j++) { + resized.add(data.get(i).get(j)); + } + } + return resized; + } + + /** + * Convert double[] to {@link java.util.ArrayList} of Double. + * + * @param toBeConverted array of double + * @return result converted Array list + */ + public static ArrayList to1DArrayList(double[] toBeConverted) { + return DoubleStream.of(toBeConverted) // + .boxed() // + .collect(toCollection(ArrayList::new)); + } + + /** + * Convert double[] to {@link java.util.ArrayList} of OffsetDateTime. + * + * @param toBeConverted array of OffsetDateTime + * @return result converted Array list + */ + public static ArrayList to1DArrayList(OffsetDateTime[] toBeConverted) { + return Arrays.stream(toBeConverted) // + .collect(toCollection(ArrayList::new)); + } + + /** + * convert 2DArrayList To 1DArray. + * + * @param data the data + * @return converted the converted + */ + public static ArrayList to1DArrayList(ArrayList> data) { + return data.stream()// + .flatMap(Collection::stream)// + .collect(toCollection(ArrayList::new)); + } + + /** + * Convert {@link java.util.List} of Integer to {@link java.util.List} of + * Double. + * + * @param toBeConverted the {@link java.util.List} of Integer + * @return result {@link java.util.List} of Double. + */ + public static List toBoxed1DList(List toBeConverted) { + return toBeConverted.stream() // + .mapToDouble(i -> i == null ? null : i) // + .boxed() // + .collect(toList()); + } + + /** + * Converts a three-dimensional array into a three-dimensional ArrayList of + * Double values. + * + * @param data The three-dimensional array to be converted. + * @return A three-dimensional ArrayList containing the elements of the input + * array. + */ + public static ArrayList>> to3DArrayList(double[][][] data) { + var returnArray = new ArrayList>>(); + for (int i = 0; i < data.length; i++) { + returnArray.add(to2DArrayList(data[i])); + } + return returnArray; + } + + /** + * Get the index of the Min element in an array. + * + * @param arr double array. + * @return iMin index of the min element in an array. + */ + public static int getMinIndex(double[] arr) { + if (arr == null || arr.length == 0) { + throw new IllegalArgumentException("Array must not be empty or null"); + } + + return range(0, arr.length) // + .boxed() // + .min((i, j) -> Double.compare(arr[i], arr[j])) // + .orElseThrow(); + } +} diff --git a/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/validator/ValidationSeasonalityModel.java b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/validator/ValidationSeasonalityModel.java new file mode 100644 index 00000000000..37a55b85858 --- /dev/null +++ b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/validator/ValidationSeasonalityModel.java @@ -0,0 +1,152 @@ +package io.openems.edge.predictor.lstm.validator; + +import static io.openems.edge.predictor.lstm.performance.PerformanceMatrix.accuracy; +import static io.openems.edge.predictor.lstm.performance.PerformanceMatrix.rmsError; +import static io.openems.edge.predictor.lstm.preprocessing.DataModification.flattern4dto3d; + +import java.time.OffsetDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import io.openems.edge.predictor.lstm.common.DataStatistics; +import io.openems.edge.predictor.lstm.common.HyperParameters; +import io.openems.edge.predictor.lstm.common.LstmPredictor; +import io.openems.edge.predictor.lstm.preprocessing.DataModification; +import io.openems.edge.predictor.lstm.preprocessingpipeline.PreprocessingPipeImpl; +import io.openems.edge.predictor.lstm.utilities.UtilityConversion; + +public class ValidationSeasonalityModel { + + public static final String SEASONALITY = "seasonality"; + + /** + * Validate the Seasonality. + * + * @param values the values + * @param dates the dates + * @param untestedSeasonalityWeight Models to validate. + * @param hyperParameters the hyperParameters + */ + public void validateSeasonality(ArrayList values, ArrayList dates, + ArrayList>>> untestedSeasonalityWeight, + HyperParameters hyperParameters) { + var rmsTemp2 = new ArrayList>(); + + var preProcessing = new PreprocessingPipeImpl(hyperParameters); + double[][][] dataGroupedByMinute = (double[][][]) preProcessing.setData(UtilityConversion.to1DArray(values)) // + .setDates(dates)// + .interpolate()// + .movingAverage()// + .scale()// + .filterOutliers()// + .groupByHoursAndMinutes()// + .execute(); + + var allModels = DataModification.reshape((flattern4dto3d(untestedSeasonalityWeight)), hyperParameters); + + for (int h = 0; h < allModels.size(); h++) { + ArrayList rmsTemp1 = new ArrayList(); + int k = 0; + for (int i = 0; i < dataGroupedByMinute.length; i++) { + for (int j = 0; j < dataGroupedByMinute[i].length; j++) { + + double[][][] intermediate = (double[][][]) preProcessing.setData(dataGroupedByMinute[i][j])// + // .differencing()// + .groupToWIndowSeasonality()// + .execute(); + + double[][][] preProcessed = (double[][][]) preProcessing.setData(intermediate)// + .normalize()// + .shuffle()// + .execute(); + + ArrayList> val = allModels.get(h).get(k); + + double[] result = (double[]) preProcessing// + .setData(UtilityConversion + .to1DArray(LstmPredictor.predictPre(preProcessed[0], val, hyperParameters)))// + .setMean(DataStatistics.getMean(intermediate[0]))// + .setStandardDeviation(DataStatistics.getStandardDeviation(intermediate[0]))// + .reverseNormalize()// + .reverseScale()// + .execute(); + + double rms = rmsError(// + (double[]) preProcessing// + .setData(intermediate[1][0])// + .reverseScale()// + .execute(), + result) // + * // + (1 - accuracy((double[]) preProcessing.setData(intermediate[1][0])// + .reverseScale()// + .execute(), result, 0.01)); + + rmsTemp1.add(rms); + k = k + 1; + + } + + } + rmsTemp2.add(rmsTemp1); + } + List> optInd = findOptimumIndex(rmsTemp2, SEASONALITY, hyperParameters); + + DataModification.updateModel(allModels, optInd, + Integer.toString(hyperParameters.getCount()) + hyperParameters.getModelName() + SEASONALITY, + SEASONALITY, hyperParameters); + } + + /** + * Find the indices of the minimum values in each column of a 2D matrix. This + * method takes a 2D matrix represented as a List of Lists and finds the row + * indices of the minimum values in each column. The result is returned as a + * List of Lists, where each inner list contains two integers: the row index and + * column index of the minimum value. + * + * @param matrix A 2D matrix represented as a List of Lists of doubles. + * @param variable the variable + * @param hyperParameters the hyperParameters + * @return A List of Lists containing the row and column indices of the minimum + * values in each column. If the input matrix is empty, an empty list is + * returned. + */ + + public static List> findOptimumIndex(ArrayList> matrix, String variable, + HyperParameters hyperParameters) { + var minimumIndices = new ArrayList>(); + + if (matrix.isEmpty() || matrix.get(0).isEmpty()) { + return minimumIndices; // Empty matrix, return empty list + } + + int numColumns = matrix.get(0).size(); + + for (int col = 0; col < numColumns; col++) { + double min = matrix.get(0).get(col); + var minIndices = new ArrayList<>(Arrays.asList(0, col)); + + for (int row = 0; row < matrix.size(); row++) { + double value = matrix.get(row).get(col); + + if (value < min) { + min = value; + minIndices.set(0, row); + } + } + + minimumIndices.add(minIndices); + } + + var err = new ArrayList(); + for (int i = 0; i < minimumIndices.size(); i++) { + err.add(matrix.get(minimumIndices.get(i).get(0)).get(minimumIndices.get(i).get(1))); + } + hyperParameters.setAllModelErrorSeason(err); + double errVal = DataStatistics.getStandardDeviation(err, hyperParameters.getTargetError()); + hyperParameters.setRmsErrorSeasonality(errVal); + System.out.println("=====> Average RMS error for " + variable + " = " + errVal); + return minimumIndices; + } +} diff --git a/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/validator/ValidationTrendModel.java b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/validator/ValidationTrendModel.java new file mode 100644 index 00000000000..f8756d8121e --- /dev/null +++ b/io.openems.edge.predictor.lstm/src/io/openems/edge/predictor/lstm/validator/ValidationTrendModel.java @@ -0,0 +1,153 @@ +package io.openems.edge.predictor.lstm.validator; + +import static io.openems.edge.predictor.lstm.preprocessing.DataModification.flattern4dto3d; + +import java.time.OffsetDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import io.openems.edge.predictor.lstm.common.DataStatistics; +import io.openems.edge.predictor.lstm.common.HyperParameters; +import io.openems.edge.predictor.lstm.common.LstmPredictor; +import io.openems.edge.predictor.lstm.performance.PerformanceMatrix; +import io.openems.edge.predictor.lstm.preprocessing.DataModification; +import io.openems.edge.predictor.lstm.preprocessingpipeline.PreprocessingPipeImpl; +import io.openems.edge.predictor.lstm.utilities.UtilityConversion; + +public class ValidationTrendModel { + public static final String TREND = "trend"; + + /** + * Validate Trend. + * + * @param values the value + * @param dates the date + * @param untestedTrendWeights Untested Models. + * @param hyperParameters the hyperParam + */ + public void validateTrend(ArrayList values, ArrayList dates, + ArrayList>>> untestedTrendWeights, HyperParameters hyperParameters) { + var allModels = DataModification.reshape((flattern4dto3d(untestedTrendWeights)), hyperParameters); + + var rmsErrors = this.validateModels(// + values, // + dates, // + allModels, // + hyperParameters); + + var optInd = findOptimumIndex(rmsErrors, TREND, hyperParameters); + + this.updateModels(// + allModels, // + optInd, Integer.toString(hyperParameters.getCount()) + hyperParameters.getModelName() + TREND, // + TREND, hyperParameters); + } + + /** + * Find the indices of the maximum values in each column of a 2D matrix. This + * method takes a 2D matrix represented as a List of Lists and finds the row + * indices of the maximum values in each column. The result is returned as a + * List of Lists, where each inner list contains two integers: the row index and + * column index of the maximum value. + * + * @param matrix A 2D matrix represented as a List of Lists of doubles. + * @param var the var + * @param hyperParameters the hyperParam + * @return A List of Lists containing the row and column indices of the maximum + * values in each column. If the input matrix is empty, an empty list is + * returned. + */ + + public static List> findOptimumIndex(ArrayList> matrix, String var, + HyperParameters hyperParameters) { + var minimumIndices = new ArrayList>(); + + if (matrix.isEmpty() || matrix.get(0).isEmpty()) { + return minimumIndices; // Empty matrix, return empty list + } + + int numColumns = matrix.get(0).size(); + + for (int col = 0; col < numColumns; col++) { + double min = matrix.get(0).get(col); + List minIndices = new ArrayList<>(Arrays.asList(0, col)); + + for (int row = 0; row < matrix.size(); row++) { + double value = matrix.get(row).get(col); + + if (value < min) { + min = value; + minIndices.set(0, row); + } + } + + minimumIndices.add(minIndices); + } + + var err = new ArrayList(); + for (int i = 0; i < minimumIndices.size(); i++) { + err.add(matrix.get(minimumIndices.get(i).get(0)).get(minimumIndices.get(i).get(1))); + } + hyperParameters.setAllModelErrorTrend(err); + double errVal = DataStatistics.getStandardDeviation(err, hyperParameters.getTargetError()); + hyperParameters.setRmsErrorTrend(errVal); + System.out.println("=====> Average RMS error for " + var + " = " + errVal); + return minimumIndices; + } + + private ArrayList> validateModels(ArrayList value, ArrayList dates, + ArrayList>>> allModels, HyperParameters hyperParameters) { + var validateTrendPreProcess = new PreprocessingPipeImpl(hyperParameters); + double[][] modifiedData = (double[][]) validateTrendPreProcess// + .setData(UtilityConversion.to1DArray(value))// + .setDates(dates)// + .interpolate()// + .movingAverage()// + .scale()// + .filterOutliers()// + .modifyForTrendPrediction()// + .execute(); + + var rmsTemp2 = new ArrayList>(); + for (var modelsForData : allModels) { + var rmsTemp1 = new ArrayList(); + + for (int j = 0; j < modifiedData.length; j++) { + double[][][] intermediate = (double[][][]) validateTrendPreProcess.setData(modifiedData[j])// + // .differencing()// + .groupToStiffedWindow()// + .execute(); + + double[][][] preprocessed = (double[][][]) validateTrendPreProcess.setData(intermediate)// + .normalize()// + .shuffle()// + .execute(); + + double[] result = (double[]) validateTrendPreProcess// + .setData(UtilityConversion.to1DArray( + LstmPredictor.predictPre(preprocessed[0], modelsForData.get(j), hyperParameters)))// + .setMean(DataStatistics.getMean(intermediate[0]))// + .setStandardDeviation(DataStatistics.getStandardDeviation(intermediate[0]))// + .reverseNormalize()// + .reverseScale()// + .execute(); + + double rms = PerformanceMatrix.rmsError( + (double[]) validateTrendPreProcess.setData(intermediate[1][0]).reverseScale().execute(), result) + * (1 - PerformanceMatrix.accuracy( + (double[]) validateTrendPreProcess.setData(intermediate[1][0]).reverseScale().execute(), + result, 0.01)); + rmsTemp1.add(rms); + } + rmsTemp2.add(rmsTemp1); + } + return rmsTemp2; + } + + private void updateModels(ArrayList>>> allModels, List> optInd, + String modelFileName, String modelType, HyperParameters hyperParameters) { + DataModification.updateModel(allModels, optInd, modelFileName, modelType, hyperParameters); + } + +} diff --git a/io.openems.edge.predictor.lstm/test/.gitignore b/io.openems.edge.predictor.lstm/test/.gitignore new file mode 100644 index 00000000000..e69de29bb2d diff --git a/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/Data.java b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/Data.java new file mode 100644 index 00000000000..70aaa892e43 --- /dev/null +++ b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/Data.java @@ -0,0 +1,249 @@ +package io.openems.edge.predictor.lstm; + +public class Data { + + public static final Integer[] data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // + + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // + + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // + + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // + + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // + + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // + + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // + + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // + + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19628148, 6550910, 22317828, 15206562, 9058611, + 23406944, 9219693, 15800971, 21296530, 4527141, 22562770, 15267745, 9018811, 23595324, 8430786, 16389406, + 21525472, 4077697, 22768848, 15157240, 9138508, 23968964, 7363085, 17083467, 21832143, 3594137, 22989236, + 15085879, 9228392, 24124509, 6571137, 17642223, 21475288, 4499115, 22445357, 14785938, 9713856, 23915723, + 6550867, 17656891, 20921635, 5474489, 22062513, 14542193, 10049813, 23690578, 6654455, 17689559, 20435467, + 6028892, 21951157, 14613696, 10015734, 23385682, 7012689, 17595474, 20197424, 6435626, 21802029, 14528000, + 10124220, 23141960, 2640169, 8624727, 15593695, 21078270, 6648533, 20702052, 16174167, 8652929, 23103674, + 9415632, 15315525, 21073269, 6429999, 20912249, 16072814, // + + 8688754, 23011505, 9745347, 15174361, 21202293, 6012314, 21216329, 15949181, 8593743, 9973991, 23510081, + 4963040, 19950775, 19491470, 5166975, 23717579, 12303753, 11982384, 23957607, 3778278, 20679632, 19257248, + 5348607, 23838658, 12263030, 12045896, 23524143, 4315132, 20480132, 18894586, 6167012, 23353918, 11973767, + 12412515, 23142252, 5014560, 20084238, 18574591, 6843968, 23042868, 11588716, 12864209, 22745707, 5830336, + 19668607, 18191762, 7343509, 22899931, 11226024, 13150314, 22541918, 6444932, 19260087, 18102400, 7389853, + 22935268, 10986745, 13260801, 22431435, 7086697, 18887108, 18044029, 7377111, 23035500, 11036566, 13216566, + 22184679, 7455357, 18808808, 18012193, 7241047, 23093461, 11080178, 13203141, 22075695, 7330308, 19025666, + 18174654, 6971185, 23062072, 11262408, 13158150, 22087116, 7025189, 19303211, 18293406, 6804257, 23044594, + 11373599, 13171686, 22254260, 6480438, 19696518, 18338323, 6581887, 23151076, // + + 11604769, 12952137, 22589192, 5622771, 20203554, 18302906, 6441386, 23357620, 11445514, 13091251, 22956998, + 4673724, 20807399, 18288487, 6105346, 23848082, 11367278, 13054746, 23273602, 3760297, 21381618, 18225835, + 5981923, 24207759, 11145130, 13123652, 23265447, 3621941, 21545214, 17901138, 6758239, 23756140, 10548255, + 13757071, 22886070, 4390518, 21041361, 17532483, 7454236, 23428798, 10129041, 14229790, 22436513, 5324878, + 20568731, 17178127, 7998347, 23282483, 9856018, 14433316, 22268708, 5949598, 20130590, 17129247, 8022482, + 23263787, 9602355, 14605403, 21927623, 6715756, 19772138, 16903530, 8167990, 23303579, 9620705, 14640926, + 21673839, 6883218, 19889343, 17009546, 7990983, 23226453, 9858403, 14569637, 21493377, 6926327, 20014532, + 17145773, 7809541, 23072085, 10109997, 14492824, 21526944, 6685882, 20217357, 17121589, 7727077, 23114353, + 10420701, 14239556, 21705368, 6126628, 20583521, 17070504, 7684599, 23136513, // + + 10611913, 14154820, 22024580, 5428125, 20992158, 17062487, 7446859, 23470283, 10582385, 14141467, 22284292, + 4557025, 21599418, 16885615, 7338373, 23841840, 10565609, 14034098, 22524607, 3819144, 22069946, 17094514, + 7115064, 24051681, 9727720, 14636115, 22991141, 3044869, 22416925, 16925266, 7464993, 24025258, 8997311, + 15272431, 22354895, 4227531, 21784802, 16444165, 8257695, 23734762, 8626851, 15632950, 22143188, 4954302, + 21267194, 16244320, 8589471, 23587662, 8393340, 15865528, 21702258, 5925147, 20787704, 16105981, 8753773, + 23559390, 8347191, 15864910, 21366536, 6330591, 20781495, 16012461, 8822725, 23382794, 8488262, 15917033, + 20935476, 6680033, 20800007, 16061273, 8776216, 23166338, 8785728, 15844450, 20941593, 6636992, 20860341, + 15951301, 8846804, 23106750, 9177339, 15578916, 20943560, 6438064, 21033893, 15783067, 8924339, 22983467, + 9605892, 15331428, 21078934, 6019274, 21317309, 15728719, 8783992, 23180832, // + + 9713243, 15249007, 21279408, 5367330, 21768778, 15580151, 8696040, 23417895, 9887160, 15097248, 21460661, + 4764798, 22218240, 15720414, 8487344, 23605753, 9446081, 15407128, 21751813, 4180490, 22494522, 15815748, + 8439888, 23815168, 8522399, 16095019, 21935882, 3721703, 22741700, 15606351, 8664988, 24207759, 7370746, + 16889912, 21944239, 3906499, 22590875, 15419180, 9032978, 23986993, 6997075, 17218623, 21527687, 4848544, + 22031346, 15128377, 9466351, 23820788, 7001541, 17206217, 21004085, 5708207, 21741097, 14890121, 9803186, + 23603337, 7024611, 17341331, 20510146, 6244005, 21716133, 14890115, 9786500, 23306787, 7363558, 17284785, + 20320208, 6511591, 21608014, 14750756, 9930128, 23124925, 7913388, 16942690, 20203263, 6587292, 21624962, + 14513392, 10113276, 22919554, 8398547, 16678506, 20181005, 6516502, 21762500, 14295383, 10193554, 22936572, + 8698312, 16537405, 20296021, 6041988, 22100770, 14183007, 10083567, 23105447, // + + 8989410, 16271687, 20399219, 5615836, 22402447, 14305604, 9902154, 23119856, 8923746, 16371915, 20571323, + 5160131, 22645294, 14358318, 9888208, 23240084, 8265843, 16909934, 20805330, 4710098, 22820691, 14360568, + 9931777, 23539247, 7375623, 9415822, 22307506, 15560049, 8671193, 23573577, 9112068, 15751951, 21660721, + 4082015, 22642615, 15402282, 8842632, 23818278, 8039991, 16575794, 21745508, 3806327, 22913415, 15130961, + 9141456, 24229806, 6818899, 17440294, 21759736, 3957445, 22736323, 14901893, 9568919, 24021685, 6471824, + 17780626, 21042461, 5155479, 22294767, 14334638, 10243117, 23786048, 6294118, 18010859, 20419313, 5826465, + 22215726, 14341022, 10276711, 23433900, 6612880, 18009829, 19996368, 6366523, 22052626, 14094256, 10512193, + 23152641, 7019419, 17898658, 19751155, 6615911, 22068261, 13659363, 10943383, 22892049, 7609590, 17637458, + 19507319, 6792320, 22143856, 13279319, 11195651, 22809641, 7998949, 17453715, // + + 19491914, 6485574, 22473502, 12939263, 11339674, 22836780, 8388122, 17241483, 19445575, 6201345, 22766294, + 12881923, 11373551, 22762814, 8080644, 17647664, 19537048, 5905348, 22931837, 12797754, 11564040, 22736543, + 7535507, 18215295, 19550015, 5732163, 22978501, 12702490, 11702830, 22951044, 6636079, 18872397, 19686218, + 5394917, 23188512, 12718043, 11684092, 23347746, 5486549, 19652009, 19587479, 5265496, 23508795, 12366861, + 12010603, 23807618, 4155776, 20472076, 19484893, 4953684, 23976940, 12168409, 12097796, 23668636, 4048813, + 20641313, 18826537, 6032773, 23556207, 11790335, 12600559, 23219029, 4655481, 20475133, 18392416, 6829019, + 23237060, 11159992, 13233021, 22704457, 5504649, 20077577, 17803590, 7545229, 23082117, 10665191, 13669123, + 22413994, 6213296, 19705135, 17678742, 7647986, 23130765, 10269325, 13953565, 22181609, 6892237, 19343592, + 17440090, 7731004, 23264261, 10163309, 14101344, 21889465, 7023527, 19556940, // + + 17361822, 7695152, 23189274, 10150679, 14282087, 21611223, 6991054, 19858916, 17332204, 7643778, 23066653, + 10275184, 14319879, 21627003, 6630320, 20202211, 17215008, 7625807, 23111607, 10505851, 14214845, 21830942, + 5959063, 20687659, 16974341, 7694019, 23208585, 10415890, 14335779, 22019651, 5135061, 21303623, 16776551, + 7543203, 23599951, 10487839, 14225557, 22214630, 4365688, 21876947, 16744734, 7479200, 23818705, 9988650, + 14615526, 22463795, 3626725, 22357371, 16724667, 7538762, 24106610, 8821290, 15516785, 22500738, 3454023, + 22466628, 16044371, 8388949, 23997234, 7947773, 16293538, 22026503, 4430789, 21968253, 15687162, 8972812, + 23832462, 7550030, 16706012, 21471067, 5444039, 21499281, 15367490, 9361479, 23699015, 7411397, 16862884, + 20969087, 5993594, 21507869, 15116464, 9616895, 23434183, 7508189, 17030382, 20426235, 6503849, 21547151, + 15059192, 9671367, 23138310, 7804793, 16991347, 20251935, 6628005, 21579210, // + + 14656789, 9984782, 22985062, 8236105, 16782028, 20165913, 6512453, 21780210, 14212013, 10297958, 22920498, + 8518032, 16683932, 20152224, 6152185, 22153479, 13915924, 10344687, 23061539, 8820218, 16522693, 20140536, + 5759600, 22556871, 13838808, 10368950, 23018087, 8559741, 16890487, 20254554, 5347371, 22787393, 13751540, + 10525072, 23064180, 7856609, 17554570, 20300641, 5118343, 22908214, 13603239, 10736493, 23356200, 6794841, + 18308454, 20525840, 4672346, 23187248, 13609829, 10748145, 23773670, 5514352, 19145320, 20475073, 4490305, + 23522685, 13058746, 11228823, 24110847, 4459320, 19869016, 19849358, 5115280, 23451562, 12779960, 11578253, + 23632326, 4796332, 19842826, 19187801, 6102427, 23125288, 12634340, 11828408, 23198848, 5345817, 19700246, + 18824251, 6737926, 22873861, 12037827, 12409570, 22814959, 6063019, 19345395, 18476572, 7197820, 22768287, + 11529247, 12896938, 22576794, 6692266, 19042234, 18276846, 7288199, 22904648, // + + 11141858, 13137570, 22401204, 7292607, 18730801, 18097388, 7279018, 23100747, 11030513, 13177245, 22183872, + 7350550, 18926763, 18097829, 7116866, 23103889, 10983888, 13362456, 21948964, 7214260, 19312614, 18045112, + 7073893, 23037177, 11027992, 13480188, 22038919, 6691771, 19728807, 18023490, 6935789, 23135144, 11185965, + 13392257, 22219528, 5940309, 20299028, 17790274, 6956401, 23242452, 11070140, 13566321, 22526643, 4974710, + 20957420, 17625559, 6757956, 23692292, 10958044, 13635770, 22785082, 4046866, 21625268, 17475702, 6732056, + 24046416, 10450486, 13963008, 23011575, 3246213, 22235904, 17422398, 6909740, 24127021, 9440164, 14839700, + 22639874, 3890974, 21883594, 16710762, 7984298, 23793381, 8857051, 15456540, 22169292, 4854947, 21368201, + 16397896, 8449589, 23598584, 8388962, 15876931, 21776886, 5699911, 20961300, 16036047, 8799304, 23626879, + 8210654, 16017666, 21336027, 6214812, 20910345, 15887038, 8955933, 23411848, // + + 8221308, 16235322, 20814755, 6615974, 21030755, 15725976, 9093706, 23155009, 8475146, 16256652, 20658188, + 6652752, 21126902, 15457722, 9247262, 23073144, 8825443, 16066263, 20602648, 6426678, 21430883, 15143338, + 9482476, 22984419, 9029727, 16066843, 20644480, 5970987, 21820003, 14827391, 9507534, 23192936, 9256136, + 15900757, 20743238, 5435837, 22278134, 14712817, 9540192, 23242194, 9098919, 16087278, 20859194, 4954511, + 22632370, 14677293, 9611700, 23344214, 8279809, 16837444, 20966693, 4590983, 22822408, 14377899, 9953260, + 23704513, 7075774, 17745043, 21096861, 4234971, 23070139, 14203610, 10057726, 24109993, 5693141, 18681318, + 21025666, 4262303, 23173846, 13850170, 10487687, 24071718, 5320218, 18927447, 20363907, 5177843, 22928321, + 13526949, 10946371, 23664238, 5483270, 19054689, 19622280, 6090506, 22748322, 13435280, 11095644, 23209325, + 5963149, 18964576, 19224673, 6710180, 22539937, 12885926, 11644548, 22931363, // + + 6550727, 18691528, 19021441, 6924526, 22512365, 12333234, 12100024, 22681976, 7094395, 18461092, 18733849, + 7047052, 22653940, 11977218, 12326272, 22637775, 7589822, 18214544, 18635699, 6919708, 22901763, 11783024, + 12440462, 22386064, 7758725, 18322875, 18587777, 6764170, 23010928, 11711592, 12615519, 22277026, 7419076, + 18792359, 18614537, 6595115, 23005535, 11666795, 12784985, 22366400, 6794275, 19289719, 18662622, 6387813, + 23116233, 11766734, 12733555, 22593784, 5906160, 19958795, 18517767, 6349274, 23256681, 11679895, 12904180, + 22978760, 4788741, 20707805, 18292821, 6174101, 23794420, 11312192, 13128047, 23215355, 3795653, 21481949, + 18120240, 6132456, 24208590, 10852530, 13431073, 23245858, 3446308, 21713892, 17753336, 6864070, 23841354, + 10045453, 14248223, 22698325, 4372338, 21262372, 17148096, 7774773, 23565387, 9500381, 14839456, 22277969, + 5258493, 20837354, 16841397, 8169576, 23446618, 9012608, 15210199, 21902217, // + + 6093800, 20440510, 16523055, 8454472, 23499292, 8890729, 15362883, 21514259, 6481556, 20465357, 16341039, + 8577290, 23332686, 8821754, 15592311, 21052378, 6736444, 20672449, 16239111, 8664187, 23106530, 8993414, + 15744851, 20931383, 6644621, 8658437, 18678106, 19513676, 6577549, 22352850, 13307070, 11220911, 23058413, + 6722916, 18356704, 19290179, 6861505, 22307151, 12956656, 11594109, 22704260, 7365840, 18044670, 19153685, + 6939335, 22387956, 12654702, 11695187, 22763499, 7659613, 17888308, 19120689, 6661917, 22648852, 12391634, + 11833808, 22632820, 8090628, 17726750, 18984666, 6550826, 22923766, 12305494, 11981807, 22545599, 7813823, + 18071344, 19131109, 6206572, 22994555, 12433015, 11971871, 22564600, 7268448, 18601799, 19237693, 5935895, + 23016018, 12334842, 12102054, 22799422, 6400726, 19215368, 19280092, 5703876, 23197162, 12396553, 12050686, + 23194007, 5334410, 19887100, 19164018, 5576837, 23511667, 12065924, 12306782, // + + 23622868, 4080603, 20712579, 19081559, 5228071, 24115611, 11874820, 12368914, 23599995, 3798160, 20981386, + 18679483, 6070552, 23690130, 11504268, 12862353, 23149031, 4501087, 20687059, 18180858, 6941855, 23316438, + 10885051, 13483119, 22652728, 5372541, 20234862, 17655542, 7623619, 23151661, 10473286, 13878866, 22420918, + 6098943, 19852138, 17555250, 7739421, 23197412, 10142209, 14126564, 22161262, 6786968, 19497045, 17392925, + 7796711, 23270766, 10041770, 14184307, 21857793, 6958854, 19598870, 17308433, 7745974, 23203566, 10095393, + 14325029, 21527484, 6963176, 19924856, 17202788, 7758382, 23077156, 10173572, 14459591, 21585809, 6671118, + 20180439, 17166377, 7720317, 23138522, 10424859, 14292828, 21713744, 6101203, 20626549, 16979851, 7722878, + 23157107, 10489919, 14314920, 21946507, 5324051, 21188151, 16830889, 7583891, 23527869, 10442655, 14319637, + 22162271, 4488870, 21779424, 16635980, 7571778, 23790018, 10211336, 14448356, // + + 22388820, 3782216, 22272059, 16707843, 7553882, 24046779, 9025574, 15343164, 22596728, 3260710, 22599398, + 16330045, 8055647, 24046000, 8225177, 16063547, 22174203, 4229892, 21984116, 15911054, 8715461, 23789002, + 7784590, 16436907, 21721867, 5185669, 21530754, 15619104, 9105793, 23699385, 7652702, 16570783, 21163955, + 5993314, 21291221, 15387257, 9396874, 23556216, 7707035, 16702941, 20742721, 6388842, 21301023, 15404540, + 9375147, 23284104, 7939228, 16725232, 20574625, 6552148, 21332800, 15120388, 9592830, 23074682, 8318688, + 16520514, 20347739, 6550180, 21517598, 14723604, 9901095, 22876358, 8783418, 16381588, 20385837, 6300682, + 21787449, 14556545, 9890382, 23061978, 8876251, 16320341, 20476953, 5771963, 22223396, 14271993, 9941271, + 23166087, 9114773, 16175465, 20439516, 5393255, 22567798, 14192929, 10075120, 23190789, 8472506, 16767177, + 20645769, 4895467, 22797989, 14255205, 10079508, 23359944, 7604190, 17494690, // + + 20835887, 4568692, 22955800, 14037796, 10295790, 23733943, 6464204, 18239932, 21009725, 4130314, 23255522, + 13997646, 10243844, 24207759, 5168454, 19039304, 20656968, 4624942, 23167800, 13426687, 9852496, 1083474, + 14401511, 12504518, 11760470, 22876805, 6882334, 18701825, 19591030, 5553179, 23075691, 12729128, 11637580, + 23195636, 5935042, 19295823, 19676761, 5290348, 23283992, 12795610, 11576564, 23740208, 4809831, 19909917, + 19786417, 4858785, 23746737, 12496082, 11789655, 23946807, 4045253, 20428663, 19288874, 5502069, 23634895, + 12464669, 11913246, 23450203, 4553808, 20282631, 18940760, 6284196, 23205745, 12059152, 12381645, 23092658, + 5274243, 19878808, 18560891, 6918818, 22953031, 11665044, 12761457, 22710132, 6063331, 19423310, 18253005, + 7351379, 22856544, 11399823, 12970589, 22552648, 6701483, 19037485, 18276846, 7288199, 22881013, 11194948, + 13045473, 22437471, 7277522, 18668126, 18194881, 7223503, 23002224, 11272126, // + + 12980994, 22235027, 7570737, 18648389, 18199546, 7079575, 23092576, 11344158, 12950794, 22148536, 7380508, + 18903079, 18374635, 6792264, 23036140, 11483778, 12941615, 22271619, 6982379, 19206892, 18511435, 6564548, + 23054622, 11648403, 12844267, 22451945, 6332730, 19643018, 18571145, 6384535, 23167304, 11776199, 12759213, + 22783475, 5467491, 20164550, 18518505, 6198329, 23459839, 11616870, 12863411, 23207627, 4412746, 20825569, + 18551736, 5816456, 23925069, 11577616, 12768346, 23509420, 3509631, 21396465, 18479896, 5900829, 24054208, + 11274147, 13072790, 23230871, 3883109, 21238146, 18014281, 6835378, 23588712, 10671534, 13698814, 22781058, + 4767364, 20777898, 17639724, 7457491, 23318302, 10228797, 14105805, 22458203, 5565319, 20305145, 17287345, + 7893967, 23234634, 9918417, 14354779, 22237385, 6273898, 19896154, 17237794, 7938721, 23265383, 9830296, + 14422620, 21909513, 6904759, 19639056, 17060244, 8059794, 23258187, 9884250, // + + 14441784, 21664245, 6998144, 19794057, 17206641, 7803948, 23190880, 10132825, 14321214, 21601674, 6903723, + 19967525, 17282574, 7672093, 23068438, 10289538, 14310632, 21648568, 6520263, 20246686, 17281895, 7593662, + 23100491, 10639937, 14090027, 21884332, 5916088, 20659926, 17146478, 7541935, 23212881, 10609063, 14112918, + 22137518, 5165440, 21112559, 17140767, 7262037, 23593894, 10627470, 14029963, 22449381, 4387880, 21620224, + 17036516, 7188465, 23901148, 10511501, 14014660, 22650195, 3635721, 22129677, 17115191, 7095639, 24162604, + 9521030, 14863812, 22852060, 3359458, 22230710, 16805215, 7694364, 23915937, 8906682, 15361011, 22338476, + 4415153, 21636633, 16337341, 8463659, 23657957, 8619140, 15646278, 22052127, 5162130, 21158554, 16237858, + 8629665, 23547994, 8395596, 15812515, 21657131, 6048249, 20712160, 16036762, 8843879, 23524539, 8450055, + 15797754, 21313416, 6413058, 20689043, 16060796, 8787268, 23328585, 8686991, // + + 15759635, 20993045, 6725825, 20714472, 16162157, 8711698, 23131537, 9014324, 15638737, 20976081, 6673640, + 20780032, 16087399, 8685277, 23096319, 9379459, 15378352, 21026225, 6385770, 21014008, 15944553, 8767459, + 22985980, 9735366, 15207478, 21206809, 5878367, 21330340, 15833491, 8636802, 23238723, 9797233, 15117025, + 21394824, 5187445, 21834285, 15672434, 8602894, 23475313, 9965772, 14986841, 21575828, 4587918, 22295084, + 15844962, 8363823, 23635333, 9337350, 15442834, 21920695, 3963374, 22542040, 15864507, 8379274, 23906847, + 8361422, 16203943, 21999170, 3632656, 22778624, 15630018, 8617641, 24198347, 7377978, 16845211, 21942632, + 4046633, 22412224, 15343078, 9158513, 23920045, 7123167, 17134635, 21451973, 5069842, 21894060, 15161509, + 9457643, 23796364, 7073572, 17134186, 7133380, 17802216, 7582668, 23030633, 10698918, 13627584, 22449998, + 6367520, 19483090, 17734427, 7613118, 23067972, 10491469, 13734358, 22250894, // + + 7020022, 19162364, 17712121, 7540578, 23162818, 10578078, 13629680, 22051954, 7229406, 19166676, 17728778, + 7437856, 23159761, 10685192, 13642402, 0, 22020842, 5485775, 20930006, 17107835, 7399238, 23432151, + 10689871, 14031720, 22285281, 4605999, 21524237, 16915342, 7318512, 23788841, 10709114, 13897782, 22553064, + 3853756, 22041180, 17155563, 7053101, 24064130, 9831320, 14543299, 22982963, 3064699, 22446253, 17014905, + 7360553, 24058692, 9066570, 15191437, 22443435, 4173088, 21785026, 16523924, 8201314, 23739668, 8686494, + 15574890, 22140380, 4949215, 21274849, 16230939, 8591944, 23592633, 8404004, 15856085, 21718829, 5887075, + 20807869, 16123129, 8733755, 23558215, 8400923, 15822116, 21400148, 6329817, 20717703, 16067168, 8790213, + 23412045, 8557373, 15873198, 20997005, 3278352, 5604555, 18603203, 19601020, 6519954, 22317594, 13443922, + 11111186, 23074649, 6756632, 18240756, 19429273, 6790209, 22220665, 8437208 // + + }; + + public static final Integer[] predictedData = { 8931905, 12081183, 13140283, 7890941, 14283475, 10869967, 9203245, + 14450631, 8856705, 12104258, 13130344, 7731838, 14527199, 10794645, 9064588, 14608937, 8823195, 12172205, + 13051236, 7545362, 14801416, 10708873, 9027609, 14723334, 8754347, 12263946, 12909274, 7489182, 14961291, + 10577290, 9215333, 14666870, 8623358, 12483200, 12643272, 7623844, 14964723, 10380271, 9467619, 14572030, + 8515648, 12750934, 12343975, 7781289, 15019471, 10155035, 9670343, 14638097, 8395602, 12894626, 12140855, + 7848232, 15058938, 10126444, 9702186, 14646087, 8281407, 12949820, 12205576, 7800028, 15005580, 10290326, + 9666610, 14525533, 8189920, 13065421, 12270269, 7888022, 14853165, 10294449, 9823395, 14312157, 8205314, + 13121095, 12306997, 8021604, 14667519, 10359409, 9873441, 14147271, 8213434, 13151785, 12394432, 8070486, + 14604112, 10274089, 9947941, 14142882, 8263493, 13041683, 12486538, 7995124, 14638527, 10254334, 9974679, + 14108152 }; + +} diff --git a/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/LstmModelImplTest.java b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/LstmModelImplTest.java new file mode 100644 index 00000000000..63811a79019 --- /dev/null +++ b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/LstmModelImplTest.java @@ -0,0 +1,50 @@ +package io.openems.edge.predictor.lstm; + +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; + +import org.junit.Test; + +import io.openems.common.test.TimeLeapClock; +import io.openems.common.types.ChannelAddress; +import io.openems.edge.common.test.ComponentTest; +import io.openems.edge.common.test.DummyComponentManager; +import io.openems.edge.predictor.api.prediction.LogVerbosity; +import io.openems.edge.timedata.test.DummyTimedata; + +public class LstmModelImplTest { + + private static final String TIMEDATA_ID = "timedata0"; + private static final String PREDICTOR_ID = "predictor0"; + + private static final ChannelAddress METER1_ACTIVE_POWER = new ChannelAddress("meter1", "ActivePower"); + + @Test + public void test() throws Exception { + final var clock = new TimeLeapClock(Instant.ofEpochSecond(1577836800) /* starts at 1. January 2020 00:00:00 */, + ZoneOffset.UTC); + + var values = Data.data; + var timedata = new DummyTimedata(TIMEDATA_ID); + var start = ZonedDateTime.of(2019, 12, 1, 0, 0, 0, 0, ZoneId.of("UTC")); + + for (var i = 0; i < values.length; i++) { + timedata.add(start.plusMinutes(i * 15), METER1_ACTIVE_POWER, values[i]); + } + + var sut = new PredictorLstmImpl(); + + new ComponentTest(sut) // + .addReference("timedata", timedata) // + .addReference("componentManager", new DummyComponentManager(clock)) // + .activate(MyConfig.create() // + .setId(PREDICTOR_ID) // + .setLogVerbosity(LogVerbosity.NONE) // + .setChannelAddress(METER1_ACTIVE_POWER.toString())// + .build()); + + } + +} diff --git a/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/LstmPredictorTest.java b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/LstmPredictorTest.java new file mode 100644 index 00000000000..c4d560a31c0 --- /dev/null +++ b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/LstmPredictorTest.java @@ -0,0 +1,191 @@ +package io.openems.edge.predictor.lstm; + +import static org.junit.Assert.assertEquals; + +import java.util.ArrayList; +import java.util.Arrays; + +import org.junit.Test; + +import io.openems.edge.predictor.lstm.common.HyperParameters; +import io.openems.edge.predictor.lstm.common.LstmPredictor; +import io.openems.edge.predictor.lstm.preprocessing.DataModification; + +/** + * Unit test class for the LstmPredictor model. + * + *

    + * This class tests the behavior of the LstmPredictor for different types of + * input patterns such as impulse, step, ramp, and exponential inputs. It + * validates that the predictions generated by the LSTM model match the expected + * outputs within a defined margin of error. + *

    + * + *

    + * The model utilizes pre-defined hyperparameters and scaling factors to process + * the input data. The test cases ensure that the scaling applied before the + * prediction and scaled-back result after the prediction are consistent with + * the hyperparameters. + *

    + * + *

    + * The model trend data used in the test is also related to the specific scaling + * factors defined in {@link HyperParameters}, and the tests confirm the proper + * interaction between the input data and the model's trend. + *

    + * + * + * + *

    + * The tests use the {@link DataModification#scale()} method to scale the input + * data before prediction and {@link DataModification#scaleBack()} to reverse + * the scaling after the prediction. The scaling ranges are provided by the + * hyperparameters through {@link HyperParameters#getScalingMin()} and + * {@link HyperParameters#getScalingMax()}. + *

    + */ +public class LstmPredictorTest { + private static HyperParameters hyperParameters = new HyperParameters(); + + private static ArrayList> modelTrend = new ArrayList<>(Arrays.asList( + createList(0.30000000000000004, -0.10191832534531027, -0.19262844428679757, 0.016925024201681654), + createList(-0.7999999999999999, -0.3142909416393413, -0.3341676120993015, -0.09089772222510135), + createList(-0.4999999999999999, 0.051555896559209405, -0.11477687998526631, 0.10826117268571883), + createList(-0.6, -1.449260711226437, -1.6789748520719996, -1.6707673970279129), + createList(1.9000000000000004, 2.0276163313785935, 2.0457575003167086, 1.716902676376759), + createList(0.09999999999999995, -0.40632251238009526, -0.2902480457595551, -0.21870167929155354), + createList(-0.08825349909436375, -0.10024408682002699, -0.0891597522413061, -0.11174726093461877), + createList(-0.2529282639641216, -0.24738024250988547, -0.18556978270548535, -0.2302537524898713)// + )); + + private static ArrayList createList(Double... values) { + return new ArrayList<>(Arrays.asList(values)); + } + + @Test + public void predictTest() { + + /* + * IMPULSE RESPONSE : impulses are the sudden change in consumption for a very + * short period of time + * + * Example : When someone runs the electric drilling machine + * + * When the change magnitude of data last indexed data is very high compared to + * other data in an array, model identifies it as an impulse. + * + * Model will make a prediction negating the drastic change + * + */ + double result; + + var impulseSimulation = new ArrayList<>(createList(50.0, 55.0, 55.0, 150.0)); + + result = LstmPredictor.predict(// + DataModification.scale(impulseSimulation, // + hyperParameters.getScalingMin(), hyperParameters.getScalingMax()), // + modelTrend.get(0), modelTrend.get(1), modelTrend.get(2), modelTrend.get(3), modelTrend.get(4), + modelTrend.get(5), modelTrend.get(7), modelTrend.get(6), hyperParameters); + + result = DataModification.scaleBack(result, // + hyperParameters.getScalingMin(), // + hyperParameters.getScalingMax()); + assertEquals(result, -4379.836081864531, 0.001); + } + + @Test + public void predictTest1() { + double result; + // STEP RESPONSE : Example: plugging in EV for charging + var stepSimulation1 = new ArrayList<>(createList(55.0, 45.0, 150.0, 150.0)); + + result = LstmPredictor.predict(// + DataModification.scale(stepSimulation1, // + hyperParameters.getScalingMin(), hyperParameters.getScalingMax()), + modelTrend.get(0), modelTrend.get(1), modelTrend.get(2), modelTrend.get(3), modelTrend.get(4), + modelTrend.get(5), modelTrend.get(7), modelTrend.get(6), hyperParameters); + + result = DataModification.scaleBack(result, // + hyperParameters.getScalingMin(), // + hyperParameters.getScalingMax()); + + assertEquals(result, -4382.945607343116, 0.001); + } + + @Test + public void predictTest2() { + double result; + + var stepSimulation2 = new ArrayList<>(createList(45.0, 150.0, 150.0, 150.0)); + result = LstmPredictor.predict(// + DataModification.scale(stepSimulation2, // + hyperParameters.getScalingMin(), hyperParameters.getScalingMax()), + modelTrend.get(0), modelTrend.get(1), modelTrend.get(2), modelTrend.get(3), modelTrend.get(4), + modelTrend.get(5), modelTrend.get(7), modelTrend.get(6), hyperParameters); + result = DataModification.scaleBack(result, // + hyperParameters.getScalingMin(), // + hyperParameters.getScalingMax()); + + assertEquals(result, -4380.577836686382, 0.0001); + + } + + @Test + public void predictTest3() { + double result; + + var stepSimulation3 = new ArrayList<>(createList(150.0, 150.0, 150.0, 150.0)); + result = LstmPredictor.predict(// + DataModification.scale(stepSimulation3, hyperParameters.getScalingMin(), + hyperParameters.getScalingMax()), + modelTrend.get(0), modelTrend.get(1), modelTrend.get(2), modelTrend.get(3), modelTrend.get(4), + modelTrend.get(5), modelTrend.get(7), modelTrend.get(6), hyperParameters); + result = DataModification.scaleBack(result, // + hyperParameters.getScalingMin(), // + hyperParameters.getScalingMax()); + + assertEquals(result, -4391.590202508077, 0.001); + + } + + @Test + public void predictTest4() { + double result; + + // RESPONSE TO RAMP INPUT + var rampInput = new ArrayList<>(createList(100.0, 200.0, 400.0, 800.0)); + result = LstmPredictor.predict(// + DataModification.scale(rampInput, // + hyperParameters.getScalingMin(), // + hyperParameters.getScalingMax()), + modelTrend.get(0), modelTrend.get(1), modelTrend.get(2), modelTrend.get(3), modelTrend.get(4), + modelTrend.get(5), modelTrend.get(7), modelTrend.get(6), hyperParameters); + result = DataModification.scaleBack(result, // + hyperParameters.getScalingMin(), // + hyperParameters.getScalingMax()); + + assertEquals(result, -4376.960088551304, 0.001); + + } + + @Test + public void predictTest5() { + double result; + + // RESPONSE to exponential input + var expInput = new ArrayList<>(createList(20.0, 400.0, 160000.0, 3200000000.0)); + result = LstmPredictor.predict(// + DataModification.scale(expInput, // + hyperParameters.getScalingMin(), // + hyperParameters.getScalingMax()), + modelTrend.get(0), modelTrend.get(1), modelTrend.get(2), modelTrend.get(3), modelTrend.get(4), + modelTrend.get(5), modelTrend.get(7), modelTrend.get(6), hyperParameters); + result = DataModification.scaleBack(result, // + hyperParameters.getScalingMin(), // + hyperParameters.getScalingMax()); + + assertEquals(result, -6666.666666666666, 0.001); + + } + +} diff --git a/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/MyConfig.java b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/MyConfig.java new file mode 100644 index 00000000000..794baf3cdbd --- /dev/null +++ b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/MyConfig.java @@ -0,0 +1,63 @@ +package io.openems.edge.predictor.lstm; + +import io.openems.common.test.AbstractComponentConfig; +import io.openems.edge.predictor.api.prediction.LogVerbosity; + +@SuppressWarnings("all") +public class MyConfig extends AbstractComponentConfig implements Config { + + protected static class Builder { + private String id; + private String channelAddress; + private LogVerbosity logVerbosity; + + private Builder() { + } + + public Builder setId(String id) { + this.id = id; + return this; + } + + public Builder setChannelAddress(String channelAddress) { + this.channelAddress = channelAddress; + return this; + } + + public Builder setLogVerbosity(LogVerbosity logVerbosity) { + this.logVerbosity = logVerbosity; + return this; + } + + public MyConfig build() { + return new MyConfig(this); + } + } + + /** + * Create a Config builder. + * + * @return a {@link Builder} + */ + public static Builder create() { + return new Builder(); + } + + private final Builder builder; + + private MyConfig(Builder builder) { + super(Config.class, builder.id); + this.builder = builder; + } + + @Override + public String channelAddress() { + return this.builder.channelAddress; + } + + @Override + public LogVerbosity logVerbosity() { + return this.builder.logVerbosity; + } + +} \ No newline at end of file diff --git a/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/StandAlonePredictorTest.java b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/StandAlonePredictorTest.java new file mode 100644 index 00000000000..08711170a4e --- /dev/null +++ b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/StandAlonePredictorTest.java @@ -0,0 +1,480 @@ +package io.openems.edge.predictor.lstm; + +import static io.openems.edge.predictor.lstm.utilities.UtilityConversion.to1DArrayList; + +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import io.openems.edge.predictor.lstm.common.HyperParameters; +import io.openems.edge.predictor.lstm.common.LstmPredictor; +import io.openems.edge.predictor.lstm.common.ReadAndSaveModels; +import io.openems.edge.predictor.lstm.common.ReadCsv; +import io.openems.edge.predictor.lstm.interpolation.InterpolationManager; +import io.openems.edge.predictor.lstm.performance.PerformanceMatrix; +import io.openems.edge.predictor.lstm.preprocessing.DataModification; + +/** + * This test class is intended for local testing and is not executed during the + * build process. To run the JUnit test cases, please uncomment the relevant + * annotations. Ensure that the necessary data and model files are accessible in + * the specified path before executing the tests. + */ +public class StandAlonePredictorTest { + + public static final String CSV = "1.csv"; + public static final ZonedDateTime GLOBAL_DATE = ZonedDateTime.of(2022, 6, 16, 0, 0, 0, 0, + ZonedDateTime.now().getZone()); + public static final HyperParameters HYPER_PARAMETERS = ReadAndSaveModels.read("ConsumptionActivePower"); + + /** + * Prediction testing. + */ + // @Test + public void predictionTest() { + + ArrayList> predictedSeasonality = new ArrayList>(); + ArrayList predictedTrend = new ArrayList(); + HYPER_PARAMETERS.printHyperParameters(); + + int predictionCount = 1; + for (int i = 0; i < predictionCount; i++) { + ZonedDateTime nowDate = GLOBAL_DATE.plusHours(24 * i); + var tempPredicted = this.predictSeasonality(HYPER_PARAMETERS, nowDate, CSV); + predictedSeasonality.add(tempPredicted); + } + + for (int i = 0; i < predictionCount; i++) { + ZonedDateTime nowDate = GLOBAL_DATE.plusHours(24 * i); + predictedTrend = this.predictTrendOneDay(HYPER_PARAMETERS, nowDate, CSV); + } + + var pre = to1DArrayList(predictedSeasonality); + + var until = GLOBAL_DATE.withMinute(getMinute(GLOBAL_DATE, HYPER_PARAMETERS)).withSecond(0).withNano(0); + var targetFrom = until.plusMinutes(HYPER_PARAMETERS.getInterval()); + var targetTo = targetFrom.plusHours(24 * predictionCount); + + var target = this.getTargetData(targetFrom, targetTo, CSV, HYPER_PARAMETERS); + var rmsSeasonality = PerformanceMatrix.rmsError(target, pre); + var rmsTrend = PerformanceMatrix.rmsError(target, predictedTrend); + + StringBuilder sb = new StringBuilder(); + String format = "%-25s %s%n"; + + sb.append(String.format(format, "Target:", DataModification.constantScaling(target, 1))) + .append(String.format(format, "PredictedSeasonality:", DataModification.constantScaling(pre, 1))) + .append(String.format(format, "Target (raw):", target)) + .append(String.format(format, "Predicted trend:", DataModification.constantScaling(predictedTrend, 1))) + .append(String.format(format, "RMS Trend:", rmsTrend)) + .append(String.format(format, "RMS Seasonality:", rmsSeasonality)) + .append(String.format(format, "Accuracy Trend:", + PerformanceMatrix.accuracy(target, predictedTrend, 0.15))) + .append(String.format(format, "Accuracy Seasonality:", PerformanceMatrix.accuracy(target, pre, 0.15))); + + System.out.println(sb.toString()); + } + + // @Test + protected void predictionTestMultivarient() { + ArrayList> predictedSeasonality = new ArrayList>(); + ArrayList predictedTrend = new ArrayList(); + HYPER_PARAMETERS.printHyperParameters(); + + int predictionFor = 1; + for (int i = 0; i < predictionFor; i++) { + ZonedDateTime nowDate = GLOBAL_DATE.plusHours(24 * i); + var tempPredicted = this.predictSeasonalityMultivarent(HYPER_PARAMETERS, nowDate, CSV); + predictedSeasonality.add(tempPredicted); + } + + for (int i = 0; i < predictionFor; i++) { + ZonedDateTime nowDate = GLOBAL_DATE.plusHours(24 * i); + predictedTrend = this.predictTrendOneDayMultivarent(HYPER_PARAMETERS, nowDate, CSV); + + } + var pre = to1DArrayList(predictedSeasonality); + var until = GLOBAL_DATE.withMinute(getMinute(GLOBAL_DATE, HYPER_PARAMETERS)).withSecond(0).withNano(0); + var targetFrom = until.plusMinutes(HYPER_PARAMETERS.getInterval()); + var targetTo = targetFrom.plusHours(24 * predictionFor); + + // changing target data for reference + var target = this.getTargetData(targetFrom, targetTo, CSV, HYPER_PARAMETERS); + var ref = this.getTargetRefrence(targetFrom, targetTo); + + var trend = DataModification.elementWiseDiv(predictedTrend, ref); + var rmsSeasonality = PerformanceMatrix.rmsError(target, pre); + var rmsTrend = PerformanceMatrix.rmsError(target, trend); + + var sb = new StringBuilder(); + String format = "%-25s %s%n"; + + sb.append(String.format(format, "Target:", DataModification.constantScaling(target, 1))) + .append(String.format(format, "PredictedSeasonality:", DataModification.constantScaling(pre, 1))) + .append(String.format(format, "Target (raw):", target)) + .append(String.format(format, "Predicted trend:", DataModification.constantScaling(trend, 1))) + .append(String.format(format, "RMS Trend:", rmsTrend)) + .append(String.format(format, "RMS Seasonality:", rmsSeasonality)) + .append(String.format(format, "Accuracy trend:", PerformanceMatrix.accuracy(target, trend, 0.15))) + .append(String.format(format, "Accuracy seasonality:", PerformanceMatrix.accuracy(target, pre, 0.15))); + + System.out.println(sb.toString()); + } + + /** + * Doing what it suppose to do. + * + * @param hyperParameters the Hyperparam + * @param nowDate nowDate + * @param csvFileName csvFileName + * @return predicted the predicted + */ + public ArrayList predictSeasonality(HyperParameters hyperParameters, ZonedDateTime nowDate, + String csvFileName) { + + var until = GLOBAL_DATE.withMinute(getMinute(GLOBAL_DATE, HYPER_PARAMETERS)).withSecond(0).withNano(0); + var windowSize = hyperParameters.getWindowSizeSeasonality(); + + nowDate = nowDate.plusMinutes(hyperParameters.getInterval()); + + var temp = until.minusDays(windowSize); + var fromDate = temp.withMinute(getMinute(nowDate, hyperParameters)).withSecond(0).withNano(0); + + final var data = this.queryData(fromDate, until, csvFileName); + final var date = this.queryDate(fromDate, until, csvFileName); + + var targetFrom = until.plusMinutes(hyperParameters.getInterval()); + + ArrayList predicted = LstmPredictor.getArranged( + LstmPredictor.getIndex(targetFrom.getHour(), targetFrom.getMinute(), hyperParameters), + LstmPredictor.predictSeasonality(data, date, hyperParameters)); + return predicted; + } + + /** + * Doing what it suppose to do. + * + * @param hyperParameters the Hyperparam + * @param nowDate nowDate + * @param csvFileName csvFileName + * @return predicted the predicted + */ + public ArrayList predictSeasonalityMultivarent(HyperParameters hyperParameters, ZonedDateTime nowDate, + String csvFileName) { + + var until = nowDate.withMinute(getMinute(nowDate, hyperParameters)).withSecond(0).withNano(0); + + var windowSize = hyperParameters.getWindowSizeSeasonality(); + + nowDate = nowDate.plusMinutes(hyperParameters.getInterval()); + + var temp = until.minusDays(windowSize); + var fromDate = temp.withMinute(getMinute(nowDate, hyperParameters)).withSecond(0).withNano(0); + + final var data = this.queryData(fromDate, until, csvFileName); + final var date = this.queryDate(fromDate, until, csvFileName); + + var refdata = this.generateRefrence(date); + var toPredictData = DataModification.elementWiseMultiplication(refdata, data); + + var targetFrom = until.plusMinutes(hyperParameters.getInterval()); + + var predicted = LstmPredictor.getArranged( + LstmPredictor.getIndex(targetFrom.getHour(), targetFrom.getMinute(), hyperParameters), + LstmPredictor.predictSeasonality(toPredictData, date, hyperParameters)); + + // postprocess + var targetRef = this.getTargetRefrence(fromDate, until); + return DataModification.elementWiseDiv(predicted, targetRef); + } + + /** + * Gives prediction for seasoality. + * + * @param hyperParameters the Hyperparam + * @param nowDate nowDate + * @param csvFileName csvFileName + * @return predicted the predicted + */ + public ArrayList predictTrendOneDay(HyperParameters hyperParameters, ZonedDateTime nowDate, + String csvFileName) { + ArrayList predicted = new ArrayList(); + for (int i = 0; i < 60 / hyperParameters.getInterval() * 24; i++) { + var nowDateTemp = nowDate.plusMinutes(i * hyperParameters.getInterval()); + var until = nowDateTemp.withMinute(getMinute(nowDateTemp, hyperParameters)).withSecond(0).withNano(0); + var forTrendPrediction = this.queryData( + until.minusMinutes(hyperParameters.getInterval() * hyperParameters.getWindowSizeTrend()), until, + csvFileName); + var dateForTrend = this.queryDate( + until.minusMinutes(hyperParameters.getInterval() * hyperParameters.getWindowSizeTrend()), until, + csvFileName); + predicted.add(LstmPredictor.predictTrend(forTrendPrediction, dateForTrend, until, hyperParameters).get(0)); + } + return predicted; + } + + /** + * Predicts the Trend. + * + * @param hyperParameters the {@link HyperParameters} + * @param nowDate the {@link ZonedDateTime} for now + * @param csvFileName the csv file name + * @return the trend + */ + public ArrayList predictTrendOneDayMultivarent(HyperParameters hyperParameters, ZonedDateTime nowDate, + String csvFileName) { + + ArrayList predicted = new ArrayList(); + for (int i = 0; i < 60 / hyperParameters.getInterval() * 24; i++) { + + var nowDateTemp = nowDate.plusMinutes(i * hyperParameters.getInterval()); + var until = nowDateTemp.withMinute(getMinute(nowDateTemp, hyperParameters)).withSecond(0).withNano(0); + var forTrendPrediction = this.queryData( + until.minusMinutes(hyperParameters.getInterval() * hyperParameters.getWindowSizeTrend()), until, + csvFileName); + var dateForTrend = this.queryDate( + until.minusMinutes(hyperParameters.getInterval() * hyperParameters.getWindowSizeTrend()), until, + csvFileName); + + // modification for multivariant + var tempData = this.generateRefrence(dateForTrend); + + tempData = DataModification.elementWiseMultiplication(forTrendPrediction, tempData); + predicted.add(LstmPredictor.predictTrend(tempData, dateForTrend, until, hyperParameters).get(0)); + } + return predicted; + } + + /** + * Gives target data to compare. + * + * @param from the From + * @param to the to + * @param csvfileName the csvfileName + * @param hyperParameter the hyperParameter + * @return the target data + */ + public ArrayList getTargetData(ZonedDateTime from, ZonedDateTime to, String csvfileName, + HyperParameters hyperParameter) { + InterpolationManager obj = new InterpolationManager(this.queryData(from, to, csvfileName), hyperParameter); + return obj.getInterpolatedData(); + + } + + /** + * Generates a Reference from {@link OffsetDateTime}s. + * + * @param dates the {@link OffsetDateTime}s + * @return the reference + */ + public ArrayList generateRefrence(ArrayList dates) { + // one hour = 360/24 degree + // one minute = 360/(24*60) degree + Objects.requireNonNull(dates, "Date list must not be null"); + + return dates.stream().map(date -> { + double hourAngle = date.getHour() * 15.0; // 360/24 = 15 degrees per hour + double minuteAngle = date.getMinute() * 0.25; // 360/(24*60) = 0.25 degrees per minute + double totalAngle = hourAngle + minuteAngle; + + return 1.5 + Math.cos(Math.toRadians(totalAngle)); + }).collect(Collectors.toCollection(ArrayList::new)); + } + + /** + * Generates a Reference from {@link ZonedDateTime}s. + * + * @param dates the {@link ZonedDateTime}s + * @return the reference + */ + public static ArrayList generateReference(ArrayList dates) { + + Objects.requireNonNull(dates, "Date list must not be null"); + + return dates.stream().map(date -> { + double hourAngle = date.getHour() * 15.0; // 360/24 = 15 degrees per hour + double minuteAngle = date.getMinute() * 0.25; // 360/(24*60) = 0.25 degrees per minute + double totalAngle = hourAngle + minuteAngle; + + return 1.5 + Math.cos(Math.toRadians(totalAngle)); + }).collect(Collectors.toCollection(ArrayList::new)); + + } + + ArrayList getTargetRefrence(ZonedDateTime from, ZonedDateTime to) { + + int interval = 5; + int hour = 24; + int dataLen = (60 / interval) * hour; + + List dates = Stream.iterate(from, date -> date.plusMinutes(interval))// + .limit(dataLen)// + .collect(Collectors.toList()); + + return generateReference(new ArrayList<>(dates)); + } + + /** + * Gets the rounded minute value for the provided ZonedDateTime. + * + * @param nowDate The ZonedDateTime for which to determine the rounded + * minute + * @param hyperParameters is the object of class HyperParameters value. + * @return The rounded minute value (0, 15, 30, or 45) based on the minute + * component of the input time. + */ + public static int getMinute(ZonedDateTime nowDate, final HyperParameters hyperParameters) { + Objects.requireNonNull(nowDate, "DateTime must not be null"); + Objects.requireNonNull(hyperParameters, "HyperParameters must not be null"); + + final int interval = hyperParameters.getInterval(); + + if (interval <= 0) { + throw new IllegalArgumentException("Interval must be positive"); + } + if (60 % interval != 0) { + throw new IllegalArgumentException(String.format("Interval %d must be a factor of 60", interval)); + } + + return (nowDate.getMinute() / interval) * interval; + } + + /** + * Queries data from a CSV file for a specified time range and returns the + * relevant data points. + * + * @param fromDate The start date and time for data retrieval. + * @param untilDate The end date and time for data retrieval. + * @param path The file path to the CSV data file. + * @return An ArrayList of data points that fall within the specified time + * range. + */ + public ArrayList queryData(ZonedDateTime fromDate, ZonedDateTime untilDate, String path) { + String dataPath = path; + ReadCsv csv = new ReadCsv(dataPath); + ArrayList data = csv.getData(); + ArrayList dates = csv.getDates(); + ArrayList toReturn = new ArrayList(); + int from = this.getindexOfDate(this.toOffsetDateTime(fromDate), dates); + int till = this.getindexOfDate(this.toOffsetDateTime(untilDate), dates); + toReturn = this.getData(from, till, data); + return toReturn; + } + + /** + * Queries and retrieves a list of OffsetDateTime values from a CSV file that + * fall within a specified time range. + * + * @param fromDate The start date and time for data retrieval. + * @param untilDate The end date and time for data retrieval. + * @param path The file path to the CSV data file. + * @return An ArrayList of OffsetDateTime values that correspond to the + * specified time range. + */ + + public ArrayList queryDate(ZonedDateTime fromDate, ZonedDateTime untilDate, String path) { + String dataPath = path; + ReadCsv csv = new ReadCsv(dataPath); + ArrayList dates = csv.getDates(); + ArrayList toReturn = new ArrayList(); + int from = this.getindexOfDate(this.toOffsetDateTime(fromDate), dates); + int till = this.getindexOfDate(this.toOffsetDateTime(untilDate), dates); + toReturn = this.getDate(from, till, dates); + return toReturn; + } + + /** + * Converts an OffsetDateTime to a ZonedDateTime, retaining the date and time + * components. + * + * @param offsetDateTime The OffsetDateTime to convert to ZonedDateTime. + * @return The converted ZonedDateTime with the same date and time components. + */ + + public ZonedDateTime toZonedDateTime(OffsetDateTime offsetDateTime) { + + Objects.requireNonNull(offsetDateTime, "OffsetDateTime must not be null"); + + return offsetDateTime.atZoneSameInstant(ZoneId.systemDefault()).withSecond(0).withNano(0); + } + + /** + * Converts a ZonedDateTime to an OffsetDateTime, retaining the date, time, and + * offset components. + * + * @param time The ZonedDateTime to convert to OffsetDateTime. + * @return The converted OffsetDateTime with the same date, time, and offset + * components. + */ + + public OffsetDateTime toOffsetDateTime(ZonedDateTime time) { + Objects.requireNonNull(time, "ZonedDateTime must not be null"); + return time.toOffsetDateTime().withSecond(0).withNano(0); + } + + /** + * Find the index of a specific OffsetDateTime within an ArrayList of + * OffsetDateTime values. + * + * @param targetDate The OffsetDateTime to search for within the ArrayList. + * @param dates An ArrayList of OffsetDateTime values to search in. + * @return The index of the specified date in the ArrayList if found; otherwise, + * null. + */ + + public Integer getindexOfDate(OffsetDateTime targetDate, ArrayList dates) { + Objects.requireNonNull(targetDate, "Target date must not be null"); + Objects.requireNonNull(dates, "Date list must not be null"); + return IntStream.range(0, dates.size()).boxed().filter(i -> targetDate.isEqual(dates.get(i))).findFirst().get(); + } + + /** + * Retrieves a subset of data from an ArrayList of Double values based on + * specified indices. + * + * @param fromIndex The starting index (inclusive) for data retrieval. + * @param toIndex The ending index (exclusive) for data retrieval. + * @param data An ArrayList of Double values containing the data. + * @return A new ArrayList containing the subset of data from the specified + * range of indices. + */ + public ArrayList getData(Integer fromIndex, Integer toIndex, ArrayList data) { + if (fromIndex < 0 || toIndex > data.size()) { + throw new IllegalArgumentException("Indices out of bounds. Valid range is 0 to " + data.size()); + } + if (fromIndex > toIndex) { + throw new IllegalArgumentException("fromIndex must be less than or equal to toIndex"); + } + + return data.stream().skip(fromIndex).limit(toIndex - fromIndex) + .collect(Collectors.toCollection(ArrayList::new)); + } + + /** + * Retrieves a subset of OffsetDateTime values from an ArrayList based on + * specified indices. + * + * @param fromIndex The starting index (inclusive) for date retrieval. + * @param toIndex The ending index (exclusive) for date retrieval. + * @param date An ArrayList of OffsetDateTime values containing the dates. + * @return A new ArrayList containing the subset of OffsetDateTime values from + * the specified range of indices. + */ + public ArrayList getDate(Integer fromIndex, Integer toIndex, ArrayList date) { + if (fromIndex < 0 || toIndex > date.size()) { + throw new IllegalArgumentException("Indices out of bounds. Valid range is 0 to " + date.size()); + } + if (fromIndex > toIndex) { + throw new IllegalArgumentException("fromIndex must be less than or equal to toIndex"); + } + + return date.stream().skip(fromIndex).limit(toIndex - fromIndex) + .collect(Collectors.toCollection(ArrayList::new)); + } +} diff --git a/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/common/DataModificationTest.java b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/common/DataModificationTest.java new file mode 100644 index 00000000000..3ebb1718d33 --- /dev/null +++ b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/common/DataModificationTest.java @@ -0,0 +1,170 @@ +package io.openems.edge.predictor.lstm.common; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.junit.Test; + +import io.openems.edge.predictor.lstm.preprocessing.DataModification; + +public class DataModificationTest { + + @Test + public void testGroupDataByHourAndMinute() { + ArrayList testData = new ArrayList<>(Arrays.asList(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0)); + ArrayList testDate = new ArrayList<>(Arrays.asList(OffsetDateTime.parse("2022-01-01T10:15:30Z"), + OffsetDateTime.parse("2022-01-01T11:30:45Z"), OffsetDateTime.parse("2022-01-01T10:45:00Z"), + OffsetDateTime.parse("2022-01-01T11:15:00Z"), OffsetDateTime.parse("2022-01-01T10:30:00Z"), + OffsetDateTime.parse("2022-01-01T10:15:30Z"), OffsetDateTime.parse("2022-01-01T11:30:45Z"), + OffsetDateTime.parse("2022-01-01T10:45:00Z"), OffsetDateTime.parse("2022-01-01T11:15:00Z"), + OffsetDateTime.parse("2022-01-01T10:30:00Z"))); + + ArrayList>> result = DataModification.groupDataByHourAndMinute(testData, testDate); + + assertEquals(2, result.size()); + } + + @Test + public void testCombinedArray() { + ArrayList> testData1 = new ArrayList<>( + Arrays.asList(new ArrayList<>(Arrays.asList(1.0, 2.0, 3.0)), + new ArrayList<>(Arrays.asList(4.0, 5.0, 6.0)), new ArrayList<>(Arrays.asList(7.0, 8.0, 9.0)))); + ArrayList expectedResult1 = new ArrayList<>(Arrays.asList(1.0, 4.0, 7.0, 2.0, 5.0, 8.0, 3.0, 6.0, 9.0)); + assertEquals(expectedResult1, DataModification.combinedArray(testData1)); + + ArrayList> testData2 = new ArrayList<>(); + ArrayList expectedResult2 = new ArrayList<>(); + assertEquals(expectedResult2, DataModification.combinedArray(testData2)); + } + + @Test + public void testModifyFortrendPrediction() { + ArrayList testData = new ArrayList<>(List.of(1.0, 2.0, 3.0, 4.0, 5.0)); + ArrayList testDates = new ArrayList<>(List.of(OffsetDateTime.parse("2022-01-01T12:30:00Z"), + OffsetDateTime.parse("2022-01-01T12:45:00Z"), OffsetDateTime.parse("2022-01-01T13:00:00Z"), + OffsetDateTime.parse("2022-01-01T13:15:00Z"), OffsetDateTime.parse("2022-01-01T13:30:00Z"))); + HyperParameters testHyperParameters = new HyperParameters(); + + ArrayList> result = DataModification.modifyFortrendPrediction(testData, testDates, + testHyperParameters); + + assertNotNull(result); + assertEquals(5, result.size()); + } + + @Test + public void testScale() { + ArrayList testData = new ArrayList<>(); + testData.add(10.0); + testData.add(20.0); + testData.add(30.0); + + ArrayList scaledData = DataModification.scale(testData, 10.0, 30.0); + + assertEquals(0.2, scaledData.get(0), 0.0001); + assertEquals(0.5, scaledData.get(1), 0.0001); + assertEquals(0.8, scaledData.get(2), 0.0001); + } + + @Test + public void testScaleBack() { + double scaledValue = 0.5; + double minOriginal = 10.0; + double maxOriginal = 30.0; + + double originalValue = DataModification.scaleBack(scaledValue, minOriginal, maxOriginal); + assertEquals(20.0, originalValue, 0.0001); + } + + @Test + public void groupByTest() { + HyperParameters hyperParameters = new HyperParameters(); + ArrayList data = new ArrayList(); + ArrayList date = new ArrayList(); + int interval = hyperParameters.getInterval(); + int forDays = 2; + int itter = forDays * 24 * 60 / interval; + // generating data + OffsetDateTime startingDate = OffsetDateTime.of(2023, 1, 1, 0, 0, 0, 0, ZoneOffset.ofHours(1)); + for (int i = 0; i < itter; i++) { + date.add(startingDate.plusMinutes(i * interval)); + data.add(i + 0.00); + + } + + ArrayList>> groupedData = DataModification.groupDataByHourAndMinute(data, date); + for (int i = 0; i < groupedData.size(); i++) { + for (int j = 0; j < groupedData.get(i).get(j).size(); j++) { + assertEquals(groupedData.get(i).get(j).size(), forDays); + } + } + + } + + @Test + public void getDataInBatchTest() { + ArrayList data = new ArrayList<>(Arrays.asList(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0)); + int numberOfGroups = 2; + ArrayList> result = DataModification.getDataInBatch(data, numberOfGroups); + assertEquals(result.size(), numberOfGroups); + int i = 0; + for (ArrayList outerVal : result) { + for (double val : outerVal) { + assertEquals(val, data.get(i), 0.00001); + i++; + } + } + } + + @Test + public void getDateInBatchTest() { + + ArrayList dateList = new ArrayList(); + HyperParameters hyperParameters = new HyperParameters(); + OffsetDateTime startingDate = OffsetDateTime.of(2023, 1, 1, 0, 0, 0, 0, ZoneOffset.ofHours(1)); + int numberOfGroups = 2; + int j = 0; + + // populating Date list + + for (int i = 0; i < 10; i++) { + dateList.add(startingDate.plusMinutes(i * hyperParameters.getInterval())); + } + + ArrayList> result = DataModification.getDateInBatch(dateList, numberOfGroups); + assertEquals(result.size(), numberOfGroups); + + for (ArrayList outerVal : result) { + for (OffsetDateTime val : outerVal) { + assertEquals(val, dateList.get(j)); + j++; + } + } + } + + @Test + public void removeNegatives() { + + ArrayList inputList = new ArrayList<>(Arrays.asList(5.0, -3.0, 2.0, -7.5)); + ArrayList expectedList = new ArrayList<>(Arrays.asList(5.0, 0.0, 2.0, 0.0)); + + ArrayList resultList = DataModification.removeNegatives(inputList); + assertEquals(expectedList, resultList); + } + + @Test + public void constantScalingTest() { + + ArrayList inputData = new ArrayList<>(Arrays.asList(1.0, 2.0, 3.0)); + double scalingFactor = 2.0; + ArrayList expectedOutput = new ArrayList<>(Arrays.asList(2.0, 4.0, 6.0)); + ArrayList actualOutput = DataModification.constantScaling(inputData, scalingFactor); + assertEquals(expectedOutput, actualOutput); + } +} diff --git a/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/common/DataStatisticsTest.java b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/common/DataStatisticsTest.java new file mode 100644 index 00000000000..2e6e63bdd32 --- /dev/null +++ b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/common/DataStatisticsTest.java @@ -0,0 +1,47 @@ +package io.openems.edge.predictor.lstm.common; + +import static org.junit.Assert.assertEquals; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.junit.Test; + +public class DataStatisticsTest { + + public static final List DATALIST = Arrays.asList(1.0, 2.0, 3.0, 4.0, 5.0); + public static final ArrayList DATA = new ArrayList<>(DATALIST); + public static final ArrayList EMPTYDATA = new ArrayList<>(); + + @Test + public void testGetMean() { + double result = DataStatistics.getMean(DATA); + assertEquals(3.0, result, 0.0001); + } + + @Test + public void testGetMeanEmptyList() { + double result = DataStatistics.getMean((ArrayList) EMPTYDATA); + assertEquals(0.0, result, 0.0001); + } + + @Test + public void testGetStandardDeviation() { + double result = DataStatistics.getStandardDeviation(DATA); + assertEquals(1.41421, result, 0.0001); + } + + @Test + public void testGetStandardDeviationEmptyList() { + assertEquals(Double.NaN, DataStatistics.getStandardDeviation(EMPTYDATA), 0.0001); + } + + @Test + public void testComputeRms() { + double[] original = { 1.0, 2.0, 3.0, 4.0, 5.0 }; + double[] computed = { 1.1, 2.2, 3.1, 4.2, 5.1 }; + double expectedRms = 0.1483239; + assertEquals(expectedRms, DataStatistics.computeRms(original, computed), 0.0001); + } +} diff --git a/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/common/ReadAndSaveObjectTest.java b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/common/ReadAndSaveObjectTest.java new file mode 100644 index 00000000000..3518ca3fa5e --- /dev/null +++ b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/common/ReadAndSaveObjectTest.java @@ -0,0 +1,70 @@ +package io.openems.edge.predictor.lstm.common; + +import static io.openems.edge.predictor.lstm.common.ReadAndSaveModels.MODEL_FOLDER; +import static org.junit.Assert.assertEquals; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; + +//import org.junit.Test; + +import io.openems.common.OpenemsConstants; + +/** + * This class contains test methods for saving and reading objects using Gson. + * Uncomment the @Test annotations to run the tests locally. These tests use the + * HyperParameters class and involve saving objects to JSON files and reading + * them back for validation. + */ +public class ReadAndSaveObjectTest { + + /** + * Test method for saving an object to a file using Gson serialization. + * Uncomment the @Test annotation to run the test locally. + */ + // @Test + public void saveObjectGsonTest() { + HyperParameters hyperParameters = new HyperParameters(); + hyperParameters.setModelName("testGson"); + hyperParameters.setCount(30); + hyperParameters.setRmsErrorTrend(0.1234); + hyperParameters.setRmsErrorTrend(0.4567); + ReadAndSaveModels.save(hyperParameters); + } + + /** + * Test method for reading a JSON object from a file. Uncomment the @Test + * annotation to run the test locally. + */ + // @Test + public void readObjectGson() { + HyperParameters hyperParameters = new HyperParameters(); + hyperParameters.setCount(30); + hyperParameters.setModelName("Consumption"); + + HyperParameters hyper = ReadAndSaveModels.read(hyperParameters.getModelName()); + assertEquals(hyper.getCount(), hyperParameters.getCount()); + + // deleting the hyperparametes + try { + Files.delete(Paths.get(getModelPath(hyperParameters.getModelName() + "fenHp.edge"))); + } catch (IOException e) { + e.printStackTrace(); + } + + } + + /** + * Gets the absolute path for a model file based on a given suffix. The path is + * constructed within the OpenEMS data directory under the "lstm" subdirectory. + * + * @param suffix The suffix to be appended to the model file path. + * @return The absolute path for the model file. + */ + public static String getModelPath(String suffix) { + File file = Paths.get(OpenemsConstants.getOpenemsDataDir()).toFile(); + return file.getAbsolutePath() + MODEL_FOLDER + suffix; + } +} \ No newline at end of file diff --git a/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/interpolation/CubicalInterpolationTest.java b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/interpolation/CubicalInterpolationTest.java new file mode 100644 index 00000000000..bd9059fb88c --- /dev/null +++ b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/interpolation/CubicalInterpolationTest.java @@ -0,0 +1,50 @@ +package io.openems.edge.predictor.lstm.interpolation; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.Arrays; + +import org.junit.Test; + +public class CubicalInterpolationTest { + + @Test + public void testCanInterpolate() { + ArrayList validData = new ArrayList<>(Arrays.asList(1.0, 2.0, 3.0, 4.0, null, Double.NaN)); + CubicalInterpolation inter = new CubicalInterpolation(validData); + + assertTrue(inter.canInterpolate()); + + ArrayList invalidData = new ArrayList<>(Arrays.asList(1.0, null, 3.0, Double.NaN)); + inter.setData(invalidData); + assertFalse(inter.canInterpolate()); + + ArrayList exactlyFourData = new ArrayList<>(Arrays.asList(1.0, 2.0, null, 4.0, 5.0)); + inter.setData(exactlyFourData); + assertTrue(inter.canInterpolate()); + + ArrayList allNullOrNaNData = new ArrayList<>(Arrays.asList(null, Double.NaN, null, Double.NaN)); + inter.setData(allNullOrNaNData); + assertFalse(inter.canInterpolate()); + + ArrayList emptyData = new ArrayList<>(); + inter.setData(emptyData); + assertFalse(inter.canInterpolate()); + } + + @Test + + public void testInterpolate() { + + ArrayList validData = new ArrayList<>(Arrays.asList(2.0, 4.0, Double.NaN, 8.0)); + ArrayList expectedResult = new ArrayList<>(Arrays.asList(2.0, 4.0, 6.0, 8.0)); + + CubicalInterpolation inter = new CubicalInterpolation(validData); + + ArrayList interpolatedData = inter.compute(); + assertEquals(interpolatedData, expectedResult); + } +} diff --git a/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/interpolation/InterpolationMangerTest.java b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/interpolation/InterpolationMangerTest.java new file mode 100644 index 00000000000..265d2b354e2 --- /dev/null +++ b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/interpolation/InterpolationMangerTest.java @@ -0,0 +1,74 @@ +package io.openems.edge.predictor.lstm.interpolation; + +import static org.junit.Assert.assertEquals; + +import java.util.ArrayList; +import java.util.Arrays; + +import org.junit.Test; + +import io.openems.edge.predictor.lstm.common.HyperParameters; + +public class InterpolationMangerTest { + + private static HyperParameters hyperParameters = new HyperParameters(); + + @Test + public void calculateMeanShouldReturnNaNForEmptyList() { + ArrayList emptyList = new ArrayList<>(); + double result = InterpolationManager.calculateMean(emptyList); + assertEquals(Double.NaN, result, 0.0001); + } + + @Test + public void calculateMean_shouldReturnMeanWithoutNaN() { + ArrayList dataList = new ArrayList<>(Arrays.asList(1.0, 2.0, Double.NaN, 4.0, 5.0)); + double result = InterpolationManager.calculateMean(dataList); + assertEquals(3.0, result, 0.0001); + } + + @Test + public void testGroup() { + + ArrayList testData = new ArrayList<>(Arrays.asList(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 3.0, 4.0)); + int group = 3; + ArrayList> expectedGroupedData = new ArrayList<>(Arrays.asList(// + new ArrayList<>(Arrays.asList(1.0, 2.0, 3.0)), // + new ArrayList<>(Arrays.asList(4.0, 5.0, 6.0)), // + new ArrayList<>(Arrays.asList(3.0, 4.0))// + )); + + ArrayList> result = InterpolationManager.group(testData, group); + assertEquals(expectedGroupedData, result); + } + + @Test + public void testUnGroup() { + + ArrayList> groupedData = new ArrayList<>(); + groupedData.add(new ArrayList<>(Arrays.asList(1.0, 2.0, 3.0))); + groupedData.add(new ArrayList<>(Arrays.asList(4.0, 5.0, 6.0))); + + ArrayList expectedResult = new ArrayList<>(Arrays.asList(1.0, 2.0, 3.0, 4.0, 5.0, 6.0)); + ArrayList result = InterpolationManager.unGroup(groupedData); + assertEquals(expectedResult, result); + } + + @Test + public void testInterpolationManagerCaseLinear() { + ArrayList data = new ArrayList<>(Arrays.asList(1.0, null, 3.0, Double.NaN, 5.0)); + ArrayList expectedData = new ArrayList<>(Arrays.asList(1.0, 2.0, 3.0, 4.0, 5.0)); + InterpolationManager interpolationManager = new InterpolationManager(data, hyperParameters); + assertEquals(interpolationManager.getInterpolatedData(), expectedData); + } + + @Test + public void testInterPolationManagerCaseCubical() { + ArrayList data = new ArrayList<>( + Arrays.asList(1.0, null, 3.0, Double.NaN, 5.0, 6.0, null, 7.0, 8.0, null, Double.NaN, 9.0)); + ArrayList expectedData = new ArrayList<>(Arrays.asList(1.0, 2.0092714608433737, 3.0, 3.9721856174698793, + 5.0, 6.0, 6.485598644578313, 7.0, 8.0, 8.671770414993308, 8.937416331994646, 9.0)); + InterpolationManager interpolationManager = new InterpolationManager(data, hyperParameters); + assertEquals(interpolationManager.getInterpolatedData(), expectedData); + } +} diff --git a/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/interpolation/LinearInterpolationTest.java b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/interpolation/LinearInterpolationTest.java new file mode 100644 index 00000000000..7d21bad2be9 --- /dev/null +++ b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/interpolation/LinearInterpolationTest.java @@ -0,0 +1,59 @@ +package io.openems.edge.predictor.lstm.interpolation; + +import static org.junit.Assert.assertEquals; + +import java.util.ArrayList; +import java.util.Arrays; + +import org.junit.Test; + +public class LinearInterpolationTest { + + @Test + public void determineInterpolatingPointsTest() { + + ArrayList data = new ArrayList<>(Arrays.asList(1.0, 2.0, 3.0, 4.0, Double.NaN, 6.0, 7.0, 8.0, + Double.NaN, Double.NaN, 11.0, 12.0, 13.0, Double.NaN, Double.NaN, Double.NaN, 17.0, Double.NaN, 19.0)); + + ArrayList> expectedResults = new ArrayList<>( + Arrays.asList(new ArrayList<>(Arrays.asList(3, 5)), new ArrayList<>(Arrays.asList(7, 10)), + new ArrayList<>(Arrays.asList(12, 16)), new ArrayList<>(Arrays.asList(16, 18)))); + + ArrayList> result = LinearInterpolation.determineInterpolatingPoints(data); + assertEquals(result, expectedResults); + } + + @Test + public void computeInterpolationTest() { + + ArrayList data = new ArrayList<>(Arrays.asList(1.0, 2.0, 3.0, 4.0, Double.NaN, 6.0, 7.0, 8.0, + Double.NaN, Double.NaN, 11.0, 12.0, 13.0, Double.NaN, Double.NaN, Double.NaN, 17.0, Double.NaN, 19.0)); + ArrayList expectedResult = new ArrayList<>(Arrays.asList(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, + 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0)); + ArrayList results = LinearInterpolation.interpolate(data); + assertEquals(results, expectedResult); + } + + @Test + public void combineTest() { + + ArrayList data = new ArrayList<>(Arrays.asList(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, + 12.0, 13.0, Double.NaN, Double.NaN, Double.NaN, 17.0, 18.0, 19.0)); + ArrayList interpoltedValue = new ArrayList<>(Arrays.asList(14.0, 15.0, 16.0)); + ArrayList expectedResult = new ArrayList<>(Arrays.asList(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, + 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0)); + ArrayList result = LinearInterpolation.combine(data, interpoltedValue, 12, 16); + assertEquals(result, expectedResult); + } + + @Test + public void computeInterPolation() { + int xval1 = 12; + int xValue2 = 16; + double yvalue1 = 13; + double yvalue2 = 17; + ArrayList expectedResult = new ArrayList<>(Arrays.asList(14.0, 15.0, 16.0)); + ArrayList result = LinearInterpolation.computeInterpolation(xval1, xValue2, yvalue1, yvalue2); + assertEquals(result, expectedResult); + } +} diff --git a/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/performance/PerformanceMatrixTest.java b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/performance/PerformanceMatrixTest.java new file mode 100644 index 00000000000..346411537f9 --- /dev/null +++ b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/performance/PerformanceMatrixTest.java @@ -0,0 +1,85 @@ +package io.openems.edge.predictor.lstm.performance; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Test; + +public class PerformanceMatrixTest { + + public static final double delta = 0.0001; + + @Test + public void meanAbsoluteErrorTest() { + ArrayList target = new ArrayList<>(List.of(1.0, 2.0, 3.0)); + ArrayList predicted = new ArrayList<>(List.of(2.0, 2.5, 2.8)); + + double result = PerformanceMatrix.meanAbsoluteError(target, predicted); + assertEquals(0.5666, result, delta); + } + + @Test + public void meanAbsoluteErrorTestWithException() { + ArrayList target = new ArrayList<>(List.of(1.0, 2.0, 3.0)); + ArrayList predicted = new ArrayList<>(List.of(2.0, 2.5)); + assertThrows(IllegalArgumentException.class, () -> PerformanceMatrix.meanAbsoluteError(target, predicted)); + } + + @Test + public void rmsErrorTest() { + ArrayList target = new ArrayList<>(List.of(1.0, 2.0, 3.0)); + ArrayList predicted = new ArrayList<>(List.of(2.0, 2.5, 2.8)); + + double expectedRmsError = 0.6557; + double result = PerformanceMatrix.rmsError(target, predicted); + assertEquals(expectedRmsError, result, delta); + } + + @Test + public void rmsErrorWithException() { + ArrayList target = new ArrayList<>(List.of(1.0, 2.0, 3.0)); + ArrayList predicted = new ArrayList<>(List.of(2.0, 2.5)); + + assertThrows(IllegalArgumentException.class, () -> PerformanceMatrix.rmsError(target, predicted)); + } + + @Test + public void meanSquaredErrorTest() { + ArrayList target = new ArrayList<>(List.of(1.0, 2.0, 3.0)); + ArrayList predicted = new ArrayList<>(List.of(2.0, 2.5, 2.8)); + + double expectedMse = 0.43; + double result = PerformanceMatrix.meanSquaredError(target, predicted); + assertEquals(expectedMse, result, delta); + } + + @Test + public void meanSquaredErrorException() { + ArrayList target = new ArrayList<>(List.of(1.0, 2.0, 3.0)); + ArrayList predicted = new ArrayList<>(List.of(2.0, 2.5)); + + assertThrows(IllegalArgumentException.class, () -> PerformanceMatrix.meanSquaredError(target, predicted)); + } + + @Test + public void accuracyTest() { + ArrayList target = new ArrayList<>(List.of(1.0, 2.0, 3.0)); + ArrayList predicted = new ArrayList<>(List.of(1.2, 2.3, 3.2)); + + double allowedPercentage = 0.1; + double expectedAccuracy = 0.3333; + assertEquals(expectedAccuracy, PerformanceMatrix.accuracy(target, predicted, allowedPercentage), delta); + } + + @Test + public void accuracyTestWithEmptyList() { + ArrayList target = new ArrayList<>(); + ArrayList predicted = new ArrayList<>(); + + double allowedPercentage = 0.1; + assertEquals(Double.NaN, PerformanceMatrix.accuracy(target, predicted, allowedPercentage), delta); + } +} \ No newline at end of file diff --git a/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/preprocessing/CombineFeatureTest.java b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/preprocessing/CombineFeatureTest.java new file mode 100644 index 00000000000..f2e37b4d91d --- /dev/null +++ b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/preprocessing/CombineFeatureTest.java @@ -0,0 +1,29 @@ +package io.openems.edge.predictor.lstm.preprocessing; + +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; + +import org.junit.Test; + +public class CombineFeatureTest { + + @Test + public void multiplication() { + double[] featureA = { 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0 }; + double[] featureB = { 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0 }; + double[] expected = { 1.0, 4.0, 9.0, 16.0, 25.0, 36.0, 49.0, 64.0, 81.0, 100.0, 121.0, 144.0 }; + + assertTrue(Arrays.equals(DataModification.elementWiseMultiplication(featureA, featureB), expected)); + + } + + @Test + public void divisioTest() { + double[] featureA = { 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0 }; + double[] featureB = { 1.0, 2.0, 3.0, 0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0 }; + double[] expected = { 1.0, 1.0, 1.0, 4.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 }; + + assertTrue(Arrays.equals(DataModification.elementWiseDiv(featureA, featureB), expected)); + } +} diff --git a/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/preprocessing/GroupByTest.java b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/preprocessing/GroupByTest.java new file mode 100644 index 00000000000..ccda4e4b2d0 --- /dev/null +++ b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/preprocessing/GroupByTest.java @@ -0,0 +1,120 @@ + +package io.openems.edge.predictor.lstm.preprocessing; + +public class GroupByTest { + +// ArrayList testDatesxx = new ArrayList<>(Arrays.asList(// +// OffsetDateTime.parse("2023-01-01T00:00:00Z"), OffsetDateTime.parse("2023-01-01T00:05:00Z"), +// OffsetDateTime.parse("2023-01-01T00:10:00Z"), OffsetDateTime.parse("2023-01-01T00:15:00Z"), +// OffsetDateTime.parse("2023-01-01T00:20:00Z"), OffsetDateTime.parse("2023-01-01T00:25:00Z"), +// OffsetDateTime.parse("2023-01-01T00:30:00Z"), OffsetDateTime.parse("2023-01-01T00:35:00Z"), +// OffsetDateTime.parse("2023-01-01T00:40:00Z"), OffsetDateTime.parse("2023-01-01T00:45:00Z"), +// OffsetDateTime.parse("2023-01-01T00:50:00Z"), OffsetDateTime.parse("2023-01-01T00:55:00Z"), +// OffsetDateTime.parse("2023-01-01T01:00:00Z"), OffsetDateTime.parse("2023-01-01T01:05:00Z"), +// OffsetDateTime.parse("2023-01-01T01:10:00Z"), OffsetDateTime.parse("2023-01-01T01:15:00Z"), +// OffsetDateTime.parse("2023-01-01T01:20:00Z"), OffsetDateTime.parse("2023-01-01T01:25:00Z"), +// OffsetDateTime.parse("2023-01-01T01:30:00Z"), OffsetDateTime.parse("2023-01-01T01:35:00Z"), +// OffsetDateTime.parse("2023-01-01T01:40:00Z"), OffsetDateTime.parse("2023-01-01T01:45:00Z"), +// OffsetDateTime.parse("2023-01-01T01:50:00Z"), OffsetDateTime.parse("2023-01-01T01:55:00Z"), +// OffsetDateTime.parse("2023-01-01T02:00:00Z"), OffsetDateTime.parse("2023-01-01T02:05:00Z"), +// OffsetDateTime.parse("2023-01-01T02:10:00Z"), OffsetDateTime.parse("2023-01-01T02:15:00Z"), +// OffsetDateTime.parse("2023-01-01T02:20:00Z"), OffsetDateTime.parse("2023-01-01T02:25:00Z"), +// OffsetDateTime.parse("2023-01-01T02:30:00Z"), OffsetDateTime.parse("2023-01-01T02:35:00Z"), +// OffsetDateTime.parse("2023-01-01T02:40:00Z"), OffsetDateTime.parse("2023-01-01T02:45:00Z"), +// OffsetDateTime.parse("2023-01-01T02:50:00Z"), OffsetDateTime.parse("2023-01-01T02:55:00Z"), +// OffsetDateTime.parse("2023-01-01T03:00:00Z"), OffsetDateTime.parse("2023-01-01T03:05:00Z"), +// OffsetDateTime.parse("2023-01-01T03:10:00Z"), OffsetDateTime.parse("2023-01-01T03:15:00Z"), +// OffsetDateTime.parse("2023-01-01T03:20:00Z"), OffsetDateTime.parse("2023-01-01T03:25:00Z"), +// OffsetDateTime.parse("2023-01-01T03:30:00Z"), OffsetDateTime.parse("2023-01-01T03:35:00Z"), +// OffsetDateTime.parse("2023-01-01T03:40:00Z"), OffsetDateTime.parse("2023-01-01T03:45:00Z"), +// OffsetDateTime.parse("2023-01-01T03:50:00Z"), OffsetDateTime.parse("2023-01-01T03:55:00Z"), +// OffsetDateTime.parse("2023-01-01T04:00:00Z"), OffsetDateTime.parse("2023-01-01T04:05:00Z"), +// OffsetDateTime.parse("2023-01-01T04:10:00Z"), OffsetDateTime.parse("2023-01-01T04:15:00Z"), +// OffsetDateTime.parse("2023-01-01T04:20:00Z"), OffsetDateTime.parse("2023-01-01T04:25:00Z"), +// OffsetDateTime.parse("2023-01-01T04:30:00Z"), OffsetDateTime.parse("2023-01-01T04:35:00Z"), +// OffsetDateTime.parse("2023-01-01T04:40:00Z"), OffsetDateTime.parse("2023-01-01T04:45:00Z"), +// OffsetDateTime.parse("2023-01-01T04:50:00Z"), OffsetDateTime.parse("2023-01-01T04:55:00Z"), +// OffsetDateTime.parse("2023-01-01T05:00:00Z"), OffsetDateTime.parse("2023-01-01T05:05:00Z"), +// OffsetDateTime.parse("2023-01-01T05:10:00Z"), OffsetDateTime.parse("2023-01-01T05:15:00Z"), +// OffsetDateTime.parse("2023-01-01T05:20:00Z"), OffsetDateTime.parse("2023-01-01T05:25:00Z"), +// OffsetDateTime.parse("2023-01-01T05:30:00Z"), OffsetDateTime.parse("2023-01-01T05:35:00Z"), +// OffsetDateTime.parse("2023-01-01T05:40:00Z"), OffsetDateTime.parse("2023-01-01T05:45:00Z"), +// OffsetDateTime.parse("2023-01-01T05:50:00Z"), OffsetDateTime.parse("2023-01-01T05:55:00Z"), +// OffsetDateTime.parse("2023-01-01T06:00:00Z"), OffsetDateTime.parse("2023-01-01T06:05:00Z"), +// OffsetDateTime.parse("2023-01-01T06:10:00Z"), OffsetDateTime.parse("2023-01-01T06:15:00Z"), +// OffsetDateTime.parse("2023-01-01T06:20:00Z"), OffsetDateTime.parse("2023-01-01T06:25:00Z"), +// OffsetDateTime.parse("2023-01-01T06:30:00Z"), OffsetDateTime.parse("2023-01-01T06:35:00Z"), +// OffsetDateTime.parse("2023-01-01T06:40:00Z"), OffsetDateTime.parse("2023-01-01T06:45:00Z"), +// OffsetDateTime.parse("2023-01-01T06:50:00Z"), OffsetDateTime.parse("2023-01-01T06:55:00Z"), +// OffsetDateTime.parse("2023-01-01T07:00:00Z"), OffsetDateTime.parse("2023-01-01T07:05:00Z"), +// OffsetDateTime.parse("2023-01-01T07:10:00Z"), OffsetDateTime.parse("2023-01-01T07:15:00Z"), +// OffsetDateTime.parse("2023-01-01T07:20:00Z"), OffsetDateTime.parse("2023-01-01T07:25:00Z"), +// OffsetDateTime.parse("2023-01-01T07:30:00Z"), OffsetDateTime.parse("2023-01-01T07:35:00Z"), +// OffsetDateTime.parse("2023-01-01T07:40:00Z"), OffsetDateTime.parse("2023-01-01T07:45:00Z"), +// OffsetDateTime.parse("2023-01-01T07:50:00Z"), OffsetDateTime.parse("2023-01-01T07:55:00Z"), +// OffsetDateTime.parse("2023-01-01T08:00:00Z"), OffsetDateTime.parse("2023-01-01T08:05:00Z"), +// OffsetDateTime.parse("2023-01-01T08:10:00Z"), OffsetDateTime.parse("2023-01-01T08:15:00Z"), +// +// OffsetDateTime.parse("2023-11-24T00:00:00+02:00"), OffsetDateTime.parse("2023-11-24T00:05:00+02:00"), +// OffsetDateTime.parse("2023-11-24T00:10:00+02:00"), OffsetDateTime.parse("2023-11-24T00:15:00+02:00"), +// OffsetDateTime.parse("2023-11-24T00:20:00+02:00"), OffsetDateTime.parse("2023-11-24T00:25:00+02:00"), +// OffsetDateTime.parse("2023-11-24T00:30:00+02:00"), OffsetDateTime.parse("2023-11-24T00:35:00+02:00"), +// OffsetDateTime.parse("2023-11-24T00:40:00+02:00"), OffsetDateTime.parse("2023-11-24T00:45:00+02:00"), +// OffsetDateTime.parse("2023-11-24T00:50:00+02:00"), OffsetDateTime.parse("2023-11-24T00:55:00+02:00") +// +// )); +// +// private ArrayList testDataxx = new ArrayList<>(Arrays.asList(421.0, 408.0, 360.0, 357.0, 330.0, 329.0, +// 330.0, 376.0, 356.0, 334.0, 352.0, 319.0, 247.0, 185.0, 174.0, 226.0, 317.0, 303.0, 299.0, 368.0, 345.0, +// 309.0, 302.0, 374.0, 366.0, 343.0, 334.0, 340.0, 348.0, 306.0, 306.0, 370.0, 335.0, 283.0, 283.0, 278.0, +// 299.0, 250.0, 244.0, 311.0, 290.0, 280.0, 282.0, 324.0, 380.0, 380.0, 372.0, 379.0, 306.0, 296.0, 312.0, +// 363.0, 367.0, 334.0, 309.0, 312.0, 308.0, 667.0, 386.0, 364.0, 336.0, 312.0, 310.0, 343.0, 317.0, 406.0, +// 396.0, 371.0, 357.0, 363.0, 318.0, 304.0, 302.0, 343.0, 327.0, 292.0, 283.0, 272.0, 262.0, 311.0, 331.0, +// 381.0, 401.0, 421.0, 474.0, 463.0, 426.0, 379.0, 801.0, 511.0, 453.0, 351.0, 415.0, 476.0, 508.0, 451.0, +// 435.0, 421.0, 466.0, 599.0, 421.0, 408.0, 360.0, 357.0, 330.0, 329.0, 330.0, 376.0, 356.0, 334.0, 352.0, +// 319.0)); +// +// @Test +// public void testGroupByHour() { +// +// System.out.println(testDatesxx.size()); +// System.out.println(testDataxx.size()); +// GroupBy groupBy = new GroupBy(testDataxx, testDatesxx); +// +// groupBy.hour(); +// +// assertEquals(2, groupBy.getGroupedDataByHour().size()); +// assertEquals(2, groupBy.getGroupedDateByHour().size()); +// +// assertEquals(Arrays.asList(1.0, 3.0, 5.0), groupBy.getGroupedDataByHour().get(0)); +// assertEquals(Arrays.asList(OffsetDateTime.parse("2022-01-01T10:15:30Z"), +// OffsetDateTime.parse("2022-01-01T10:45:00Z"), // +// OffsetDateTime.parse("2022-01-01T10:30:00Z")), groupBy.getGroupedDateByHour().get(0)); +// +// assertEquals(Arrays.asList(2.0, 4.0), groupBy.getGroupedDataByHour().get(1)); +// assertEquals(Arrays.asList(OffsetDateTime.parse("2022-01-01T11:30:45Z"), +// OffsetDateTime.parse("2022-01-01T11:15:00Z")), groupBy.getGroupedDateByHour().get(1)); +// } +// +// @Test +// public void testGroupByMinute() { +// +// GroupBy groupBy = new GroupBy(testDataxx, testDatesxx); +// groupBy.minute(); +// +// assertEquals(3, groupBy.getGroupedDataByMinute().size()); +// assertEquals(3, groupBy.getGroupedDateByMinute().size()); +// +// assertEquals(Arrays.asList(1.0, 4.0), groupBy.getGroupedDataByMinute().get(0)); +// assertEquals( +// Arrays.asList(OffsetDateTime.parse("2022-01-01T10:15:30Z"), +// OffsetDateTime.parse("2022-01-01T11:15:00Z")), // +// groupBy.getGroupedDateByMinute().get(0)); +// +// assertEquals(Arrays.asList(2.0, 5.0), groupBy.getGroupedDataByMinute().get(1)); +// assertEquals(Arrays.asList(OffsetDateTime.parse("2022-01-01T11:30:45Z"), // +// OffsetDateTime.parse("2022-01-01T10:30Z")// +// +// ), groupBy.getGroupedDateByMinute().get(1)); +// } +} diff --git a/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/preprocessing/GroupToStiffWindowlTest.java b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/preprocessing/GroupToStiffWindowlTest.java new file mode 100644 index 00000000000..9a8c225f305 --- /dev/null +++ b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/preprocessing/GroupToStiffWindowlTest.java @@ -0,0 +1,75 @@ +package io.openems.edge.predictor.lstm.preprocessing; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +import java.util.ArrayList; +import java.util.Arrays; + +import org.junit.Test; + +import io.openems.edge.predictor.lstm.preprocessingpipeline.GroupToStiffWindowPipe; + +public class GroupToStiffWindowlTest { + @Test + public void testGroupToStiffedWindow() { + ArrayList inputValues = new ArrayList<>(Arrays.asList(1.0, 2.0, 3.0, 4.0, 5.0, 6.0)); + int windowSize = 2; + + double[][] result = GroupToStiffWindowPipe.groupToStiffedWindow(inputValues, windowSize); + + double[][] expected = { { 1.0, 2.0 }, { 4.0, 5.0 } }; + assertArrayEquals("Windowing is incorrect", expected, result); + } + + @Test + public void testGroupToStiffedWindow1() { + ArrayList inputValues = new ArrayList<>(Arrays.asList(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0)); + int windowSize = 2; + + double[][] result = GroupToStiffWindowPipe.groupToStiffedWindow(inputValues, windowSize); + // double[][] resultX = + // GroupToStiffWindowPipe.groupToStiffedWindowX(inputValues, windowSize); + + double[][] expected = { { 1.0, 2.0 }, { 4.0, 5.0 }, { 7.0, 8.0 } }; + assertArrayEquals("Windowing is incorrect", expected, result); + } + + @Test + public void testGroupToStiffedWindowWithInvalidSize() { + ArrayList inputValues = new ArrayList<>(Arrays.asList(1.0, 2.0, 3.0, 4.0, 5.0, 6.0)); + int windowSize = 7; + + IllegalArgumentException exception = assertThrows(// + IllegalArgumentException.class, () -> { + GroupToStiffWindowPipe.groupToStiffedWindow(inputValues, windowSize); + }); + + assertEquals("Invalid window size", exception.getMessage()); + } + + @Test + public void testGroupToStiffedTarget() { + ArrayList inputValues = new ArrayList<>(Arrays.asList(1.0, 2.0, 3.0, 4.0, 5.0, 6.0)); + int windowSize = 2; + + double[] result = GroupToStiffWindowPipe.groupToStiffedTarget(inputValues, windowSize); + + double[] expected = { 3.0, 6.0 }; + assertArrayEquals(expected, result, 0.001); + } + + @Test + public void testGroupToStiffedTargetWithInvalidSize() { + ArrayList inputValues = new ArrayList<>(Arrays.asList(1.0, 2.0, 3.0, 4.0, 5.0, 6.0)); + int windowSize = 7; + + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> { + GroupToStiffWindowPipe.groupToStiffedTarget(inputValues, windowSize); + }); + + assertEquals("Invalid window size", exception.getMessage()); + } + +} diff --git a/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/preprocessing/MovingAverageTest.java b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/preprocessing/MovingAverageTest.java new file mode 100644 index 00000000000..216fa54e253 --- /dev/null +++ b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/preprocessing/MovingAverageTest.java @@ -0,0 +1,38 @@ +package io.openems.edge.predictor.lstm.preprocessing; + +import org.junit.Test; + +import io.openems.edge.predictor.lstm.utilities.UtilityConversion; + +public class MovingAverageTest { + + @Test + public void test() { + + double[] data = { 393.0, 555.0, 482.0, 297.0, 317.0, 162.0, 157.0, 208.0, 243.0, 312.0, 377.0, 393.0, 308.0, + 287.0, 229.0, 226.0, 221.0, 259.0, 277.0, 284.0, 265.0, 250.0, 218.0, 151.0, 155.0, 214.0, 184.0, 148.0, + 221.0, 249.0, 290.0, 199.0, 240.0, 264.0, 193.0, 176.0, 147.0, 232.0, 275.0, 269.0, 319.0, 247.0, 230.0, + 225.0, 228.0, 221.0, 238.0, 321.0, 325.0, 228.0, 221.0, 198.0, 227.0, 278.0, 288.0, 338.0, 304.0, 307.0, + 238.0, 195.0, 153.0, 166.0, 205.0, 263.0, 157.0, 190.0, 280.0, 275.0, 240.0, 288.0, 306.0, 285.0, 281.0, + 273.0, 285.0, 346.0, 374.0, 338.0, 334.0, 288.0, 221.0, 168.0, 160.0, 161.0, 221.0, 279.0, 257.0, 324.0, + 178.0, 167.0, 192.0, 204.0, 188.0, 174.0, 233.0, 217.0, 194.0, 203.0, 294.0, 450.0, 583.0, 1835.0, + 2672.0, 2652.0, 3003.0, 2690.0, 2747.0, 2880.0, 2761.0, 2826.0, 2881.0, 2926.0, 2971.0, 2951.0, 3342.0, + 3592.0, 3367.0, 3271.0, 3323.0, 3446.0, 5255.0, 5354.0, 5467.0, 5127.0, 3525.0, 3206.0, 3106.0, 2961.0, + 3085.0, 3116.0, 2466.0, 2771.0, 2817.0, 3004.0, 5151.0, 5386.0, 5333.0, 5471.0, 5172.0, 5177.0, 5248.0, + 5259.0, 5255.0, 5631.0, 5410.0, 5409.0, 5415.0, 5350.0, 5782.0, 6611.0, 6545.0, 6145.0, 5406.0, 5192.0, + 3745.0, 4630.0, 4149.0, 4106.0, 5432.0, 7020.0, 4766.0, 4047.0, 3858.0, 3689.0, 2505.0, 1773.0, 1257.0, + 1018.0, 1000.0, 1422.0, 960.0, 707.0, 765.0, 797.0, 641.0, 699.0, 693.0, 681.0, 627.0, 581.0, 605.0, + 608.0, 572.0, 648.0, 637.0, 711.0, 695.0, 736.0, 705.0, 696.0, 710.0, 725.0, 662.0, 630.0, 627.0, 638.0, + 608.0, 518.0, 448.0, 417.0, 353.0, 394.0, 353.0, 397.0, 421.0, 413.0, 409.0, 374.0, 353.0, 356.0, 392.0, + 419.0, 419.0, 345.0, 410.0, 342.0, 339.0, 343.0, 242.0, 230.0, 226.0, 274.0, 367.0, 325.0, 276.0, 249.0, + 281.0, 313.0, 247.0, 230.0, 233.0, 208.0, 286.0, 231.0, 208.0, 210.0, 281.0, 346.0, 324.0, 674.0, 469.0, + 283.0, 369.0, 340.0, 325.0, 379.0, 353.0, 351.0, 416.0, 432.0, 576.0, 655.0, 689.0, 734.0, 662.0, 696.0, + 686.0, 694.0, 668.0, 589.0, 489.0, 453.0, 453.0, 454.0, 494.0, 440.0, 435.0, 463.0, 490.0, 460.0, 478.0, + 434.0, 506.0, 489.0, 545.0, 622.0, 696.0, 613.0, 562.0, 521.0, 551.0, 492.0, 475.0, 507.0, 489.0, 488.0, + 471.0, 401.0 }; + + System.out.println("Converted = " + UtilityConversion.to1DArrayList(MovingAverage.movingAverage(data))); + System.out.println("orginal = " + UtilityConversion.to1DArrayList(data)); + } + +} diff --git a/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/preprocessing/NormalizePipelineTest.java b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/preprocessing/NormalizePipelineTest.java new file mode 100644 index 00000000000..51193de9042 --- /dev/null +++ b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/preprocessing/NormalizePipelineTest.java @@ -0,0 +1,23 @@ +package io.openems.edge.predictor.lstm.preprocessing; + +import org.junit.Test; + +import io.openems.edge.predictor.lstm.common.HyperParameters; +import io.openems.edge.predictor.lstm.preprocessingpipeline.NormalizePipe; + +public class NormalizePipelineTest { + + @Test + public void test() { + double[] data = { 1.0, 2.0 }; + double[][] data2D = { { 1.0, 2.0 }, { 3.0, 4.0 } }; + + var hyperParameters = new HyperParameters(); + var np = new NormalizePipe(hyperParameters); + np.execute(data); + var result = (double[][]) np.execute(data2D); + System.out.print(result[0][0]); + + } + +} diff --git a/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/preprocessing/OutliersDetectionTest.java b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/preprocessing/OutliersDetectionTest.java new file mode 100644 index 00000000000..95381b962c1 --- /dev/null +++ b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/preprocessing/OutliersDetectionTest.java @@ -0,0 +1,20 @@ +package io.openems.edge.predictor.lstm.preprocessing; + +import org.junit.Test; + +import io.openems.edge.predictor.lstm.common.HyperParameters; +import io.openems.edge.predictor.lstm.utilities.UtilityConversion; + +public class OutliersDetectionTest { + + @Test + public void test() { + HyperParameters hyperParameters = new HyperParameters(); + double[] data = { 1, 2, 4, 8, 1000, 4000, 9, 8, 7, 2, 3, 8, 7, 7, 9, 7 }; + var dataScaled = DataModification.scale(data, hyperParameters.getScalingMin(), hyperParameters.getScalingMax()); + var dataWithoutOutliers = FilterOutliers.filterOutlier(dataScaled); + System.out.println(UtilityConversion.to1DArrayList(DataModification.scaleBack(dataWithoutOutliers, + hyperParameters.getScalingMin(), hyperParameters.getScalingMax()))); + } + +} diff --git a/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/preprocessing/PreprocessingPipe.java b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/preprocessing/PreprocessingPipe.java new file mode 100644 index 00000000000..7d6aae4dde3 --- /dev/null +++ b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/preprocessing/PreprocessingPipe.java @@ -0,0 +1,71 @@ +package io.openems.edge.predictor.lstm.preprocessing; + +//import static org.junit.Assert.assertEquals; +// +//import java.util.ArrayList; +// +//import org.junit.Test; +// +//import io.openems.edge.predictor.lstm.common.HyperParameters; +//import io.openems.edge.predictor.lstm.preprocessingpipeline.PreprocessingPipeImpl; +//import io.openems.edge.predictor.lstm.preprocessingpipeline.ScalingPipe; +//import io.openems.edge.predictor.lstm.preprocessingpipeline.TrainandTestSplitPipe; +//import io.openems.edge.predictor.lstm.utilities.UtilityConversion; +// +//public class PreprocessingPipe { +// +// @Test +// public void trainandTestSplitPipeTest() { +// double[] data = { 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0 }; +// double[] res1 = { 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, }; +// double[] res2 = { 7.0, 8.0, 9.0, 10.0 }; +// HyperParameters hyp = new HyperParameters(); +// hyp.setDatasplitTrain(.7); +// TrainandTestSplitPipe ttsp = new TrainandTestSplitPipe(hyp); +// assertEquals(ttsp.execute(data)[0], res1); +// assertEquals(ttsp.execute(data)[1], res2); +// +// } +// +// @Test +// +// public void scalingPipeTest() { +// double[] data = { 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0 }; +// +// double[] result = { 0.2, 0.2666666666666667, 0.33333333333333337, 0.4, 0.46666666666666673, 0.5333333333333334, +// 0.6000000000000001, 0.6666666666666667, 0.7333333333333334, 0.8 }; +// +// HyperParameters hyp = new HyperParameters(); +// hyp.setScalingMax(10.0); +// hyp.setScalingMin(1); +// ScalingPipe sp = new ScalingPipe(hyp); +// +// } +// +// @Test +// public void preprocessingPiplineTest() { +// double[] data = { 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, +// 18.0 }; +// HyperParameters hyp = new HyperParameters(); +// hyp.setScalingMax(100); +// hyp.setScalingMin(1); +// hyp.setDatasplitTrain(.7); +// hyp.setWindowSizeTrend(2); +// hyp.setWindowSizeSeasonality(7); +// +// PreprocessingPipeImpl ppimplObj1 = new PreprocessingPipeImpl(hyp); +// ArrayList> temp = UtilityConversion +// .convert2DArrayTo2DArrayList((double[][]) ppimplObj1.setData(data).scale().trainTestSplit().execute()); +// // System.out.println(temp); +// +// PreprocessingPipeImpl ppimplObj2 = new PreprocessingPipeImpl(hyp); +// double[][][] normalized = (double[][][]) ppimplObj2.setData(data).groupToStiffedWindow().execute(); +// ArrayList> normalizedData = UtilityConversion.convert2DArrayTo2DArrayList(normalized[0]); +// ArrayList target = UtilityConversion.convert1DArrayTo1DArrayList(normalized[1][0]); +// +// System.out.println("Target = " + target); +// System.out.println("data = " + normalizedData); +// +// } +// +//} diff --git a/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/preprocessing/ShuffleTest.java b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/preprocessing/ShuffleTest.java new file mode 100644 index 00000000000..35830520089 --- /dev/null +++ b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/preprocessing/ShuffleTest.java @@ -0,0 +1,35 @@ +package io.openems.edge.predictor.lstm.preprocessing; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +import org.junit.Test; + +public class ShuffleTest { + + @Test + public void testShuffle() { + double[][] originalData = { // + { 1.0, 2.0, 3.0 }, // + { 4.0, 5.0, 6.0 }, // + { 7.0, 8.0, 9.0 }, // + { 10.0, 11.0, 12.0 }, // + { 13.0, 14.0, 15.0 }// + }; + + double[] originalTarget = { 10.0, 20.0, 30.0, 40.0, 50.0 }; + + Shuffle shuffle = new Shuffle(originalData, originalTarget); + + double[][] shuffledData = shuffle.getData(); + double[] shuffledTarget = shuffle.getTarget(); + + assertNotEquals(originalData, shuffledData); + assertNotEquals(originalTarget, shuffledTarget); + + assertEquals(originalData.length, shuffledData.length); + assertEquals(originalTarget.length, shuffledTarget.length); + + } + +} diff --git a/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/train/BatchImplementationTest.java b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/train/BatchImplementationTest.java new file mode 100644 index 00000000000..a8305713f33 --- /dev/null +++ b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/train/BatchImplementationTest.java @@ -0,0 +1,155 @@ +package io.openems.edge.predictor.lstm.train; + +import java.time.OffsetDateTime; +import java.util.ArrayList; + +import org.junit.Ignore; +import org.junit.Test; + +import io.openems.edge.predictor.lstm.common.HyperParameters; +import io.openems.edge.predictor.lstm.common.ReadAndSaveModels; +import io.openems.edge.predictor.lstm.common.ReadCsv; +import io.openems.edge.predictor.lstm.preprocessing.DataModification; + +public class BatchImplementationTest { + /** + * Batch testing. + */ + @Ignore + @Test + public void trainInBatchtest() { + + HyperParameters hyperParameters; + String modelName = "ConsumptionActivePower"; + + hyperParameters = ReadAndSaveModels.read(modelName); + + int check = hyperParameters.getOuterLoopCount(); + + for (int i = check; i <= 25; i++) { + + hyperParameters.setOuterLoopCount(i); + + final String pathTrain = Integer.toString(4) + ".csv"; + final String pathValidate = Integer.toString(4) + ".csv"; + System.out.println(""); + + hyperParameters.printHyperParameters(); + hyperParameters.setLearningRateLowerLimit(0.00001); + hyperParameters.setLearningRateUpperLimit(0.001); + + System.out.println(""); + + System.out.println(pathTrain); + System.out.println(pathValidate); + + ReadCsv obj1 = new ReadCsv(pathTrain); + final ReadCsv obj2 = new ReadCsv(pathValidate); + + var validateBatchData = DataModification.getDataInBatch(obj2.getData(), 6).get(1); + var validateBatchDate = DataModification.getDateInBatch(obj2.getDates(), 6).get(1); + + // ReadAndSaveModels.adapt(hyperParameters, validateBatchData, + // validateBatchDate); + + new TrainAndValidateBatch( + DataModification.constantScaling(DataModification.removeNegatives(obj1.getData()), 1), + obj1.getDates(), + DataModification.constantScaling(DataModification.removeNegatives(validateBatchData), 1), + validateBatchDate, hyperParameters); + + hyperParameters.setEpochTrack(0); + hyperParameters.setBatchTrack(0); + hyperParameters.setOuterLoopCount(hyperParameters.getOuterLoopCount() + 1); + ReadAndSaveModels.save(hyperParameters); + + } + } + + // @Test + protected void trainInBatchtestMultivarient() { + + HyperParameters hyperParameters; + String modelName = "ConsumptionActivePower"; + + hyperParameters = ReadAndSaveModels.read(modelName); + + int check = hyperParameters.getOuterLoopCount(); + + for (int i = check; i <= 25; i++) { + + hyperParameters.setOuterLoopCount(i); + + final String pathTrain = Integer.toString(i + 4) + ".csv"; + final String pathValidate = Integer.toString(i + 4) + ".csv"; + System.out.println(""); + + hyperParameters.printHyperParameters(); + hyperParameters.setLearningRateLowerLimit(0.00001); + hyperParameters.setLearningRateUpperLimit(0.001); + + System.out.println(""); + + System.out.println(pathTrain); + System.out.println(pathValidate); + + ReadCsv obj1 = new ReadCsv(pathTrain); + final ReadCsv obj2 = new ReadCsv(pathValidate); + + var trainingref = this.generateRefrence(obj1.getDates()); + var validationref = this.generateRefrence(obj2.getDates()); + + var trainingData = DataModification.elementWiseMultiplication(trainingref, obj1.getData()); + var validationData = DataModification.elementWiseMultiplication(validationref, obj2.getData()); + + var validateBatchData = DataModification.getDataInBatch(validationData, 6).get(1); + var validateBatchDate = DataModification.getDateInBatch(obj2.getDates(), 6).get(1); + + // ReadAndSaveModels.adapt(hyperParameters, validateBatchData, + // validateBatchDate); + + new TrainAndValidateBatch(trainingData, obj1.getDates(), validateBatchData, validateBatchDate, + hyperParameters); + + hyperParameters.setEpochTrack(0); + hyperParameters.setBatchTrack(0); + hyperParameters.setOuterLoopCount(hyperParameters.getOuterLoopCount() + 1); + ReadAndSaveModels.save(hyperParameters); + + } + + } + + /** + * Generates a list of reference values based on the provided list of + * OffsetDateTime objects. Each reference value is calculated using the cosine + * of the angle corresponding to the time of day represented by each + * OffsetDateTime. The formula used is: - One hour corresponds to 360/24 + * degrees. - One minute corresponds to 360/(24*60) degrees. + * + * @param date an ArrayList of OffsetDateTime objects representing the date and + * time. + * @return an ArrayList of Double values representing the generated reference + * values. + */ + public ArrayList generateRefrence(ArrayList date) { + ArrayList data = new ArrayList(); + + for (int i = 0; i < date.size(); i++) { + // Extract the hour and minute from the current OffsetDateTime. + int hour = date.get(i).getHour(); + int minute = date.get(i).getMinute(); + + // Calculate the degree values for the hour and minute. + double deg = 360.0 * hour / 24.0; + double degDec = 360.0 * minute / (24.0 * 60.0); + double angle = deg + degDec; + + // Calculate the cosine of the angle in radians and add 1.5 to the result. + double addVal = Math.cos(Math.toRadians(angle)); + data.add(1.5 + addVal); + } + return data; + } + +} \ No newline at end of file diff --git a/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/train/MakeModelImplementationTest.java b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/train/MakeModelImplementationTest.java new file mode 100644 index 00000000000..3b72896221d --- /dev/null +++ b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/train/MakeModelImplementationTest.java @@ -0,0 +1,59 @@ +//package io.openems.edge.predictor.lstm.train; +// +//import java.io.IOException; +// +//import org.junit.Test; +// +//import io.openems.edge.predictor.lstm.common.DeletModels; +//import io.openems.edge.predictor.lstm.common.GetObject; +//import io.openems.edge.predictor.lstm.common.HyperParameters; +//import io.openems.edge.predictor.lstm.common.ReadCsv; +//import io.openems.edge.predictor.lstm.common.SaveObject; +// +//public class MakeModelImplementationTest { +// +// @Test +// public static void itter() { +// HyperParameters hyperParameters; +// String modelName = "Consumption"; +// try { +// hyperParameters = (HyperParameters) GetObject.get(modelName); +// } catch (ClassNotFoundException | IOException e) { +// // TODO Auto-generated catch block +// System.out.println("Creating new hyperparameter object"); +// hyperParameters = HyperParameters.getInstance(); +// hyperParameters.setModelName(modelName); +// } +// +// // checking if the training has been completed in previous batch +// +// if (hyperParameters.getEpochTrack() == hyperParameters.getEpoch()) { +// // hyperParameters.setEpochTrack(0); +// +// } +// +// int check = hyperParameters.getOuterLoopCount(); +// for (int i = check; i <= 8; i++) { +// hyperParameters.setOuterLoopCount(i); +// System.out.println("Batch:" + i + "/" + 28); +// System.out.println("count :" + hyperParameters.getCount()); +// System.out.println("Rms Error of all train for the trend is = " + hyperParameters.getRmsErrorTrend()); +// System.out.println( +// "Rms Error of all train for the seasonality is = " + hyperParameters.getRmsErrorSeasonality()); +// +// String pathTrain = Integer.toString(i + 1) + ".csv"; +// String pathValidate = Integer.toString(9) + ".csv"; +// +// ReadCsv obj1 = new ReadCsv(pathTrain); +// final ReadCsv obj2 = new ReadCsv(pathValidate); +// +// new TrainAndValidate(obj1.getData(), obj1.getDates(), obj2.getData(), obj2.getDates(), hyperParameters); +// +// hyperParameters.setEpochTrack(0); +// hyperParameters.setOuterLoopCount(hyperParameters.getOuterLoopCount() + 1); +// SaveObject.save(hyperParameters); +// DeletModels.delet(hyperParameters); +// } +// +// } +//} diff --git a/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/train/MakeModelTest.java b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/train/MakeModelTest.java new file mode 100644 index 00000000000..07c2a49d11a --- /dev/null +++ b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/train/MakeModelTest.java @@ -0,0 +1,35 @@ +package io.openems.edge.predictor.lstm.train; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.util.ArrayList; + +import org.junit.Test; + +import io.openems.edge.predictor.lstm.common.HyperParameters; + +public class MakeModelTest { + + @Test + public void testGenerateInitialWeightMatrixOriginal() { + // Result should be + // [ + // [1.0, 1.0, 1.0], + // [1.0, 1.0, 1.0], + // [1.0, 1.0, 1.0], + // [-1.0, -1.0, -1.0], + // [-1.0, -1.0, -1.0], + // [-1.0, -1.0, -1.0], + // [0.0, 0.0, 0.0], + // [0.0, 0.0, 0.0] + // ] + + int windowSize = 3; + ArrayList> result = MakeModel.generateInitialWeightMatrix(windowSize, new HyperParameters()); + + assertNotNull(result); + assertEquals(8, result.size()); + assertEquals(windowSize, result.get(0).size()); + } +} diff --git a/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/util/AdaptiveLearningRateTest.java b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/util/AdaptiveLearningRateTest.java new file mode 100644 index 00000000000..0d17bef1b24 --- /dev/null +++ b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/util/AdaptiveLearningRateTest.java @@ -0,0 +1,100 @@ +package io.openems.edge.predictor.lstm.util; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +import io.openems.edge.predictor.lstm.common.HyperParameters; + +public class AdaptiveLearningRateTest { + + @Test + public void scheduleTest() { + AdaptiveLearningRate obj = new AdaptiveLearningRate(); + HyperParameters hyperParameter = new HyperParameters(); + + hyperParameter.setLearningRateUpperLimit(0.2); + hyperParameter.setLearningRateLowerLimit(0.05); + hyperParameter.setEpochTrack(10); + + double perc = (double) hyperParameter.getEpochTrack() / hyperParameter.getEpoch(); + double lr = obj.scheduler(hyperParameter); + double val = hyperParameter.getLearningRateLowerLimit() + + 0.5 * (hyperParameter.getLearningRateUpperLimit() - hyperParameter.getLearningRateLowerLimit()) + * (1 + Math.cos(perc * Math.PI)); + assertEquals(lr, val, 0.0001); + + } + + @Test + + public void adagradOptimizerTest() { + AdaptiveLearningRate obj = new AdaptiveLearningRate(); + double globalLearningRate = 0.001; + double localLearningRate = 0.1; + + // Test case; i = 0, gradient =0 + int i = 0; + double gradient = 0.0; + double lr = obj.adagradOptimizer(globalLearningRate, localLearningRate, gradient, i); + assertEquals(lr, globalLearningRate, 0.0001); + + // Test Case i>0 gradient =! 0 + i = 0; + gradient = 10; + lr = obj.adagradOptimizer(globalLearningRate, localLearningRate, gradient, i); + // double expected = globalLearningRate / gradient; + assertEquals(lr, globalLearningRate, 0.0001); + + // Test Case 3 i > 0 gradient = 0 , local learning rate = 0 + i = 1; + gradient = 0; + localLearningRate = 0; + lr = obj.adagradOptimizer(globalLearningRate, localLearningRate, gradient, i); + assertEquals(lr, globalLearningRate, 0.0001); + + // Test Case 3 i > 0 gradient = 0 , local learning rate =! 0 + + i = 1; + gradient = 0; + localLearningRate = 0.001; + lr = obj.adagradOptimizer(globalLearningRate, localLearningRate, gradient, i); + double temp1 = globalLearningRate / localLearningRate; + double temp2 = Math.pow(temp1, 2); + double temp3 = temp2 + Math.pow(gradient, 2); + double expected = globalLearningRate / Math.pow(temp3, 0.5); + assertEquals(lr, expected, 0.0001); + + } + + @Test + // To test if the learning rate decreases with epoch + + public void adagradTestWithScheduler() { + HyperParameters hyperParameters = new HyperParameters(); + AdaptiveLearningRate obj = new AdaptiveLearningRate(); + double localLearningRate = 0.1; + double globalLearningRate = 0.1; + double gradient = 1; + double previousLocalLearningRate = localLearningRate; + double previousGlobalLearningRate = globalLearningRate; + hyperParameters.setLearningRateUpperLimit(0.1); + hyperParameters.setLearningRateLowerLimit(0.0005); + for (int i = 1; i < hyperParameters.getEpoch(); i++) { + hyperParameters.setEpochTrack(i); + localLearningRate = obj.scheduler(hyperParameters); + // System.out.println("Local Learning Rate: " + localLearningRate); + gradient = gradient + gradient * (hyperParameters.getEpochTrack() / hyperParameters.getEpoch()); + localLearningRate = obj.adagradOptimizer(globalLearningRate, localLearningRate, gradient, i); + globalLearningRate = localLearningRate; + assert (previousLocalLearningRate > localLearningRate); + assert (previousGlobalLearningRate > globalLearningRate); + + previousLocalLearningRate = localLearningRate; + previousGlobalLearningRate = globalLearningRate; + + } + + } + +} diff --git a/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/util/CellTest.java b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/util/CellTest.java new file mode 100644 index 00000000000..38c06e1ba51 --- /dev/null +++ b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/util/CellTest.java @@ -0,0 +1,148 @@ +package io.openems.edge.predictor.lstm.util; + +import static org.junit.Assert.assertEquals; + +import io.openems.edge.predictor.lstm.utilities.MathUtils; + +public class CellTest { + private double wi = 1; + private double wo = 1; + private double wz = 1; + private double ri = 1; + private double ro = 1; + private double rz = 1; + private double yt = 1; + private double input = 1; + private double target = 1; + private double ctMinusOne = 1; + private double ytMinusOne = 1; + + private double dropOutProb = 0.02; // do not change this + private Cell obj = new Cell(this.input, this.target, this.wi, this.wo, this.wz, this.ri, this.ro, this.rz, this.yt); + + /** + * forwardPropagationTest. + */ + // @Test + public void forwardPropagationTest() { + this.obj.setCtMinusOne(this.ctMinusOne); + this.obj.setYtMinusOne(this.ytMinusOne); + this.obj.forwardPropogation(); + + double itExpected = MathUtils.sigmoid(this.wi * this.input + this.ri * this.ytMinusOne); + double otExpected = MathUtils.sigmoid(this.wo * this.input + this.ro * this.ytMinusOne); + double ztExpected = MathUtils.tanh(this.wz * this.input + this.rz * this.ytMinusOne); + + // computation without drop out + + double ctExpectedWithoutDropout = this.ctMinusOne + itExpected * ztExpected; + double ytExpectedWithoutDropout = otExpected * MathUtils.tanh(ctExpectedWithoutDropout); + final double errorExpectedWithoutDropout = ytExpectedWithoutDropout - this.target; + + // computation with drop out + double ctExpectedWithDropout = this.ctMinusOne + itExpected * ztExpected * this.dropOutProb; + double ytExpectedWithDropout = this.ytMinusOne * (1 - this.dropOutProb) + + otExpected * MathUtils.tanh(ctExpectedWithDropout) * (this.dropOutProb); + final double errorExpectedWithDropout = ytExpectedWithDropout - this.target; + + assertEquals(itExpected, this.obj.getIt(), 0.00001); + assertEquals(otExpected, this.obj.getOt(), 0.00001); + assertEquals(ztExpected, this.obj.getZt(), 0.00001); + + boolean matchCheckcT = ctExpectedWithoutDropout == this.obj.getCt() + || ctExpectedWithDropout == this.obj.getCt(); + boolean matchCheckyT = ytExpectedWithoutDropout == this.obj.getYt() + || ytExpectedWithDropout == this.obj.getYt(); + boolean matchCheckerror = errorExpectedWithoutDropout == this.obj.getError() + || errorExpectedWithDropout == this.obj.getError(); + assert (matchCheckcT && matchCheckyT && matchCheckerror); + + } + + /** + * Backpropagation. + */ + // @Test + public void backwardPropogationTest() { + + this.obj.setCtMinusOne(this.ctMinusOne); + this.obj.setYtMinusOne(this.ytMinusOne); + this.obj.setDlByDc(0.0); + + this.obj.forwardPropogation(); + this.obj.backwardPropogation(); + // common calculation + double itExpected = MathUtils.sigmoid(this.wi * this.input + this.ri * this.ytMinusOne); + double otExpected = MathUtils.sigmoid(this.wo * this.input + this.ro * this.ytMinusOne); + double ztExpected = MathUtils.tanh(this.wz * this.input + this.rz * this.ytMinusOne); + + // without drop out + // Forward propagation + double ctExpectedWithoutDropout = this.ctMinusOne + itExpected * ztExpected; + double ytExpectedWithoutDropout = otExpected * MathUtils.tanh(ctExpectedWithoutDropout); + double errorExpectedWithoutDropout = ytExpectedWithoutDropout - this.target; + // backward propagation + double dlByDyWithoutDropout = errorExpectedWithoutDropout; + + double dlByDoWithoutDropout = dlByDyWithoutDropout * MathUtils.tanh(ctExpectedWithoutDropout); + double dlByDcWithoutDropout = 0; + double dlByDcWithDropout = 0; + + dlByDcWithoutDropout = dlByDyWithoutDropout * otExpected * MathUtils.tanhDerivative(ctExpectedWithoutDropout) + + dlByDcWithoutDropout; + + double dlByDiWithoutDropout = dlByDcWithoutDropout * ztExpected; + double dlByDzWithoutDropout = dlByDcWithoutDropout * itExpected; + double delIWithoutDropout = dlByDiWithoutDropout + * MathUtils.sigmoidDerivative(this.wi * this.input + this.ri * this.ytMinusOne); + double delOWithoutDropout = dlByDoWithoutDropout + * MathUtils.sigmoidDerivative(this.wo * this.input + this.ro * this.ytMinusOne); + double delZWithoutDropout = dlByDzWithoutDropout + * MathUtils.tanhDerivative(this.wz * this.input + this.rz * this.ytMinusOne); + + // computation with drop out + // Forwar Propagatin + + double ctExpectedWithDropout = this.ctMinusOne + itExpected * ztExpected * this.dropOutProb; + double ytExpectedWithDropout = this.ytMinusOne * (1 - this.dropOutProb) + + otExpected * MathUtils.tanh(ctExpectedWithDropout) * (this.dropOutProb); + double errorExpectedWithDropout = ytExpectedWithDropout - this.target; + // backward propagation + double dlByDyWithDropout = errorExpectedWithDropout; + + double dlByDoWithDropout = dlByDyWithDropout * MathUtils.tanh(ctExpectedWithDropout); + + dlByDcWithDropout = dlByDyWithDropout * otExpected * MathUtils.tanhDerivative(ctExpectedWithDropout) + + dlByDcWithDropout; + + double dlByDiWithDropout = dlByDcWithDropout * ztExpected; + double dlByDzWithDropout = dlByDcWithDropout * itExpected; + double delIWithDropout = dlByDiWithDropout + * MathUtils.sigmoidDerivative(this.wi * this.input + this.ri * this.ytMinusOne); + double delOWithDropout = dlByDoWithDropout + * MathUtils.sigmoidDerivative(this.wo * this.input + this.ro * this.ytMinusOne); + double delZWithDropout = dlByDzWithDropout + * MathUtils.tanhDerivative(this.wz * this.input + this.rz * this.ytMinusOne); + + boolean dlByDydecission = this.obj.getDlByDy() == dlByDyWithDropout + || this.obj.getDlByDy() == dlByDyWithoutDropout; + boolean dlByDodecission = this.obj.getDlByDo() == dlByDoWithDropout + || this.obj.getDlByDo() == dlByDoWithoutDropout; + + boolean dlByDcdecission = this.obj.getDlByDc() == dlByDcWithDropout + || this.obj.getDlByDc() == dlByDcWithoutDropout; + boolean dlByDidecission = this.obj.getDlByDi() == dlByDiWithDropout + || this.obj.getDlByDi() == dlByDiWithoutDropout; + boolean dlByDzdecission = this.obj.getDlByDz() == dlByDzWithDropout + || this.obj.getDlByDz() == dlByDzWithoutDropout; + + boolean delIdecission = this.obj.getDelI() == delIWithDropout || this.obj.getDelI() == delIWithoutDropout; + boolean delOdecission = this.obj.getDelO() == delOWithDropout || this.obj.getDelO() == delOWithoutDropout; + boolean delZdecission = this.obj.getDelZ() == delZWithDropout || this.obj.getDelZ() == delZWithoutDropout; + + assert (dlByDydecission && dlByDodecission && dlByDcdecission && dlByDidecission && dlByDzdecission + && delIdecission && delOdecission && delZdecission); + + } + +} diff --git a/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/util/LstmTest.java b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/util/LstmTest.java new file mode 100644 index 00000000000..420e3623391 --- /dev/null +++ b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/util/LstmTest.java @@ -0,0 +1,38 @@ +package io.openems.edge.predictor.lstm.util; + +import static org.junit.Assert.assertEquals; + +import java.util.ArrayList; +import java.util.Arrays; + +import org.junit.Test; + +import io.openems.edge.predictor.lstm.utilities.UtilityConversion; + +public class LstmTest { + @Test + public void findGlobalMinima() { + + double[] errorList = { 1, 2, 3, 4, 5, 6, 3, 4, 0, 0.05, -5, 10 }; + int val = Lstm.findGlobalMinima(UtilityConversion.to1DArrayList(errorList)); + int expectedIndex = 8; // + assertEquals(val, expectedIndex); + + ArrayList testData1 = new ArrayList<>(Arrays.asList(2.5, -3.7, 1.8, -4.2, 5.1)); + int expectedIndex1 = 2; // + int actualIndex1 = Lstm.findGlobalMinima(testData1); + assertEquals(expectedIndex1, actualIndex1); + + ArrayList testData2 = new ArrayList<>(Arrays.asList(0.0, 0.0, 0.0, 0.0)); + int expectedIndex2 = 0; // + int actualIndex2 = Lstm.findGlobalMinima(testData2); + assertEquals(expectedIndex2, actualIndex2); + + ArrayList testData3 = new ArrayList<>(Arrays.asList(5.5, -2.2, 3.3, -4.4)); + int expectedIndex3 = 1; + int actualIndex3 = Lstm.findGlobalMinima(testData3); + assertEquals(expectedIndex3, actualIndex3); + + } + +} diff --git a/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/utillities/DataUtilityTest.java b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/utillities/DataUtilityTest.java new file mode 100644 index 00000000000..b832c35179b --- /dev/null +++ b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/utillities/DataUtilityTest.java @@ -0,0 +1,49 @@ +package io.openems.edge.predictor.lstm.utillities; + +import static io.openems.edge.predictor.lstm.utilities.DataUtility.combine; +import static io.openems.edge.predictor.lstm.utilities.DataUtility.getMinute; +import static org.junit.Assert.assertEquals; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.Arrays; + +import org.junit.Test; + +import io.openems.edge.predictor.lstm.common.HyperParameters; + +public class DataUtilityTest { + + private static final HyperParameters HYPER_PARAMETERS = new HyperParameters(); + + @Test + public void test() { + var dateTime1 = ZonedDateTime.of(2022, 1, 1, 12, 4, 0, 0, ZoneId.systemDefault()); + var res = getMinute(dateTime1, HYPER_PARAMETERS).intValue(); + assertEquals(0, res); + + var dateTime2 = ZonedDateTime.of(2022, 1, 1, 12, 8, 0, 0, ZoneId.systemDefault()); + var res1 = getMinute(dateTime2, HYPER_PARAMETERS).intValue(); + assertEquals(5, res1); + + var dateTime3 = ZonedDateTime.of(2022, 1, 1, 12, 36, 0, 0, ZoneId.systemDefault()); + var res2 = getMinute(dateTime3, HYPER_PARAMETERS).intValue(); + + assertEquals(35, res2); + + var dateTime4 = ZonedDateTime.of(2022, 1, 1, 12, 50, 0, 0, ZoneId.systemDefault()); + var res3 = getMinute(dateTime4, HYPER_PARAMETERS).intValue(); + assertEquals(50, res3); + } + + @Test + public void testCombine() { + var testData = new ArrayList<>(Arrays.asList(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0)); + var testData1 = new ArrayList<>(Arrays.asList(100.0, 200.0, 300.0, 400.0)); + var res = combine(testData1, testData); + var expectedResult = new ArrayList<>(Arrays.asList(100.0, 200.0, 300.0, 400.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0)); + assertEquals(expectedResult, res); + } + +} diff --git a/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/utillities/UtilityConversionTest.java b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/utillities/UtilityConversionTest.java new file mode 100644 index 00000000000..84121394a00 --- /dev/null +++ b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/utillities/UtilityConversionTest.java @@ -0,0 +1,29 @@ +package io.openems.edge.predictor.lstm.utillities; + +import static io.openems.edge.predictor.lstm.utilities.UtilityConversion.getMinIndex; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +import org.junit.Test; + +public class UtilityConversionTest { + + @Test + public void testGetMinIndex() { + double[] arr = { 3.5, 2.0, 5.1, 1.2, 4.8 }; + assertEquals(3, getMinIndex(arr)); + } + + @Test + public void testGetMinIndexEmptyArray() { + double[] arr = {}; + assertThrows(IllegalArgumentException.class, () -> getMinIndex(arr)); + } + + @Test + public void testGetMinIndexNullArray() { + double[] arr = null; + assertThrows(IllegalArgumentException.class, () -> getMinIndex(arr)); + } + +} diff --git a/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/validation/FindOptimumIndexTest.java b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/validation/FindOptimumIndexTest.java new file mode 100644 index 00000000000..5706cfa68e8 --- /dev/null +++ b/io.openems.edge.predictor.lstm/test/io/openems/edge/predictor/lstm/validation/FindOptimumIndexTest.java @@ -0,0 +1,32 @@ +package io.openems.edge.predictor.lstm.validation; + +import static org.junit.Assert.assertEquals; + +import java.util.ArrayList; +import java.util.Arrays; + +import org.junit.Ignore; +import org.junit.Test; + +import io.openems.edge.predictor.lstm.common.HyperParameters; +import io.openems.edge.predictor.lstm.validator.ValidationSeasonalityModel; + +public class FindOptimumIndexTest { + + @Ignore + @Test + public void testFindOptimumIndex() { + ArrayList> matrix = new ArrayList<>(// + Arrays.asList(// + new ArrayList<>(Arrays.asList(1.0, 2.0, 7.0)), // + new ArrayList<>(Arrays.asList(4.0, 5.0, 8.0)), // + new ArrayList<>(Arrays.asList(7.0, 8.0, 6.0))// + )// + ); + + var result = ValidationSeasonalityModel.findOptimumIndex(matrix, "Test", new HyperParameters()); + + assertEquals(Arrays.asList(Arrays.asList(2, 0), Arrays.asList(2, 1), Arrays.asList(1, 2)), result); + } + +} diff --git a/io.openems.edge.predictor.persistencemodel/.classpath b/io.openems.edge.predictor.persistencemodel/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.predictor.persistencemodel/.classpath +++ b/io.openems.edge.predictor.persistencemodel/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.predictor.persistencemodel/test/io/openems/edge/predictor/persistencemodel/PredictorPersistenceModelImplTest.java b/io.openems.edge.predictor.persistencemodel/test/io/openems/edge/predictor/persistencemodel/PredictorPersistenceModelImplTest.java index 4cd7b472cdd..0090e9e4a33 100644 --- a/io.openems.edge.predictor.persistencemodel/test/io/openems/edge/predictor/persistencemodel/PredictorPersistenceModelImplTest.java +++ b/io.openems.edge.predictor.persistencemodel/test/io/openems/edge/predictor/persistencemodel/PredictorPersistenceModelImplTest.java @@ -1,34 +1,28 @@ package io.openems.edge.predictor.persistencemodel; +import static io.openems.common.test.TestUtils.createDummyClock; +import static io.openems.edge.predictor.api.prediction.LogVerbosity.NONE; import static io.openems.edge.predictor.api.prediction.Prediction.EMPTY_PREDICTION; +import static java.time.temporal.ChronoUnit.HOURS; import static org.junit.Assert.assertEquals; -import java.time.Instant; import java.time.ZoneId; -import java.time.ZoneOffset; import java.time.ZonedDateTime; -import java.time.temporal.ChronoUnit; import org.junit.Test; -import io.openems.common.test.TimeLeapClock; import io.openems.common.types.ChannelAddress; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyComponentManager; -import io.openems.edge.predictor.api.prediction.LogVerbosity; import io.openems.edge.timedata.test.DummyTimedata; public class PredictorPersistenceModelImplTest { - private static final String TIMEDATA_ID = "timedata0"; - private static final String PREDICTOR_ID = "predictor0"; - private static final ChannelAddress METER1_ACTIVE_POWER = new ChannelAddress("meter1", "ActivePower"); @Test public void test() throws Exception { - final var clock = new TimeLeapClock(Instant.ofEpochSecond(1577836800) /* starts at 1. January 2020 00:00:00 */, - ZoneOffset.UTC); + final var clock = createDummyClock(); int[] values = { // Day 1 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 9, 146, 348, 636, 1192, 2092, 2882, 3181, @@ -43,7 +37,7 @@ public void test() throws Exception { 477, 501, 547, 589, 1067, 13304, 17367, 14825, 13654, 12545, 8371, 10468, 9810, 8537, 6228, 3758, 4131, 3572, 1698, 1017, 569, 188, 14, 2, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - var timedata = new DummyTimedata(TIMEDATA_ID); + var timedata = new DummyTimedata("timedata0"); var start = ZonedDateTime.of(2019, 12, 30, 0, 0, 0, 0, ZoneId.of("UTC")); for (var i = 0; i < values.length; i++) { timedata.add(start.plusMinutes(i * 15), METER1_ACTIVE_POWER, values[i]); @@ -55,9 +49,9 @@ public void test() throws Exception { .addReference("timedata", timedata) // .addReference("componentManager", new DummyComponentManager(clock)) // .activate(MyConfig.create() // - .setId(PREDICTOR_ID) // + .setId("predictor0") // .setChannelAddresses(METER1_ACTIVE_POWER.toString()) // - .setLogVerbosity(LogVerbosity.NONE) // + .setLogVerbosity(NONE) // .build()); var prediction = sut.getPrediction(METER1_ACTIVE_POWER); @@ -68,13 +62,13 @@ public void test() throws Exception { assertEquals((Integer) 6, p[21]); assertEquals((Integer) 146, p[22]); assertEquals((Integer) 297, p[23]); - assertEquals(190, prediction.valuePerQuarter.size()); + assertEquals(190, prediction.asArray().length); } @Test public void test2() throws Exception { var start = ZonedDateTime.of(2019, 12, 30, 0, 0, 0, 0, ZoneId.of("UTC")); - final var clock = new TimeLeapClock(start.toInstant(), ZoneOffset.UTC); + final var clock = createDummyClock(); int[] values = { // Day 1 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 19, 74, 323, @@ -87,7 +81,7 @@ public void test2() throws Exception { 7320, 5950, 5644, 7157, 6847, 6549, 6498, 6296, 6096, 5895, 5658, 5372, 5011, 4603, 4159, 3831, 3400, 2757, 727, 194, 70, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - var timedata = new DummyTimedata(TIMEDATA_ID); + var timedata = new DummyTimedata("timedata0"); for (var i = 0; i < values.length; i++) { timedata.add(start.plusMinutes(i * 15), METER1_ACTIVE_POWER, values[i]); } @@ -98,30 +92,29 @@ public void test2() throws Exception { .addReference("timedata", timedata) // .addReference("componentManager", new DummyComponentManager(clock)) // .activate(MyConfig.create() // - .setId(PREDICTOR_ID) // + .setId("predictor0") // .setChannelAddresses(METER1_ACTIVE_POWER.toString()) // - .setLogVerbosity(LogVerbosity.NONE) // + .setLogVerbosity(NONE) // .build()); - clock.leap(39, ChronoUnit.HOURS); + clock.leap(39, HOURS); sut.getPrediction(METER1_ACTIVE_POWER); } @Test public void testEmpty() throws Exception { - final var clock = new TimeLeapClock(Instant.ofEpochSecond(1577836800) /* starts at 1. January 2020 00:00:00 */, - ZoneOffset.UTC); - var timedata = new DummyTimedata(TIMEDATA_ID); + final var clock = createDummyClock(); + var timedata = new DummyTimedata("timedata0"); var sut = new PredictorPersistenceModelImpl(); new ComponentTest(sut) // .addReference("timedata", timedata) // .addReference("componentManager", new DummyComponentManager(clock)) // .activate(MyConfig.create() // - .setId(PREDICTOR_ID) // + .setId("predictor0") // .setChannelAddresses(METER1_ACTIVE_POWER.toString()) // - .setLogVerbosity(LogVerbosity.NONE) // + .setLogVerbosity(NONE) // .build()); assertEquals(EMPTY_PREDICTION, sut.getPrediction(METER1_ACTIVE_POWER)); diff --git a/io.openems.edge.predictor.similardaymodel/.classpath b/io.openems.edge.predictor.similardaymodel/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.predictor.similardaymodel/.classpath +++ b/io.openems.edge.predictor.similardaymodel/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.predictor.similardaymodel/src/io/openems/edge/predictor/similardaymodel/PredictorSimilardayModelImpl.java b/io.openems.edge.predictor.similardaymodel/src/io/openems/edge/predictor/similardaymodel/PredictorSimilardayModelImpl.java index f30f8ba6bcd..91ccd97e601 100644 --- a/io.openems.edge.predictor.similardaymodel/src/io/openems/edge/predictor/similardaymodel/PredictorSimilardayModelImpl.java +++ b/io.openems.edge.predictor.similardaymodel/src/io/openems/edge/predictor/similardaymodel/PredictorSimilardayModelImpl.java @@ -137,8 +137,7 @@ protected Prediction createNewPrediction(ChannelAddress channelAddress) { // Getting the average predictions var nextOneDayPredictions = getAverage(lastFourSimilarDays); - return Prediction.from(Prediction.getValueRange(this.sum, channelAddress), now, - nextOneDayPredictions.stream().toArray(Integer[]::new)); + return Prediction.from(this.sum, channelAddress, now, nextOneDayPredictions.stream().toArray(Integer[]::new)); } /** diff --git a/io.openems.edge.predictor.similardaymodel/test/io/openems/edge/predictor/similardaymodel/PredictorSimilardayModelImplTest.java b/io.openems.edge.predictor.similardaymodel/test/io/openems/edge/predictor/similardaymodel/PredictorSimilardayModelImplTest.java index 6374c6a2b55..8f9e3d25fd3 100644 --- a/io.openems.edge.predictor.similardaymodel/test/io/openems/edge/predictor/similardaymodel/PredictorSimilardayModelImplTest.java +++ b/io.openems.edge.predictor.similardaymodel/test/io/openems/edge/predictor/similardaymodel/PredictorSimilardayModelImplTest.java @@ -18,9 +18,6 @@ public class PredictorSimilardayModelImplTest { - private static final String TIMEDATA_ID = "timedata0"; - private static final String PREDICTOR_ID = "predictor0"; - private static final ChannelAddress METER1_ACTIVE_POWER = new ChannelAddress("meter1", "ActivePower"); @Test @@ -32,7 +29,7 @@ public void test() throws Exception { var values = Data.data; var predictedValues = Data.predictedData; - var timedata = new DummyTimedata(TIMEDATA_ID); + var timedata = new DummyTimedata("timedata0"); var start = ZonedDateTime.of(2019, 12, 1, 0, 0, 0, 0, ZoneId.of("UTC")); for (var i = 0; i < values.length; i++) { @@ -45,7 +42,7 @@ public void test() throws Exception { .addReference("timedata", timedata) // .addReference("componentManager", new DummyComponentManager(clock)) // .activate(MyConfig.create() // - .setId(PREDICTOR_ID) // + .setId("predictor0") // .setNumOfWeeks(4) // .setChannelAddresses(METER1_ACTIVE_POWER.toString()) // .setLogVerbosity(LogVerbosity.NONE) // diff --git a/io.openems.edge.pvinverter.api/.classpath b/io.openems.edge.pvinverter.api/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.pvinverter.api/.classpath +++ b/io.openems.edge.pvinverter.api/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.pvinverter.api/src/io/openems/edge/pvinverter/api/ManagedSymmetricPvInverter.java b/io.openems.edge.pvinverter.api/src/io/openems/edge/pvinverter/api/ManagedSymmetricPvInverter.java index 0aec718a553..7db5a9dc100 100644 --- a/io.openems.edge.pvinverter.api/src/io/openems/edge/pvinverter/api/ManagedSymmetricPvInverter.java +++ b/io.openems.edge.pvinverter.api/src/io/openems/edge/pvinverter/api/ManagedSymmetricPvInverter.java @@ -4,6 +4,7 @@ import io.openems.common.channel.PersistencePriority; import io.openems.common.channel.Unit; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.types.MeterType; import io.openems.common.types.OpenemsType; import io.openems.edge.common.channel.Doc; import io.openems.edge.common.channel.IntegerDoc; @@ -13,7 +14,6 @@ import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.modbusslave.ModbusSlaveNatureTable; import io.openems.edge.meter.api.ElectricityMeter; -import io.openems.edge.meter.api.MeterType; /** * Represents a 3-Phase, symmetric PV-Inverter. diff --git a/io.openems.edge.pvinverter.cluster/.classpath b/io.openems.edge.pvinverter.cluster/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.pvinverter.cluster/.classpath +++ b/io.openems.edge.pvinverter.cluster/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.pvinverter.cluster/test/io/openems/edge/pvinverter/cluster/PvInverterClusterImplTest.java b/io.openems.edge.pvinverter.cluster/test/io/openems/edge/pvinverter/cluster/PvInverterClusterImplTest.java index 8ea8a4efda1..c5d1d1c31a6 100644 --- a/io.openems.edge.pvinverter.cluster/test/io/openems/edge/pvinverter/cluster/PvInverterClusterImplTest.java +++ b/io.openems.edge.pvinverter.cluster/test/io/openems/edge/pvinverter/cluster/PvInverterClusterImplTest.java @@ -8,14 +8,12 @@ public class PvInverterClusterImplTest { - private static final String COMPONENT_ID = "io0"; - @Test public void test() throws Exception { new ComponentTest(new PvInverterClusterImpl()) // .addReference("componentManager", new DummyComponentManager()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("io0") // .setPvInverterIds() // .build()) // .next(new TestCase()) // diff --git a/io.openems.edge.pvinverter.fronius/.classpath b/io.openems.edge.pvinverter.fronius/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.pvinverter.fronius/.classpath +++ b/io.openems.edge.pvinverter.fronius/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.pvinverter.fronius/test/io/openems/edge/pvinverter/fronius/PvInverterFroniusImplTest.java b/io.openems.edge.pvinverter.fronius/test/io/openems/edge/pvinverter/fronius/PvInverterFroniusImplTest.java index 04e6f406c5f..db5e7b321d7 100644 --- a/io.openems.edge.pvinverter.fronius/test/io/openems/edge/pvinverter/fronius/PvInverterFroniusImplTest.java +++ b/io.openems.edge.pvinverter.fronius/test/io/openems/edge/pvinverter/fronius/PvInverterFroniusImplTest.java @@ -8,18 +8,15 @@ public class PvInverterFroniusImplTest { - private static final String PV_INVERTER_ID = "pvInverter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new PvInverterFroniusImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(PV_INVERTER_ID) // + .setId("pvInverter0") // .setReadOnly(true) // - .setModbusId(MODBUS_ID) // + .setModbusId("modbus0") // .setModbusUnitId(1) // .build()) // ; diff --git a/io.openems.edge.pvinverter.kaco.blueplanet/.classpath b/io.openems.edge.pvinverter.kaco.blueplanet/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.pvinverter.kaco.blueplanet/.classpath +++ b/io.openems.edge.pvinverter.kaco.blueplanet/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.pvinverter.kaco.blueplanet/test/io/openems/edge/pvinverter/kaco/blueplanet/PvInverterKacoBlueplanetImplTest.java b/io.openems.edge.pvinverter.kaco.blueplanet/test/io/openems/edge/pvinverter/kaco/blueplanet/PvInverterKacoBlueplanetImplTest.java index 80811823b8e..af619a2deb4 100644 --- a/io.openems.edge.pvinverter.kaco.blueplanet/test/io/openems/edge/pvinverter/kaco/blueplanet/PvInverterKacoBlueplanetImplTest.java +++ b/io.openems.edge.pvinverter.kaco.blueplanet/test/io/openems/edge/pvinverter/kaco/blueplanet/PvInverterKacoBlueplanetImplTest.java @@ -8,18 +8,15 @@ public class PvInverterKacoBlueplanetImplTest { - private static final String PV_INVERTER_ID = "pvInverter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new PvInverterKacoBlueplanetImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(PV_INVERTER_ID) // + .setId("pvInverter0") // .setReadOnly(true) // - .setModbusId(MODBUS_ID) // + .setModbusId("modbus0") // .setModbusUnitId(1) // .build()) // ; diff --git a/io.openems.edge.pvinverter.kostal/.classpath b/io.openems.edge.pvinverter.kostal/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.pvinverter.kostal/.classpath +++ b/io.openems.edge.pvinverter.kostal/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.pvinverter.kostal/test/io/openems/edge/pvinverter/kostal/PvInverterKostalImplTest.java b/io.openems.edge.pvinverter.kostal/test/io/openems/edge/pvinverter/kostal/PvInverterKostalImplTest.java index d9b74670758..c9f44649eb9 100644 --- a/io.openems.edge.pvinverter.kostal/test/io/openems/edge/pvinverter/kostal/PvInverterKostalImplTest.java +++ b/io.openems.edge.pvinverter.kostal/test/io/openems/edge/pvinverter/kostal/PvInverterKostalImplTest.java @@ -8,18 +8,15 @@ public class PvInverterKostalImplTest { - private static final String PV_INVERTER_ID = "pvInverter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new PvInverterKostalImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(PV_INVERTER_ID) // + .setId("pvInverter0") // .setReadOnly(true) // - .setModbusId(MODBUS_ID) // + .setModbusId("modbus0") // .setModbusUnitId(1) // .build()) // ; diff --git a/io.openems.edge.pvinverter.sma/.classpath b/io.openems.edge.pvinverter.sma/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.pvinverter.sma/.classpath +++ b/io.openems.edge.pvinverter.sma/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.pvinverter.sma/test/io/openems/edge/pvinverter/sma/PvInverterSmaSunnyTripowerImplTest.java b/io.openems.edge.pvinverter.sma/test/io/openems/edge/pvinverter/sma/PvInverterSmaSunnyTripowerImplTest.java index 27b7a2d9d03..6e1b94cf7b0 100644 --- a/io.openems.edge.pvinverter.sma/test/io/openems/edge/pvinverter/sma/PvInverterSmaSunnyTripowerImplTest.java +++ b/io.openems.edge.pvinverter.sma/test/io/openems/edge/pvinverter/sma/PvInverterSmaSunnyTripowerImplTest.java @@ -1,28 +1,26 @@ package io.openems.edge.pvinverter.sma; +import static io.openems.edge.pvinverter.sunspec.Phase.ALL; + import org.junit.Test; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.pvinverter.sunspec.Phase; public class PvInverterSmaSunnyTripowerImplTest { - private static final String PV_INVERTER_ID = "pvInverter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new PvInverterSmaSunnyTripowerImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(PV_INVERTER_ID) // + .setId("pvInverter0") // .setReadOnly(true) // - .setModbusId(MODBUS_ID) // + .setModbusId("modbus0") // .setModbusUnitId(1) // - .setPhase(Phase.ALL) // + .setPhase(ALL) // .build()) // ; } diff --git a/io.openems.edge.pvinverter.solarlog/.classpath b/io.openems.edge.pvinverter.solarlog/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.pvinverter.solarlog/.classpath +++ b/io.openems.edge.pvinverter.solarlog/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.pvinverter.solarlog/src/io/openems/edge/pvinverter/solarlog/PvInverterSolarlogImpl.java b/io.openems.edge.pvinverter.solarlog/src/io/openems/edge/pvinverter/solarlog/PvInverterSolarlogImpl.java index cf8c62c0da1..31992fe3563 100644 --- a/io.openems.edge.pvinverter.solarlog/src/io/openems/edge/pvinverter/solarlog/PvInverterSolarlogImpl.java +++ b/io.openems.edge.pvinverter.solarlog/src/io/openems/edge/pvinverter/solarlog/PvInverterSolarlogImpl.java @@ -22,6 +22,7 @@ import io.openems.common.channel.AccessMode; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.types.MeterType; import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent; import io.openems.edge.bridge.modbus.api.BridgeModbus; import io.openems.edge.bridge.modbus.api.ChannelMetaInfoReadAndWrite; @@ -41,7 +42,6 @@ import io.openems.edge.common.modbusslave.ModbusSlaveTable; import io.openems.edge.common.taskmanager.Priority; import io.openems.edge.meter.api.ElectricityMeter; -import io.openems.edge.meter.api.MeterType; import io.openems.edge.pvinverter.api.ManagedSymmetricPvInverter; import io.openems.edge.timedata.api.Timedata; import io.openems.edge.timedata.api.TimedataProvider; diff --git a/io.openems.edge.pvinverter.solarlog/test/io/openems/edge/pvinverter/solarlog/PvInverterSolarlogImplTest.java b/io.openems.edge.pvinverter.solarlog/test/io/openems/edge/pvinverter/solarlog/PvInverterSolarlogImplTest.java index 7d1f87a7b69..778f9f85f63 100644 --- a/io.openems.edge.pvinverter.solarlog/test/io/openems/edge/pvinverter/solarlog/PvInverterSolarlogImplTest.java +++ b/io.openems.edge.pvinverter.solarlog/test/io/openems/edge/pvinverter/solarlog/PvInverterSolarlogImplTest.java @@ -8,17 +8,14 @@ public class PvInverterSolarlogImplTest { - private static final String METER_ID = "meter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new PvInverterSolarlogImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // + .setId("meter0") // + .setModbusId("modbus0") // .build()) // ; } diff --git a/io.openems.edge.pvinverter.sunspec/.classpath b/io.openems.edge.pvinverter.sunspec/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.pvinverter.sunspec/.classpath +++ b/io.openems.edge.pvinverter.sunspec/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.pvinverter.sunspec/src/io/openems/edge/pvinverter/sunspec/AbstractSunSpecPvInverter.java b/io.openems.edge.pvinverter.sunspec/src/io/openems/edge/pvinverter/sunspec/AbstractSunSpecPvInverter.java index 34ff14dc35d..836904c4da0 100644 --- a/io.openems.edge.pvinverter.sunspec/src/io/openems/edge/pvinverter/sunspec/AbstractSunSpecPvInverter.java +++ b/io.openems.edge.pvinverter.sunspec/src/io/openems/edge/pvinverter/sunspec/AbstractSunSpecPvInverter.java @@ -26,6 +26,7 @@ import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.types.MeterType; import io.openems.edge.bridge.modbus.sunspec.AbstractOpenemsSunSpecComponent; import io.openems.edge.bridge.modbus.sunspec.DefaultSunSpecModel; import io.openems.edge.bridge.modbus.sunspec.DefaultSunSpecModel.S101; @@ -45,7 +46,6 @@ import io.openems.edge.common.event.EdgeEventConstants; import io.openems.edge.common.taskmanager.Priority; import io.openems.edge.meter.api.ElectricityMeter; -import io.openems.edge.meter.api.MeterType; import io.openems.edge.meter.api.SinglePhase; import io.openems.edge.meter.api.SinglePhaseMeter; import io.openems.edge.pvinverter.api.ManagedSymmetricPvInverter; diff --git a/io.openems.edge.scheduler.allalphabetically/.classpath b/io.openems.edge.scheduler.allalphabetically/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.scheduler.allalphabetically/.classpath +++ b/io.openems.edge.scheduler.allalphabetically/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.scheduler.allalphabetically/test/io/openems/edge/scheduler/allalphabetically/SchedulerAllAlphabeticallyImplTest.java b/io.openems.edge.scheduler.allalphabetically/test/io/openems/edge/scheduler/allalphabetically/SchedulerAllAlphabeticallyImplTest.java index 204e4e48109..0b4ca7c84d5 100644 --- a/io.openems.edge.scheduler.allalphabetically/test/io/openems/edge/scheduler/allalphabetically/SchedulerAllAlphabeticallyImplTest.java +++ b/io.openems.edge.scheduler.allalphabetically/test/io/openems/edge/scheduler/allalphabetically/SchedulerAllAlphabeticallyImplTest.java @@ -18,33 +18,25 @@ public class SchedulerAllAlphabeticallyImplTest { - private static final String SCHEDULER_ID = "scheduler0"; - - private static final String CTRL0_ID = "ctrl0"; - private static final String CTRL1_ID = "ctrl1"; - private static final String CTRL2_ID = "ctrl2"; - private static final String CTRL3_ID = "ctrl3"; - private static final String CTRL4_ID = "ctrl4"; - @Test public void testWithFixedPriorities() throws Exception { final SchedulerAllAlphabetically sut = new SchedulerAllAlphabeticallyImpl(); new ComponentTest(sut) // .addReference("componentManager", new DummyComponentManager()) // - .addComponent(new DummyController(CTRL0_ID)) // - .addComponent(new DummyController(CTRL1_ID)) // - .addComponent(new DummyController(CTRL2_ID)) // - .addComponent(new DummyController(CTRL3_ID)) // - .addComponent(new DummyController(CTRL4_ID)) // + .addComponent(new DummyController("ctrl0")) // + .addComponent(new DummyController("ctrl1")) // + .addComponent(new DummyController("ctrl2")) // + .addComponent(new DummyController("ctrl3")) // + .addComponent(new DummyController("ctrl4")) // .activate(MyConfig.create() // - .setId(SCHEDULER_ID) // - .setControllersIds(CTRL2_ID, CTRL1_ID, "") // + .setId("scheduler0") // + .setControllersIds("ctrl2", "ctrl1", "") // .build()) .next(new TestCase()) // .deactivate(); assertEquals(// - Arrays.asList(CTRL2_ID, CTRL1_ID, CTRL0_ID, CTRL3_ID, CTRL4_ID), // + Arrays.asList("ctrl2", "ctrl1", "ctrl0", "ctrl3", "ctrl4"), // getControllerIds(sut)); } @@ -77,7 +69,7 @@ public void testOnlyAlphabeticalOrdering() throws Exception { test // .addReference("componentManager", new DummyComponentManager()) // .activate(MyConfig.create() // - .setId(SCHEDULER_ID) // + .setId("scheduler0") // .setControllersIds() // .build()) // .next(new TestCase()); diff --git a/io.openems.edge.scheduler.api/.classpath b/io.openems.edge.scheduler.api/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.scheduler.api/.classpath +++ b/io.openems.edge.scheduler.api/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.scheduler.api/src/io/openems/edge/scheduler/api/test/AbstractDummyScheduler.java b/io.openems.edge.scheduler.api/src/io/openems/edge/scheduler/api/test/AbstractDummyScheduler.java new file mode 100644 index 00000000000..530267953b4 --- /dev/null +++ b/io.openems.edge.scheduler.api/src/io/openems/edge/scheduler/api/test/AbstractDummyScheduler.java @@ -0,0 +1,15 @@ +package io.openems.edge.scheduler.api.test; + +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.common.test.AbstractDummyOpenemsComponent; +import io.openems.edge.scheduler.api.Scheduler; + +public abstract class AbstractDummyScheduler> + extends AbstractDummyOpenemsComponent implements Scheduler, OpenemsComponent { + + protected AbstractDummyScheduler(String id, io.openems.edge.common.channel.ChannelId[] firstInitialChannelIds, + io.openems.edge.common.channel.ChannelId[]... furtherInitialChannelIds) { + super(id, firstInitialChannelIds, furtherInitialChannelIds); + } + +} diff --git a/io.openems.edge.scheduler.api/src/io/openems/edge/scheduler/api/test/DummyScheduler.java b/io.openems.edge.scheduler.api/src/io/openems/edge/scheduler/api/test/DummyScheduler.java new file mode 100644 index 00000000000..00de718a1ad --- /dev/null +++ b/io.openems.edge.scheduler.api/src/io/openems/edge/scheduler/api/test/DummyScheduler.java @@ -0,0 +1,46 @@ +package io.openems.edge.scheduler.api.test; + +import java.util.Arrays; +import java.util.LinkedHashSet; + +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.controller.api.Controller; +import io.openems.edge.scheduler.api.Scheduler; + +/** + * Provides a simple, simulated {@link Scheduler} component that can be used + * together with the OpenEMS Component test framework. + */ +public class DummyScheduler extends AbstractDummyScheduler implements Scheduler, OpenemsComponent { + + private LinkedHashSet controllers = new LinkedHashSet<>(); + + public DummyScheduler(String id) { + super(id, // + OpenemsComponent.ChannelId.values() // + ); + } + + @Override + protected final DummyScheduler self() { + return this; + } + + /** + * Sets the {@link Controller}s. + * + * @param controllerIds Component-IDs of the Controllers + * @return myself + */ + public final DummyScheduler setControllers(String... controllerIds) { + var cs = new LinkedHashSet(); + Arrays.stream(controllerIds).forEach(c -> cs.add(c)); + this.controllers = cs; + return this.self(); + } + + @Override + public LinkedHashSet getControllers() { + return this.controllers; + } +} diff --git a/io.openems.edge.scheduler.api/src/io/openems/edge/scheduler/api/test/package-info.java b/io.openems.edge.scheduler.api/src/io/openems/edge/scheduler/api/test/package-info.java new file mode 100644 index 00000000000..3a0c0e70c08 --- /dev/null +++ b/io.openems.edge.scheduler.api/src/io/openems/edge/scheduler/api/test/package-info.java @@ -0,0 +1,3 @@ +@org.osgi.annotation.versioning.Version("1.0.0") +@org.osgi.annotation.bundle.Export +package io.openems.edge.scheduler.api.test; diff --git a/io.openems.edge.scheduler.daily/.classpath b/io.openems.edge.scheduler.daily/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.scheduler.daily/.classpath +++ b/io.openems.edge.scheduler.daily/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.scheduler.daily/test/io/openems/edge/scheduler/daily/SchedulerDailyImplTest.java b/io.openems.edge.scheduler.daily/test/io/openems/edge/scheduler/daily/SchedulerDailyImplTest.java index 9f0d5e94cb1..54bbbfef1a2 100644 --- a/io.openems.edge.scheduler.daily/test/io/openems/edge/scheduler/daily/SchedulerDailyImplTest.java +++ b/io.openems.edge.scheduler.daily/test/io/openems/edge/scheduler/daily/SchedulerDailyImplTest.java @@ -1,18 +1,16 @@ package io.openems.edge.scheduler.daily; +import static io.openems.common.test.TestUtils.createDummyClock; +import static io.openems.common.utils.JsonUtils.buildJsonArray; +import static io.openems.common.utils.JsonUtils.buildJsonObject; +import static java.time.temporal.ChronoUnit.HOURS; import static org.junit.Assert.assertEquals; -import java.time.Instant; -import java.time.ZoneOffset; -import java.time.temporal.ChronoUnit; -import java.util.Arrays; import java.util.List; import org.junit.Test; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.common.test.TimeLeapClock; -import io.openems.common.utils.JsonUtils; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyComponentManager; @@ -21,56 +19,49 @@ public class SchedulerDailyImplTest { - private static final String SCHEDULER_ID = "scheduler0"; - - private static final String CTRL0_ID = "ctrl0"; - private static final String CTRL1_ID = "ctrl1"; - private static final String CTRL2_ID = "ctrl2"; - private static final String CTRL3_ID = "ctrl3"; - private static final String CTRL4_ID = "ctrl4"; - @Test public void test() throws Exception { - final var clock = new TimeLeapClock(Instant.parse("2020-01-01T00:00:00.00Z"), ZoneOffset.UTC); + final var clock = createDummyClock(); final SchedulerDaily sut = new SchedulerDailyImpl(); new ComponentTest(sut) // .addReference("componentManager", new DummyComponentManager(clock)) // - .addComponent(new DummyController(CTRL0_ID)) // - .addComponent(new DummyController(CTRL1_ID)) // - .addComponent(new DummyController(CTRL2_ID)) // - .addComponent(new DummyController(CTRL3_ID)) // - .addComponent(new DummyController(CTRL4_ID)) // + .addComponent(new DummyController("ctrl0")) // + .addComponent(new DummyController("ctrl1")) // + .addComponent(new DummyController("ctrl2")) // + .addComponent(new DummyController("ctrl3")) // + .addComponent(new DummyController("ctrl4")) // .activate(MyConfig.create() // - .setId(SCHEDULER_ID) // - .setAlwaysRunBeforeControllerIds(CTRL2_ID).setControllerScheduleJson(JsonUtils.buildJsonArray() // - .add(JsonUtils.buildJsonObject() // + .setId("scheduler0") // + .setAlwaysRunBeforeControllerIds("ctrl2") // + .setControllerScheduleJson(buildJsonArray() // + .add(buildJsonObject() // .addProperty("time", "08:00:00") // - .add("controllers", JsonUtils.buildJsonArray() // - .add(CTRL0_ID) // + .add("controllers", buildJsonArray() // + .add("ctrl0") // .build()) // .build()) // - .add(JsonUtils.buildJsonObject() // + .add(buildJsonObject() // .addProperty("time", "13:45:00") // - .add("controllers", JsonUtils.buildJsonArray() // - .add(CTRL4_ID) // + .add("controllers", buildJsonArray() // + .add("ctrl4") // .build()) // .build()) // .build().toString()) - .setAlwaysRunAfterControllerIds(CTRL3_ID, CTRL1_ID) // + .setAlwaysRunAfterControllerIds("ctrl3", "ctrl1") // .build()) // .next(new TestCase("00:00") // .onBeforeControllersCallbacks(() -> assertEquals(// - Arrays.asList(CTRL2_ID, CTRL4_ID, CTRL3_ID, CTRL1_ID), // + List.of("ctrl2", "ctrl4", "ctrl3", "ctrl1"), // getControllerIds(sut)))) // .next(new TestCase("12:00") // - .timeleap(clock, 12, ChronoUnit.HOURS) // + .timeleap(clock, 12, HOURS) // .onBeforeControllersCallbacks(() -> assertEquals(// - Arrays.asList(CTRL2_ID, CTRL0_ID, CTRL3_ID, CTRL1_ID), // + List.of("ctrl2", "ctrl0", "ctrl3", "ctrl1"), // getControllerIds(sut)))) .next(new TestCase("14:00") // - .timeleap(clock, 12, ChronoUnit.HOURS) // + .timeleap(clock, 12, HOURS) // .onBeforeControllersCallbacks(() -> assertEquals(// - Arrays.asList(CTRL2_ID, CTRL4_ID, CTRL3_ID, CTRL1_ID), // + List.of("ctrl2", "ctrl4", "ctrl3", "ctrl1"), // getControllerIds(sut)))); } diff --git a/io.openems.edge.scheduler.fixedorder/.classpath b/io.openems.edge.scheduler.fixedorder/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.scheduler.fixedorder/.classpath +++ b/io.openems.edge.scheduler.fixedorder/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.scheduler.fixedorder/test/io/openems/edge/scheduler/fixedorder/SchedulerFixedOrderImplTest.java b/io.openems.edge.scheduler.fixedorder/test/io/openems/edge/scheduler/fixedorder/SchedulerFixedOrderImplTest.java index 47bc0a88a65..710c1adddc0 100644 --- a/io.openems.edge.scheduler.fixedorder/test/io/openems/edge/scheduler/fixedorder/SchedulerFixedOrderImplTest.java +++ b/io.openems.edge.scheduler.fixedorder/test/io/openems/edge/scheduler/fixedorder/SchedulerFixedOrderImplTest.java @@ -2,7 +2,6 @@ import static org.junit.Assert.assertEquals; -import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; @@ -40,7 +39,7 @@ public void test() throws Exception { test.next(new TestCase()); // assertEquals(// - Arrays.asList(CTRL3_ID, CTRL1_ID), // + List.of(CTRL3_ID, CTRL1_ID), // getControllerIds(sut)); } diff --git a/io.openems.edge.simulator/.classpath b/io.openems.edge.simulator/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.simulator/.classpath +++ b/io.openems.edge.simulator/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.simulator/src/io/openems/edge/simulator/datasource/api/DummyDatasource.java b/io.openems.edge.simulator/src/io/openems/edge/simulator/datasource/api/DummyDatasource.java new file mode 100644 index 00000000000..0f1a58189f5 --- /dev/null +++ b/io.openems.edge.simulator/src/io/openems/edge/simulator/datasource/api/DummyDatasource.java @@ -0,0 +1,38 @@ +package io.openems.edge.simulator.datasource.api; + +import java.util.List; +import java.util.Set; + +import io.openems.common.types.ChannelAddress; +import io.openems.common.types.OpenemsType; + +public class DummyDatasource implements SimulatorDatasource { + + public final Object value; + + public DummyDatasource(Object value) { + this.value = value; + } + + @Override + public Set getKeys() { + return Set.of(); + } + + @Override + public int getTimeDelta() { + return 0; + } + + @SuppressWarnings("unchecked") + @Override + public List getValues(OpenemsType type, ChannelAddress channelAddress) { + return (List) List.of(this.value); + } + + @SuppressWarnings("unchecked") + @Override + public T getValue(OpenemsType type, ChannelAddress channelAddress) { + return (T) this.value; + } +} diff --git a/io.openems.edge.simulator/src/io/openems/edge/simulator/evcs/SimulatorEvcs.java b/io.openems.edge.simulator/src/io/openems/edge/simulator/evcs/SimulatorEvcs.java index 59c4acf3eef..bd93a48548f 100644 --- a/io.openems.edge.simulator/src/io/openems/edge/simulator/evcs/SimulatorEvcs.java +++ b/io.openems.edge.simulator/src/io/openems/edge/simulator/evcs/SimulatorEvcs.java @@ -5,13 +5,12 @@ import io.openems.common.channel.Unit; import io.openems.common.types.OpenemsType; import io.openems.edge.common.channel.Doc; -import io.openems.edge.common.channel.LongReadChannel; -import io.openems.edge.common.channel.value.Value; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.evcs.api.Evcs; import io.openems.edge.evcs.api.ManagedEvcs; +import io.openems.edge.meter.api.ElectricityMeter; -public interface SimulatorEvcs extends ManagedEvcs, Evcs, OpenemsComponent, EventHandler { +public interface SimulatorEvcs extends ManagedEvcs, Evcs, ElectricityMeter, OpenemsComponent, EventHandler { public enum ChannelId implements io.openems.edge.common.channel.ChannelId { SIMULATED_CHARGE_POWER(Doc.of(OpenemsType.INTEGER).unit(Unit.WATT)); @@ -27,24 +26,4 @@ public Doc doc() { return this.doc; } } - - @Override - public default Value getActiveConsumptionEnergy() { - return this.getActiveConsumptionEnergyChannel().getNextValue(); - } - - @Override - public default void _setActiveConsumptionEnergy(long value) { - this.channel(Evcs.ChannelId.ACTIVE_CONSUMPTION_ENERGY).setNextValue(value); - } - - @Override - public default void _setActiveConsumptionEnergy(Long value) { - this.channel(Evcs.ChannelId.ACTIVE_CONSUMPTION_ENERGY).setNextValue(value); - } - - @Override - public default LongReadChannel getActiveConsumptionEnergyChannel() { - return this.channel(Evcs.ChannelId.ACTIVE_CONSUMPTION_ENERGY); - } } diff --git a/io.openems.edge.simulator/src/io/openems/edge/simulator/evcs/SimulatorEvcsImpl.java b/io.openems.edge.simulator/src/io/openems/edge/simulator/evcs/SimulatorEvcsImpl.java index 371c2ed8619..f554ad4dba9 100644 --- a/io.openems.edge.simulator/src/io/openems/edge/simulator/evcs/SimulatorEvcsImpl.java +++ b/io.openems.edge.simulator/src/io/openems/edge/simulator/evcs/SimulatorEvcsImpl.java @@ -17,6 +17,7 @@ import org.osgi.service.metatype.annotations.Designate; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.types.MeterType; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.event.EdgeEventConstants; import io.openems.edge.evcs.api.AbstractManagedEvcsComponent; @@ -24,6 +25,7 @@ import io.openems.edge.evcs.api.EvcsPower; import io.openems.edge.evcs.api.ManagedEvcs; import io.openems.edge.evcs.api.Status; +import io.openems.edge.meter.api.ElectricityMeter; @Designate(ocd = Config.class, factory = true) @Component(// @@ -36,7 +38,7 @@ EdgeEventConstants.TOPIC_CYCLE_EXECUTE_WRITE, // }) public class SimulatorEvcsImpl extends AbstractManagedEvcsComponent - implements SimulatorEvcs, ManagedEvcs, Evcs, OpenemsComponent, EventHandler { + implements SimulatorEvcs, ManagedEvcs, Evcs, ElectricityMeter, OpenemsComponent, EventHandler { @Reference private EvcsPower evcsPower; @@ -49,6 +51,7 @@ public class SimulatorEvcsImpl extends AbstractManagedEvcsComponent public SimulatorEvcsImpl() { super(// OpenemsComponent.ChannelId.values(), // + ElectricityMeter.ChannelId.values(), // ManagedEvcs.ChannelId.values(), // Evcs.ChannelId.values(), // SimulatorEvcs.ChannelId.values() // @@ -91,18 +94,18 @@ public void handleEvent(Event event) { private void updateChannels() { int chargePowerLimit = this.getSetChargePowerLimit().orElse(0); - this._setChargePower(chargePowerLimit); + this._setActivePower(chargePowerLimit); /* * Set Simulated "meter" Active Power */ - this._setChargePower(chargePowerLimit); + this._setActivePower(chargePowerLimit); /* * Set calculated energy */ var timeDiff = ChronoUnit.MILLIS.between(this.lastUpdate, LocalDateTime.now()); - var energyTransfered = timeDiff / 1000.0 / 60.0 / 60.0 * this.getChargePower().orElse(0); + var energyTransfered = timeDiff / 1000.0 / 60.0 / 60.0 * this.getActivePower().orElse(0); this.exactEnergySession = this.exactEnergySession + energyTransfered; this._setEnergySession((int) this.exactEnergySession); @@ -110,9 +113,14 @@ private void updateChannels() { this.lastUpdate = LocalDateTime.now(); } + @Override + public MeterType getMeterType() { + return MeterType.MANAGED_CONSUMPTION_METERED; + } + @Override public String debugLog() { - return this.getChargePower().asString(); + return this.getActivePower().asString(); } @Override @@ -138,7 +146,7 @@ public boolean getConfiguredDebugMode() { @Override public boolean applyChargePowerLimit(int power) throws OpenemsException { this._setSetChargePowerLimit(power); - this._setChargePower(power); + this._setActivePower(power); this._setStatus(power > 0 ? Status.CHARGING : Status.CHARGING_REJECTED); return true; } diff --git a/io.openems.edge.simulator/src/io/openems/edge/simulator/meter/grid/acting/Config.java b/io.openems.edge.simulator/src/io/openems/edge/simulator/meter/grid/acting/Config.java index 0c62512f586..abaa040ae8f 100644 --- a/io.openems.edge.simulator/src/io/openems/edge/simulator/meter/grid/acting/Config.java +++ b/io.openems.edge.simulator/src/io/openems/edge/simulator/meter/grid/acting/Config.java @@ -20,6 +20,13 @@ @AttributeDefinition(name = "Datasource-ID", description = "ID of Simulator Datasource.") String datasource_id() default "datasource0"; + @AttributeDefinition(name = "Need frequency Step response?", description = "Need frequency Step response?") + boolean needFrequencyStepResponse() default false; + + @AttributeDefinition(name = "Start Time for frequency step response", description = "Time to Start(format: 2024-01-29 20:12:00). " + + "if the time specified is not in future or start time is not entered, Current time is used instead .") + String startTime() default "2024-01-29 20:12:00"; + @AttributeDefinition(name = "Datasource target filter", description = "This is auto-generated by 'Datasource-ID'.") String datasource_target() default "(enabled=true)"; diff --git a/io.openems.edge.simulator/src/io/openems/edge/simulator/meter/grid/acting/SimulatorGridMeterActing.java b/io.openems.edge.simulator/src/io/openems/edge/simulator/meter/grid/acting/SimulatorGridMeterActing.java index 578f38db722..c7b5b733618 100644 --- a/io.openems.edge.simulator/src/io/openems/edge/simulator/meter/grid/acting/SimulatorGridMeterActing.java +++ b/io.openems.edge.simulator/src/io/openems/edge/simulator/meter/grid/acting/SimulatorGridMeterActing.java @@ -2,9 +2,13 @@ import org.osgi.service.event.EventHandler; +import io.openems.common.channel.PersistencePriority; import io.openems.common.channel.Unit; import io.openems.common.types.OpenemsType; +import io.openems.edge.common.channel.Channel; import io.openems.edge.common.channel.Doc; +import io.openems.edge.common.channel.StateChannel; +import io.openems.edge.common.channel.value.Value; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.meter.api.ElectricityMeter; import io.openems.edge.timedata.api.TimedataProvider; @@ -12,6 +16,10 @@ public interface SimulatorGridMeterActing extends ElectricityMeter, OpenemsComponent, TimedataProvider, EventHandler { public enum ChannelId implements io.openems.edge.common.channel.ChannelId { + STATE_MACHINE(Doc.of(State.values()) // + .persistencePriority(PersistencePriority.HIGH)// + .text("Current State of State-Machine")), + SIMULATED_ACTIVE_POWER(Doc.of(OpenemsType.INTEGER) // .unit(Unit.WATT)); @@ -26,4 +34,32 @@ public Doc doc() { return this.doc; } } + + /** + * Gets the Channel for {@link ChannelId#STATE_MACHINE}. + * + * @return the Channel + */ + public default Channel getStateMachineChannel() { + return this.channel(ChannelId.STATE_MACHINE); + } + + /** + * Gets the {@link StateChannel} for {@link ChannelId#STATE_MACHINE}. + * + * @return the Channel {@link Value} + */ + public default Value getStateMachine() { + return this.getStateMachineChannel().value(); + } + + /** + * Internal method to set the 'nextValue' on {@link ChannelId#STATE_MACHINE} + * Channel. + * + * @param value the next value + */ + public default void _setStateMachine(State value) { + this.getStateMachineChannel().setNextValue(value); + } } diff --git a/io.openems.edge.simulator/src/io/openems/edge/simulator/meter/grid/acting/SimulatorGridMeterActingImpl.java b/io.openems.edge.simulator/src/io/openems/edge/simulator/meter/grid/acting/SimulatorGridMeterActingImpl.java index 1a60a481d58..002fea68f86 100644 --- a/io.openems.edge.simulator/src/io/openems/edge/simulator/meter/grid/acting/SimulatorGridMeterActingImpl.java +++ b/io.openems.edge.simulator/src/io/openems/edge/simulator/meter/grid/acting/SimulatorGridMeterActingImpl.java @@ -1,6 +1,11 @@ package io.openems.edge.simulator.meter.grid.acting; import java.io.IOException; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; @@ -18,17 +23,20 @@ import org.osgi.service.event.EventHandler; import org.osgi.service.event.propertytypes.EventTopics; import org.osgi.service.metatype.annotations.Designate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import io.openems.common.types.ChannelAddress; +import io.openems.common.types.MeterType; import io.openems.common.types.OpenemsType; import io.openems.edge.common.component.AbstractOpenemsComponent; +import io.openems.edge.common.component.ComponentManager; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.event.EdgeEventConstants; import io.openems.edge.common.type.TypeUtils; import io.openems.edge.ess.api.ManagedSymmetricEss; import io.openems.edge.ess.api.MetaEss; import io.openems.edge.meter.api.ElectricityMeter; -import io.openems.edge.meter.api.MeterType; import io.openems.edge.simulator.datasource.api.SimulatorDatasource; import io.openems.edge.timedata.api.Timedata; import io.openems.edge.timedata.api.TimedataProvider; @@ -49,11 +57,18 @@ public class SimulatorGridMeterActingImpl extends AbstractOpenemsComponent implements SimulatorGridMeterActing, ElectricityMeter, OpenemsComponent, TimedataProvider, EventHandler { + private final Logger log = LoggerFactory.getLogger(SimulatorGridMeterActingImpl.class); private final CalculateEnergyFromPower calculateProductionEnergy = new CalculateEnergyFromPower(this, ElectricityMeter.ChannelId.ACTIVE_PRODUCTION_ENERGY); private final CalculateEnergyFromPower calculateConsumptionEnergy = new CalculateEnergyFromPower(this, ElectricityMeter.ChannelId.ACTIVE_CONSUMPTION_ENERGY); + private StepResponseHandler stepResponseHandler; + private Config config = null; + + @Reference + private ComponentManager componentManager; + @Reference private ConfigurationAdmin cm; @@ -72,16 +87,23 @@ public SimulatorGridMeterActingImpl() { ElectricityMeter.ChannelId.values(), // SimulatorGridMeterActing.ChannelId.values() // ); + } @Activate private void activate(ComponentContext context, Config config) throws IOException { + this.config = config; super.activate(context, config.id(), config.alias(), config.enabled()); // update filter for 'datasource' if (OpenemsComponent.updateReferenceFilter(this.cm, this.servicePid(), "datasource", config.datasource_id())) { return; } + + if (this.config.needFrequencyStepResponse()) { + Instant startTime = this.convertTime(this.config.startTime()); + this.stepResponseHandler = new StepResponseHandler(this, startTime); + } } @Override @@ -103,6 +125,9 @@ public void handleEvent(Event event) { switch (event.getTopic()) { case EdgeEventConstants.TOPIC_CYCLE_BEFORE_PROCESS_IMAGE: this.updateChannels(); + if (this.config.needFrequencyStepResponse()) { + this.stepResponseHandler.doStepResponse(); + } break; case EdgeEventConstants.TOPIC_CYCLE_AFTER_PROCESS_IMAGE: this.calculateEnergy(); @@ -168,4 +193,44 @@ public Timedata getTimedata() { return this.timedata; } + /** + * Converts a string representation of time to an Instant object. + * + *

    + * If the input time is null or empty, the current time is returned. If the + * input time is successfully parsed, it is converted to an Instant object. If + * the parsed time is in the past, and the current time is returned. If there's + * an error parsing the input time, and the current time is returned. + * + * @param inputTime the string representation of time to be converted + * @return an Instant converted time + */ + public Instant convertTime(String inputTime) { + + Instant currentTime = this.getCurrentTime(); + if (inputTime == null || inputTime.isEmpty()) { + return currentTime; + } + try { + LocalDateTime localDateTime = LocalDateTime.parse(inputTime, + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + Instant futureTime = localDateTime.atZone(ZoneId.systemDefault()).toInstant(); + return currentTime.isAfter(futureTime) ? currentTime : futureTime; + } catch (DateTimeParseException e) { + this.log.error( + "Error parsing input time: " + inputTime + " instead current time: " + currentTime + " is taken."); + return currentTime; + } + } + + /** + * Retrieves the current time component manager. + * + * @return An Instant representing the current time. + */ + public Instant getCurrentTime() { + var currentTime = this.componentManager.getClock().withZone(ZoneId.systemDefault()); + return Instant.now(currentTime); + } + } diff --git a/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/enums/InfoBits.java b/io.openems.edge.simulator/src/io/openems/edge/simulator/meter/grid/acting/State.java similarity index 50% rename from io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/enums/InfoBits.java rename to io.openems.edge.simulator/src/io/openems/edge/simulator/meter/grid/acting/State.java index 079ac6461fe..e5c26f2332c 100644 --- a/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/enums/InfoBits.java +++ b/io.openems.edge.simulator/src/io/openems/edge/simulator/meter/grid/acting/State.java @@ -1,16 +1,18 @@ -package io.openems.edge.battery.bmw.enums; +package io.openems.edge.simulator.meter.grid.acting; import io.openems.common.types.OptionsEnum; -public enum InfoBits implements OptionsEnum { - UNDEFINED(-1, "Undefined"); +public enum State implements OptionsEnum { + UNDEFINED(-1), // + INITIAL_FREQ(1), // + FIRST_STEPDOWN_FREQUENCY(2), // + SECOND_STEPDOWN_FREQUENCY(3), // + FINISH(4); private final int value; - private final String name; - private InfoBits(int value, String name) { + private State(int value) { this.value = value; - this.name = name; } @Override @@ -19,12 +21,13 @@ public int getValue() { } @Override - public String getName() { - return this.name; + public OptionsEnum getUndefined() { + return UNDEFINED; } @Override - public OptionsEnum getUndefined() { - return UNDEFINED; + public String getName() { + return this.name(); } + } diff --git a/io.openems.edge.simulator/src/io/openems/edge/simulator/meter/grid/acting/StepResponseHandler.java b/io.openems.edge.simulator/src/io/openems/edge/simulator/meter/grid/acting/StepResponseHandler.java new file mode 100644 index 00000000000..5a75825775e --- /dev/null +++ b/io.openems.edge.simulator/src/io/openems/edge/simulator/meter/grid/acting/StepResponseHandler.java @@ -0,0 +1,57 @@ +package io.openems.edge.simulator.meter.grid.acting; + +import java.time.Duration; +import java.time.Instant; + +public class StepResponseHandler { + + private State state; + private int repetitionCounter; + private SimulatorGridMeterActingImpl meter; + private Instant startTime; + + private static int TIME_THRESHOLD_SECONDS = 120; // [120 seconds in each step] + + public StepResponseHandler(SimulatorGridMeterActingImpl meter, Instant startTime) { + this.state = State.UNDEFINED; + // repeats atleast once + this.repetitionCounter = 1; + this.meter = meter; + this.startTime = startTime; + } + + /** + * State Machine for frequency step response. + */ + public void doStepResponse() { + switch (this.state) { + case UNDEFINED -> this.state = State.INITIAL_FREQ; + case INITIAL_FREQ -> this.handleStateTransition(50000, State.FIRST_STEPDOWN_FREQUENCY); + case FIRST_STEPDOWN_FREQUENCY -> this.handleStateTransition(49750, State.SECOND_STEPDOWN_FREQUENCY); + case SECOND_STEPDOWN_FREQUENCY -> this.handleStateTransition(49650, State.FINISH); + case FINISH -> { + if (this.repetitionCounter <= 1) { + this.handleFinishTransition(50000, State.FINISH); + } else { + this.repetitionCounter--; + this.handleFinishTransition(50000, State.INITIAL_FREQ); + } + } + } + this.meter._setStateMachine(this.state); + } + + private void handleStateTransition(int frequency, State nextState) { + var now = this.meter.getCurrentTime(); + this.meter._setFrequency(frequency); + if (Duration.between(this.startTime, now).getSeconds() > TIME_THRESHOLD_SECONDS) { + this.startTime = now; + this.state = nextState; + } + } + + private void handleFinishTransition(int frequency, State nextState) { + this.meter._setFrequency(frequency); + this.state = nextState; + } +} diff --git a/io.openems.edge.simulator/src/io/openems/edge/simulator/meter/grid/reacting/SimulatorGridMeterReactingImpl.java b/io.openems.edge.simulator/src/io/openems/edge/simulator/meter/grid/reacting/SimulatorGridMeterReactingImpl.java index 664e6d74428..0401d3e0999 100644 --- a/io.openems.edge.simulator/src/io/openems/edge/simulator/meter/grid/reacting/SimulatorGridMeterReactingImpl.java +++ b/io.openems.edge.simulator/src/io/openems/edge/simulator/meter/grid/reacting/SimulatorGridMeterReactingImpl.java @@ -19,6 +19,7 @@ import org.osgi.service.event.propertytypes.EventTopics; import org.osgi.service.metatype.annotations.Designate; +import io.openems.common.types.MeterType; import io.openems.edge.common.channel.value.Value; import io.openems.edge.common.component.AbstractOpenemsComponent; import io.openems.edge.common.component.OpenemsComponent; @@ -27,7 +28,6 @@ import io.openems.edge.ess.api.ManagedSymmetricEss; import io.openems.edge.ess.api.MetaEss; import io.openems.edge.meter.api.ElectricityMeter; -import io.openems.edge.meter.api.MeterType; import io.openems.edge.simulator.Constants; import io.openems.edge.timedata.api.Timedata; import io.openems.edge.timedata.api.TimedataProvider; @@ -139,6 +139,7 @@ public void handleEvent(Event event) { try { switch (m.getMeterType()) { case CONSUMPTION_METERED: + case MANAGED_CONSUMPTION_METERED: case GRID: // ignore break; diff --git a/io.openems.edge.simulator/src/io/openems/edge/simulator/meter/nrc/acting/SimulatorNrcMeterActingImpl.java b/io.openems.edge.simulator/src/io/openems/edge/simulator/meter/nrc/acting/SimulatorNrcMeterActingImpl.java index c65c5bb54b0..3c33cf96b4f 100644 --- a/io.openems.edge.simulator/src/io/openems/edge/simulator/meter/nrc/acting/SimulatorNrcMeterActingImpl.java +++ b/io.openems.edge.simulator/src/io/openems/edge/simulator/meter/nrc/acting/SimulatorNrcMeterActingImpl.java @@ -18,13 +18,13 @@ import org.osgi.service.metatype.annotations.Designate; import io.openems.common.types.ChannelAddress; +import io.openems.common.types.MeterType; import io.openems.common.types.OpenemsType; import io.openems.edge.common.component.AbstractOpenemsComponent; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.event.EdgeEventConstants; import io.openems.edge.common.type.TypeUtils; import io.openems.edge.meter.api.ElectricityMeter; -import io.openems.edge.meter.api.MeterType; import io.openems.edge.simulator.datasource.api.SimulatorDatasource; import io.openems.edge.timedata.api.Timedata; import io.openems.edge.timedata.api.TimedataProvider; diff --git a/io.openems.edge.simulator/src/io/openems/edge/simulator/meter/production/acting/SimulatorProductionMeterActingImpl.java b/io.openems.edge.simulator/src/io/openems/edge/simulator/meter/production/acting/SimulatorProductionMeterActingImpl.java index 6ed67e2a82e..12f7a7dc1d9 100644 --- a/io.openems.edge.simulator/src/io/openems/edge/simulator/meter/production/acting/SimulatorProductionMeterActingImpl.java +++ b/io.openems.edge.simulator/src/io/openems/edge/simulator/meter/production/acting/SimulatorProductionMeterActingImpl.java @@ -18,13 +18,13 @@ import org.osgi.service.metatype.annotations.Designate; import io.openems.common.types.ChannelAddress; +import io.openems.common.types.MeterType; import io.openems.common.types.OpenemsType; import io.openems.edge.common.component.AbstractOpenemsComponent; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.event.EdgeEventConstants; import io.openems.edge.common.type.TypeUtils; import io.openems.edge.meter.api.ElectricityMeter; -import io.openems.edge.meter.api.MeterType; import io.openems.edge.simulator.datasource.api.SimulatorDatasource; import io.openems.edge.timedata.api.Timedata; import io.openems.edge.timedata.api.TimedataProvider; diff --git a/io.openems.edge.simulator/src/io/openems/edge/simulator/pvinverter/SimulatorPvInverterImpl.java b/io.openems.edge.simulator/src/io/openems/edge/simulator/pvinverter/SimulatorPvInverterImpl.java index 822c2854765..a5678c811a6 100644 --- a/io.openems.edge.simulator/src/io/openems/edge/simulator/pvinverter/SimulatorPvInverterImpl.java +++ b/io.openems.edge.simulator/src/io/openems/edge/simulator/pvinverter/SimulatorPvInverterImpl.java @@ -18,12 +18,12 @@ import org.osgi.service.metatype.annotations.Designate; import io.openems.common.types.ChannelAddress; +import io.openems.common.types.MeterType; import io.openems.common.types.OpenemsType; import io.openems.edge.common.component.AbstractOpenemsComponent; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.event.EdgeEventConstants; import io.openems.edge.meter.api.ElectricityMeter; -import io.openems.edge.meter.api.MeterType; import io.openems.edge.pvinverter.api.ManagedSymmetricPvInverter; import io.openems.edge.simulator.datasource.api.SimulatorDatasource; import io.openems.edge.timedata.api.Timedata; diff --git a/io.openems.edge.simulator/test/io/openems/edge/simulator/battery/SimulatorBatteryImplTest.java b/io.openems.edge.simulator/test/io/openems/edge/simulator/battery/SimulatorBatteryImplTest.java index 0762e332f6d..d2f19787009 100644 --- a/io.openems.edge.simulator/test/io/openems/edge/simulator/battery/SimulatorBatteryImplTest.java +++ b/io.openems.edge.simulator/test/io/openems/edge/simulator/battery/SimulatorBatteryImplTest.java @@ -7,13 +7,11 @@ public class SimulatorBatteryImplTest { - private static final String COMPONENT_ID = "battery0"; - @Test public void test() throws Exception { new ComponentTest(new SimulatorBatteryImpl()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("battery0") // .setCapacityKWh(20) // .setChargeMaxCurrent(40) // .setChargeMaxVoltage(700) // diff --git a/io.openems.edge.simulator/test/io/openems/edge/simulator/datasource/csv/predefined/SimulatorDatasourceCsvPredefinedImplTest.java b/io.openems.edge.simulator/test/io/openems/edge/simulator/datasource/csv/predefined/SimulatorDatasourceCsvPredefinedImplTest.java index 1ba5d54f0ca..c10d2e08e70 100644 --- a/io.openems.edge.simulator/test/io/openems/edge/simulator/datasource/csv/predefined/SimulatorDatasourceCsvPredefinedImplTest.java +++ b/io.openems.edge.simulator/test/io/openems/edge/simulator/datasource/csv/predefined/SimulatorDatasourceCsvPredefinedImplTest.java @@ -9,14 +9,12 @@ public class SimulatorDatasourceCsvPredefinedImplTest { - private static final String COMPONENT_ID = "datasource0"; - @Test public void test() throws Exception { new ComponentTest(new SimulatorDatasourceCsvPredefinedImpl()) // .addReference("componentManager", new DummyComponentManager()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("datasource0") // .setFactor(1) // .setFormat(CsvFormat.ENGLISH) // .setSource(Source.H0_HOUSEHOLD_SUMMER_WEEKDAY_NON_REGULATED_CONSUMPTION) // diff --git a/io.openems.edge.simulator/test/io/openems/edge/simulator/datasource/single/direct/SimulatorDatasourceSingleDirectImplTest.java b/io.openems.edge.simulator/test/io/openems/edge/simulator/datasource/single/direct/SimulatorDatasourceSingleDirectImplTest.java index 7ac397de8c1..2328e523a45 100644 --- a/io.openems.edge.simulator/test/io/openems/edge/simulator/datasource/single/direct/SimulatorDatasourceSingleDirectImplTest.java +++ b/io.openems.edge.simulator/test/io/openems/edge/simulator/datasource/single/direct/SimulatorDatasourceSingleDirectImplTest.java @@ -8,14 +8,12 @@ public class SimulatorDatasourceSingleDirectImplTest { - private static final String COMPONENT_ID = "datasource0"; - @Test public void test() throws Exception { new ComponentTest(new SimulatorDatasourceSingleDirectImpl()) // .addReference("componentManager", new DummyComponentManager()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("datasource0") // .setTimeDelta(0) // .setValues() // .build()) // diff --git a/io.openems.edge.simulator/test/io/openems/edge/simulator/ess/asymmetric/reacting/SimulatorEssAsymmetricReactingImplTest.java b/io.openems.edge.simulator/test/io/openems/edge/simulator/ess/asymmetric/reacting/SimulatorEssAsymmetricReactingImplTest.java index 8a2656a7343..35c7a7f745c 100644 --- a/io.openems.edge.simulator/test/io/openems/edge/simulator/ess/asymmetric/reacting/SimulatorEssAsymmetricReactingImplTest.java +++ b/io.openems.edge.simulator/test/io/openems/edge/simulator/ess/asymmetric/reacting/SimulatorEssAsymmetricReactingImplTest.java @@ -8,22 +8,19 @@ import io.openems.edge.common.test.DummyConfigurationAdmin; import io.openems.edge.ess.test.DummyPower; import io.openems.edge.ess.test.ManagedSymmetricEssTest; -import io.openems.edge.simulator.datasource.csv.direct.SimulatorDatasourceCsvDirectImpl; +import io.openems.edge.simulator.datasource.csv.direct.SimulatorDatasourceCsvDirectImplTest; public class SimulatorEssAsymmetricReactingImplTest { - private static final String ESS_ID = "ess0"; - private static final String DATASOURCE_ID = "datasource0"; - @Test public void test() throws OpenemsException, Exception { new ManagedSymmetricEssTest(new SimulatorEssAsymmetricReactingImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("datasource", new SimulatorDatasourceCsvDirectImpl()) // + .addReference("datasource", SimulatorDatasourceCsvDirectImplTest.create("datasource0", "123")) // .addReference("power", new DummyPower()) // .activate(MyConfig.create() // - .setId(ESS_ID) // - .setDatasourceId(DATASOURCE_ID) // + .setId("ess0") // + .setDatasourceId("datasource0") // .setCapacity(10_000) // .setMaxApparentPower(10_000) // .setInitialSoc(50) // diff --git a/io.openems.edge.simulator/test/io/openems/edge/simulator/ess/singlephase/reacting/SimulatorEssSinglePhaseReactingImplTest.java b/io.openems.edge.simulator/test/io/openems/edge/simulator/ess/singlephase/reacting/SimulatorEssSinglePhaseReactingImplTest.java index d00be5bce1c..38be1552e53 100644 --- a/io.openems.edge.simulator/test/io/openems/edge/simulator/ess/singlephase/reacting/SimulatorEssSinglePhaseReactingImplTest.java +++ b/io.openems.edge.simulator/test/io/openems/edge/simulator/ess/singlephase/reacting/SimulatorEssSinglePhaseReactingImplTest.java @@ -9,7 +9,7 @@ import io.openems.edge.ess.api.SinglePhase; import io.openems.edge.ess.test.DummyPower; import io.openems.edge.ess.test.ManagedSymmetricEssTest; -import io.openems.edge.simulator.datasource.csv.direct.SimulatorDatasourceCsvDirectImpl; +import io.openems.edge.simulator.datasource.csv.direct.SimulatorDatasourceCsvDirectImplTest; public class SimulatorEssSinglePhaseReactingImplTest { @@ -20,7 +20,7 @@ public class SimulatorEssSinglePhaseReactingImplTest { public void test() throws OpenemsException, Exception { new ManagedSymmetricEssTest(new SimulatorEssSinglePhaseReactingImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("datasource", new SimulatorDatasourceCsvDirectImpl()) // + .addReference("datasource", SimulatorDatasourceCsvDirectImplTest.create("datasource0", "123")) // .addReference("power", new DummyPower()) // .activate(MyConfig.create() // .setId(ESS_ID) // diff --git a/io.openems.edge.simulator/test/io/openems/edge/simulator/evcs/SimulatorEvcsImplTest.java b/io.openems.edge.simulator/test/io/openems/edge/simulator/evcs/SimulatorEvcsImplTest.java index 5090dd90d7d..4aed1982e28 100644 --- a/io.openems.edge.simulator/test/io/openems/edge/simulator/evcs/SimulatorEvcsImplTest.java +++ b/io.openems.edge.simulator/test/io/openems/edge/simulator/evcs/SimulatorEvcsImplTest.java @@ -10,15 +10,13 @@ public class SimulatorEvcsImplTest { - private static final String ESS_ID = "evcs0"; - @Test public void test() throws OpenemsException, Exception { new ComponentTest(new SimulatorEvcsImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("evcsPower", new DummyEvcsPower()) // .activate(MyConfig.create() // - .setId(ESS_ID) // + .setId("evcs0") // .setMinHwPower(1000) // .setMaxHwPower(10000) // .build()) // diff --git a/io.openems.edge.simulator/test/io/openems/edge/simulator/io/SimulatorIoDigitalInputOutputImplTest.java b/io.openems.edge.simulator/test/io/openems/edge/simulator/io/SimulatorIoDigitalInputOutputImplTest.java index c147bc6a0b1..bd83f9838c9 100644 --- a/io.openems.edge.simulator/test/io/openems/edge/simulator/io/SimulatorIoDigitalInputOutputImplTest.java +++ b/io.openems.edge.simulator/test/io/openems/edge/simulator/io/SimulatorIoDigitalInputOutputImplTest.java @@ -8,13 +8,11 @@ public class SimulatorIoDigitalInputOutputImplTest { - private static final String COMPONENT_ID = "io0"; - @Test public void test() throws OpenemsException, Exception { new ComponentTest(new SimulatorIoDigitalInputOutputImpl()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("io0") // .setNumberOfOutputs(3) // .build()) // .next(new TestCase()); // diff --git a/io.openems.edge.simulator/test/io/openems/edge/simulator/meter/grid/acting/MyConfig.java b/io.openems.edge.simulator/test/io/openems/edge/simulator/meter/grid/acting/MyConfig.java index 6367e673ebd..04a1c548104 100644 --- a/io.openems.edge.simulator/test/io/openems/edge/simulator/meter/grid/acting/MyConfig.java +++ b/io.openems.edge.simulator/test/io/openems/edge/simulator/meter/grid/acting/MyConfig.java @@ -9,6 +9,8 @@ public class MyConfig extends AbstractComponentConfig implements Config { protected static class Builder { private String id; private String datasourceId; + private String startTime; + private boolean needFrequencyStepResponse; private Builder() { } @@ -23,6 +25,16 @@ public Builder setDatasourceId(String datasourceId) { return this; } + public Builder setStartTime(String startTime) { + this.startTime = startTime; + return this; + } + + public Builder needFrequencyStepResponse(boolean needFrequencyStepResponse) { + this.needFrequencyStepResponse = needFrequencyStepResponse; + return this; + } + public MyConfig build() { return new MyConfig(this); } @@ -54,4 +66,14 @@ public String datasource_target() { return ConfigUtils.generateReferenceTargetFilter(this.id(), this.datasource_id()); } + @Override + public boolean needFrequencyStepResponse() { + return this.builder.needFrequencyStepResponse; + } + + @Override + public String startTime() { + return this.builder.startTime; + } + } \ No newline at end of file diff --git a/io.openems.edge.simulator/test/io/openems/edge/simulator/meter/grid/acting/SimulatorGridMeterActingImplTest.java b/io.openems.edge.simulator/test/io/openems/edge/simulator/meter/grid/acting/SimulatorGridMeterActingImplTest.java index 32fa540b54c..dd2d9614318 100644 --- a/io.openems.edge.simulator/test/io/openems/edge/simulator/meter/grid/acting/SimulatorGridMeterActingImplTest.java +++ b/io.openems.edge.simulator/test/io/openems/edge/simulator/meter/grid/acting/SimulatorGridMeterActingImplTest.java @@ -1,26 +1,48 @@ package io.openems.edge.simulator.meter.grid.acting; +import java.time.Instant; +import java.time.ZoneOffset; + import org.junit.Test; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.test.TimeLeapClock; +import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; +import io.openems.edge.common.test.DummyComponentManager; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.simulator.datasource.csv.direct.SimulatorDatasourceCsvDirectImpl; +import io.openems.edge.simulator.datasource.api.DummyDatasource; public class SimulatorGridMeterActingImplTest { - private static final String COMPONENT_ID = "meter0"; - private static final String DATASOURCE_ID = "datasource0"; - @Test public void test() throws OpenemsException, Exception { new ComponentTest(new SimulatorGridMeterActingImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("datasource", new SimulatorDatasourceCsvDirectImpl()) // + .addReference("datasource", new DummyDatasource(123)) // + .activate(MyConfig.create() // + .setId("meter0") // + .setStartTime("")// + .needFrequencyStepResponse(false)// + .setDatasourceId("datasource0") // + .build()) // + .next(new TestCase()) // + .deactivate(); + } + + @Test + public void test1() throws OpenemsException, Exception { + final var clock = new TimeLeapClock(Instant.parse("2024-01-29T19:05:00Z"), ZoneOffset.UTC); + new ComponentTest(new SimulatorGridMeterActingImpl()) // + .addReference("componentManager", new DummyComponentManager(clock)) // + .addReference("cm", new DummyConfigurationAdmin()) // + .addReference("datasource", new DummyDatasource(123)) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // - .setDatasourceId(DATASOURCE_ID) // - .build()); // - // .next(new TestCase()); // TODO requires DummyDatasource + .setId("meter0") // + .setStartTime("")// + .needFrequencyStepResponse(true)// + .setDatasourceId("datasource0") // + .build()) // + .deactivate(); } } diff --git a/io.openems.edge.simulator/test/io/openems/edge/simulator/meter/grid/reacting/SimulatorGridMeterReactingImplTest.java b/io.openems.edge.simulator/test/io/openems/edge/simulator/meter/grid/reacting/SimulatorGridMeterReactingImplTest.java index 96222001f3e..dedc66db72d 100644 --- a/io.openems.edge.simulator/test/io/openems/edge/simulator/meter/grid/reacting/SimulatorGridMeterReactingImplTest.java +++ b/io.openems.edge.simulator/test/io/openems/edge/simulator/meter/grid/reacting/SimulatorGridMeterReactingImplTest.java @@ -9,14 +9,12 @@ public class SimulatorGridMeterReactingImplTest { - private static final String COMPONENT_ID = "meter0"; - @Test public void test() throws OpenemsException, Exception { new ComponentTest(new SimulatorGridMeterReactingImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("meter0") // .build()) // .next(new TestCase()); } diff --git a/io.openems.edge.simulator/test/io/openems/edge/simulator/modbus/SimulatorModbusImplTest.java b/io.openems.edge.simulator/test/io/openems/edge/simulator/modbus/SimulatorModbusImplTest.java index 3a60a20f877..6f059aa6a80 100644 --- a/io.openems.edge.simulator/test/io/openems/edge/simulator/modbus/SimulatorModbusImplTest.java +++ b/io.openems.edge.simulator/test/io/openems/edge/simulator/modbus/SimulatorModbusImplTest.java @@ -8,13 +8,11 @@ public class SimulatorModbusImplTest { - private static final String COMPONENT_ID = "modbus0"; - @Test public void test() throws OpenemsException, Exception { new ComponentTest(new SimulatorModbusImpl()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("modbus0") // .build()) // .next(new TestCase()); } diff --git a/io.openems.edge.simulator/test/io/openems/edge/simulator/predictor/SimulatorPredictorImplTest.java b/io.openems.edge.simulator/test/io/openems/edge/simulator/predictor/SimulatorPredictorImplTest.java index cb52b93658c..6f5b0cc74c1 100644 --- a/io.openems.edge.simulator/test/io/openems/edge/simulator/predictor/SimulatorPredictorImplTest.java +++ b/io.openems.edge.simulator/test/io/openems/edge/simulator/predictor/SimulatorPredictorImplTest.java @@ -49,7 +49,7 @@ public void test() throws OpenemsException, Exception { assertEquals(Integer.valueOf(20), p.asArray()[0]); assertEquals(Integer.valueOf(23), p.asArray()[1]); assertEquals(Integer.valueOf(27), p.asArray()[2]); - assertEquals(ZonedDateTime.of(2000, 1, 1, 0, 0, 0, 0, ZoneId.of("UTC")), p.valuePerQuarter.firstKey()); + assertEquals(ZonedDateTime.of(2000, 1, 1, 0, 0, 0, 0, ZoneId.of("UTC")), p.getFirstTime()); } } diff --git a/io.openems.edge.simulator/test/io/openems/edge/simulator/pvinverter/SimulatorPvInverterImplTest.java b/io.openems.edge.simulator/test/io/openems/edge/simulator/pvinverter/SimulatorPvInverterImplTest.java index 5310e732d61..9294bd195a1 100644 --- a/io.openems.edge.simulator/test/io/openems/edge/simulator/pvinverter/SimulatorPvInverterImplTest.java +++ b/io.openems.edge.simulator/test/io/openems/edge/simulator/pvinverter/SimulatorPvInverterImplTest.java @@ -9,17 +9,14 @@ public class SimulatorPvInverterImplTest { - private static final String COMPONENT_ID = "pvInverter0"; - private static final String DATASOURCE_ID = "datasource0"; - @Test public void test() throws OpenemsException, Exception { new ComponentTest(new SimulatorPvInverterImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("datasource", new SimulatorDatasourceCsvDirectImpl()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // - .setDatasourceId(DATASOURCE_ID) // + .setId("pvInverter0") // + .setDatasourceId("datasource0") // .build()); // // .next(new TestCase()); // TODO requires DummyDatasource } diff --git a/io.openems.edge.simulator/test/io/openems/edge/simulator/thermometer/SimulatorThermometerImplTest.java b/io.openems.edge.simulator/test/io/openems/edge/simulator/thermometer/SimulatorThermometerImplTest.java index c07899add9f..a6620e2fd54 100644 --- a/io.openems.edge.simulator/test/io/openems/edge/simulator/thermometer/SimulatorThermometerImplTest.java +++ b/io.openems.edge.simulator/test/io/openems/edge/simulator/thermometer/SimulatorThermometerImplTest.java @@ -8,13 +8,11 @@ public class SimulatorThermometerImplTest { - private static final String COMPONENT_ID = "thermometer0"; - @Test public void test() throws OpenemsException, Exception { new ComponentTest(new SimulatorThermometerImpl()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("thermometer0") // .setTemperature(20) // .build()) // .next(new TestCase()); diff --git a/io.openems.edge.simulator/test/io/openems/edge/simulator/timedata/SimulatorTimedataImplTest.java b/io.openems.edge.simulator/test/io/openems/edge/simulator/timedata/SimulatorTimedataImplTest.java index bd1d9358847..13d83a24d12 100644 --- a/io.openems.edge.simulator/test/io/openems/edge/simulator/timedata/SimulatorTimedataImplTest.java +++ b/io.openems.edge.simulator/test/io/openems/edge/simulator/timedata/SimulatorTimedataImplTest.java @@ -9,13 +9,11 @@ public class SimulatorTimedataImplTest { - private static final String COMPONENT_ID = "thermometer0"; - @Test public void test() throws OpenemsException, Exception { new ComponentTest(new SimulatorTimedataImpl()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("thermometer0") // .setFilename("") // .setFormat(CsvFormat.ENGLISH) // .build()) // diff --git a/io.openems.edge.solaredge/.classpath b/io.openems.edge.solaredge/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.solaredge/.classpath +++ b/io.openems.edge.solaredge/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.solaredge/src/io/openems/edge/solaredge/gridmeter/Config.java b/io.openems.edge.solaredge/src/io/openems/edge/solaredge/gridmeter/Config.java index e8c2aa5e28f..fbbc7def519 100644 --- a/io.openems.edge.solaredge/src/io/openems/edge/solaredge/gridmeter/Config.java +++ b/io.openems.edge.solaredge/src/io/openems/edge/solaredge/gridmeter/Config.java @@ -3,7 +3,7 @@ import org.osgi.service.metatype.annotations.AttributeDefinition; import org.osgi.service.metatype.annotations.ObjectClassDefinition; -import io.openems.edge.meter.api.MeterType; +import io.openems.common.types.MeterType; @ObjectClassDefinition(name = "Meter SolarEdge", // description = "Implements the SolarEdge Meter.") diff --git a/io.openems.edge.solaredge/src/io/openems/edge/solaredge/gridmeter/SolarEdgeGridMeterImpl.java b/io.openems.edge.solaredge/src/io/openems/edge/solaredge/gridmeter/SolarEdgeGridMeterImpl.java index fdb83a0ae8f..555a15013b9 100644 --- a/io.openems.edge.solaredge/src/io/openems/edge/solaredge/gridmeter/SolarEdgeGridMeterImpl.java +++ b/io.openems.edge.solaredge/src/io/openems/edge/solaredge/gridmeter/SolarEdgeGridMeterImpl.java @@ -18,6 +18,7 @@ import com.google.common.collect.ImmutableMap; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.types.MeterType; import io.openems.edge.bridge.modbus.api.BridgeModbus; import io.openems.edge.bridge.modbus.api.ModbusComponent; import io.openems.edge.bridge.modbus.sunspec.DefaultSunSpecModel; @@ -26,7 +27,6 @@ import io.openems.edge.common.event.EdgeEventConstants; import io.openems.edge.common.taskmanager.Priority; import io.openems.edge.meter.api.ElectricityMeter; -import io.openems.edge.meter.api.MeterType; import io.openems.edge.meter.sunspec.AbstractSunSpecMeter; @Designate(ocd = Config.class, factory = true) diff --git a/io.openems.edge.solaredge/test/io/openems/edge/solaredge/gridmeter/MyConfig.java b/io.openems.edge.solaredge/test/io/openems/edge/solaredge/gridmeter/MyConfig.java index 99558daca4c..15e59eb4c4c 100644 --- a/io.openems.edge.solaredge/test/io/openems/edge/solaredge/gridmeter/MyConfig.java +++ b/io.openems.edge.solaredge/test/io/openems/edge/solaredge/gridmeter/MyConfig.java @@ -1,8 +1,8 @@ package io.openems.edge.solaredge.gridmeter; import io.openems.common.test.AbstractComponentConfig; +import io.openems.common.types.MeterType; import io.openems.common.utils.ConfigUtils; -import io.openems.edge.meter.api.MeterType; @SuppressWarnings("all") public class MyConfig extends AbstractComponentConfig implements Config { diff --git a/io.openems.edge.solaredge/test/io/openems/edge/solaredge/gridmeter/SolarEdgeGridMeterImplTest.java b/io.openems.edge.solaredge/test/io/openems/edge/solaredge/gridmeter/SolarEdgeGridMeterImplTest.java index 6c4ccbc5b0d..c04d3b3b5a4 100644 --- a/io.openems.edge.solaredge/test/io/openems/edge/solaredge/gridmeter/SolarEdgeGridMeterImplTest.java +++ b/io.openems.edge.solaredge/test/io/openems/edge/solaredge/gridmeter/SolarEdgeGridMeterImplTest.java @@ -1,27 +1,25 @@ package io.openems.edge.solaredge.gridmeter; +import static io.openems.common.types.MeterType.GRID; + import org.junit.Test; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.meter.api.MeterType; public class SolarEdgeGridMeterImplTest { - private static final String METER_ID = "meter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new SolarEdgeGridMeterImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // + .setId("meter0") // + .setModbusId("modbus0") // .setModbusUnitId(1) // - .setType(MeterType.GRID) // + .setType(GRID) // .build()) // ; } diff --git a/io.openems.edge.solaredge/test/io/openems/edge/solaredge/pvinverter/SolarEdgePvInverterImplTest.java b/io.openems.edge.solaredge/test/io/openems/edge/solaredge/pvinverter/SolarEdgePvInverterImplTest.java index c90b0a9e978..438399b1243 100644 --- a/io.openems.edge.solaredge/test/io/openems/edge/solaredge/pvinverter/SolarEdgePvInverterImplTest.java +++ b/io.openems.edge.solaredge/test/io/openems/edge/solaredge/pvinverter/SolarEdgePvInverterImplTest.java @@ -1,28 +1,26 @@ package io.openems.edge.solaredge.pvinverter; +import static io.openems.edge.pvinverter.sunspec.Phase.L1; + import org.junit.Test; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.pvinverter.sunspec.Phase; public class SolarEdgePvInverterImplTest { - private static final String PV_INVERTER_ID = "pvInverter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new SolarEdgePvInverterImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(PV_INVERTER_ID) // + .setId("pvInverter0") // .setReadOnly(true) // - .setModbusId(MODBUS_ID) // + .setModbusId("modbus0") // .setModbusUnitId(1) // - .setPhase(Phase.L1) // + .setPhase(L1) // .build()) // ; } diff --git a/io.openems.edge.tesla.powerwall2/.classpath b/io.openems.edge.tesla.powerwall2/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.tesla.powerwall2/.classpath +++ b/io.openems.edge.tesla.powerwall2/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.tesla.powerwall2/src/io/openems/edge/tesla/powerwall2/core/ReadWorker.java b/io.openems.edge.tesla.powerwall2/src/io/openems/edge/tesla/powerwall2/core/ReadWorker.java index 02c9174d672..a9ffa703907 100644 --- a/io.openems.edge.tesla.powerwall2/src/io/openems/edge/tesla/powerwall2/core/ReadWorker.java +++ b/io.openems.edge.tesla.powerwall2/src/io/openems/edge/tesla/powerwall2/core/ReadWorker.java @@ -4,7 +4,7 @@ import java.io.IOException; import java.io.InputStreamReader; import java.net.Inet4Address; -import java.net.URL; +import java.net.URI; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; @@ -122,7 +122,7 @@ protected void forever() throws Throwable { */ private JsonObject getResponse(String path) throws OpenemsNamedException { try { - var url = new URL(this.baseUrl + path); + var url = URI.create(this.baseUrl + path).toURL(); var connection = (HttpsURLConnection) url.openConnection(); connection.setHostnameVerifier((hostname, session) -> true); try (var reader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) { diff --git a/io.openems.edge.tesla.powerwall2/test/io/openems/edge/tesla/powerwall2/battery/TeslaPowerwall2BatteryImplTest.java b/io.openems.edge.tesla.powerwall2/test/io/openems/edge/tesla/powerwall2/battery/TeslaPowerwall2BatteryImplTest.java index 1a3d4b39b01..d8deb4b3dcb 100644 --- a/io.openems.edge.tesla.powerwall2/test/io/openems/edge/tesla/powerwall2/battery/TeslaPowerwall2BatteryImplTest.java +++ b/io.openems.edge.tesla.powerwall2/test/io/openems/edge/tesla/powerwall2/battery/TeslaPowerwall2BatteryImplTest.java @@ -10,21 +10,18 @@ public class TeslaPowerwall2BatteryImplTest { - private static final String COMPONENT_ID = "ess0"; - private static final String CORE_ID = "core0"; - @Test public void test() throws Exception { new ComponentTest(new TeslaPowerwall2BatteryImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("core", new TeslaPowerwall2CoreImpl()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // - .setCoreId(CORE_ID) // + .setId("ess0") // + .setCoreId("core0") // .setPhase(SinglePhase.L1) // .build()) // .next(new TestCase()) // - ; + .deactivate(); } } diff --git a/io.openems.edge.tesla.powerwall2/test/io/openems/edge/tesla/powerwall2/core/TeslaPowerwall2CoreImplTest.java b/io.openems.edge.tesla.powerwall2/test/io/openems/edge/tesla/powerwall2/core/TeslaPowerwall2CoreImplTest.java index 99c4be260cd..df66e5fcfa6 100644 --- a/io.openems.edge.tesla.powerwall2/test/io/openems/edge/tesla/powerwall2/core/TeslaPowerwall2CoreImplTest.java +++ b/io.openems.edge.tesla.powerwall2/test/io/openems/edge/tesla/powerwall2/core/TeslaPowerwall2CoreImplTest.java @@ -7,17 +7,15 @@ public class TeslaPowerwall2CoreImplTest { - private static final String COMPONENT_ID = "core0"; - @Test public void test() throws Exception { new ComponentTest(new TeslaPowerwall2CoreImpl()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("core0") // .setIpAddress("127.0.0.1") // .setPort(443) // .build()) // .next(new TestCase()) // - ; + .deactivate(); } } diff --git a/io.openems.edge.thermometer.api/.classpath b/io.openems.edge.thermometer.api/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.thermometer.api/.classpath +++ b/io.openems.edge.thermometer.api/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.timedata.api/.classpath b/io.openems.edge.timedata.api/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.timedata.api/.classpath +++ b/io.openems.edge.timedata.api/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.timedata.influxdb/.classpath b/io.openems.edge.timedata.influxdb/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.timedata.influxdb/.classpath +++ b/io.openems.edge.timedata.influxdb/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.timedata.influxdb/test/io/openems/edge/timedata/influxdb/TimedataInfluxDbImplTest.java b/io.openems.edge.timedata.influxdb/test/io/openems/edge/timedata/influxdb/TimedataInfluxDbImplTest.java index 4c097c21c69..05890b4b34c 100644 --- a/io.openems.edge.timedata.influxdb/test/io/openems/edge/timedata/influxdb/TimedataInfluxDbImplTest.java +++ b/io.openems.edge.timedata.influxdb/test/io/openems/edge/timedata/influxdb/TimedataInfluxDbImplTest.java @@ -1,19 +1,18 @@ package io.openems.edge.timedata.influxdb; +import static io.openems.common.channel.PersistencePriority.MEDIUM; +import static io.openems.shared.influxdb.QueryLanguageConfig.INFLUX_QL; + import org.junit.Test; -import io.openems.common.channel.PersistencePriority; import io.openems.common.oem.DummyOpenemsEdgeOem; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyComponentManager; import io.openems.edge.common.test.DummyCycle; -import io.openems.shared.influxdb.QueryLanguageConfig; public class TimedataInfluxDbImplTest { - private static final String COMPONENT_ID = "influx0"; - @Test public void test() throws Exception { new ComponentTest(new TimedataInfluxDbImpl()) // @@ -21,8 +20,8 @@ public void test() throws Exception { .addReference("cycle", new DummyCycle(1000)) // .addReference("oem", new DummyOpenemsEdgeOem()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // - .setQueryLanguage(QueryLanguageConfig.INFLUX_QL) // + .setId("influx0") // + .setQueryLanguage(INFLUX_QL) // .setUrl("http://localhost:8086") // .setOrg("-") // .setApiKey("username:password") // @@ -31,7 +30,7 @@ public void test() throws Exception { .setNoOfCycles(1) // .setMaxQueueSize(5000) // .setReadOnly(false) // - .setPersistencePriority(PersistencePriority.MEDIUM) + .setPersistencePriority(MEDIUM) // .build()) // .next(new TestCase()) // ; diff --git a/io.openems.edge.timedata.rrd4j/.classpath b/io.openems.edge.timedata.rrd4j/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.timedata.rrd4j/.classpath +++ b/io.openems.edge.timedata.rrd4j/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.timedata.rrd4j/src/io/openems/edge/timedata/rrd4j/Rrd4jReadHandler.java b/io.openems.edge.timedata.rrd4j/src/io/openems/edge/timedata/rrd4j/Rrd4jReadHandler.java index e12637892de..0be558ae28e 100644 --- a/io.openems.edge.timedata.rrd4j/src/io/openems/edge/timedata/rrd4j/Rrd4jReadHandler.java +++ b/io.openems.edge.timedata.rrd4j/src/io/openems/edge/timedata/rrd4j/Rrd4jReadHandler.java @@ -552,8 +552,7 @@ public CompletableFuture> getLatestValue(// try { channel = this.componentManager.getChannel(channelAddress); } catch (Exception e) { - // unable to get channel - this.log.warn("Unable to query RRD4j", e); + this.log.warn("Unable to query [" + channelAddress + "] from RRD4j: " + e.getMessage()); return Optional.empty(); } diff --git a/io.openems.edge.timedata.rrd4j/src/io/openems/edge/timedata/rrd4j/Rrd4jSupplier.java b/io.openems.edge.timedata.rrd4j/src/io/openems/edge/timedata/rrd4j/Rrd4jSupplier.java index fc0d4f2b50b..3c17decc69f 100644 --- a/io.openems.edge.timedata.rrd4j/src/io/openems/edge/timedata/rrd4j/Rrd4jSupplier.java +++ b/io.openems.edge.timedata.rrd4j/src/io/openems/edge/timedata/rrd4j/Rrd4jSupplier.java @@ -143,7 +143,7 @@ public static ChannelDef getDsDefForChannel(final Unit channelUnit) { MICROVOLT, MILLIAMPERE_HOURS, MILLIAMPERE, MILLIHERTZ, MILLIOHM, MILLISECONDS, MILLIVOLT, MILLIWATT, MINUTE, NONE, WATT, VOLT, VOLT_AMPERE, VOLT_AMPERE_REACTIVE, WATT_HOURS_BY_WATT_PEAK, OHM, SECONDS, THOUSANDTH, WATT_HOURS, KILOWATT_HOURS, VOLT_AMPERE_HOURS, VOLT_AMPERE_REACTIVE_HOURS, - KILOVOLT_AMPERE_REACTIVE_HOURS, BAR -> + KILOVOLT_AMPERE_REACTIVE_HOURS, BAR, MILLIBAR, TENTHOUSANDTH, DEZIAMPERE, DEZIVOLT -> new ChannelDef(DsType.GAUGE, Double.NaN, Double.NaN, ConsolFun.AVERAGE); case PERCENT -> new ChannelDef(DsType.GAUGE, 0, 100, ConsolFun.AVERAGE); case ON_OFF -> new ChannelDef(DsType.GAUGE, 0, 1, ConsolFun.AVERAGE); diff --git a/io.openems.edge.timedata.rrd4j/test/io/openems/edge/timedata/rrd4j/DummyRecordWorkerFactory.java b/io.openems.edge.timedata.rrd4j/test/io/openems/edge/timedata/rrd4j/DummyRecordWorkerFactory.java index b4af1e2e5a3..90492eead0f 100644 --- a/io.openems.edge.timedata.rrd4j/test/io/openems/edge/timedata/rrd4j/DummyRecordWorkerFactory.java +++ b/io.openems.edge.timedata.rrd4j/test/io/openems/edge/timedata/rrd4j/DummyRecordWorkerFactory.java @@ -1,20 +1,18 @@ package io.openems.edge.timedata.rrd4j; -import java.lang.reflect.InvocationTargetException; +import static io.openems.common.utils.ReflectionUtils.setAttributeViaReflection; import org.osgi.framework.ServiceReference; import org.osgi.service.component.ComponentServiceObjects; -import io.openems.common.utils.ReflectionUtils; +import io.openems.common.utils.ReflectionUtils.ReflectionException; import io.openems.edge.common.component.ComponentManager; public class DummyRecordWorkerFactory extends RecordWorkerFactory { - public DummyRecordWorkerFactory(ComponentManager componentManager) - throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { + public DummyRecordWorkerFactory(ComponentManager componentManager) throws ReflectionException { super(); - ReflectionUtils.setAttribute(RecordWorkerFactory.class, this, "cso", - new DummyRecordWorkerCso(componentManager)); + setAttributeViaReflection(this, "cso", new DummyRecordWorkerCso(componentManager)); } private static class DummyRecordWorkerCso implements ComponentServiceObjects { @@ -29,11 +27,7 @@ public DummyRecordWorkerCso(ComponentManager componentManager) { @Override public RecordWorker getService() { final var worker = new RecordWorker(); - try { - ReflectionUtils.setAttribute(RecordWorker.class, worker, "componentManager", this.componentManager); - } catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException e) { - throw new RuntimeException(e); - } + setAttributeViaReflection(worker, "componentManager", this.componentManager); return worker; } diff --git a/io.openems.edge.timedata.rrd4j/test/io/openems/edge/timedata/rrd4j/Rrd4jReadHandlerTest.java b/io.openems.edge.timedata.rrd4j/test/io/openems/edge/timedata/rrd4j/Rrd4jReadHandlerTest.java index f0c0eab0d27..c9135ed3ab4 100644 --- a/io.openems.edge.timedata.rrd4j/test/io/openems/edge/timedata/rrd4j/Rrd4jReadHandlerTest.java +++ b/io.openems.edge.timedata.rrd4j/test/io/openems/edge/timedata/rrd4j/Rrd4jReadHandlerTest.java @@ -1,5 +1,6 @@ package io.openems.edge.timedata.rrd4j; +import static io.openems.common.utils.ReflectionUtils.setAttributeViaReflection; import static java.util.stream.Collectors.toMap; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -27,7 +28,6 @@ import io.openems.common.timedata.Resolution; import io.openems.common.types.ChannelAddress; import io.openems.common.types.OpenemsType; -import io.openems.common.utils.ReflectionUtils; import io.openems.edge.common.channel.Doc; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.test.AbstractDummyOpenemsComponent; @@ -80,9 +80,9 @@ public void setUp() throws Exception { final var versionHandler = new VersionHandler(); versionHandler.bindVersion(version3); - ReflectionUtils.setAttribute(Rrd4jReadHandler.class, this.readHandler, "componentManager", dcm); - ReflectionUtils.setAttribute(Rrd4jReadHandler.class, this.readHandler, "rrd4jSupplier", rrd4jSupplier); - ReflectionUtils.setAttribute(Rrd4jSupplier.class, rrd4jSupplier, "versionHandler", versionHandler); + setAttributeViaReflection(this.readHandler, "componentManager", dcm); + setAttributeViaReflection(this.readHandler, "rrd4jSupplier", rrd4jSupplier); + setAttributeViaReflection(rrd4jSupplier, "versionHandler", versionHandler); } diff --git a/io.openems.edge.timedata.rrd4j/test/io/openems/edge/timedata/rrd4j/TimedataRrd4jImplTest.java b/io.openems.edge.timedata.rrd4j/test/io/openems/edge/timedata/rrd4j/TimedataRrd4jImplTest.java index 980994dfb48..2b0590abc1b 100644 --- a/io.openems.edge.timedata.rrd4j/test/io/openems/edge/timedata/rrd4j/TimedataRrd4jImplTest.java +++ b/io.openems.edge.timedata.rrd4j/test/io/openems/edge/timedata/rrd4j/TimedataRrd4jImplTest.java @@ -1,5 +1,6 @@ package io.openems.edge.timedata.rrd4j; +import static io.openems.common.channel.PersistencePriority.MEDIUM; import static org.junit.Assert.assertEquals; import java.io.IOException; @@ -15,15 +16,12 @@ import org.rrd4j.core.RrdDef; import org.rrd4j.core.RrdMemoryBackendFactory; -import io.openems.common.channel.PersistencePriority; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyComponentManager; public class TimedataRrd4jImplTest { - private static final String COMPONENT_ID = "rrd4j0"; - @Test public void test() throws Exception { final var componentManager = new DummyComponentManager(); @@ -31,8 +29,8 @@ public void test() throws Exception { .addReference("workerFactory", new DummyRecordWorkerFactory(componentManager)) // .addReference("readHandler", new Rrd4jReadHandler()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // - .setPersistencePriority(PersistencePriority.MEDIUM) // + .setId("rrd4j0") // + .setPersistencePriority(MEDIUM) // .build()) // .next(new TestCase()) // ; diff --git a/io.openems.edge.timeofusetariff.api/.classpath b/io.openems.edge.timeofusetariff.api/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.timeofusetariff.api/.classpath +++ b/io.openems.edge.timeofusetariff.api/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.timeofusetariff.api/src/io/openems/edge/timeofusetariff/api/TimeOfUsePrices.java b/io.openems.edge.timeofusetariff.api/src/io/openems/edge/timeofusetariff/api/TimeOfUsePrices.java index a53b7f990ee..be7cdd5b5c3 100644 --- a/io.openems.edge.timeofusetariff.api/src/io/openems/edge/timeofusetariff/api/TimeOfUsePrices.java +++ b/io.openems.edge.timeofusetariff.api/src/io/openems/edge/timeofusetariff/api/TimeOfUsePrices.java @@ -1,14 +1,16 @@ package io.openems.edge.timeofusetariff.api; +import static com.google.common.collect.ImmutableSortedMap.toImmutableSortedMap; import static io.openems.common.utils.DateUtils.roundDownToQuarter; -import static java.util.Collections.emptySortedMap; -import static java.util.Collections.unmodifiableSortedMap; import java.time.ZonedDateTime; -import java.util.ArrayList; +import java.util.Comparator; +import java.util.Map.Entry; import java.util.SortedMap; -import java.util.TreeMap; -import java.util.stream.IntStream; + +import com.google.common.collect.ImmutableSortedMap; + +import io.openems.edge.common.type.QuarterlyValues; /** * Holds individual Time-of-Use prices - one value per 15 minutes. @@ -16,7 +18,7 @@ *

    * Values have unit '_meta/Currency'/MWh. */ -public class TimeOfUsePrices { +public class TimeOfUsePrices extends QuarterlyValues { /** * Holds an 'empty' {@link TimeOfUsePrices} object, i.e. `pricePerQuarter` map @@ -24,7 +26,7 @@ public class TimeOfUsePrices { * * @return an 'empty' {@link TimeOfUsePrices} object */ - public static final TimeOfUsePrices EMPTY_PRICES = new TimeOfUsePrices(emptySortedMap()); + public static final TimeOfUsePrices EMPTY_PRICES = new TimeOfUsePrices(ImmutableSortedMap.of()); /** * Constructs a {@link TimeOfUsePrices} object. @@ -33,11 +35,14 @@ public class TimeOfUsePrices { * Trailing `nulls` are cut out. * * @param time the base time of the prices, rounded down to 15 minutes - * @param values the quarterly price values. + * @param values the quarterly price values; no nulls * @return a {@link TimeOfUsePrices} object */ public static TimeOfUsePrices from(ZonedDateTime time, Double... values) { - return from(buildMap(time, values)); + if (values.length == 0) { + return EMPTY_PRICES; + } + return new TimeOfUsePrices(time, values); } /** @@ -55,8 +60,7 @@ public static TimeOfUsePrices from(ZonedDateTime time, Double... values) { * @param map a {@link SortedMap} of times and prices * @return a {@link TimeOfUsePrices} object */ - public static TimeOfUsePrices from(SortedMap map) { - map = postprocesMap(map); + public static TimeOfUsePrices from(ImmutableSortedMap map) { if (map.isEmpty()) { return EMPTY_PRICES; } @@ -76,16 +80,18 @@ public static TimeOfUsePrices from(SortedMap map) { * @return a {@link TimeOfUsePrices} object */ public static TimeOfUsePrices from(ZonedDateTime time, TimeOfUsePrices prices) { - if (time == null || prices == null || prices.isEmpty()) { + if (time == null || prices == null || prices.valuePerQuarter.isEmpty()) { // prices is EMPTY return EMPTY_PRICES; } - time = roundDownToQuarter(time); - if (prices.pricePerQuarter.firstKey().isEqual(time)) { + final var baseTime = roundDownToQuarter(time); + if (prices.valuePerQuarter.firstKey().isEqual(baseTime)) { // prices is still valid return prices; } - final var newMap = prices.pricePerQuarter.tailMap(time); + final var newMap = prices.valuePerQuarter.entrySet().stream() // + .filter(e -> !baseTime.isAfter(e.getKey())) // + .collect(toImmutableSortedMap(Comparator.naturalOrder(), Entry::getKey, Entry::getValue)); if (newMap.isEmpty()) { // new prices would be empty return EMPTY_PRICES; @@ -93,37 +99,12 @@ public static TimeOfUsePrices from(ZonedDateTime time, TimeOfUsePrices prices) { return new TimeOfUsePrices(newMap); } - /** - * Unmodifiable Map of Quarters (rounded to 15 minutes) and their respective - * prices. Values can be null. - */ - public final SortedMap pricePerQuarter; - - private TimeOfUsePrices(SortedMap pricePerQuarter) { - // We use unmodifiableSortedMap instead of the more expressive Guava - // ImmutableSortedMap, because we require `null` values. - this.pricePerQuarter = unmodifiableSortedMap(pricePerQuarter); + private TimeOfUsePrices(ImmutableSortedMap pricePerQuarter) { + super(pricePerQuarter); } - /** - * Returns {@code true} if this map contains no prices. - * - * @return {@code true} if this map contains no prices - */ - public boolean isEmpty() { - return this.pricePerQuarter.isEmpty(); - } - - /** - * Gets the first price in the map; or null if the map is empty. - * - * @return price or null - */ - public Double getFirst() { - if (this.pricePerQuarter.isEmpty()) { - return null; - } - return this.pricePerQuarter.get(this.pricePerQuarter.firstKey()); + private TimeOfUsePrices(ZonedDateTime time, Double... values) { + super(time, values); } /** @@ -132,38 +113,6 @@ public Double getFirst() { * @return prices array */ public Double[] asArray() { - return this.pricePerQuarter.values().toArray(Double[]::new); - } - - protected static SortedMap buildMap(ZonedDateTime time, Double... values) { - time = roundDownToQuarter(time); - var result = new TreeMap(); - for (var value : values) { - result.put(time, value); - time = time.plusMinutes(15); - } - return result; - } - - protected static SortedMap postprocesMap(SortedMap map) { - var values = new ArrayList<>(map.values()); - var lastNonNullIndex = IntStream.range(0, values.size()) // - .filter(i -> values.get(i) != null) // - .reduce((first, second) -> second) // - .orElse(-1); - - var result = map.entrySet().stream() // - .limit(lastNonNullIndex + 1) // remove trailing nulls - .collect(TreeMap::new, - (m, e) -> m.put(roundDownToQuarter(e.getKey()), e.getValue()), TreeMap::putAll); - - if (!result.isEmpty()) { - // Fill gaps - for (var time = result.firstKey(); time.isBefore(result.lastKey()); time = time.plusMinutes(15)) { - result.putIfAbsent(time, null); - } - } - - return result; + return super.asArray(Double[]::new); } } \ No newline at end of file diff --git a/io.openems.edge.timeofusetariff.api/src/io/openems/edge/timeofusetariff/api/utils/ExchangeRateApi.java b/io.openems.edge.timeofusetariff.api/src/io/openems/edge/timeofusetariff/api/utils/ExchangeRateApi.java index bdb5eeb4575..6f3c4b3e727 100644 --- a/io.openems.edge.timeofusetariff.api/src/io/openems/edge/timeofusetariff/api/utils/ExchangeRateApi.java +++ b/io.openems.edge.timeofusetariff.api/src/io/openems/edge/timeofusetariff/api/utils/ExchangeRateApi.java @@ -1,12 +1,17 @@ package io.openems.edge.timeofusetariff.api.utils; -import static io.openems.common.utils.JsonUtils.getAsDouble; -import static io.openems.common.utils.JsonUtils.getAsJsonObject; -import static io.openems.common.utils.JsonUtils.parseToJsonObject; +import static io.openems.common.utils.XmlUtils.getXmlRootDocument; +import static io.openems.common.utils.XmlUtils.stream; import java.io.IOException; +import javax.xml.parsers.ParserConfigurationException; + +import org.xml.sax.SAXException; + import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.exceptions.OpenemsException; +import io.openems.common.utils.XmlUtils; import io.openems.edge.common.currency.Currency; import okhttp3.OkHttpClient; import okhttp3.Request; @@ -17,31 +22,58 @@ *

    + * The method checks if the specified {@code preferredResolution} exists in the + * table's row keys. If it exists, the corresponding {@link Duration} is + * returned. Otherwise, the method selects the shortest duration from the + * available row keys. + * + * @param allPrices the {@link ImmutableTable}. + * @param preferredResolution the preferred resolution to look for, encapsulated + * in a {@link Resolution}. + * @return the matching {@link Duration} if the preferred resolution exists in + * the table's row keys, otherwise the shortest {@link Duration} from + * the row keys. + */ + protected static Duration getDuration(ImmutableTable allPrices, + Resolution preferredResolution) { + return allPrices.rowKeySet().stream() // + // match preferredResolution + .filter(e -> e.equals(preferredResolution.duration)) // + .findFirst() // + // otherwise get shortest duration + .orElseGet(() -> allPrices.rowKeySet().stream() // + .sorted() // + .findFirst()// + .get()); + } } diff --git a/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/MyConfig.java b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/MyConfig.java index 1bdbe072264..109aca8327b 100644 --- a/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/MyConfig.java +++ b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/MyConfig.java @@ -8,8 +8,8 @@ public class MyConfig extends AbstractComponentConfig implements Config { protected static class Builder { private String id; private String securityToken; - private String exchangerateAccesskey; private BiddingZone biddingZone; + private Resolution resolution; private Builder() { } @@ -24,13 +24,13 @@ public Builder setSecurityToken(String securityToken) { return this; } - public Builder setExchangerateAccesskey(String exchangerateAccesskey) { - this.exchangerateAccesskey = exchangerateAccesskey; + public Builder setBiddingZone(BiddingZone biddingZone) { + this.biddingZone = biddingZone; return this; } - public Builder setBiddingZone(BiddingZone biddingZone) { - this.biddingZone = biddingZone; + public Builder setResolution(Resolution resolution) { + this.resolution = resolution; return this; } @@ -61,13 +61,13 @@ public String securityToken() { } @Override - public String exchangerateAccesskey() { - return this.builder.exchangerateAccesskey; + public BiddingZone biddingZone() { + return this.builder.biddingZone; } @Override - public BiddingZone biddingZone() { - return this.builder.biddingZone; + public Resolution resolution() { + return this.builder.resolution; } } \ No newline at end of file diff --git a/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/ParserTest.java b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/ParserTest.java index 90fee5576a2..d570f9ee9f9 100644 --- a/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/ParserTest.java +++ b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/ParserTest.java @@ -1,11 +1,18 @@ package io.openems.edge.timeofusetariff.entsoe; +import static io.openems.common.test.TestUtils.createDummyClock; +import static io.openems.edge.timeofusetariff.entsoe.Utils.getDuration; import static io.openems.edge.timeofusetariff.entsoe.Utils.parseCurrency; import static io.openems.edge.timeofusetariff.entsoe.Utils.parsePrices; import static org.junit.Assert.assertEquals; +import java.time.Duration; +import java.time.ZonedDateTime; + import org.junit.Test; +import com.google.common.collect.ImmutableTable; + import io.openems.edge.common.currency.Currency; public class ParserTest { @@ -540,19 +547,287 @@ public class ParserTest { """; + private static String MISSING_DATA_AND_MULTIPLE_PERIODS_XML = """ + + + b29cfa5b47e54691a8cc110df00a748b + 1 + A44 + 10X1001A1001A450 + A32 + 10X1001A1001A450 + A33 + 2024-10-23T12:43:41Z + + 2024-10-22T22:00Z + 2024-10-24T22:00Z + + + 1 + A01 + A62 + 10Y1001A1001A46L + 10Y1001A1001A46L + A01 + EUR + MWH + A03 + + + 2024-10-23T22:00Z + 2024-10-24T22:00Z + + PT60M + + 1 + -1 + + + 3 + -0.8 + + + 4 + -0.52 + + + 5 + 0 + + + 6 + 0.84 + + + 7 + 15.47 + + + 8 + 27.59 + + + 9 + 31.86 + + + 10 + 36.97 + + + 11 + 32.96 + + + 12 + 31.14 + + + 13 + 29.92 + + + 14 + 29.55 + + + 15 + 29.71 + + + 16 + 29.76 + + + 17 + 29.98 + + + 18 + 59.65 + + + 19 + 79.41 + + + 20 + 39.97 + + + 21 + 29.93 + + + 22 + 27.68 + + + 23 + 24.95 + + + 24 + 13.27 + + + + + 2 + A01 + A62 + 10Y1001A1001A46L + 10Y1001A1001A46L + A01 + EUR + MWH + A03 + + + 2024-10-22T22:00Z + 2024-10-23T22:00Z + + PT60M + + 1 + 0 + + + 2 + -0.04 + + + 3 + -0.54 + + + 4 + -0.8 + + + 5 + -0.58 + + + 6 + 0 + + + 7 + 0.48 + + + 8 + 4.96 + + + 9 + 4.9 + + + 10 + 2.15 + + + 11 + 0.92 + + + 12 + 0.01 + + + 13 + -0.01 + + + 14 + -0.06 + + + 15 + -0.11 + + + 16 + -0.01 + + + 17 + 0 + + + 18 + 0.8 + + + 19 + 0.96 + + + 20 + 0.01 + + + 21 + 0 + + + 23 + -0.09 + + + 24 + -0.81 + + + + + """; + @Test public void testParsePrices() throws Exception { var currencyExchangeValue = 1.0; - { - var prices = parsePrices(XML, "PT15M", currencyExchangeValue).asArray(); - assertEquals(109.93, prices[0], 0.001); - assertEquals(65.07, prices[prices.length - 1], 0.001); - } - { - var prices = parsePrices(XML, "PT60M", currencyExchangeValue).asArray(); - assertEquals(84.15, prices[0], 0.001); - assertEquals(86.53, prices[prices.length - 1], 0.001); - } + + // Quarterly resolution. + var preferredResolution = Resolution.QUARTERLY; + var prices = parsePrices(XML, currencyExchangeValue, preferredResolution); + var startTime = prices.getFirstTime(); + assertEquals(109.93, prices.getFirst(), 0.001); + + var secondPrice = prices.getAt(startTime.plusMinutes(15)); + assertEquals(85.84, secondPrice, 0.001); + + var thirdPrice = prices.getAt(startTime.plusMinutes(30)); + assertEquals(65.09, thirdPrice, 0.001); + + // Last price + assertEquals(65.07, prices.getAt(prices.getLastTime()), 0.001); + + // Hourly resolution. + preferredResolution = Resolution.HOURLY; + prices = parsePrices(XML, currencyExchangeValue, preferredResolution); + assertEquals(84.15, prices.getFirst(), 0.001); + + secondPrice = prices.getAt(startTime.plusMinutes(15)); + assertEquals(84.15, secondPrice, 0.001); + + thirdPrice = prices.getAt(startTime.plusMinutes(60)); + assertEquals(74.3, thirdPrice, 0.001); + + // Last price + assertEquals(86.53, prices.getAt(prices.getLastTime()), 0.001); + } + + @Test + public void testParsePrices2() throws Exception { + var currencyExchangeValue = 1.0; + var preferredResolution = Resolution.QUARTERLY; + var prices = parsePrices(MISSING_DATA_AND_MULTIPLE_PERIODS_XML, currencyExchangeValue, preferredResolution); + assertEquals(192, prices.asArray().length); + var array = prices.asArray(); + assertEquals(array[96], array[97], 0.001); // Missing value check + assertEquals(array[0], 0, 0.001); // Making sure that Periods are sorted before prices are stored. } @Test @@ -560,4 +835,43 @@ public void testParseCurrency() throws Exception { var res = parseCurrency(XML); assertEquals(res, Currency.EUR.toString()); } + + @Test + public void testPreferredResolutionExists() { + final var clock = createDummyClock(); + // Create sample data + var table = ImmutableTable.builder() + .put(Duration.ofMinutes(15), ZonedDateTime.now(clock), 100.0) + .put(Duration.ofMinutes(15), ZonedDateTime.now(clock).plusMinutes(15), 200.0) + .put(Duration.ofMinutes(60), ZonedDateTime.now(clock), 300.0) // + .build(); + + // Preferred resolution + Resolution preferredResolution = Resolution.QUARTERLY; + + // Call the method + Duration result = getDuration(table, preferredResolution); + + // Assert + assertEquals("The preferred resolution should match.", Duration.ofMinutes(15), result); + } + + @Test + public void testPreferredResolutionDoesNotExist() { + // Create sample data + ImmutableTable table = ImmutableTable + .builder().put(Duration.ofMinutes(15), ZonedDateTime.now(), 100.0) + .put(Duration.ofMinutes(15), ZonedDateTime.now().plusMinutes(15), 200.0) + .put(Duration.ofMinutes(15), ZonedDateTime.now().plusMinutes(30), 300.0).build(); + + // Preferred resolution that does not exist + Resolution preferredResolution = Resolution.HOURLY; + + // Call the method + Duration result = getDuration(table, preferredResolution); + + // Assert + assertEquals("The shortest duration should be returned when preferred is unavailable.", Duration.ofMinutes(15), + result); + } } diff --git a/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/TouEntsoeTest.java b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/TouEntsoeTest.java index 2c3098097d2..b187da644b0 100644 --- a/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/TouEntsoeTest.java +++ b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/TouEntsoeTest.java @@ -10,8 +10,6 @@ public class TouEntsoeTest { - private static final String COMPONENT_ID = "tou0"; - @Test public void test() throws Exception { var entsoe = new TouEntsoeImpl(); @@ -21,10 +19,10 @@ public void test() throws Exception { .addReference("meta", dummyMeta) // .addReference("oem", new DummyOpenemsEdgeOem()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("tou0") // .setSecurityToken("") // - .setExchangerateAccesskey("") // .setBiddingZone(BiddingZone.GERMANY) // + .setResolution(Resolution.HOURLY) // .build()); } } diff --git a/io.openems.edge.timeofusetariff.groupe/.classpath b/io.openems.edge.timeofusetariff.groupe/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.timeofusetariff.groupe/.classpath +++ b/io.openems.edge.timeofusetariff.groupe/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.timeofusetariff.groupe/readme.adoc b/io.openems.edge.timeofusetariff.groupe/readme.adoc index 2d36804c53d..9c6552c4410 100644 --- a/io.openems.edge.timeofusetariff.groupe/readme.adoc +++ b/io.openems.edge.timeofusetariff.groupe/readme.adoc @@ -4,6 +4,6 @@ This implementation uses the Groupe-E platform to receive day-ahead quarterly pr Prices retrieved from Groupe-E are subsequently converted to the user's currency (defined in Core.Meta Component) using the Exchange Rates API. -For detailed information about the Exchange Rates API, please refer to: https://exchangerate.host/#/docs +For detailed information about the Exchange Rates API, please refer to: https://www.ecb.europa.eu/stats/policy_and_exchange_rates/euro_reference_exchange_rates/html/index.en.html https://github.com/OpenEMS/openems/tree/develop/io.openems.edge.timeofusetariff.groupe[Source Code icon:github[]] \ No newline at end of file diff --git a/io.openems.edge.timeofusetariff.groupe/src/io/openems/edge/timeofusetariff/groupe/Config.java b/io.openems.edge.timeofusetariff.groupe/src/io/openems/edge/timeofusetariff/groupe/Config.java index 4204fc86375..ea6cb86adfe 100644 --- a/io.openems.edge.timeofusetariff.groupe/src/io/openems/edge/timeofusetariff/groupe/Config.java +++ b/io.openems.edge.timeofusetariff.groupe/src/io/openems/edge/timeofusetariff/groupe/Config.java @@ -1,7 +1,6 @@ package io.openems.edge.timeofusetariff.groupe; import org.osgi.service.metatype.annotations.AttributeDefinition; -import org.osgi.service.metatype.annotations.AttributeType; import org.osgi.service.metatype.annotations.ObjectClassDefinition; @ObjectClassDefinition(// @@ -17,9 +16,6 @@ @AttributeDefinition(name = "Is enabled?", description = "Is this Component enabled?") boolean enabled() default true; - - @AttributeDefinition(name = "Exchangerate.host API Access Key", description = "Access key for Exchangerate.host: Please register at https://exchangerate.host/ to get your personal access key", type = AttributeType.PASSWORD) - String exchangerateAccesskey() default ""; String webconsole_configurationFactory_nameHint() default "Time-Of-Use Tariff GroupeE [{id}]"; } diff --git a/io.openems.edge.timeofusetariff.groupe/src/io/openems/edge/timeofusetariff/groupe/TimeOfUseTariffGroupeImpl.java b/io.openems.edge.timeofusetariff.groupe/src/io/openems/edge/timeofusetariff/groupe/TimeOfUseTariffGroupeImpl.java index b05f68bc2c2..3a644d09fd9 100644 --- a/io.openems.edge.timeofusetariff.groupe/src/io/openems/edge/timeofusetariff/groupe/TimeOfUseTariffGroupeImpl.java +++ b/io.openems.edge.timeofusetariff.groupe/src/io/openems/edge/timeofusetariff/groupe/TimeOfUseTariffGroupeImpl.java @@ -3,17 +3,15 @@ import static io.openems.common.utils.JsonUtils.getAsDouble; import static io.openems.common.utils.JsonUtils.getAsString; import static io.openems.common.utils.JsonUtils.parseToJsonArray; -import static io.openems.common.utils.StringUtils.definedOrElse; +import static io.openems.edge.timeofusetariff.api.utils.ExchangeRateApi.getExchangeRateOrElse; import static io.openems.edge.timeofusetariff.api.utils.TimeOfUseTariffUtils.generateDebugLog; import static java.util.Collections.emptyMap; -import java.io.IOException; import java.time.Clock; import java.time.Duration; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; -import java.util.TreeMap; import java.util.concurrent.atomic.AtomicReference; import java.util.function.BiConsumer; @@ -27,9 +25,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.collect.ImmutableSortedMap; + import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.common.exceptions.OpenemsException; -import io.openems.common.oem.OpenemsEdgeOem; import io.openems.common.timedata.DurationUnit; import io.openems.edge.bridge.http.api.BridgeHttp; import io.openems.edge.bridge.http.api.BridgeHttp.Endpoint; @@ -47,7 +45,6 @@ import io.openems.edge.common.meta.Meta; import io.openems.edge.timeofusetariff.api.TimeOfUsePrices; import io.openems.edge.timeofusetariff.api.TimeOfUseTariff; -import io.openems.edge.timeofusetariff.api.utils.ExchangeRateApi; @Designate(ocd = Config.class, factory = true) @Component(// @@ -63,15 +60,11 @@ public class TimeOfUseTariffGroupeImpl extends AbstractOpenemsComponent private final Logger log = LoggerFactory.getLogger(TimeOfUseTariffGroupeImpl.class); private final AtomicReference prices = new AtomicReference<>(TimeOfUsePrices.EMPTY_PRICES); - private String exchangerateAccesskey = null; @Reference private BridgeHttpFactory httpBridgeFactory; private BridgeHttp httpBridge; - @Reference - private OpenemsEdgeOem oem; - @Reference private Meta meta; @@ -86,7 +79,7 @@ public TimeOfUseTariffGroupeImpl() { } private final BiConsumer, Value> onCurrencyChange = (a, b) -> { - this.httpBridge = this.httpBridgeFactory.get(); + this.httpBridge.removeAllTimeEndpoints(); this.httpBridge.subscribeTime(new GroupeDelayTimeProvider(this.componentManager.getClock()), // this::createGroupeEndpoint, // this::handleEndpointResponse, // @@ -101,12 +94,6 @@ private void activate(ComponentContext context, Config config) { return; } - this.exchangerateAccesskey = definedOrElse(config.exchangerateAccesskey(), this.oem.getExchangeRateAccesskey()); - if (this.exchangerateAccesskey == null) { - this.logError(this.log, "Please configure personal Access key to access Exchange rate host API"); - return; - } - // React on updates to Currency. this.meta.getCurrencyChannel().onChange(this.onCurrencyChange); @@ -167,19 +154,12 @@ public Delay onSuccessRunDelay(HttpResponse result) { } } - private void handleEndpointResponse(HttpResponse response) throws OpenemsNamedException, IOException { + private void handleEndpointResponse(HttpResponse response) throws OpenemsNamedException { this.channel(TimeOfUseTariffGroupe.ChannelId.HTTP_STATUS_CODE).setNextValue(response.status().code()); final var groupeCurrency = Currency.CHF.name(); // Swiss Franc final var globalCurrency = this.meta.getCurrency(); - final var exchangerateAccesskey = this.exchangerateAccesskey; - if (globalCurrency == Currency.UNDEFINED) { - throw new OpenemsException("Global Currency is UNDEFINED. Please configure it in Core.Meta component"); - } - - final var exchangeRate = globalCurrency.name().equals(groupeCurrency) // - ? 1. // No need to fetch exchange rate from API. - : ExchangeRateApi.getExchangeRate(exchangerateAccesskey, groupeCurrency, globalCurrency); + final double exchangeRate = getExchangeRateOrElse(groupeCurrency, globalCurrency, 1.); // Parse the response for the prices this.prices.set(parsePrices(response.data(), exchangeRate)); @@ -213,7 +193,7 @@ public TimeOfUsePrices getPrices() { * JSON data. */ public static TimeOfUsePrices parsePrices(String jsonData, double exchangeRate) throws OpenemsNamedException { - var result = new TreeMap(); + var result = ImmutableSortedMap.naturalOrder(); var data = parseToJsonArray(jsonData); for (var element : data) { var priceString = "vario_plus"; @@ -229,7 +209,7 @@ public static TimeOfUsePrices parsePrices(String jsonData, double exchangeRate) // Adding the values in the Map. result.put(startTimeStamp, marketPrice); } - return TimeOfUsePrices.from(result); + return TimeOfUsePrices.from(result.build()); } @Override diff --git a/io.openems.edge.timeofusetariff.groupe/test/io/openems/edge/timeofusetariff/groupe/MyConfig.java b/io.openems.edge.timeofusetariff.groupe/test/io/openems/edge/timeofusetariff/groupe/MyConfig.java index 13fa3e31c71..6fcaa6cc93c 100644 --- a/io.openems.edge.timeofusetariff.groupe/test/io/openems/edge/timeofusetariff/groupe/MyConfig.java +++ b/io.openems.edge.timeofusetariff.groupe/test/io/openems/edge/timeofusetariff/groupe/MyConfig.java @@ -7,7 +7,6 @@ public class MyConfig extends AbstractComponentConfig implements Config { public static class Builder { private String id; - private String exchangerateAccesskey; private Builder() { } @@ -17,11 +16,6 @@ public Builder setId(String id) { return this; } - public Builder setExchangerateAccesskey(String exchangerateAccesskey) { - this.exchangerateAccesskey = exchangerateAccesskey; - return this; - } - public MyConfig build() { return new MyConfig(this); } @@ -43,9 +37,4 @@ private MyConfig(Builder builder) { this.builder = builder; } - @Override - public String exchangerateAccesskey() { - return this.builder.exchangerateAccesskey; - } - } \ No newline at end of file diff --git a/io.openems.edge.timeofusetariff.groupe/test/io/openems/edge/timeofusetariff/groupe/TimeOfUseTariffGroupeImplTest.java b/io.openems.edge.timeofusetariff.groupe/test/io/openems/edge/timeofusetariff/groupe/TimeOfUseTariffGroupeImplTest.java index 8bdb01e4e1a..e0e116e20df 100644 --- a/io.openems.edge.timeofusetariff.groupe/test/io/openems/edge/timeofusetariff/groupe/TimeOfUseTariffGroupeImplTest.java +++ b/io.openems.edge.timeofusetariff.groupe/test/io/openems/edge/timeofusetariff/groupe/TimeOfUseTariffGroupeImplTest.java @@ -1,19 +1,15 @@ package io.openems.edge.timeofusetariff.groupe; +import static io.openems.common.test.TestUtils.createDummyClock; import static io.openems.edge.common.currency.Currency.CHF; import static io.openems.edge.timeofusetariff.groupe.TimeOfUseTariffGroupeImpl.parsePrices; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThrows; -import java.time.Instant; -import java.time.ZoneOffset; - import org.junit.Test; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.common.oem.DummyOpenemsEdgeOem; -import io.openems.common.test.TimeLeapClock; import io.openems.edge.bridge.http.dummy.DummyBridgeHttpFactory; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyComponentManager; @@ -21,7 +17,6 @@ public class TimeOfUseTariffGroupeImplTest { - private static final String CTRL_ID = "ctrl0"; private static final double GROUPE_E_EXCHANGE_RATE = 1; private static final String PRICE_RESULT_STRING = """ @@ -407,19 +402,16 @@ public class TimeOfUseTariffGroupeImplTest { @Test public void test() throws Exception { - final TimeLeapClock clock = new TimeLeapClock(Instant.parse("2020-01-01T01:00:00.00Z"), ZoneOffset.UTC); - final DummyComponentManager cm = new DummyComponentManager(clock); + final var clock = createDummyClock(); var groupe = new TimeOfUseTariffGroupeImpl(); var dummyMeta = new DummyMeta("foo0") // .withCurrency(CHF); new ComponentTest(groupe) // .addReference("httpBridgeFactory", DummyBridgeHttpFactory.ofDummyBridge()) // .addReference("meta", dummyMeta) // - .addReference("oem", new DummyOpenemsEdgeOem()) // - .addReference("componentManager", cm) // + .addReference("componentManager", new DummyComponentManager(clock)) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setExchangerateAccesskey("") // + .setId("ctrl0") // .build()) // ; } diff --git a/io.openems.edge.timeofusetariff.hassfurt/.classpath b/io.openems.edge.timeofusetariff.hassfurt/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.timeofusetariff.hassfurt/.classpath +++ b/io.openems.edge.timeofusetariff.hassfurt/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.timeofusetariff.hassfurt/src/io/openems/edge/timeofusetariff/hassfurt/Config.java b/io.openems.edge.timeofusetariff.hassfurt/src/io/openems/edge/timeofusetariff/hassfurt/Config.java index 1875b929bd1..79612fa199e 100644 --- a/io.openems.edge.timeofusetariff.hassfurt/src/io/openems/edge/timeofusetariff/hassfurt/Config.java +++ b/io.openems.edge.timeofusetariff.hassfurt/src/io/openems/edge/timeofusetariff/hassfurt/Config.java @@ -16,7 +16,7 @@ @AttributeDefinition(name = "Is enabled?", description = "Is this Component enabled?") boolean enabled() default true; - + @AttributeDefinition(name = "Tariff Type", description = "Tariff type that the customer has subscribed to") TariffType tariffType() default TariffType.STROM_FLEX; diff --git a/io.openems.edge.timeofusetariff.hassfurt/src/io/openems/edge/timeofusetariff/hassfurt/TariffType.java b/io.openems.edge.timeofusetariff.hassfurt/src/io/openems/edge/timeofusetariff/hassfurt/TariffType.java index 77a9e13618a..92ca11309ba 100644 --- a/io.openems.edge.timeofusetariff.hassfurt/src/io/openems/edge/timeofusetariff/hassfurt/TariffType.java +++ b/io.openems.edge.timeofusetariff.hassfurt/src/io/openems/edge/timeofusetariff/hassfurt/TariffType.java @@ -5,7 +5,7 @@ public enum TariffType { * haStrom Flex. */ STROM_FLEX, // - + /** * haStrom Flex Pro. */ diff --git a/io.openems.edge.timeofusetariff.hassfurt/src/io/openems/edge/timeofusetariff/hassfurt/TimeOfUseTariffHassfurtImpl.java b/io.openems.edge.timeofusetariff.hassfurt/src/io/openems/edge/timeofusetariff/hassfurt/TimeOfUseTariffHassfurtImpl.java index b03228a4117..322f2081ff4 100644 --- a/io.openems.edge.timeofusetariff.hassfurt/src/io/openems/edge/timeofusetariff/hassfurt/TimeOfUseTariffHassfurtImpl.java +++ b/io.openems.edge.timeofusetariff.hassfurt/src/io/openems/edge/timeofusetariff/hassfurt/TimeOfUseTariffHassfurtImpl.java @@ -14,7 +14,6 @@ import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; -import java.util.TreeMap; import java.util.concurrent.atomic.AtomicReference; import org.osgi.service.component.ComponentContext; @@ -27,6 +26,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.collect.ImmutableSortedMap; + import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.edge.bridge.http.api.BridgeHttp; import io.openems.edge.bridge.http.api.BridgeHttp.Endpoint; @@ -211,7 +212,7 @@ public TimeOfUsePrices getPrices() { * JSON data. */ public static TimeOfUsePrices parsePrices(String jsonData, TariffType tariffType) throws OpenemsNamedException { - var result = new TreeMap(); + var result = ImmutableSortedMap.naturalOrder(); final var data = getAsJsonArray(parseToJsonObject(jsonData), "data"); final var priceString = switch (tariffType) { @@ -237,7 +238,7 @@ public static TimeOfUsePrices parsePrices(String jsonData, TariffType tariffType result.put(startTimeStamp.plusMinutes(30), marketPrice); result.put(startTimeStamp.plusMinutes(45), marketPrice); } - return TimeOfUsePrices.from(result); + return TimeOfUsePrices.from(result.build()); } @Override diff --git a/io.openems.edge.timeofusetariff.hassfurt/test/io/openems/edge/timeofusetariff/hassfurt/TimeOfUseTariffHassfurtImplTest.java b/io.openems.edge.timeofusetariff.hassfurt/test/io/openems/edge/timeofusetariff/hassfurt/TimeOfUseTariffHassfurtImplTest.java index 2f363b27876..52797a738b5 100644 --- a/io.openems.edge.timeofusetariff.hassfurt/test/io/openems/edge/timeofusetariff/hassfurt/TimeOfUseTariffHassfurtImplTest.java +++ b/io.openems.edge.timeofusetariff.hassfurt/test/io/openems/edge/timeofusetariff/hassfurt/TimeOfUseTariffHassfurtImplTest.java @@ -1,24 +1,20 @@ package io.openems.edge.timeofusetariff.hassfurt; +import static io.openems.common.test.TestUtils.createDummyClock; import static io.openems.edge.timeofusetariff.hassfurt.TimeOfUseTariffHassfurtImpl.parsePrices; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThrows; -import java.time.Instant; -import java.time.ZoneOffset; - import org.junit.Test; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.common.test.TimeLeapClock; import io.openems.edge.bridge.http.dummy.DummyBridgeHttpFactory; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyComponentManager; public class TimeOfUseTariffHassfurtImplTest { - private static final String CTRL_ID = "ctrl0"; private static final String STROM_FLEX_PRO_STRING = """ { "object": "list", @@ -192,14 +188,12 @@ public class TimeOfUseTariffHassfurtImplTest { @Test public void test() throws Exception { - final TimeLeapClock clock = new TimeLeapClock(Instant.parse("2020-01-01T01:00:00.00Z"), ZoneOffset.UTC); - final DummyComponentManager cm = new DummyComponentManager(clock); - var hassfurt = new TimeOfUseTariffHassfurtImpl(); - new ComponentTest(hassfurt) // + final var clock = createDummyClock(); + new ComponentTest(new TimeOfUseTariffHassfurtImpl()) // .addReference("httpBridgeFactory", DummyBridgeHttpFactory.ofDummyBridge()) // - .addReference("componentManager", cm) // + .addReference("componentManager", new DummyComponentManager(clock)) // .activate(MyConfig.create() // - .setId(CTRL_ID) // + .setId("ctrl0") // .setTariffType(TariffType.STROM_FLEX) // .build()) // ; diff --git a/io.openems.edge.timeofusetariff.rabotcharge/.classpath b/io.openems.edge.timeofusetariff.rabotcharge/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.timeofusetariff.rabotcharge/.classpath +++ b/io.openems.edge.timeofusetariff.rabotcharge/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.timeofusetariff.rabotcharge/src/io/openems/edge/timeofusetariff/rabotcharge/Config.java b/io.openems.edge.timeofusetariff.rabotcharge/src/io/openems/edge/timeofusetariff/rabotcharge/Config.java index 3074a7be4a0..f46bf9ebd61 100644 --- a/io.openems.edge.timeofusetariff.rabotcharge/src/io/openems/edge/timeofusetariff/rabotcharge/Config.java +++ b/io.openems.edge.timeofusetariff.rabotcharge/src/io/openems/edge/timeofusetariff/rabotcharge/Config.java @@ -1,7 +1,6 @@ package io.openems.edge.timeofusetariff.rabotcharge; import org.osgi.service.metatype.annotations.AttributeDefinition; -import org.osgi.service.metatype.annotations.AttributeType; import org.osgi.service.metatype.annotations.ObjectClassDefinition; @ObjectClassDefinition(// @@ -17,9 +16,15 @@ @AttributeDefinition(name = "Is enabled?", description = "Is this Component enabled?") boolean enabled() default true; - - @AttributeDefinition(name = "Access Token", description = "Access token for rabot.charge API", type = AttributeType.PASSWORD) - String accessToken() default ""; + + @AttributeDefinition(name = "ZIP Code", description = "German ZIP Code of the customer location") + String zipcode(); + + @AttributeDefinition(name = "Client Id", description = "Client Id of the client registration at rabot.charge") + String clientId(); + + @AttributeDefinition(name = "Client Secret", description = "Client Secret of the client registration at rabot.charge") + String clientSecret(); String webconsole_configurationFactory_nameHint() default "Time-Of-Use Tariff rabot.charge [{id}]"; } diff --git a/io.openems.edge.timeofusetariff.rabotcharge/src/io/openems/edge/timeofusetariff/rabotcharge/TimeOfUseTariffRabotCharge.java b/io.openems.edge.timeofusetariff.rabotcharge/src/io/openems/edge/timeofusetariff/rabotcharge/TimeOfUseTariffRabotCharge.java index cfb8faa495e..0dbb8159770 100644 --- a/io.openems.edge.timeofusetariff.rabotcharge/src/io/openems/edge/timeofusetariff/rabotcharge/TimeOfUseTariffRabotCharge.java +++ b/io.openems.edge.timeofusetariff.rabotcharge/src/io/openems/edge/timeofusetariff/rabotcharge/TimeOfUseTariffRabotCharge.java @@ -1,7 +1,10 @@ package io.openems.edge.timeofusetariff.rabotcharge; +import io.openems.common.channel.Level; import io.openems.common.types.OpenemsType; +import io.openems.edge.common.channel.Channel; import io.openems.edge.common.channel.Doc; +import io.openems.edge.common.channel.value.Value; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.timeofusetariff.api.TimeOfUseTariff; @@ -9,7 +12,15 @@ public interface TimeOfUseTariffRabotCharge extends TimeOfUseTariff, OpenemsComp public enum ChannelId implements io.openems.edge.common.channel.ChannelId { HTTP_STATUS_CODE(Doc.of(OpenemsType.INTEGER)// - .text("Displays the HTTP status code"))// + .translationKey(TimeOfUseTariffRabotCharge.class, "httpStatusCode")), // + STATUS_AUTHENTICATION_FAILED(Doc.of(Level.WARNING) // + .translationKey(TimeOfUseTariffRabotCharge.class, "statusAuthenticationFailed")), // + /** + * Should never happen. Only happens if the request has missing fields or wrong + * format of timestamps. + */ + STATUS_BAD_REQUEST(Doc.of(Level.FAULT) // + .translationKey(TimeOfUseTariffRabotCharge.class, "statusBadRequest")), // ; private final Doc doc; @@ -23,4 +34,89 @@ public Doc doc() { return this.doc; } } + + /** + * Internal method to set the 'nextValue' on {@link ChannelId#HTTP_STATUS_CODE} + * Channel. + * + * @param value the next value + */ + public default void _setHttpStatusCode(int value) { + this.getHttpStatusCodeChannel().setNextValue(value); + } + + /** + * Gets the HttpStatusCode value. See {@link ChannelId#HTTP_STATUS_CODE}. + * + * @return the Channel {@link Value} + */ + public default Value getHttpStatusCode() { + return this.getHttpStatusCodeChannel().value(); + } + + /** + * Gets the Channel for {@link ChannelId#HTTP_STATUS_CODE}. + * + * @return the Channel + */ + public default Channel getHttpStatusCodeChannel() { + return this.channel(ChannelId.HTTP_STATUS_CODE); + } + + /** + * Internal method to set the 'nextValue' on + * {@link ChannelId#STATUS_AUTHENTICATION_FAILED} Channel. + * + * @param value the next value + */ + public default void _setStatusAuthenticationFailed(boolean value) { + this.getStatusAuthenticationFailedChannel().setNextValue(value); + } + + /** + * Gets the status value. See {@link ChannelId#STATUS_AUTHENTICATION_FAILED}. + * + * @return the Channel {@link Value} + */ + public default Value getStatusAuthenticationFailed() { + return this.getStatusAuthenticationFailedChannel().value(); + } + + /** + * Gets the Channel for {@link ChannelId#STATUS_AUTHENTICATION_FAILED}. + * + * @return the Channel + */ + public default Channel getStatusAuthenticationFailedChannel() { + return this.channel(ChannelId.STATUS_AUTHENTICATION_FAILED); + } + + /** + * Internal method to set the 'nextValue' on + * {@link ChannelId#STATUS_BAD_REQUEST} Channel. + * + * @param value the next value + */ + public default void _setStatusBadRequest(boolean value) { + this.getStatusBadRequestChannel().setNextValue(value); + } + + /** + * Gets the status value. See {@link ChannelId#STATUS_BAD_REQUEST}. + * + * @return the Channel {@link Value} + */ + public default Value getStatusBadRequest() { + return this.getStatusBadRequestChannel().value(); + } + + /** + * Gets the Channel for {@link ChannelId#STATUS_BAD_REQUEST}. + * + * @return the Channel + */ + public default Channel getStatusBadRequestChannel() { + return this.channel(ChannelId.STATUS_BAD_REQUEST); + } + } diff --git a/io.openems.edge.timeofusetariff.rabotcharge/src/io/openems/edge/timeofusetariff/rabotcharge/TimeOfUseTariffRabotChargeImpl.java b/io.openems.edge.timeofusetariff.rabotcharge/src/io/openems/edge/timeofusetariff/rabotcharge/TimeOfUseTariffRabotChargeImpl.java index 42d223b88b9..37afa08b64d 100644 --- a/io.openems.edge.timeofusetariff.rabotcharge/src/io/openems/edge/timeofusetariff/rabotcharge/TimeOfUseTariffRabotChargeImpl.java +++ b/io.openems.edge.timeofusetariff.rabotcharge/src/io/openems/edge/timeofusetariff/rabotcharge/TimeOfUseTariffRabotChargeImpl.java @@ -11,7 +11,7 @@ import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; import java.util.Map; -import java.util.TreeMap; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicReference; import org.osgi.service.component.ComponentContext; @@ -24,15 +24,23 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.collect.ImmutableSortedMap; + import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.common.utils.JsonUtils; +import io.openems.common.oem.OpenemsEdgeOem; +import io.openems.common.oem.OpenemsEdgeOem.OAuthClientRegistration; +import io.openems.common.timedata.DurationUnit; +import io.openems.common.types.HttpStatus; import io.openems.edge.bridge.http.api.BridgeHttp; import io.openems.edge.bridge.http.api.BridgeHttp.Endpoint; import io.openems.edge.bridge.http.api.BridgeHttpFactory; import io.openems.edge.bridge.http.api.HttpError; import io.openems.edge.bridge.http.api.HttpMethod; import io.openems.edge.bridge.http.api.HttpResponse; +import io.openems.edge.bridge.http.api.UrlBuilder; +import io.openems.edge.bridge.http.time.DefaultDelayTimeProvider; import io.openems.edge.bridge.http.time.DelayTimeProvider; +import io.openems.edge.bridge.http.time.DelayTimeProvider.Delay; import io.openems.edge.bridge.http.time.DelayTimeProviderChain; import io.openems.edge.common.component.AbstractOpenemsComponent; import io.openems.edge.common.component.ComponentManager; @@ -50,17 +58,26 @@ public class TimeOfUseTariffRabotChargeImpl extends AbstractOpenemsComponent implements TimeOfUseTariff, OpenemsComponent, TimeOfUseTariffRabotCharge { - private static final String RABOT_CHARGE_API_URL = "https://api.rabot-charge.de/api/day-ahead-prices-limited"; - private static final int API_EXECUTE_HOUR = 14; + protected static final UrlBuilder RABOT_CHARGE_TOKEN_URL = UrlBuilder + .parse("https://auth.rabot-charge.de/connect/token"); + + private static final UrlBuilder RABOT_CHARGE_BASE_URL = UrlBuilder.parse("https://api.rabot-charge.de"); + protected static final UrlBuilder RABOT_CHARGE_PRIZE_COMPONENT_URL = RABOT_CHARGE_BASE_URL + .withPath("/hems/v1/price-components"); + protected static final UrlBuilder RABOT_CHARGE_API_URL = RABOT_CHARGE_BASE_URL + .withPath("/hems/v1/day-ahead-prices/limited"); + private static final int INTERNAL_ERROR = -1; // parsing, handle exception... private final Logger log = LoggerFactory.getLogger(TimeOfUseTariffRabotChargeImpl.class); private final AtomicReference prices = new AtomicReference<>(TimeOfUsePrices.EMPTY_PRICES); - private String accessToken; @Reference private Meta meta; + @Reference + private OpenemsEdgeOem oem; + @Reference private ComponentManager componentManager; @@ -68,6 +85,9 @@ public class TimeOfUseTariffRabotChargeImpl extends AbstractOpenemsComponent private BridgeHttpFactory httpBridgeFactory; private BridgeHttp httpBridge; + private OAuthClientRegistration clientRegistration; + private String zipcode; + public TimeOfUseTariffRabotChargeImpl() { super(// OpenemsComponent.ChannelId.values(), // @@ -83,19 +103,97 @@ private void activate(ComponentContext context, Config config) { return; } - if (config.accessToken() == null || config.accessToken().isEmpty()) { + if (config.zipcode() == null || config.zipcode().isEmpty()) { return; } - this.accessToken = config.accessToken(); + OAuthClientRegistration clientRegistration; + if (config.clientId() != null && !config.clientId().isBlank() // + && config.clientSecret() != null && !config.clientSecret().isBlank()) { + clientRegistration = new OAuthClientRegistration(config.clientId(), config.clientSecret()); + } else { + clientRegistration = this.oem.getRabotChargeCredentials(); + } + + if (clientRegistration == null) { + return; + } + this.clientRegistration = clientRegistration; + + this.zipcode = config.zipcode(); + this.httpBridge = this.httpBridgeFactory.get(); - this.httpBridge.subscribeTime(new RabotChargeDelayTimeProvider(this.componentManager.getClock()), // - this.createRabotChargeEndpoint(), // - this::handleEndpointResponse, // - this::handleEndpointError); + + this.scheduleRequest(); + } + + @Override + @Deactivate + protected void deactivate() { + super.deactivate(); + this.httpBridgeFactory.unget(this.httpBridge); + } + + private CompletableFuture refreshToken() { + final var endpoint = new Endpoint(RABOT_CHARGE_TOKEN_URL.toEncodedString(), HttpMethod.POST, + BridgeHttp.DEFAULT_CONNECT_TIMEOUT, BridgeHttp.DEFAULT_READ_TIMEOUT, "grant_type=client_credentials" // + + "&scope=api:hems" // + + "&client_id=" + this.clientRegistration.clientId() // + + "&client_secret=" + this.clientRegistration.clientSecret(), + Map.of("Content-Type", "application/x-www-form-urlencoded")); + + final var tokenFuture = new CompletableFuture(); + this.httpBridge.subscribeJsonTime(new DefaultDelayTimeProvider(() -> Delay.immediate(), t -> Delay.infinite(), + error -> Delay.of(Duration.ofMinutes(30))), endpoint, response -> { + final var token = response.data().getAsJsonObject().get("access_token").getAsString(); + this._setStatusAuthenticationFailed(false); + tokenFuture.complete(token); + }, error -> { + this.log.error("Unable to get token", error); + this._setHttpStatusCode( + error instanceof HttpError.ResponseError r ? r.status.code() : INTERNAL_ERROR); + this._setStatusAuthenticationFailed(true); + }); + + return tokenFuture; + } + + private void scheduleRequest() { + this.refreshToken().thenAccept(token -> { + this.httpBridge + .requestJson(new Endpoint( + RABOT_CHARGE_PRIZE_COMPONENT_URL.withQueryParam("location.postCode", this.zipcode) + .toEncodedString(), + HttpMethod.GET, BridgeHttp.DEFAULT_CONNECT_TIMEOUT, BridgeHttp.DEFAULT_READ_TIMEOUT, null, + this.buildRequestHeaders(token))) + .thenApply(response -> { + final var object = response.data().getAsJsonObject(); + return new PriceComponents(// + object.get("taxAndFeeKwHPrice").getAsDouble(), // + object.get("gridFeeKwHPrice").getAsDouble(), // + object.get("gridFeeFixed").getAsDouble() // + ); + }).whenComplete((priceComponent, error) -> { + if (priceComponent == null) { + this.log.error("Unable to get price components", error); + this._setHttpStatusCode( + error instanceof HttpError.ResponseError r ? r.status.code() : INTERNAL_ERROR); + return; + } + this._setHttpStatusCode(HttpStatus.OK.code()); + + this.httpBridge.subscribeTime( + new RabotChargeDelayTimeProvider(this.componentManager.getClock()), // + this.createRabotChargeEndpoint(token), // + // pass priceComponent + response -> this.handleEndpointResponse(response, priceComponent), + this::handleEndpointError); + }); + + }); } - public static class RabotChargeDelayTimeProvider implements DelayTimeProvider { + public class RabotChargeDelayTimeProvider implements DelayTimeProvider { private final Clock clock; @@ -111,22 +209,19 @@ public Delay onFirstRunDelay() { @Override public Delay onSuccessRunDelay(HttpResponse result) { - var now = ZonedDateTime.now(this.clock).truncatedTo(ChronoUnit.HOURS); - ZonedDateTime nextRun; - - if (now.getHour() < API_EXECUTE_HOUR) { - nextRun = now.withHour(API_EXECUTE_HOUR); - } else { - nextRun = now.plusDays(1).withHour(API_EXECUTE_HOUR); - } - - return DelayTimeProviderChain.fixedDelay(Duration.between(now, nextRun)) + return DelayTimeProviderChain.fixedAtEveryFull(this.clock, DurationUnit.ofDays(1)) // .plusRandomDelay(60, ChronoUnit.SECONDS) // .getDelay(); } @Override public Delay onErrorRunDelay(HttpError error) { + if (error instanceof HttpError.ResponseError r && r.status.equals(HttpStatus.UNAUTHORIZED)) { + // reschedule after authenticated + TimeOfUseTariffRabotChargeImpl.this.scheduleRequest(); + return Delay.infinite(); + } + return DelayTimeProviderChain.fixedDelay(Duration.ofHours(1))// .plusRandomDelay(60, ChronoUnit.SECONDS) // .getDelay(); @@ -134,53 +229,46 @@ public Delay onErrorRunDelay(HttpError error) { } - private Endpoint createRabotChargeEndpoint() { - - return new Endpoint(RABOT_CHARGE_API_URL, // - HttpMethod.POST, // + private Endpoint createRabotChargeEndpoint(String accessToken) { + return new Endpoint(RABOT_CHARGE_API_URL.toEncodedString(), // + HttpMethod.GET, // BridgeHttp.DEFAULT_CONNECT_TIMEOUT, // BridgeHttp.DEFAULT_READ_TIMEOUT, // - this.buildRequestBody(), // - this.buildRequestHeaders()); - } - - private String buildRequestBody() { - return JsonUtils.buildJsonObject() // - .build().toString(); + null, // + this.buildRequestHeaders(accessToken)); } - private Map buildRequestHeaders() { + private Map buildRequestHeaders(String accessToken) { return Map.of(// - "Authorization", "Bearer " + this.accessToken, // - "Content-Type", "application/json" // + "Authorization", "Bearer " + accessToken, // + "Accept", "application/json" // ); } - private void handleEndpointResponse(HttpResponse response) throws OpenemsNamedException { + private void handleEndpointResponse(HttpResponse response, PriceComponents priceComponent) + throws OpenemsNamedException { this.channel(TimeOfUseTariffRabotCharge.ChannelId.HTTP_STATUS_CODE).setNextValue(response.status().code()); + this._setStatusBadRequest(false); + this._setStatusAuthenticationFailed(false); - // Parse the response for the prices - this.prices.set(parsePrices(response.data())); + // Parse the response for the prices, passing the priceComponent. + this.prices.set(parsePrices(response.data(), priceComponent)); } private void handleEndpointError(HttpError error) { var httpStatusCode = INTERNAL_ERROR; if (error instanceof HttpError.ResponseError re) { httpStatusCode = re.status.code(); + + this._setStatusAuthenticationFailed(httpStatusCode == HttpStatus.UNAUTHORIZED.code()); + this._setStatusBadRequest(httpStatusCode == HttpStatus.BAD_REQUEST.code()); } this.channel(TimeOfUseTariffRabotCharge.ChannelId.HTTP_STATUS_CODE).setNextValue(httpStatusCode); this.log.error(error.getMessage(), error); } - @Override - @Deactivate - protected void deactivate() { - super.deactivate(); - this.httpBridgeFactory.unget(this.httpBridge); - } - @Override public TimeOfUsePrices getPrices() { return TimeOfUsePrices.from(ZonedDateTime.now(this.componentManager.getClock()), this.prices.get()); @@ -189,34 +277,48 @@ public TimeOfUsePrices getPrices() { /** * Parse the JSON to {@link TimeOfUsePrices}. * - * @param jsonData the JSON + * @param jsonData the JSON + * @param priceComponent the {@link PriceComponents} * @return the {@link TimeOfUsePrices} * @throws OpenemsNamedException on error */ - public static TimeOfUsePrices parsePrices(String jsonData) throws OpenemsNamedException { - var result = new TreeMap(); + public static TimeOfUsePrices parsePrices(String jsonData, PriceComponents priceComponent) + throws OpenemsNamedException { + var result = ImmutableSortedMap.naturalOrder(); var data = getAsJsonArray(parseToJsonObject(jsonData), "records"); for (var element : data) { // Cent/kWh -> Currency/MWh // Example: 12 Cent/kWh => 0.12 EUR/kWh * 1000 kWh/MWh = 120 EUR/MWh. - var marketPrice = getAsDouble(element, "price_inCentPerKwh") * 10; + final var basePrice = getAsDouble(element, "priceInCentPerKwh") * 10; + final var additionalCosts = (priceComponent.taxAndFeeKwHPrice + priceComponent.gridFeeKwHPrice + + priceComponent.gridFeeFixed) * 10; + + final var marketPrice = basePrice + additionalCosts; // Converting time string to ZonedDateTime. - var startTimeStamp = ZonedDateTime // - .parse(getAsString(element, "moment")) // + final var startTimeStamp = ZonedDateTime // + .parse(getAsString(element, "timestamp")) // .truncatedTo(ChronoUnit.HOURS); - // Adding the values in the Map. - result.put(startTimeStamp, marketPrice); - result.put(startTimeStamp.plusMinutes(15), marketPrice); - result.put(startTimeStamp.plusMinutes(30), marketPrice); - result.put(startTimeStamp.plusMinutes(45), marketPrice); + // Adding the values in the Map for each 15-minute interval. + for (var minutes = 0; minutes <= 45; minutes += 15) { + result.put(startTimeStamp.plusMinutes(minutes), marketPrice); + } } - return TimeOfUsePrices.from(result); + return TimeOfUsePrices.from(result.build()); } @Override public String debugLog() { return generateDebugLog(this, this.meta.getCurrency()); } + + protected record PriceComponents(// + double taxAndFeeKwHPrice, // + double gridFeeKwHPrice, // + double gridFeeFixed // + ) { + + } + } diff --git a/io.openems.edge.timeofusetariff.rabotcharge/src/io/openems/edge/timeofusetariff/rabotcharge/translation_en.properties b/io.openems.edge.timeofusetariff.rabotcharge/src/io/openems/edge/timeofusetariff/rabotcharge/translation_en.properties new file mode 100644 index 00000000000..991bbc00ca8 --- /dev/null +++ b/io.openems.edge.timeofusetariff.rabotcharge/src/io/openems/edge/timeofusetariff/rabotcharge/translation_en.properties @@ -0,0 +1,3 @@ +httpStatusCode = Displays the HTTP status code +statusAuthenticationFailed = Unable to update prices: Authentication failed +statusBadRequest = Unable to update prices: internal error \ No newline at end of file diff --git a/io.openems.edge.timeofusetariff.rabotcharge/test/io/openems/edge/timeofusetariff/rabotcharge/MyConfig.java b/io.openems.edge.timeofusetariff.rabotcharge/test/io/openems/edge/timeofusetariff/rabotcharge/MyConfig.java index f408845325b..6a0131a3278 100644 --- a/io.openems.edge.timeofusetariff.rabotcharge/test/io/openems/edge/timeofusetariff/rabotcharge/MyConfig.java +++ b/io.openems.edge.timeofusetariff.rabotcharge/test/io/openems/edge/timeofusetariff/rabotcharge/MyConfig.java @@ -7,7 +7,9 @@ public class MyConfig extends AbstractComponentConfig implements Config { public static class Builder { private String id; - private String accessToken; + private String zipcode; + private String clientId; + private String clientSecret; private Builder() { } @@ -17,8 +19,18 @@ public Builder setId(String id) { return this; } - public Builder setAccessToken(String accessToken) { - this.accessToken = accessToken; + public Builder setZipcode(String zipcode) { + this.zipcode = zipcode; + return this; + } + + public Builder setClientId(String clientId) { + this.clientId = clientId; + return this; + } + + public Builder setClientSecret(String clientSecret) { + this.clientSecret = clientSecret; return this; } @@ -44,8 +56,18 @@ private MyConfig(Builder builder) { } @Override - public String accessToken() { - return this.builder.accessToken; + public String zipcode() { + return this.builder.zipcode; + } + + @Override + public String clientId() { + return this.builder.clientId; + } + + @Override + public String clientSecret() { + return this.builder.clientSecret; } } \ No newline at end of file diff --git a/io.openems.edge.timeofusetariff.rabotcharge/test/io/openems/edge/timeofusetariff/rabotcharge/TimeOfUseTariffRabotChargeImplTest.java b/io.openems.edge.timeofusetariff.rabotcharge/test/io/openems/edge/timeofusetariff/rabotcharge/TimeOfUseTariffRabotChargeImplTest.java index 46c41dec126..df7f605f959 100644 --- a/io.openems.edge.timeofusetariff.rabotcharge/test/io/openems/edge/timeofusetariff/rabotcharge/TimeOfUseTariffRabotChargeImplTest.java +++ b/io.openems.edge.timeofusetariff.rabotcharge/test/io/openems/edge/timeofusetariff/rabotcharge/TimeOfUseTariffRabotChargeImplTest.java @@ -1,126 +1,176 @@ package io.openems.edge.timeofusetariff.rabotcharge; +import static io.openems.common.test.TestUtils.createDummyClock; +import static io.openems.edge.bridge.http.dummy.DummyBridgeHttpFactory.cycleSubscriber; +import static io.openems.edge.bridge.http.dummy.DummyBridgeHttpFactory.dummyBridgeHttpExecutor; +import static io.openems.edge.bridge.http.dummy.DummyBridgeHttpFactory.dummyEndpointFetcher; +import static io.openems.edge.bridge.http.dummy.DummyBridgeHttpFactory.ofBridgeImpl; +import static io.openems.edge.timeofusetariff.rabotcharge.TimeOfUseTariffRabotChargeImpl.RABOT_CHARGE_API_URL; +import static io.openems.edge.timeofusetariff.rabotcharge.TimeOfUseTariffRabotChargeImpl.RABOT_CHARGE_PRIZE_COMPONENT_URL; +import static io.openems.edge.timeofusetariff.rabotcharge.TimeOfUseTariffRabotChargeImpl.RABOT_CHARGE_TOKEN_URL; import static io.openems.edge.timeofusetariff.rabotcharge.TimeOfUseTariffRabotChargeImpl.parsePrices; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThrows; -import java.time.Instant; -import java.time.ZoneOffset; - import org.junit.Test; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.common.test.TimeLeapClock; -import io.openems.edge.bridge.http.dummy.DummyBridgeHttpFactory; +import io.openems.common.oem.DummyOpenemsEdgeOem; +import io.openems.common.types.ChannelAddress; +import io.openems.common.types.HttpStatus; +import io.openems.common.utils.JsonUtils; +import io.openems.edge.bridge.http.api.HttpError; +import io.openems.edge.bridge.http.api.HttpResponse; +import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyComponentManager; +import io.openems.edge.timeofusetariff.rabotcharge.TimeOfUseTariffRabotChargeImpl.PriceComponents; public class TimeOfUseTariffRabotChargeImplTest { - private static final String CTRL_ID = "ctrl0"; + private static final String DUMMY_PRICES = """ + { + "records": [ + { + "timestamp": "2024-05-14T09:00:00+02:00", + "priceInCentPerKwh": 0.564 + }, + { + "timestamp": "2024-05-14T10:00:00+02:00", + "priceInCentPerKwh": -0.233 + }, + { + "timestamp": "2024-05-14T11:00:00+02:00", + "priceInCentPerKwh": -1.112 + }, + { + "timestamp": "2024-05-14T12:00:00+02:00", + "priceInCentPerKwh": -3.633 + }, + { + "timestamp": "2024-05-14T13:00:00+02:00", + "priceInCentPerKwh": -4.295 + }, + { + "timestamp": "2024-05-14T14:00:00+02:00", + "priceInCentPerKwh": -3.966 + }, + { + "timestamp": "2024-05-14T15:00:00+02:00", + "priceInCentPerKwh": -1.986 + }, + { + "timestamp": "2024-05-14T16:00:00+02:00", + "priceInCentPerKwh": -0.293 + }, + { + "timestamp": "2024-05-14T17:00:00+02:00", + "priceInCentPerKwh": -0.004 + }, + { + "timestamp": "2024-05-14T18:00:00+02:00", + "priceInCentPerKwh": 4.374 + }, + { + "timestamp": "2024-05-14T19:00:00+02:00", + "priceInCentPerKwh": 8.390 + }, + { + "timestamp": "2024-05-14T20:00:00+02:00", + "priceInCentPerKwh": 9.347 + }, + { + "timestamp": "2024-05-14T21:00:00+02:00", + "priceInCentPerKwh": 7.641 + }, + { + "timestamp": "2024-05-14T22:00:00+02:00", + "priceInCentPerKwh": 4.535 + }, + { + "timestamp": "2024-05-14T23:00:00+02:00", + "priceInCentPerKwh": 2.771 + } + ] + }"""; + + private static final PriceComponents PRICE_COMPONENTS = new PriceComponents(5.0611, 9.3415, 6.4627); @Test public void test() throws Exception { - final TimeLeapClock clock = new TimeLeapClock(Instant.parse("2020-01-01T01:00:00.00Z"), ZoneOffset.UTC); - final DummyComponentManager cm = new DummyComponentManager(clock); - var rabotCharge = new TimeOfUseTariffRabotChargeImpl(); - new ComponentTest(rabotCharge) // - .addReference("httpBridgeFactory", DummyBridgeHttpFactory.ofDummyBridge()) // - .addReference("componentManager", cm) // + final var clock = createDummyClock(); + + final var endpointFetcher = dummyEndpointFetcher(); + endpointFetcher.addEndpointHandler(endpoint -> { + + if (endpoint.url().equals(RABOT_CHARGE_TOKEN_URL.toEncodedString())) { + return HttpResponse.ok(JsonUtils.buildJsonObject() // + .addProperty("access_token", "FJAWognawn") // + .build().toString()); + } + + if (endpoint.url().equals(RABOT_CHARGE_API_URL.toEncodedString())) { + return HttpResponse.ok(DUMMY_PRICES); + } + + if (endpoint.url().startsWith(RABOT_CHARGE_PRIZE_COMPONENT_URL.toEncodedString())) { + return HttpResponse.ok(JsonUtils.buildJsonObject() // + .addProperty("taxAndFeeKwHPrice", 3.44) // + .addProperty("gridFeeKwHPrice", 7.89) // + .addProperty("gridFeeFixed", 5.22) // + .build().toString()); + } + + throw HttpError.ResponseError.notFound(); + }); + + final var executor = dummyBridgeHttpExecutor(); + + final var factory = ofBridgeImpl(// + () -> cycleSubscriber(), // + () -> endpointFetcher, // + () -> executor // + ); + + final var sut = new TimeOfUseTariffRabotChargeImpl(); + new ComponentTest(sut) // + .addReference("httpBridgeFactory", factory) // + .addReference("componentManager", new DummyComponentManager(clock)) // + .addReference("oem", new DummyOpenemsEdgeOem()) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setAccessToken("foo-bar") // + .setId("ctrl0") // + .setZipcode("00000000") // + .setClientId("clientId") // + .setClientSecret("clientSecret") // .build()) // - ; + .next(new TestCase() // + .onBeforeProcessImage(() -> { + // get auth token + executor.update(); + + // get prices + executor.update(); + }) // + .output(new ChannelAddress("ctrl0", "HttpStatusCode"), HttpStatus.OK.code())); } @Test public void nonEmptyStringTest() throws OpenemsNamedException { // Parsing with custom data - var prices = parsePrices(""" - { - "records": [ - { - "moment": "2024-05-14T09:00:00+02:00", - "price_inCentPerKwh": 0.564 - }, - { - "moment": "2024-05-14T10:00:00+02:00", - "price_inCentPerKwh": -0.233 - }, - { - "moment": "2024-05-14T11:00:00+02:00", - "price_inCentPerKwh": -1.112 - }, - { - "moment": "2024-05-14T12:00:00+02:00", - "price_inCentPerKwh": -3.633 - }, - { - "moment": "2024-05-14T13:00:00+02:00", - "price_inCentPerKwh": -4.295 - }, - { - "moment": "2024-05-14T14:00:00+02:00", - "price_inCentPerKwh": -3.966 - }, - { - "moment": "2024-05-14T15:00:00+02:00", - "price_inCentPerKwh": -1.986 - }, - { - "moment": "2024-05-14T16:00:00+02:00", - "price_inCentPerKwh": -0.293 - }, - { - "moment": "2024-05-14T17:00:00+02:00", - "price_inCentPerKwh": -0.004 - }, - { - "moment": "2024-05-14T18:00:00+02:00", - "price_inCentPerKwh": 4.374 - }, - { - "moment": "2024-05-14T19:00:00+02:00", - "price_inCentPerKwh": 8.390 - }, - { - "moment": "2024-05-14T20:00:00+02:00", - "price_inCentPerKwh": 9.347 - }, - { - "moment": "2024-05-14T21:00:00+02:00", - "price_inCentPerKwh": 7.641 - }, - { - "moment": "2024-05-14T22:00:00+02:00", - "price_inCentPerKwh": 4.535 - }, - { - "moment": "2024-05-14T23:00:00+02:00", - "price_inCentPerKwh": 2.771 - } - ], - "success": true, - "metadata": { - "messages": [], - "maintenanceMode": null - } - - }"""); // + var prices = parsePrices(DUMMY_PRICES, PRICE_COMPONENTS); // // To check if the Map is not empty assertFalse(prices.isEmpty()); // To check if a value is present in map. - assertEquals(5.64, prices.getFirst(), 0.001); + assertEquals(214.2929, prices.getFirst(), 0.001); } @Test public void emptyStringTest() throws OpenemsNamedException { assertThrows(OpenemsNamedException.class, () -> { - parsePrices(""); + parsePrices("", PRICE_COMPONENTS); }); } } diff --git a/io.openems.edge.timeofusetariff.swisspower/.classpath b/io.openems.edge.timeofusetariff.swisspower/.classpath new file mode 100644 index 00000000000..b4cffd0fe60 --- /dev/null +++ b/io.openems.edge.timeofusetariff.swisspower/.classpath @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/io.openems.edge.timeofusetariff.swisspower/.gitignore b/io.openems.edge.timeofusetariff.swisspower/.gitignore new file mode 100644 index 00000000000..c2b941a96de --- /dev/null +++ b/io.openems.edge.timeofusetariff.swisspower/.gitignore @@ -0,0 +1,2 @@ +/bin_test/ +/generated/ diff --git a/io.openems.edge.timeofusetariff.swisspower/.project b/io.openems.edge.timeofusetariff.swisspower/.project new file mode 100644 index 00000000000..f72a46f1fa2 --- /dev/null +++ b/io.openems.edge.timeofusetariff.swisspower/.project @@ -0,0 +1,23 @@ + + + io.openems.edge.timeofusetariff.swisspower + + + + + + org.eclipse.jdt.core.javabuilder + + + + + bndtools.core.bndbuilder + + + + + + org.eclipse.jdt.core.javanature + bndtools.core.bndnature + + diff --git a/io.openems.edge.timeofusetariff.swisspower/.settings/org.eclipse.core.resources.prefs b/io.openems.edge.timeofusetariff.swisspower/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 00000000000..99f26c0203a --- /dev/null +++ b/io.openems.edge.timeofusetariff.swisspower/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding/=UTF-8 diff --git a/io.openems.edge.timeofusetariff.swisspower/bnd.bnd b/io.openems.edge.timeofusetariff.swisspower/bnd.bnd new file mode 100644 index 00000000000..5c1ca041564 --- /dev/null +++ b/io.openems.edge.timeofusetariff.swisspower/bnd.bnd @@ -0,0 +1,14 @@ +Bundle-Name: OpenEMS Edge Time-Of-Use Tariff Swisspower +Bundle-Vendor: FENECON GmbH +Bundle-License: https://opensource.org/licenses/EPL-2.0 +Bundle-Version: 1.0.0.${tstamp} + +-buildpath: \ + ${buildpath},\ + io.openems.common,\ + io.openems.edge.bridge.http,\ + io.openems.edge.common,\ + io.openems.edge.timeofusetariff.api,\ + +-testpath: \ + ${testpath} \ No newline at end of file diff --git a/io.openems.edge.timeofusetariff.swisspower/readme.adoc b/io.openems.edge.timeofusetariff.swisspower/readme.adoc new file mode 100644 index 00000000000..c470cec24ab --- /dev/null +++ b/io.openems.edge.timeofusetariff.swisspower/readme.adoc @@ -0,0 +1,11 @@ += Time-Of-Use Tariff Swisspower + +Retrieves the quarterly prices from the Swisspower ESIT API. + +For detailed information about the Swisspower ESIT API, please refer to: https://esit-test.code-fabrik.ch/doc_scalar/ + +Prices retrieved from Swisspower are subsequently converted to the user's currency (defined in Core.Meta Component) using the Exchange Rates API. + +For detailed information about the Exchange Rates API, please refer to: https://www.ecb.europa.eu/stats/policy_and_exchange_rates/euro_reference_exchange_rates/html/index.en.html + +https://github.com/OpenEMS/openems/tree/develop/io.openems.edge.timeofusetariff.swisspower[Source Code icon:github[]] \ No newline at end of file diff --git a/io.openems.edge.timeofusetariff.swisspower/src/io/openems/edge/timeofusetariff/swisspower/Config.java b/io.openems.edge.timeofusetariff.swisspower/src/io/openems/edge/timeofusetariff/swisspower/Config.java new file mode 100644 index 00000000000..bb805a5543f --- /dev/null +++ b/io.openems.edge.timeofusetariff.swisspower/src/io/openems/edge/timeofusetariff/swisspower/Config.java @@ -0,0 +1,28 @@ +package io.openems.edge.timeofusetariff.swisspower; + +import org.osgi.service.metatype.annotations.AttributeDefinition; +import org.osgi.service.metatype.annotations.AttributeType; +import org.osgi.service.metatype.annotations.ObjectClassDefinition; + +@ObjectClassDefinition(// + name = "Time-Of-Use Tariff Swisspower", // + description = "Time-Of-Use Tariff implementation for Swisspower.") +@interface Config { + + @AttributeDefinition(name = "Component-ID", description = "Unique ID of this Component") + String id() default "timeOfUseTariff0"; + + @AttributeDefinition(name = "Alias", description = "Human-readable name of this Component; defaults to Component-ID") + String alias() default ""; + + @AttributeDefinition(name = "Is enabled?", description = "Is this Component enabled?") + boolean enabled() default true; + + @AttributeDefinition(name = "Access Token", description = "Access token for the Swisspower Platform", type = AttributeType.PASSWORD) + String accessToken() default ""; + + @AttributeDefinition(name = "Measuring point number", description = "Measuring point number for which the tariff is to be retrieved. If this option is used, the tariff name is automatically selected.") + String meteringCode() default ""; + + String webconsole_configurationFactory_nameHint() default "Time-Of-Use Tariff Swisspower [{id}]"; +} diff --git a/io.openems.edge.timeofusetariff.swisspower/src/io/openems/edge/timeofusetariff/swisspower/TimeOfUseTariffSwisspower.java b/io.openems.edge.timeofusetariff.swisspower/src/io/openems/edge/timeofusetariff/swisspower/TimeOfUseTariffSwisspower.java new file mode 100644 index 00000000000..f718d966da5 --- /dev/null +++ b/io.openems.edge.timeofusetariff.swisspower/src/io/openems/edge/timeofusetariff/swisspower/TimeOfUseTariffSwisspower.java @@ -0,0 +1,37 @@ +package io.openems.edge.timeofusetariff.swisspower; + +import io.openems.common.channel.Level; +import io.openems.common.types.OpenemsType; +import io.openems.edge.common.channel.Doc; +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.timeofusetariff.api.TimeOfUseTariff; + +public interface TimeOfUseTariffSwisspower extends TimeOfUseTariff, OpenemsComponent { + + public enum ChannelId implements io.openems.edge.common.channel.ChannelId { + HTTP_STATUS_CODE(Doc.of(OpenemsType.INTEGER)// + .text("Displays the HTTP status code")), // + STATUS_INVALID_FIELDS(Doc.of(Level.WARNING) // + .text("Unable to update prices: please check your access token and metering code")), // + /** + * Should never happen. Only happens if the request has missing fields or wrong + * format of timestamps. + */ + STATUS_BAD_REQUEST(Doc.of(Level.FAULT) // + .text("Unable to update prices: internal error")), // + STATUS_READ_TIMEOUT(Doc.of(Level.WARNING) // + .text("Unable to update prices: read timeout error")), // + ; + + private final Doc doc; + + private ChannelId(Doc doc) { + this.doc = doc; + } + + @Override + public Doc doc() { + return this.doc; + } + } +} diff --git a/io.openems.edge.timeofusetariff.swisspower/src/io/openems/edge/timeofusetariff/swisspower/TimeOfUseTariffSwisspowerImpl.java b/io.openems.edge.timeofusetariff.swisspower/src/io/openems/edge/timeofusetariff/swisspower/TimeOfUseTariffSwisspowerImpl.java new file mode 100644 index 00000000000..4fd7c4a7b82 --- /dev/null +++ b/io.openems.edge.timeofusetariff.swisspower/src/io/openems/edge/timeofusetariff/swisspower/TimeOfUseTariffSwisspowerImpl.java @@ -0,0 +1,273 @@ +package io.openems.edge.timeofusetariff.swisspower; + +import static io.openems.common.utils.JsonUtils.getAsDouble; +import static io.openems.common.utils.JsonUtils.getAsJsonArray; +import static io.openems.common.utils.JsonUtils.getAsString; +import static io.openems.common.utils.JsonUtils.parseToJsonObject; +import static io.openems.edge.timeofusetariff.api.utils.ExchangeRateApi.getExchangeRateOrElse; +import static io.openems.edge.timeofusetariff.api.utils.TimeOfUseTariffUtils.generateDebugLog; + +import java.io.IOException; +import java.net.SocketTimeoutException; +import java.time.Clock; +import java.time.Duration; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.BiConsumer; + +import org.osgi.service.component.ComponentContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.ConfigurationPolicy; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.metatype.annotations.Designate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.ImmutableSortedMap; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.timedata.DurationUnit; +import io.openems.edge.bridge.http.api.BridgeHttp; +import io.openems.edge.bridge.http.api.BridgeHttp.Endpoint; +import io.openems.edge.bridge.http.api.BridgeHttpFactory; +import io.openems.edge.bridge.http.api.HttpError; +import io.openems.edge.bridge.http.api.HttpMethod; +import io.openems.edge.bridge.http.api.HttpResponse; +import io.openems.edge.bridge.http.api.UrlBuilder; +import io.openems.edge.bridge.http.time.DelayTimeProvider; +import io.openems.edge.bridge.http.time.DelayTimeProviderChain; +import io.openems.edge.common.channel.value.Value; +import io.openems.edge.common.component.AbstractOpenemsComponent; +import io.openems.edge.common.component.ComponentManager; +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.common.currency.Currency; +import io.openems.edge.common.meta.Meta; +import io.openems.edge.timeofusetariff.api.TimeOfUsePrices; +import io.openems.edge.timeofusetariff.api.TimeOfUseTariff; + +@Designate(ocd = Config.class, factory = true) +@Component(// + name = "TimeOfUseTariff.Swisspower", // + immediate = true, // + configurationPolicy = ConfigurationPolicy.REQUIRE // +) +public class TimeOfUseTariffSwisspowerImpl extends AbstractOpenemsComponent + implements TimeOfUseTariff, OpenemsComponent, TimeOfUseTariffSwisspower { + private static final UrlBuilder URL_BASE = UrlBuilder.parse("https://esit.code-fabrik.ch/api/v1/metering_code"); + private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ISO_OFFSET_DATE_TIME; + private static final int INTERNAL_ERROR = -1; // parsing, handle exception... + private static final int DEFAULT_READ_TIMEOUT = 200; + protected static final int SERVER_ERROR_CODE = 500; + protected static final int BAD_REQUEST_ERROR_CODE = 400; + + private final Logger log = LoggerFactory.getLogger(TimeOfUseTariffSwisspowerImpl.class); + private final AtomicReference prices = new AtomicReference<>(TimeOfUsePrices.EMPTY_PRICES); + private String accessToken = null; + private String meteringCode = null; + + @Reference + private BridgeHttpFactory httpBridgeFactory; + private BridgeHttp httpBridge; + + @Reference + private Meta meta; + + @Reference + private ComponentManager componentManager; + + public TimeOfUseTariffSwisspowerImpl() { + super(// + OpenemsComponent.ChannelId.values(), // + TimeOfUseTariffSwisspower.ChannelId.values() // + ); + } + + private final BiConsumer, Value> onCurrencyChange = (a, b) -> { + this.httpBridge.removeAllTimeEndpoints(); + this.httpBridge.subscribeTime(new SwisspowerProvider(this.componentManager.getClock()), // + this::createSwisspowerEndpoint, // + this::handleEndpointResponse, // + this::handleEndpointError); + }; + + @Activate + private void activate(ComponentContext context, Config config) { + super.activate(context, config.id(), config.alias(), config.enabled()); + + if (!config.enabled()) { + return; + } + + this.accessToken = config.accessToken(); + if (this.accessToken == null) { + this.logError(this.log, "Please configure personal Access token to access Swisspower API"); + return; + } + + this.meteringCode = config.meteringCode(); + if (this.meteringCode == null) { + this.logError(this.log, "Please configure meteringCode to access Swisspower API"); + return; + } + + // React on updates to Currency. + this.meta.getCurrencyChannel().onChange(this.onCurrencyChange); + + this.httpBridge = this.httpBridgeFactory.get(); + this.httpBridge.subscribeTime(new SwisspowerProvider(this.componentManager.getClock()), // + this::createSwisspowerEndpoint, // + this::handleEndpointResponse, // + this::handleEndpointError); + } + + private Endpoint createSwisspowerEndpoint() { + final var now = ZonedDateTime.now().truncatedTo(ChronoUnit.DAYS); + final var startTimestamp = now.format(DATE_FORMATTER); // eg. 2024-05-22T00:00:00+02:00 + final var endTimestamp = now.plusDays(1).format(DATE_FORMATTER); + + final var url = URL_BASE.withQueryParam("start_timestamp", startTimestamp) // + .withQueryParam("end_timestamp", endTimestamp) // + .withQueryParam("metering_code", this.meteringCode); + + return new Endpoint(url.toEncodedString(), // + HttpMethod.GET, // + BridgeHttp.DEFAULT_CONNECT_TIMEOUT, // + DEFAULT_READ_TIMEOUT, // + null, // + this.buildRequestHeaders()); + } + + private Map buildRequestHeaders() { + return Map.of(// + "Authorization", "Bearer " + this.accessToken // + ); + } + + @Override + @Deactivate + protected void deactivate() { + super.deactivate(); + this.meta.getCurrencyChannel().removeOnChangeCallback(this.onCurrencyChange); + this.httpBridgeFactory.unget(this.httpBridge); + } + + public static class SwisspowerProvider implements DelayTimeProvider { + + private final Clock clock; + + public SwisspowerProvider(Clock clock) { + super(); + this.clock = clock; + } + + @Override + public Delay onFirstRunDelay() { + return Delay.of(Duration.ofMinutes(1)); + } + + @Override + public Delay onErrorRunDelay(HttpError error) { + return DelayTimeProviderChain.fixedDelay(Duration.ofHours(1))// + .plusRandomDelay(60, ChronoUnit.SECONDS) // + .getDelay(); + } + + @Override + public Delay onSuccessRunDelay(HttpResponse result) { + return DelayTimeProviderChain.fixedAtEveryFull(this.clock, DurationUnit.ofDays(1)) + .plusRandomDelay(60, ChronoUnit.SECONDS) // + .getDelay(); + } + } + + private void handleEndpointResponse(HttpResponse response) throws OpenemsNamedException, IOException { + this.setChannelValues(response.status().code(), false, false, false); + + final var swissPowerCurrency = Currency.CHF.name(); // Swiss Franc + final var globalCurrency = this.meta.getCurrency(); + final double exchangeRate = getExchangeRateOrElse(swissPowerCurrency, globalCurrency, 1.); + + // Parse the response for the prices + this.prices.set(parsePrices(response.data(), exchangeRate)); + } + + private void handleEndpointError(HttpError error) { + var httpStatusCode = (error instanceof HttpError.ResponseError re) ? re.status.code() : INTERNAL_ERROR; + var serverError = (httpStatusCode == SERVER_ERROR_CODE); + var badRequest = (httpStatusCode == BAD_REQUEST_ERROR_CODE); + var timeoutError = (error instanceof HttpError.UnknownError e + && e.getCause() instanceof SocketTimeoutException); + + this.setChannelValues(httpStatusCode, serverError, badRequest, timeoutError); + this.log.error("HTTP Error [{}]: {}", httpStatusCode, error.getMessage()); + } + + @Override + public TimeOfUsePrices getPrices() { + return TimeOfUsePrices.from(ZonedDateTime.now(this.componentManager.getClock()), this.prices.get()); + } + + /** + * Parses JSON data to extract time-of-use prices and returns a + * {@link TimeOfUsePrices} object. + * + * @param jsonData the JSON data as a {@code String} containing the + * electricity price information. + * @param exchangeRate The exchange rate of user currency to EUR. + * @return a {@link TimeOfUsePrices} object containing the parsed prices mapped + * to their respective timestamps. + * @throws OpenemsNamedException if an error occurs during the parsing of the + * JSON data. + */ + protected static TimeOfUsePrices parsePrices(String jsonData, double exchangeRate) throws OpenemsNamedException { + var result = ImmutableSortedMap.naturalOrder(); + var data = parseToJsonObject(jsonData); + var prices = getAsJsonArray(data, "prices"); + + for (var element : prices) { + + var startTimeString = getAsString(element, "start_timestamp"); + var integrated = getAsJsonArray(element, "integrated"); + + // CHF/kWh -> Currency/MWh + // Example: 0.1 CHF/kWh * 1000 = 100 CHF/MWh. + var marketPrice = getAsDouble(integrated.get(0), "value") * 1000 * exchangeRate; + + // Convert LocalDateTime to ZonedDateTime + var startTimeStamp = ZonedDateTime.parse(startTimeString, DateTimeFormatter.ISO_OFFSET_DATE_TIME); + + // Adding the values in the Map. + result.put(startTimeStamp, marketPrice); + } + return TimeOfUsePrices.from(result.build()); + } + + /** + * This method updates the channel values in response to an HTTP request. + * + * @param httpStatusCode the HTTP status code returned from the endpoint + * response + * @param serverError a boolean indicating if a server error occurred (status + * code 500) + * @param badRequest a boolean indicating if the request was invalid (status + * code 400) + * @param timeoutError a boolean indicating if the request could not be read + * in time + */ + private void setChannelValues(int httpStatusCode, boolean serverError, boolean badRequest, boolean timeoutError) { + this.channel(TimeOfUseTariffSwisspower.ChannelId.HTTP_STATUS_CODE).setNextValue(httpStatusCode); + this.channel(TimeOfUseTariffSwisspower.ChannelId.STATUS_INVALID_FIELDS).setNextValue(serverError); + this.channel(TimeOfUseTariffSwisspower.ChannelId.STATUS_BAD_REQUEST).setNextValue(badRequest); + this.channel(TimeOfUseTariffSwisspower.ChannelId.STATUS_READ_TIMEOUT).setNextValue(timeoutError); + } + + @Override + public String debugLog() { + return generateDebugLog(this, this.meta.getCurrency()); + } +} diff --git a/io.openems.edge.timeofusetariff.swisspower/test/.gitignore b/io.openems.edge.timeofusetariff.swisspower/test/.gitignore new file mode 100644 index 00000000000..e69de29bb2d diff --git a/io.openems.edge.timeofusetariff.swisspower/test/io/openems/edge/timeofusetariff/swisspower/MyConfig.java b/io.openems.edge.timeofusetariff.swisspower/test/io/openems/edge/timeofusetariff/swisspower/MyConfig.java new file mode 100644 index 00000000000..ea1be73e72e --- /dev/null +++ b/io.openems.edge.timeofusetariff.swisspower/test/io/openems/edge/timeofusetariff/swisspower/MyConfig.java @@ -0,0 +1,62 @@ +package io.openems.edge.timeofusetariff.swisspower; + +import io.openems.common.test.AbstractComponentConfig; + +@SuppressWarnings("all") +public class MyConfig extends AbstractComponentConfig implements Config { + + public static class Builder { + private String id; + private String accessToken; + private String meteringCode; + + private Builder() { + } + + public Builder setId(String id) { + this.id = id; + return this; + } + + public Builder setAccessToken(String accessToken) { + this.accessToken = accessToken; + return this; + } + + public Builder setMeteringCode(String meteringCode) { + this.meteringCode = meteringCode; + return this; + } + + public MyConfig build() { + return new MyConfig(this); + } + } + + /** + * Create a Config builder. + * + * @return a {@link Builder} + */ + public static Builder create() { + return new Builder(); + } + + private final Builder builder; + + private MyConfig(Builder builder) { + super(Config.class, builder.id); + this.builder = builder; + } + + @Override + public String accessToken() { + return this.builder.accessToken; + } + + @Override + public String meteringCode() { + return this.builder.meteringCode; + } + +} \ No newline at end of file diff --git a/io.openems.edge.timeofusetariff.swisspower/test/io/openems/edge/timeofusetariff/swisspower/TimeOfUseTariffSwisspowerImplTest.java b/io.openems.edge.timeofusetariff.swisspower/test/io/openems/edge/timeofusetariff/swisspower/TimeOfUseTariffSwisspowerImplTest.java new file mode 100644 index 00000000000..d859b6dd77b --- /dev/null +++ b/io.openems.edge.timeofusetariff.swisspower/test/io/openems/edge/timeofusetariff/swisspower/TimeOfUseTariffSwisspowerImplTest.java @@ -0,0 +1,226 @@ +package io.openems.edge.timeofusetariff.swisspower; + +import static io.openems.common.test.TestUtils.createDummyClock; +import static io.openems.edge.bridge.http.dummy.DummyBridgeHttpFactory.ofDummyBridge; +import static io.openems.edge.common.currency.Currency.EUR; +import static io.openems.edge.timeofusetariff.swisspower.TimeOfUseTariffSwisspowerImpl.parsePrices; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +import org.junit.Test; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.edge.common.test.ComponentTest; +import io.openems.edge.common.test.DummyComponentManager; +import io.openems.edge.common.test.DummyMeta; + +public class TimeOfUseTariffSwisspowerImplTest { + + private static final String CTRL_ID = "ctrl0"; + private static final double GROUPE_E_EXCHANGE_RATE = 1; + + private static final String PRICE_RESULT_STRING = """ + { + "status": "ok", + "prices": [ + { + "start_timestamp": "2024-08-12T00:00:00+02:00", + "end_timestamp": "2024-08-12T00:15:00+02:00", + "integrated": [ + { + "value": 0.49249999999999994, + "unit": "CHF/kWh", + "component": "work" + } + ] + }, + { + "start_timestamp": "2024-08-12T00:15:00+02:00", + "end_timestamp": "2024-08-12T00:30:00+02:00", + "integrated": [ + { + "value": 0.491133, + "unit": "CHF/kWh", + "component": "work" + } + ] + }, + { + "start_timestamp": "2024-08-12T00:30:00+02:00", + "end_timestamp": "2024-08-12T00:45:00+02:00", + "integrated": [ + { + "value": 0.486722, + "unit": "CHF/kWh", + "component": "work" + } + ] + }, + { + "start_timestamp": "2024-08-12T00:45:00+02:00", + "end_timestamp": "2024-08-12T01:00:00+02:00", + "integrated": [ + { + "value": 0.478854, + "unit": "CHF/kWh", + "component": "work" + } + ] + }, + { + "start_timestamp": "2024-08-12T01:00:00+02:00", + "end_timestamp": "2024-08-12T01:15:00+02:00", + "integrated": [ + { + "value": 0.46720300000000003, + "unit": "CHF/kWh", + "component": "work" + } + ] + }, + { + "start_timestamp": "2024-08-12T01:15:00+02:00", + "end_timestamp": "2024-08-12T01:30:00+02:00", + "integrated": [ + { + "value": 0.451539, + "unit": "CHF/kWh", + "component": "work" + } + ] + }, + { + "start_timestamp": "2024-08-12T01:30:00+02:00", + "end_timestamp": "2024-08-12T01:45:00+02:00", + "integrated": [ + { + "value": 0.431753, + "unit": "CHF/kWh", + "component": "work" + } + ] + }, + { + "start_timestamp": "2024-08-12T01:45:00+02:00", + "end_timestamp": "2024-08-12T02:00:00+02:00", + "integrated": [ + { + "value": 0.407858, + "unit": "CHF/kWh", + "component": "work" + } + ] + }, + { + "start_timestamp": "2024-08-12T02:00:00+02:00", + "end_timestamp": "2024-08-12T02:15:00+02:00", + "integrated": [ + { + "value": 0.38, + "unit": "CHF/kWh", + "component": "work" + } + ] + }, + { + "start_timestamp": "2024-08-12T02:15:00+02:00", + "end_timestamp": "2024-08-12T02:30:00+02:00", + "integrated": [ + { + "value": 0.348458, + "unit": "CHF/kWh", + "component": "work" + } + ] + }, + { + "start_timestamp": "2024-08-12T02:30:00+02:00", + "end_timestamp": "2024-08-12T02:45:00+02:00", + "integrated": [ + { + "value": 0.3425, + "unit": "CHF/kWh", + "component": "work" + } + ] + }, + { + "start_timestamp": "2024-08-12T02:45:00+02:00", + "end_timestamp": "2024-08-12T03:00:00+02:00", + "integrated": [ + { + "value": 0.3425, + "unit": "CHF/kWh", + "component": "work" + } + ] + }, + { + "start_timestamp": "2024-08-12T03:00:00+02:00", + "end_timestamp": "2024-08-12T03:15:00+02:00", + "integrated": [ + { + "value": 0.3425, + "unit": "CHF/kWh", + "component": "work" + } + ] + }, + { + "start_timestamp": "2024-08-12T03:15:00+02:00", + "end_timestamp": "2024-08-12T03:30:00+02:00", + "integrated": [ + { + "value": 0.3425, + "unit": "CHF/kWh", + "component": "work" + } + ] + }, + { + "start_timestamp": "2024-08-12T03:30:00+02:00", + "end_timestamp": "2024-08-12T03:45:00+02:00", + "integrated": [ + { + "value": 0.3425, + "unit": "CHF/kWh", + "component": "work" + } + ] + } + ] + } + + """; + + @Test + public void test() throws Exception { + final var clock = createDummyClock(); + var swissPower = new TimeOfUseTariffSwisspowerImpl(); + var dummyMeta = new DummyMeta("foo0") // + .withCurrency(EUR); + new ComponentTest(swissPower) // + .addReference("httpBridgeFactory", ofDummyBridge()) // + .addReference("meta", dummyMeta) // + .addReference("componentManager", new DummyComponentManager(clock)) // + .activate(MyConfig.create() // + .setId(CTRL_ID) // + .setAccessToken("foo-bar") // + .setMeteringCode("") // + .build()) // + ; + } + + @Test + public void nonEmptyStringTest() throws OpenemsNamedException { + // Parsing with custom data + var prices = parsePrices(PRICE_RESULT_STRING, GROUPE_E_EXCHANGE_RATE); // + + // To check if the Map is not empty + assertFalse(prices.isEmpty()); + + // To check if a value is present in map. + assertEquals(492.499, prices.getFirst(), 0.001); + } + +} diff --git a/io.openems.edge.timeofusetariff.tibber/.classpath b/io.openems.edge.timeofusetariff.tibber/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.timeofusetariff.tibber/.classpath +++ b/io.openems.edge.timeofusetariff.tibber/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.timeofusetariff.tibber/src/io/openems/edge/timeofusetariff/tibber/Utils.java b/io.openems.edge.timeofusetariff.tibber/src/io/openems/edge/timeofusetariff/tibber/Utils.java index 07ccaa9304b..776077f13ed 100644 --- a/io.openems.edge.timeofusetariff.tibber/src/io/openems/edge/timeofusetariff/tibber/Utils.java +++ b/io.openems.edge.timeofusetariff.tibber/src/io/openems/edge/timeofusetariff/tibber/Utils.java @@ -16,11 +16,11 @@ import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; import java.util.Random; -import java.util.TreeMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.collect.ImmutableSortedMap; import com.google.gson.JsonArray; import com.google.gson.JsonElement; @@ -141,7 +141,7 @@ private static TimeOfUsePrices parseHome(JsonElement json, String filter) throws // Adding to an array to avoid individual variables for individual for loops. JsonArray[] days = { today, tomorrow }; - var result = new TreeMap(); + var result = ImmutableSortedMap.naturalOrder(); // parse the arrays for price and time stamps. for (var day : days) { @@ -158,7 +158,7 @@ private static TimeOfUsePrices parseHome(JsonElement json, String filter) throws result.put(startsAt.plusMinutes(45), price); } } - return TimeOfUsePrices.from(result); + return TimeOfUsePrices.from(result.build()); } /** diff --git a/io.openems.edge.timeofusetariff.tibber/test/io/openems/edge/timeofusetariff/tibber/TimeOfUseTariffTibberImplTest.java b/io.openems.edge.timeofusetariff.tibber/test/io/openems/edge/timeofusetariff/tibber/TimeOfUseTariffTibberImplTest.java index 59f2b6a3558..7190b8a7795 100644 --- a/io.openems.edge.timeofusetariff.tibber/test/io/openems/edge/timeofusetariff/tibber/TimeOfUseTariffTibberImplTest.java +++ b/io.openems.edge.timeofusetariff.tibber/test/io/openems/edge/timeofusetariff/tibber/TimeOfUseTariffTibberImplTest.java @@ -6,14 +6,11 @@ public class TimeOfUseTariffTibberImplTest { - private static final String CTRL_ID = "ctrl0"; - @Test public void test() throws Exception { - var tibber = new TimeOfUseTariffTibberImpl(); - new ComponentTest(tibber) // + new ComponentTest(new TimeOfUseTariffTibberImpl()) // .activate(MyConfig.create() // - .setId(CTRL_ID) // + .setId("ctrl0") // .setAccessToken("foo-bar") // .setFilter("") // .build()) // diff --git a/io.openems.edge.timeofusetariff.tibber/test/io/openems/edge/timeofusetariff/tibber/UtilsTest.java b/io.openems.edge.timeofusetariff.tibber/test/io/openems/edge/timeofusetariff/tibber/UtilsTest.java index 42c1ce726d7..4cb6c2880dd 100644 --- a/io.openems.edge.timeofusetariff.tibber/test/io/openems/edge/timeofusetariff/tibber/UtilsTest.java +++ b/io.openems.edge.timeofusetariff.tibber/test/io/openems/edge/timeofusetariff/tibber/UtilsTest.java @@ -7,6 +7,7 @@ import static io.openems.edge.timeofusetariff.tibber.Utils.parsePrices; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import org.junit.Test; @@ -205,8 +206,8 @@ public void nonEmptyStringTest() throws OpenemsNamedException { assertEquals(0.2466 * 1000, prices.getFirst(), 0.001); // To check 15 minutes values are taken instead of one hour values. - var firstHour = prices.pricePerQuarter.firstKey(); - assertTrue(prices.pricePerQuarter.containsKey(firstHour.plusMinutes(15))); + var firstHour = prices.getFirstTime(); + assertNotNull(prices.getAt(firstHour.plusMinutes(15))); } @Test(expected = OpenemsNamedException.class) diff --git a/io.openems.oem.openems/.classpath b/io.openems.oem.openems/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.oem.openems/.classpath +++ b/io.openems.oem.openems/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.shared.influxdb/.classpath b/io.openems.shared.influxdb/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.shared.influxdb/.classpath +++ b/io.openems.shared.influxdb/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.wrapper/.classpath b/io.openems.wrapper/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.wrapper/.classpath +++ b/io.openems.wrapper/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.wrapper/aXMLRPC.bnd b/io.openems.wrapper/aXMLRPC.bnd index 21c82f88316..f2f8e5c2536 100644 --- a/io.openems.wrapper/aXMLRPC.bnd +++ b/io.openems.wrapper/aXMLRPC.bnd @@ -2,10 +2,10 @@ Bundle-Name: aXMLRPC Bundle-Description: aXMLRPC is a Java library with a lightweight XML-RPC client. Bundle-DocURL: https://github.com/gturri/aXMLRPC Bundle-License: https://opensource.org/licenses/MIT -Bundle-Version: 1.14.0 +Bundle-Version: 1.16.0 Include-Resource: \ - @aXMLRPC-1.14.0.jar,\ + @aXMLRPC-1.16.0.jar,\ Export-Package: \ de.timroes.axmlrpc,\ diff --git a/io.openems.wrapper/bnd.bnd b/io.openems.wrapper/bnd.bnd index d024f94a589..c1d497e0a9f 100644 --- a/io.openems.wrapper/bnd.bnd +++ b/io.openems.wrapper/bnd.bnd @@ -18,6 +18,10 @@ Bundle-Description: This wraps external java libraries that do not have OSGi hea eu.chargetime.ocpp:OCPP-J;version='1.0.2',\ eu.chargetime.ocpp:common;version='1.0.2',\ eu.chargetime.ocpp:v1_6;version='1.1.0',\ + io.helins:linux-common;version='0.1.4',\ + io.helins:linux-errno;version='1.0.2',\ + io.helins:linux-i2c;version='1.0.2',\ + io.helins:linux-io;version='0.0.4',\ io.jenetics:jenetics;version='7.2.0',\ info.faljse:SDNotify;version='1.5.0',\ io.reactivex.rxjava3.rxjava;version='3.1.8',\ diff --git a/io.openems.wrapper/eu.chargetime.ocpp.bnd b/io.openems.wrapper/eu.chargetime.ocpp.bnd index e5ba284eed6..b7c38bb3bcb 100644 --- a/io.openems.wrapper/eu.chargetime.ocpp.bnd +++ b/io.openems.wrapper/eu.chargetime.ocpp.bnd @@ -15,7 +15,6 @@ Include-Resource: \ Import-Package: \ com.sun.activation.registries,\ - javax.xml.soap,\ com.google.gson,\ javax.xml.transform,\ org.java_websocket,\ diff --git a/io.openems.wrapper/fastexcel.bnd b/io.openems.wrapper/fastexcel.bnd index 68c2302413d..fc25a00335c 100644 --- a/io.openems.wrapper/fastexcel.bnd +++ b/io.openems.wrapper/fastexcel.bnd @@ -5,7 +5,7 @@ Bundle-Version: 0.18.4 Include-Resource: \ @fastexcel-0.18.4.jar,\ - @fastexcel-reader-0.18.4.jar,\ + @fastexcel-reader-0.18.4.jar;onduplicate:=SKIP,\ -dsannotations: * diff --git a/io.openems.wrapper/helins-linux-i2c.bnd b/io.openems.wrapper/helins-linux-i2c.bnd new file mode 100644 index 00000000000..1bda4835a21 --- /dev/null +++ b/io.openems.wrapper/helins-linux-i2c.bnd @@ -0,0 +1,20 @@ +Bundle-Name: Helins linux-i2c lib +Bundle-SymbolicName: io.openems.wrapper.helins-linux-i2c +Bundle-DocURL: https://github.com/helins/linux-i2c.jav +Bundle-License: https://opensource.org/license/mpl-2-0 +Bundle-Version: 1.0.2 + +Include-Resource: \ + @linux-i2c-1.0.2.jar,\ + @linux-common-0.1.4.jar,\ + @linux-errno-1.0.2.jar,\ + @linux-io-0.0.4.jar,\ + +-dsannotations: * + +-metatypeannotations: * + +Export-Package: \ + io.helins.linux.i2c,\ + +-sources: false \ No newline at end of file diff --git a/io.openems.wrapper/jenetics.bnd b/io.openems.wrapper/jenetics.bnd index b8c7910d5ef..0aeb498fdaf 100644 --- a/io.openems.wrapper/jenetics.bnd +++ b/io.openems.wrapper/jenetics.bnd @@ -2,9 +2,9 @@ Bundle-Name: Jenetics Bundle-Description: Java Genetic Algorithm Library Bundle-DocURL: https://github.com/dhatim/fastexcel Bundle-License: https://opensource.org/licenses/Apache-2.0 -Bundle-Version: 7.2.0 +Bundle-Version: 8.1.0 -Include-Resource: @jenetics-7.2.0.jar +Include-Resource: @jenetics-8.1.0.jar -dsannotations: * diff --git a/io.openems.wrapper/kotlinx-coroutines-core-jvm.bnd b/io.openems.wrapper/kotlinx-coroutines-core-jvm.bnd index 3b276e9c952..58296c64571 100644 --- a/io.openems.wrapper/kotlinx-coroutines-core-jvm.bnd +++ b/io.openems.wrapper/kotlinx-coroutines-core-jvm.bnd @@ -2,10 +2,10 @@ Bundle-Name: kotlinx-coroutines-core-jvm Bundle-Description: The Java InfluxDB 2.0 Client Core Bundle-DocURL: https://github.com/influxdata/influxdb-client-client Bundle-License: https://opensource.org/licenses/MIT -Bundle-Version: 1.9.0 +Bundle-Version: 1.10.1 Include-Resource: \ - @kotlinx-coroutines-core-jvm-1.9.0.jar,\ + @kotlinx-coroutines-core-jvm-1.10.1.jar,\ Export-Package: \ kotlinx.coroutines,\ diff --git a/io.openems.wrapper/pgbulkinsert.bnd b/io.openems.wrapper/pgbulkinsert.bnd index 41cff7e3259..20cde1ff293 100644 --- a/io.openems.wrapper/pgbulkinsert.bnd +++ b/io.openems.wrapper/pgbulkinsert.bnd @@ -1,10 +1,10 @@ Bundle-Name: pgbulkinsert Bundle-DocURL: https://github.com/PgBulkInsert/PgBulkInsert Bundle-License: https://opensource.org/licenses/MIT -Bundle-Version: 8.1.4 +Bundle-Version: 8.1.5 Include-Resource: \ - @pgbulkinsert-8.1.4.jar,\ + @pgbulkinsert-8.1.5.jar,\ -dsannotations: * diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000000..8196d1ddf2e --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "openems", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/tools/debian/DEBIAN/control b/tools/debian/DEBIAN/control index 1a727b83ba3..18dc6d86725 100755 --- a/tools/debian/DEBIAN/control +++ b/tools/debian/DEBIAN/control @@ -3,7 +3,7 @@ Version: Section: misc Priority: extra Architecture: all -Depends: systemd, java17-runtime-headless +Depends: systemd, java21-runtime-headless Maintainer: OpenEMS Association e.V. Homepage: https://openems.io Description: OpenEMS Edge diff --git a/tools/debian/DEBIAN/postinst b/tools/debian/DEBIAN/postinst index 281499f7bc8..1b231ebae56 100755 --- a/tools/debian/DEBIAN/postinst +++ b/tools/debian/DEBIAN/postinst @@ -36,7 +36,7 @@ function start_service { } case "$1" in - configure) + configure|upgrade) # Always restart openems /bin/systemctl stop openems diff --git a/tools/debian/etc/systemd/system/openems.service b/tools/debian/etc/systemd/system/openems.service index cb04992892a..e367e7a22a2 100644 --- a/tools/debian/etc/systemd/system/openems.service +++ b/tools/debian/etc/systemd/system/openems.service @@ -7,7 +7,7 @@ User=root Group=root Type=notify WorkingDirectory=/usr/lib/openems -ExecStart=/usr/lib/jvm/temurin-17-jre-armhf/bin/java \ +ExecStart=/usr/lib/jvm/temurin-21-jre-armhf/bin/java \ -Dorg.osgi.framework.storage=/tmp/org.osgi.framework.storage \ -Dosgi.clean=true \ -Dfelix.cm.dir=/etc/openems.d/ \ diff --git a/tools/docker/backend/README.md b/tools/docker/backend/README.md index 259f8b5c14d..a785852113b 100644 --- a/tools/docker/backend/README.md +++ b/tools/docker/backend/README.md @@ -78,3 +78,9 @@ ``` *for UI Image see [ui/README.md](../ui/README.md)* + +# Common Problems and Solutions +``` +ERROR: failed to solve: error from sender: context canceled +``` +When building the Docker image this error may occur because another program is accessing the project files. Try closing these programs (e.g. Eclipse IDE) and run the build command again. \ No newline at end of file diff --git a/tools/docker/edge/README.md b/tools/docker/edge/README.md index 441b234c9a4..2cce0d4d245 100644 --- a/tools/docker/edge/README.md +++ b/tools/docker/edge/README.md @@ -35,4 +35,11 @@ docker build . -t openems_edge -f tools/docker/edge/Dockerfile ``` - *for UI Image see [ui/README.md](../ui/README.md)* \ No newline at end of file + *for UI Image see [ui/README.md](../ui/README.md)* + +# Common Problems and Solutions +``` +ERROR: failed to solve: error from sender: context canceled +``` +When building the Docker image this error may occur because another program is accessing the project files. Try closing these programs (e.g. Eclipse IDE) and run the build command again. + diff --git a/tools/prepare-commit.sh b/tools/prepare-commit.sh index 2f7db41cca9..8e94a4c4133 100755 --- a/tools/prepare-commit.sh +++ b/tools/prepare-commit.sh @@ -78,7 +78,7 @@ EOT - + @@ -150,6 +150,7 @@ head -n $(grep -n '\-runrequires:' $bndrun | grep -Eo '^[^:]+' | head -n1) "$bnd echo " bnd.identity;id='org.ops4j.pax.logging.pax-logging-api',\\" >> "$bndrun.new" echo " bnd.identity;id='org.ops4j.pax.logging.pax-logging-log4j2',\\" >> "$bndrun.new" echo " bnd.identity;id='org.apache.felix.http.jetty',\\" >> "$bndrun.new" +echo " bnd.identity;id='org.apache.felix.http.servlet-api',\\" >> "$bndrun.new" echo " bnd.identity;id='org.apache.felix.webconsole',\\" >> "$bndrun.new" echo " bnd.identity;id='org.apache.felix.webconsole.plugins.ds',\\" >> "$bndrun.new" echo " bnd.identity;id='org.apache.felix.inventory',\\" >> "$bndrun.new" @@ -175,6 +176,7 @@ echo " bnd.identity;id='org.ops4j.pax.logging.pax-logging-api',\\" >> "$bndrun.n echo " bnd.identity;id='org.ops4j.pax.logging.pax-logging-log4j2',\\" >> "$bndrun.new" echo " bnd.identity;id='org.osgi.service.jdbc',\\" >> "$bndrun.new" echo " bnd.identity;id='org.apache.felix.http.jetty',\\" >> "$bndrun.new" +echo " bnd.identity;id='org.apache.felix.http.servlet-api',\\" >> "$bndrun.new" echo " bnd.identity;id='org.apache.felix.webconsole',\\" >> "$bndrun.new" echo " bnd.identity;id='org.apache.felix.webconsole.plugins.ds',\\" >> "$bndrun.new" echo " bnd.identity;id='org.apache.felix.inventory',\\" >> "$bndrun.new" diff --git a/ui/.eslintrc.json b/ui/.eslintrc.json index 8a3902ecddb..94564f9bfc6 100644 --- a/ui/.eslintrc.json +++ b/ui/.eslintrc.json @@ -20,18 +20,44 @@ "createDefaultProgram": true }, "plugins": [ + "import", "unused-imports", - "@stylistic" + "@stylistic", + "check-file" ], "extends": [ "eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:@angular-eslint/recommended", - "plugin:@angular-eslint/template/process-inline-templates" + "plugin:@angular-eslint/template/process-inline-templates", + "plugin:import/recommended" ], "rules": { + "check-file/filename-naming-convention": [ + "off", + { + "**/*.{ts}": "KEBAB_CASE" + } + ], "curly": "error", "unused-imports/no-unused-imports": "error", + "import/order": [ + "error", + { + "groups": [ + "builtin", + "external", + "internal", + "parent", + "sibling", + "index" + ], + "alphabetize": { + "order": "asc", + "caseInsensitive": true + } + } + ], "@typescript-eslint/explicit-member-accessibility": [ "error", { @@ -48,6 +74,7 @@ "@angular-eslint/use-lifecycle-interface": [ "error" ], + "@angular-eslint/prefer-standalone": "off", "@angular-eslint/directive-selector": [ "error", { @@ -79,27 +106,43 @@ ], "@typescript-eslint/no-explicit-any": 0, "@typescript-eslint/no-namespace": 0, - "@typescript-eslint/ban-types": [ - "error", - { - "extendDefaults": true, - "types": { - "{}": false - } - } - ], + "@typescript-eslint/no-restricted-types": 0, "@typescript-eslint/member-ordering": "error", + "@typescript-eslint/no-unused-expressions": "off", + "@typescript-eslint/no-empty-object-type": "off", "@stylistic/no-multiple-empty-lines": "error", "@stylistic/quotes": [ "error", "double" ], - "@typescript-eslint/switch-exhaustiveness-check": [ + "no-restricted-syntax": [ "error", { - "allowDefaultCaseForExhaustiveSwitch": false + "selector": "CallExpression[callee.name='fdescribe']", + "message": "Using 'fdescribe' is not allowed." + }, + { + "selector": "CallExpression[callee.name='xdescribe']", + "message": "Using 'xdescribe' is not allowed." } ] + }, + "overrides": [ + { + "files": [ + "*.component.ts", + "*.service.ts", + "*.module.ts" + ], + "rules": { + "check-file/filename-naming-convention": "off" + } + } + ], + "settings": { + "import/resolver": { + "typescript": {} + } } }, { diff --git a/ui/.project b/ui/.project new file mode 100644 index 00000000000..4f185983a47 --- /dev/null +++ b/ui/.project @@ -0,0 +1,11 @@ + + + fems-ui + + + + + + + + diff --git a/ui/README.md b/ui/README.md index 96ef2800acb..e5a4c386f2d 100644 --- a/ui/README.md +++ b/ui/README.md @@ -65,7 +65,9 @@ This project was generated with [angular-cli](https://github.com/angular/angular > [!IMPORTANT] > Crucial information necessary for users to succeed. Only provide /resources/logo-dark.png and logo.png * Move the files from res(except values and xml) to ```/android/app/src/$theme/``` (```/main``` acts as default) -* Build apps: `gradlew bundle{$theme}Release` +* Build apps (execute in order): + - `NODE_ENV="{$theme}" ./node_modules/.bin/ionic cap build android -c "$theme,$theme-backend-deploy-app" --no-open;` + - `THEME="{$theme}" gradlew bundleThemeRelease` Important (if not generated, can be copied and adjusted from existing theme): - `ui\android\app\src\{$theme}\res\xml\file_paths.xml` @@ -77,6 +79,7 @@ Use `gradlew install{$theme}Release to install it on any device` - Available Tasks: `gradlew tasks` - list available devices + emulators: `$npx native-run android --list --json` +- use Android Studio for Debugging: `$ionic cap open android` ## i18n - internationalization diff --git a/ui/angular.json b/ui/angular.json index e1fba2f0688..ae312552523 100644 --- a/ui/angular.json +++ b/ui/angular.json @@ -8,7 +8,6 @@ "sourceRoot": "src", "projectType": "application", "prefix": "app", - "schematics": {}, "architect": { "build": { "builder": "@angular-devkit/build-angular:browser", @@ -44,7 +43,14 @@ "aot": true, "buildOptimizer": true, "extractLicenses": true, - "optimization": true, + "optimization": { + "scripts": true, + "styles": { + "minify": true, + "inlineCritical": false + }, + "fonts": true + }, "outputHashing": "all", "sourceMap": false, "serviceWorker": true @@ -70,7 +76,8 @@ }, "styles": [ "src/themes/openems/scss/variables.scss", - "src/global.scss" + "src/global.scss", + "src/global-ion-custom.scss" ] }, "openems-backend-dev": { @@ -162,23 +169,23 @@ "serve": { "builder": "@angular-devkit/build-angular:dev-server", "options": { - "browserTarget": "app:build:openems,openems-edge-dev" + "buildTarget": "app:build:openems,openems-edge-dev" }, "configurations": { "openems-backend-dev": { - "browserTarget": "app:build:openems,openems-backend-dev" + "buildTarget": "app:build:openems,openems-backend-dev" }, "openems-edge-dev": { - "browserTarget": "app:build:openems,openems-edge-dev" + "buildTarget": "app:build:openems,openems-edge-dev" }, "openems-backend-prod": { - "browserTarget": "app:build:openems,openems-backend-prod,prod" + "buildTarget": "app:build:openems,openems-backend-prod,prod" }, "openems-edge-prod": { - "browserTarget": "app:build:openems,openems-edge-prod,prod" + "buildTarget": "app:build:openems,openems-edge-prod,prod" }, "openems-gitpod": { - "browserTarget": "app:build:openems,openems-gitpod" + "buildTarget": "app:build:openems,openems-gitpod" } } }, @@ -211,13 +218,5 @@ }, "cli": { "analytics": false - }, - "schematics": { - "@ionic/angular-toolkit:component": { - "styleext": "scss" - }, - "@ionic/angular-toolkit:page": { - "styleext": "scss" - } } } diff --git a/ui/e2e/protractor.conf.js b/ui/e2e/protractor.conf.js deleted file mode 100644 index d23f1d99631..00000000000 --- a/ui/e2e/protractor.conf.js +++ /dev/null @@ -1,37 +0,0 @@ -// @ts-check -// Protractor configuration file, see link for more information -// https://github.com/angular/protractor/blob/master/lib/config.ts - -const { SpecReporter, StacktraceOption } = require('jasmine-spec-reporter'); - -/** - * @type { import("protractor").Config } - */ -exports.config = { - allScriptsTimeout: 11000, - specs: [ - './src/**/*.e2e-spec.ts' - ], - capabilities: { - browserName: 'chrome' - }, - directConnect: true, - SELENIUM_PROMISE_MANAGER: false, - baseUrl: 'http://localhost:4200/', - framework: 'jasmine', - jasmineNodeOpts: { - showColors: true, - defaultTimeoutInterval: 30000, - print: function () { } - }, - onPrepare() { - require('ts-node').register({ - project: require('path').join(__dirname, './tsconfig.json') - }); - jasmine.getEnv().addReporter(new SpecReporter({ - spec: { - displayStacktrace: StacktraceOption.PRETTY - } - })); - } -}; diff --git a/ui/e2e/src/app.e2e-spec.ts b/ui/e2e/src/app.e2e-spec.ts deleted file mode 100644 index 33efa08de0b..00000000000 --- a/ui/e2e/src/app.e2e-spec.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { AppPage } from './app.po'; - -describe('new App', () => { - let page: AppPage; - - beforeEach(() => { - page = new AppPage(); - }); - - it('should be blank', () => { - page.navigateTo(); - expect(page.getParagraphText()).toContain('Start with Ionic UI Components'); - }); -}); diff --git a/ui/e2e/src/app.po.ts b/ui/e2e/src/app.po.ts deleted file mode 100644 index c121fd9b869..00000000000 --- a/ui/e2e/src/app.po.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { browser, by, element } from 'protractor'; - -export class AppPage { - navigateTo() { - return browser.get('/'); - } - - getParagraphText() { - return element(by.deepCss('app-root ion-content')).getText(); - } -} diff --git a/ui/e2e/tsconfig.json b/ui/e2e/tsconfig.json deleted file mode 100644 index a82df00eef3..00000000000 --- a/ui/e2e/tsconfig.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "extends": "../tsconfig.json", - "compilerOptions": { - "outDir": "../out-tsc/e2e", - "module": "commonjs", - "target": "es2018", - "types": [ - "jasmine", - "node" - ] - } -} diff --git a/ui/package-lock.json b/ui/package-lock.json index 2dd132f8d58..98a1ea01792 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -1,95 +1,98 @@ { "name": "openems-ui", - "version": "2024.11.0-SNAPSHOT", + "version": "2025.2.0-SNAPSHOT", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "openems-ui", - "version": "2024.11.0-SNAPSHOT", + "version": "2025.2.0-SNAPSHOT", "license": "AGPL-3.0", "dependencies": { - "@angular/animations": "18.2.5", - "@angular/common": "18.2.5", - "@angular/core": "18.2.5", - "@angular/forms": "18.2.5", - "@angular/platform-browser": "18.2.5", - "@angular/platform-browser-dynamic": "18.2.5", - "@angular/router": "18.2.5", - "@angular/service-worker": "18.2.5", + "@angular/animations": "19.0.5", + "@angular/common": "19.0.5", + "@angular/core": "19.0.5", + "@angular/forms": "19.0.5", + "@angular/platform-browser": "19.0.5", + "@angular/platform-browser-dynamic": "19.0.5", + "@angular/router": "19.0.5", + "@angular/service-worker": "19.0.5", + "@awesome-cordova-plugins/core": "^6.13.0", + "@awesome-cordova-plugins/file-opener": "^6.13.0", "@capacitor-community/file-opener": "^6.0.1", - "@capacitor/android": "^6.1.2", - "@capacitor/app": "^6.0.1", - "@capacitor/core": "^6.1.2", - "@capacitor/filesystem": "^6.0.1", - "@capacitor/ios": "^6.1.2", - "@capacitor/splash-screen": "^6.0.2", - "@ionic-native/core": "^5.36.0", - "@ionic-native/file-opener": "^5.36.0", - "@ionic/angular": "^6.7.5", - "@ngx-formly/core": "^6.3.7", - "@ngx-formly/ionic": "^6.3.7", - "@ngx-formly/schematics": "^6.3.7", - "@ngx-translate/core": "^15.0.0", + "@capacitor/android": "^6.2.0", + "@capacitor/app": "^6.0.2", + "@capacitor/core": "^6.2.0", + "@capacitor/filesystem": "^6.0.2", + "@capacitor/ios": "^6.2.0", + "@capacitor/splash-screen": "^6.0.3", + "@ionic/angular": "^7.8.6", + "@ngx-formly/core": "^6.3.12", + "@ngx-formly/ionic": "^6.3.12", + "@ngx-formly/schematics": "^6.3.12", + "@ngx-translate/core": "^16.0.4", "@nodro7/angular-mydatepicker": "^0.14.0", - "capacitor-blob-writer": "^1.1.17", - "capacitor-ios-autofill-save-password": "^3.0.0", + "capacitor-blob-writer": "^1.1.19", + "capacitor-ios-autofill-save-password": "^4.0.0", "capacitor-secure-storage-plugin": "^0.10.0", - "chart.js": "^4.4.4", + "chart.js": "^4.4.7", "chartjs-adapter-date-fns": "^3.0.0", - "chartjs-plugin-annotation": "^3.0.1", + "chartjs-plugin-annotation": "^3.1.0", "chartjs-plugin-datalabels": "^2.2.0", - "chartjs-plugin-zoom": "^2.0.1", + "chartjs-plugin-zoom": "^2.2.0", "classlist.js": "^1.1.20150312", "compare-versions": "^6.1.1", "d3": "^7.9.0", - "date-fns": "^2.30.0", + "date-fns": "^4.1.0", "file-saver-es": "^2.0.5", - "ng2-charts": "4.1.1", - "ngx-cookie-service": "18.0.0", - "ngx-device-detector": "^8.0.0", - "ngx-spinner": "^16.0.2", + "ng2-charts": "7.0.0", + "ngx-cookie-service": "19.0.0", + "ngx-device-detector": "^9.0.0", + "ngx-spinner": "^17.0.0", "roboto-fontface": "^0.10.0", - "rxjs": "~6.6.7", - "swiper": "11.1.14", - "tslib": "^2.6.2", - "uuid": "^10.0.0", - "zone.js": "~0.14.7" + "rxjs": "~7.8.1", + "swiper": "11.1.15", + "tslib": "^2.8.1", + "uuid": "^11.0.3", + "zone.js": "~0.15.0" }, "devDependencies": { - "@angular-devkit/build-angular": "^18.2.5", - "@angular-devkit/core": "18.2.5", - "@angular-devkit/schematics": "18.2.5", - "@angular-eslint/builder": "^18.3.1", - "@angular-eslint/eslint-plugin": "^18.3.1", - "@angular-eslint/eslint-plugin-template": "^18.3.1", - "@angular-eslint/template-parser": "^18.3.1", - "@angular/cli": "18.2.5", - "@angular/compiler": "18.2.5", - "@angular/compiler-cli": "18.2.5", - "@angular/language-service": "18.2.5", + "@angular-devkit/build-angular": "^19.0.6", + "@angular-devkit/core": "^19.0.6", + "@angular-devkit/schematics": "^19.0.6", + "@angular-eslint/builder": "^19.0.2", + "@angular-eslint/eslint-plugin": "^19.0.2", + "@angular-eslint/eslint-plugin-template": "^19.0.2", + "@angular-eslint/template-parser": "^19.0.2", + "@angular/cli": "^19.0.6", + "@angular/compiler": "19.0.5", + "@angular/compiler-cli": "19.0.5", + "@angular/language-service": "19.0.5", "@capacitor/assets": "^3.0.5", - "@capacitor/cli": "6.1.2", - "@ionic/angular-toolkit": "^11.0.1", + "@capacitor/cli": "6.2.0", + "@ionic/angular-toolkit": "^12.1.1", "@ionic/cli": "^7.2.0", - "@stylistic/eslint-plugin": "^2.8.0", - "@types/jasmine": "~4.3.6", + "@schematics/angular": "^19.0.6", + "@stylistic/eslint-plugin": "^2.12.1", + "@types/jasmine": "~5.1.5", "@types/jasminewd2": "~2.0.13", "@types/json-schema": "^7.0.15", "@types/node": "^20.12.6", - "@types/qs": "^6.9.16", + "@types/qs": "^6.9.17", "@types/range-parser": "^1.2.7", "@types/send": "^0.17.4", "@types/uuid": "^10.0.0", - "@typescript-eslint/eslint-plugin": "^7.0.0", - "@typescript-eslint/parser": "^7.0.0", - "@typescript-eslint/types": "^8.7.0", - "eslint": "^8.57.0", - "eslint-plugin-import": "2.30.0", - "eslint-plugin-jsdoc": "50.2.4", + "@typescript-eslint/eslint-plugin": "^8.19.0", + "@typescript-eslint/parser": "^8.19.0", + "@typescript-eslint/types": "^8.19.0", + "eslint": "^9.17.0", + "eslint-import-resolver-typescript": "^3.7.0", + "eslint-plugin-check-file": "^2.8.0", + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-jsdoc": "50.6.1", "eslint-plugin-prefer-arrow": "1.2.3", "eslint-plugin-unused-imports": "^4.1.4", - "jasmine-core": "~5.3.0", + "jasmine-core": "~5.5.0", "jasmine-spec-reporter": "~7.0.0", "karma": "~6.4.4", "karma-chrome-launcher": "~3.2.0", @@ -97,9 +100,8 @@ "karma-coverage-istanbul-reporter": "~3.0.3", "karma-jasmine": "~5.1.0", "karma-jasmine-html-reporter": "^2.1.0", - "protractor": "~7.0.0", "ts-node": "^10.9.2", - "typescript": "~5.4.5", + "typescript": "~5.6.3", "typescript-strict-plugin": "^2.4.4" } }, @@ -108,7 +110,6 @@ "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" @@ -118,12 +119,13 @@ } }, "node_modules/@angular-devkit/architect": { - "version": "0.1802.5", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1802.5.tgz", - "integrity": "sha512-c7sVoW85Yqj7IYvNKxtNSGS5I7gWpORorg/xxLZX3OkHWXDrwYbb5LN/2p5/Aytxyb0aXl4o5fFOu6CUwcaLUw==", + "version": "0.1900.6", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1900.6.tgz", + "integrity": "sha512-w11bAXQnNWBawTJfQPjvaTRrzrqsOUm9tK9WNvaia/xjiRFpmO0CfmKtn3axNSEJM8jb/czaNQrgTwG+TGc/8g==", "dev": true, + "license": "MIT", "dependencies": { - "@angular-devkit/core": "18.2.5", + "@angular-devkit/core": "19.0.6", "rxjs": "7.8.1" }, "engines": { @@ -132,50 +134,39 @@ "yarn": ">= 1.13.0" } }, - "node_modules/@angular-devkit/architect/node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dev": true, - "dependencies": { - "tslib": "^2.1.0" - } - }, "node_modules/@angular-devkit/build-angular": { - "version": "18.2.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-18.2.6.tgz", - "integrity": "sha512-u12cJZttgs5j7gICHWSmcaTCu0EFXEzKqI8nkYCwq2MtuJlAXiMQSXYuEP9OU3Go4vMAPtQh2kShyOWCX5b4EQ==", + "version": "19.0.6", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-19.0.6.tgz", + "integrity": "sha512-dWTAsE6BSI8z0xglQdYBdqTBwg1Q+RWE3OrmlGs+520Dcoq/F0Z41Y1F3MiuHuQPdDAIQr88iB0APkIRW4clMg==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.1802.6", - "@angular-devkit/build-webpack": "0.1802.6", - "@angular-devkit/core": "18.2.6", - "@angular/build": "18.2.6", - "@babel/core": "7.25.2", - "@babel/generator": "7.25.0", - "@babel/helper-annotate-as-pure": "7.24.7", + "@angular-devkit/architect": "0.1900.6", + "@angular-devkit/build-webpack": "0.1900.6", + "@angular-devkit/core": "19.0.6", + "@angular/build": "19.0.6", + "@babel/core": "7.26.0", + "@babel/generator": "7.26.2", + "@babel/helper-annotate-as-pure": "7.25.9", "@babel/helper-split-export-declaration": "7.24.7", - "@babel/plugin-transform-async-generator-functions": "7.25.0", - "@babel/plugin-transform-async-to-generator": "7.24.7", - "@babel/plugin-transform-runtime": "7.24.7", - "@babel/preset-env": "7.25.3", - "@babel/runtime": "7.25.0", - "@discoveryjs/json-ext": "0.6.1", - "@ngtools/webpack": "18.2.6", + "@babel/plugin-transform-async-generator-functions": "7.25.9", + "@babel/plugin-transform-async-to-generator": "7.25.9", + "@babel/plugin-transform-runtime": "7.25.9", + "@babel/preset-env": "7.26.0", + "@babel/runtime": "7.26.0", + "@discoveryjs/json-ext": "0.6.3", + "@ngtools/webpack": "19.0.6", "@vitejs/plugin-basic-ssl": "1.1.0", "ansi-colors": "4.1.3", "autoprefixer": "10.4.20", - "babel-loader": "9.1.3", + "babel-loader": "9.2.1", "browserslist": "^4.21.5", "copy-webpack-plugin": "12.0.2", - "critters": "0.0.24", "css-loader": "7.1.2", - "esbuild-wasm": "0.23.0", + "esbuild-wasm": "0.24.0", "fast-glob": "3.3.2", - "http-proxy-middleware": "3.0.0", - "https-proxy-agent": "7.0.5", + "http-proxy-middleware": "3.0.3", "istanbul-lib-instrument": "6.0.3", "jsonc-parser": "3.3.1", "karma-source-map-support": "1.4.0", @@ -183,31 +174,26 @@ "less-loader": "12.2.0", "license-webpack-plugin": "4.0.2", "loader-utils": "3.3.1", - "magic-string": "0.30.11", - "mini-css-extract-plugin": "2.9.0", - "mrmime": "2.0.0", + "mini-css-extract-plugin": "2.9.2", "open": "10.1.0", "ora": "5.4.1", - "parse5-html-rewriting-stream": "7.0.0", "picomatch": "4.0.2", - "piscina": "4.6.1", - "postcss": "8.4.41", + "piscina": "4.7.0", + "postcss": "8.4.49", "postcss-loader": "8.1.1", "resolve-url-loader": "5.0.0", "rxjs": "7.8.1", - "sass": "1.77.6", - "sass-loader": "16.0.0", + "sass": "1.80.7", + "sass-loader": "16.0.3", "semver": "7.6.3", "source-map-loader": "5.0.0", "source-map-support": "0.5.21", - "terser": "5.31.6", + "terser": "5.36.0", "tree-kill": "1.2.2", - "tslib": "2.6.3", - "vite": "5.4.6", - "watchpack": "2.4.1", - "webpack": "5.94.0", + "tslib": "2.8.1", + "webpack": "5.96.1", "webpack-dev-middleware": "7.4.2", - "webpack-dev-server": "5.0.4", + "webpack-dev-server": "5.1.0", "webpack-merge": "6.0.1", "webpack-subresource-integrity": "5.1.0" }, @@ -217,22 +203,23 @@ "yarn": ">= 1.13.0" }, "optionalDependencies": { - "esbuild": "0.23.0" + "esbuild": "0.24.0" }, "peerDependencies": { - "@angular/compiler-cli": "^18.0.0", - "@angular/localize": "^18.0.0", - "@angular/platform-server": "^18.0.0", - "@angular/service-worker": "^18.0.0", - "@web/test-runner": "^0.18.0", + "@angular/compiler-cli": "^19.0.0", + "@angular/localize": "^19.0.0", + "@angular/platform-server": "^19.0.0", + "@angular/service-worker": "^19.0.0", + "@angular/ssr": "^19.0.6", + "@web/test-runner": "^0.19.0", "browser-sync": "^3.0.2", "jest": "^29.5.0", "jest-environment-jsdom": "^29.5.0", "karma": "^6.3.0", - "ng-packagr": "^18.0.0", + "ng-packagr": "^19.0.0", "protractor": "^7.0.0", "tailwindcss": "^2.0.0 || ^3.0.0", - "typescript": ">=5.4 <5.6" + "typescript": ">=5.5 <5.7" }, "peerDependenciesMeta": { "@angular/localize": { @@ -244,6 +231,9 @@ "@angular/service-worker": { "optional": true }, + "@angular/ssr": { + "optional": true + }, "@web/test-runner": { "optional": true }, @@ -270,28 +260,31 @@ } } }, - "node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/architect": { - "version": "0.1802.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1802.6.tgz", - "integrity": "sha512-oF7cPFdTLxeuvXkK/opSdIxZ1E4LrBbmuytQ/nCoAGOaKBWdqvwagRZ6jVhaI0Gwu48rkcV7Zhesg/ESNnROdw==", + "node_modules/@angular-devkit/build-webpack": { + "version": "0.1900.6", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1900.6.tgz", + "integrity": "sha512-WehtVrbBow4fc7hsaUKb+BZ6MDE5lO98/tgv7GR5PkRdGKnyLA0pW1AfPLJJQDgcaKjneramMhDFNc1eGSX0mQ==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "18.2.6", + "@angular-devkit/architect": "0.1900.6", "rxjs": "7.8.1" }, "engines": { "node": "^18.19.1 || ^20.11.1 || >=22.0.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "webpack": "^5.30.0", + "webpack-dev-server": "^5.0.2" } }, - "node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/core": { - "version": "18.2.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.6.tgz", - "integrity": "sha512-la4CFvs5PcRWSkQ/H7TB5cPZirFVA9GoWk5LzIk8si6VjWBJRm8b3keKJoC9LlNeABRUIR5z0ocYkyQQUhdMfg==", + "node_modules/@angular-devkit/core": { + "version": "19.0.6", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.0.6.tgz", + "integrity": "sha512-WUWJhzQDsovfMY6jtb9Ktz/5sJszsaErj+XV2aXab85f1OweI/Iv2urPZnJwUSilvVN5Ok/fy3IJ6SuihK4Ceg==", "dev": true, - "license": "MIT", "dependencies": { "ajv": "8.17.1", "ajv-formats": "3.0.1", @@ -306,7 +299,7 @@ "yarn": ">= 1.13.0" }, "peerDependencies": { - "chokidar": "^3.5.2" + "chokidar": "^4.0.0" }, "peerDependenciesMeta": { "chokidar": { @@ -314,53 +307,172 @@ } } }, - "node_modules/@angular-devkit/build-angular/node_modules/@angular/build": { - "version": "18.2.6", - "resolved": "https://registry.npmjs.org/@angular/build/-/build-18.2.6.tgz", - "integrity": "sha512-TQzX6Mi7uXFvmz7+OVl4Za7WawYPcx+B5Ewm6IY/DdMyB9P/Z4tbKb1LO+ynWUXYwm7avXo6XQQ4m5ArDY5F/A==", + "node_modules/@angular-devkit/schematics": { + "version": "19.0.6", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-19.0.6.tgz", + "integrity": "sha512-R9hlHfAh1HKoIWgnYJlOEKhUezhTNl0fpUmHxG2252JSY5FLRxmYArTtJYYmbNdBbsBLNg3UHyM/GBPvJSA3NQ==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "19.0.6", + "jsonc-parser": "3.3.1", + "magic-string": "0.30.12", + "ora": "5.4.1", + "rxjs": "7.8.1" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular-eslint/builder": { + "version": "19.0.2", + "resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-19.0.2.tgz", + "integrity": "sha512-BdmMSndQt2fSBiTVniskUcUpQaeweUapbsL0IDfQ7a13vL0NVXpc3K89YXuVE/xsb08uHtqphuwxPAAj6kX3OA==", + "dev": true, + "dependencies": { + "@angular-devkit/architect": ">= 0.1900.0 < 0.2000.0", + "@angular-devkit/core": ">= 19.0.0 < 20.0.0" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/bundled-angular-compiler": { + "version": "19.0.2", + "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-19.0.2.tgz", + "integrity": "sha512-HPmp92r70SNO/0NdIaIhxrgVSpomqryuUk7jszvNRtu+OzYCJGcbLhQD38T3dbBWT/AV0QXzyzExn6/2ai9fEw==", + "dev": true + }, + "node_modules/@angular-eslint/eslint-plugin": { + "version": "19.0.2", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-19.0.2.tgz", + "integrity": "sha512-DLuNVVGGFicSThOcMSJyNje+FZSPdG0B3lCBRiqcgKH/16kfM4pV8MobPM7RGK2NhaOmmZ4zzJNwpwWPSgi+Lw==", + "dev": true, + "dependencies": { + "@angular-eslint/bundled-angular-compiler": "19.0.2", + "@angular-eslint/utils": "19.0.2" + }, + "peerDependencies": { + "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/eslint-plugin-template": { + "version": "19.0.2", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-19.0.2.tgz", + "integrity": "sha512-f/OCF9ThnxQ8m0eNYPwnCrySQPhYfCOF6STL7F9LnS8Bs3ZeW3/oT1yLaMIZ1Eg0ogIkgxksMAJZjrJPUPBD1Q==", + "dev": true, + "dependencies": { + "@angular-eslint/bundled-angular-compiler": "19.0.2", + "@angular-eslint/utils": "19.0.2", + "aria-query": "5.3.2", + "axobject-query": "4.1.0" + }, + "peerDependencies": { + "@typescript-eslint/types": "^7.11.0 || ^8.0.0", + "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/template-parser": { + "version": "19.0.2", + "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-19.0.2.tgz", + "integrity": "sha512-z3rZd2sBfuYcFf9rGDsB2zz2fbGX8kkF+0ftg9eocyQmzWrlZHFmuw9ha7oP/Mz8gpblyCS/aa1U/Srs6gz0UQ==", + "dev": true, + "dependencies": { + "@angular-eslint/bundled-angular-compiler": "19.0.2", + "eslint-scope": "^8.0.2" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/utils": { + "version": "19.0.2", + "resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-19.0.2.tgz", + "integrity": "sha512-HotBT8OKr7zCaX1S9k27JuhRiTVIbbYVl6whlb3uwdMIPIWY8iOcEh1tjI4qDPUafpLfR72Dhwi5bO1E17F3/Q==", + "dev": true, + "dependencies": { + "@angular-eslint/bundled-angular-compiler": "19.0.2" + }, + "peerDependencies": { + "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": "*" + } + }, + "node_modules/@angular/animations": { + "version": "19.0.5", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-19.0.5.tgz", + "integrity": "sha512-HCOF2CrhUvjoZWusd4nh32VOxpUrg6bV+3Z8Q36Ix3aZdni8v0qoP2rl5wGbotaPtYg5RtyDH60Z2AOPKqlrZg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" + }, + "peerDependencies": { + "@angular/core": "19.0.5" + } + }, + "node_modules/@angular/build": { + "version": "19.0.6", + "resolved": "https://registry.npmjs.org/@angular/build/-/build-19.0.6.tgz", + "integrity": "sha512-KEVNLgTZUF2dfpOYQn+yR2HONHUTxq/2rFVhiK9qAvrm/m+uKJNEXx7hGtbRyoqenZff4ScJq+7feITUldfX8g==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.1802.6", - "@babel/core": "7.25.2", - "@babel/helper-annotate-as-pure": "7.24.7", + "@angular-devkit/architect": "0.1900.6", + "@babel/core": "7.26.0", + "@babel/helper-annotate-as-pure": "7.25.9", "@babel/helper-split-export-declaration": "7.24.7", - "@babel/plugin-syntax-import-attributes": "7.24.7", - "@inquirer/confirm": "3.1.22", + "@babel/plugin-syntax-import-attributes": "7.26.0", + "@inquirer/confirm": "5.0.2", "@vitejs/plugin-basic-ssl": "1.1.0", + "beasties": "0.1.0", "browserslist": "^4.23.0", - "critters": "0.0.24", - "esbuild": "0.23.0", + "esbuild": "0.24.0", "fast-glob": "3.3.2", "https-proxy-agent": "7.0.5", - "listr2": "8.2.4", - "lmdb": "3.0.13", - "magic-string": "0.30.11", + "istanbul-lib-instrument": "6.0.3", + "listr2": "8.2.5", + "magic-string": "0.30.12", "mrmime": "2.0.0", "parse5-html-rewriting-stream": "7.0.0", "picomatch": "4.0.2", - "piscina": "4.6.1", - "rollup": "4.22.4", - "sass": "1.77.6", + "piscina": "4.7.0", + "rollup": "4.26.0", + "sass": "1.80.7", "semver": "7.6.3", - "vite": "5.4.6", - "watchpack": "2.4.1" + "vite": "5.4.11", + "watchpack": "2.4.2" }, "engines": { "node": "^18.19.1 || ^20.11.1 || >=22.0.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" }, + "optionalDependencies": { + "lmdb": "3.1.5" + }, "peerDependencies": { - "@angular/compiler-cli": "^18.0.0", - "@angular/localize": "^18.0.0", - "@angular/platform-server": "^18.0.0", - "@angular/service-worker": "^18.0.0", + "@angular/compiler": "^19.0.0", + "@angular/compiler-cli": "^19.0.0", + "@angular/localize": "^19.0.0", + "@angular/platform-server": "^19.0.0", + "@angular/service-worker": "^19.0.0", + "@angular/ssr": "^19.0.6", "less": "^4.2.0", "postcss": "^8.4.0", "tailwindcss": "^2.0.0 || ^3.0.0", - "typescript": ">=5.4 <5.6" + "typescript": ">=5.5 <5.7" }, "peerDependenciesMeta": { "@angular/localize": { @@ -372,6 +484,9 @@ "@angular/service-worker": { "optional": true }, + "@angular/ssr": { + "optional": true + }, "less": { "optional": true }, @@ -383,644 +498,156 @@ } } }, - "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.4.tgz", - "integrity": "sha512-Fxamp4aEZnfPOcGA8KSNEohV8hX7zVHOemC8jVBoBUHu5zpJK/Eu3uJwt6BMgy9fkvzxDaurgj96F/NiLukF2w==", - "cpu": [ - "arm" - ], + "node_modules/@angular/build/node_modules/@inquirer/confirm": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.0.2.tgz", + "integrity": "sha512-KJLUHOaKnNCYzwVbryj3TNBxyZIrr56fR5N45v6K9IPrbT6B7DcudBMfylkV1A8PUdJE15mybkEQyp2/ZUpxUA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ] + "dependencies": { + "@inquirer/core": "^10.1.0", + "@inquirer/type": "^3.0.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + } }, - "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-android-arm64": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.4.tgz", - "integrity": "sha512-VXoK5UMrgECLYaMuGuVTOx5kcuap1Jm8g/M83RnCHBKOqvPPmROFJGQaZhGccnsFtfXQ3XYa4/jMCJvZnbJBdA==", - "cpu": [ - "arm64" - ], + "node_modules/@angular/build/node_modules/https-proxy-agent": { + "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, "license": "MIT", - "optional": true, - "os": [ - "android" - ] + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } }, - "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.4.tgz", - "integrity": "sha512-xMM9ORBqu81jyMKCDP+SZDhnX2QEVQzTcC6G18KlTQEzWK8r/oNZtKuZaCcHhnsa6fEeOBionoyl5JsAbE/36Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] + "node_modules/@angular/cdk": { + "version": "19.0.3", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-19.0.3.tgz", + "integrity": "sha512-sPdIKbSgNk4z02FqdTTMUS62aLVA2R/DsnOk3qdH+nEfeS4nNWQEzwrvMf6dDsTeLQ6YJLWXfZfemsGYpOoiWg==", + "peer": true, + "dependencies": { + "tslib": "^2.3.0" + }, + "optionalDependencies": { + "parse5": "^7.1.2" + }, + "peerDependencies": { + "@angular/common": "^19.0.0 || ^20.0.0", + "@angular/core": "^19.0.0 || ^20.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } }, - "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-darwin-x64": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.4.tgz", - "integrity": "sha512-aJJyYKQwbHuhTUrjWjxEvGnNNBCnmpHDvrb8JFDbeSH3m2XdHcxDd3jthAzvmoI8w/kSjd2y0udT+4okADsZIw==", - "cpu": [ - "x64" - ], + "node_modules/@angular/cli": { + "version": "19.0.6", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-19.0.6.tgz", + "integrity": "sha512-ZEHhgRRVIdn10dbsAjB8TE9Co32hfuL9/im5Jcfa1yrn6KJefmigz6KN8Xu7FXMH5FkdqfQ11QpLBxJSPb9aww==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] + "dependencies": { + "@angular-devkit/architect": "0.1900.6", + "@angular-devkit/core": "19.0.6", + "@angular-devkit/schematics": "19.0.6", + "@inquirer/prompts": "7.1.0", + "@listr2/prompt-adapter-inquirer": "2.0.18", + "@schematics/angular": "19.0.6", + "@yarnpkg/lockfile": "1.1.0", + "ini": "5.0.0", + "jsonc-parser": "3.3.1", + "listr2": "8.2.5", + "npm-package-arg": "12.0.0", + "npm-pick-manifest": "10.0.0", + "pacote": "20.0.0", + "resolve": "1.22.8", + "semver": "7.6.3", + "symbol-observable": "4.0.0", + "yargs": "17.7.2" + }, + "bin": { + "ng": "bin/ng.js" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } }, - "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.4.tgz", - "integrity": "sha512-j63YtCIRAzbO+gC2L9dWXRh5BFetsv0j0va0Wi9epXDgU/XUi5dJKo4USTttVyK7fGw2nPWK0PbAvyliz50SCQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "node_modules/@angular/common": { + "version": "19.0.5", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-19.0.5.tgz", + "integrity": "sha512-fFK+euCj1AjBHBCpj9VnduMSeqoMRhZZHbhPYiND7tucRRJ8vwGU0sYK2KI/Ko+fsrNIXL/0O4F36jVPl09Smg==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" + }, + "peerDependencies": { + "@angular/core": "19.0.5", + "rxjs": "^6.5.3 || ^7.4.0" + } }, - "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.4.tgz", - "integrity": "sha512-dJnWUgwWBX1YBRsuKKMOlXCzh2Wu1mlHzv20TpqEsfdZLb3WoJW2kIEsGwLkroYf24IrPAvOT/ZQ2OYMV6vlrg==", - "cpu": [ - "arm" - ], - "dev": true, + "node_modules/@angular/compiler": { + "version": "19.0.5", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-19.0.5.tgz", + "integrity": "sha512-S8ku5Ljp0kqX3shfmE9DVo09629jeYJSlBRGbj2Glb92dd+VQZPOz7KxqKRTwmAl7lQIV/+4Lr6G/GVTsoC4vg==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" + }, + "peerDependencies": { + "@angular/core": "19.0.5" + }, + "peerDependenciesMeta": { + "@angular/core": { + "optional": true + } + } }, - "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.4.tgz", - "integrity": "sha512-AdPRoNi3NKVLolCN/Sp4F4N1d98c4SBnHMKoLuiG6RXgoZ4sllseuGioszumnPGmPM2O7qaAX/IJdeDU8f26Aw==", - "cpu": [ - "arm64" - ], + "node_modules/@angular/compiler-cli": { + "version": "19.0.5", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-19.0.5.tgz", + "integrity": "sha512-KSzuWCTZlvJsoAenxM9cjTOzNM8mrFxDBInj0KVPz7QU83amGS4rcv1pWO/QGYQcErfskcN84TAdMegaRWWCmA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@babel/core": "7.26.0", + "@jridgewell/sourcemap-codec": "^1.4.14", + "chokidar": "^4.0.0", + "convert-source-map": "^1.5.1", + "reflect-metadata": "^0.2.0", + "semver": "^7.0.0", + "tslib": "^2.3.0", + "yargs": "^17.2.1" + }, + "bin": { + "ng-xi18n": "bundles/src/bin/ng_xi18n.js", + "ngc": "bundles/src/bin/ngc.js", + "ngcc": "bundles/ngcc/index.js" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" + }, + "peerDependencies": { + "@angular/compiler": "19.0.5", + "typescript": ">=5.5 <5.7" + } }, - "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.4.tgz", - "integrity": "sha512-Gl0AxBtDg8uoAn5CCqQDMqAx22Wx22pjDOjBdmG0VIWX3qUBHzYmOKh8KXHL4UpogfJ14G4wk16EQogF+v8hmA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.4.tgz", - "integrity": "sha512-3aVCK9xfWW1oGQpTsYJJPF6bfpWfhbRnhdlyhak2ZiyFLDaayz0EP5j9V1RVLAAxlmWKTDfS9wyRyY3hvhPoOg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.4.tgz", - "integrity": "sha512-ePYIir6VYnhgv2C5Xe9u+ico4t8sZWXschR6fMgoPUK31yQu7hTEJb7bCqivHECwIClJfKgE7zYsh1qTP3WHUA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.4.tgz", - "integrity": "sha512-GqFJ9wLlbB9daxhVlrTe61vJtEY99/xB3C8e4ULVsVfflcpmR6c8UZXjtkMA6FhNONhj2eA5Tk9uAVw5orEs4Q==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.4.tgz", - "integrity": "sha512-87v0ol2sH9GE3cLQLNEy0K/R0pz1nvg76o8M5nhMR0+Q+BBGLnb35P0fVz4CQxHYXaAOhE8HhlkaZfsdUOlHwg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.4.tgz", - "integrity": "sha512-UV6FZMUgePDZrFjrNGIWzDo/vABebuXBhJEqrHxrGiU6HikPy0Z3LfdtciIttEUQfuDdCn8fqh7wiFJjCNwO+g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.4.tgz", - "integrity": "sha512-BjI+NVVEGAXjGWYHz/vv0pBqfGoUH0IGZ0cICTn7kB9PyjrATSkX+8WkguNjWoj2qSr1im/+tTGRaY+4/PdcQw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.4.tgz", - "integrity": "sha512-SiWG/1TuUdPvYmzmYnmd3IEifzR61Tragkbx9D3+R8mzQqDBz8v+BvZNDlkiTtI9T15KYZhP0ehn3Dld4n9J5g==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.4.tgz", - "integrity": "sha512-j8pPKp53/lq9lMXN57S8cFz0MynJk8OWNuUnXct/9KCpKU7DgU3bYMJhwWmcqC0UU29p8Lr0/7KEVcaM6bf47Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@angular-devkit/build-angular/node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@angular-devkit/build-angular/node_modules/rollup": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.4.tgz", - "integrity": "sha512-vD8HJ5raRcWOyymsR6Z3o6+RzfEPCnVLMFJ6vRslO1jt4LO6dUo5Qnpg7y4RkZFM2DMe3WUirkI5c16onjrc6A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.5" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.22.4", - "@rollup/rollup-android-arm64": "4.22.4", - "@rollup/rollup-darwin-arm64": "4.22.4", - "@rollup/rollup-darwin-x64": "4.22.4", - "@rollup/rollup-linux-arm-gnueabihf": "4.22.4", - "@rollup/rollup-linux-arm-musleabihf": "4.22.4", - "@rollup/rollup-linux-arm64-gnu": "4.22.4", - "@rollup/rollup-linux-arm64-musl": "4.22.4", - "@rollup/rollup-linux-powerpc64le-gnu": "4.22.4", - "@rollup/rollup-linux-riscv64-gnu": "4.22.4", - "@rollup/rollup-linux-s390x-gnu": "4.22.4", - "@rollup/rollup-linux-x64-gnu": "4.22.4", - "@rollup/rollup-linux-x64-musl": "4.22.4", - "@rollup/rollup-win32-arm64-msvc": "4.22.4", - "@rollup/rollup-win32-ia32-msvc": "4.22.4", - "@rollup/rollup-win32-x64-msvc": "4.22.4", - "fsevents": "~2.3.2" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dev": true, - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/tslib": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", - "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", - "dev": true - }, - "node_modules/@angular-devkit/build-webpack": { - "version": "0.1802.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1802.6.tgz", - "integrity": "sha512-JMLcXFaitJplwZMKkqhbYirINCRD6eOPZuIGaIOVynXYGWgvJkLT9t5C2wm9HqSLtp1K7NcYG2Y7PtTVR4krnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@angular-devkit/architect": "0.1802.6", - "rxjs": "7.8.1" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "webpack": "^5.30.0", - "webpack-dev-server": "^5.0.2" - } - }, - "node_modules/@angular-devkit/build-webpack/node_modules/@angular-devkit/architect": { - "version": "0.1802.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1802.6.tgz", - "integrity": "sha512-oF7cPFdTLxeuvXkK/opSdIxZ1E4LrBbmuytQ/nCoAGOaKBWdqvwagRZ6jVhaI0Gwu48rkcV7Zhesg/ESNnROdw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@angular-devkit/core": "18.2.6", - "rxjs": "7.8.1" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - } - }, - "node_modules/@angular-devkit/build-webpack/node_modules/@angular-devkit/core": { - "version": "18.2.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.6.tgz", - "integrity": "sha512-la4CFvs5PcRWSkQ/H7TB5cPZirFVA9GoWk5LzIk8si6VjWBJRm8b3keKJoC9LlNeABRUIR5z0ocYkyQQUhdMfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "8.17.1", - "ajv-formats": "3.0.1", - "jsonc-parser": "3.3.1", - "picomatch": "4.0.2", - "rxjs": "7.8.1", - "source-map": "0.7.4" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "chokidar": "^3.5.2" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } - } - }, - "node_modules/@angular-devkit/build-webpack/node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/@angular-devkit/core": { - "version": "18.2.5", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.5.tgz", - "integrity": "sha512-r9TumPlJ8PvA2+yz4sp+bUHgtznaVKzhvXTN5qL1k4YP8LJ7iZWMR2FOP+HjukHZOTsenzmV9pszbogabqwoZQ==", - "dev": true, - "dependencies": { - "ajv": "8.17.1", - "ajv-formats": "3.0.1", - "jsonc-parser": "3.3.1", - "picomatch": "4.0.2", - "rxjs": "7.8.1", - "source-map": "0.7.4" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "chokidar": "^3.5.2" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } - } - }, - "node_modules/@angular-devkit/core/node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/@angular-devkit/schematics": { - "version": "18.2.5", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-18.2.5.tgz", - "integrity": "sha512-NUmz2UQ1Xl4cf4j1AgkwIfsCjBzAPgfeC3IBrD29hSOBE1Y3j6auqjBkvw50v6mbSPxESND995Xy13HpK1Xflw==", - "dev": true, - "dependencies": { - "@angular-devkit/core": "18.2.5", - "jsonc-parser": "3.3.1", - "magic-string": "0.30.11", - "ora": "5.4.1", - "rxjs": "7.8.1" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - } - }, - "node_modules/@angular-devkit/schematics/node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/@angular-eslint/builder": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-18.3.1.tgz", - "integrity": "sha512-cPc7Ye9zDs5M4i+feL6vob+mh7yX5vxvOS5KQIhneUrp5e9D+IGuNFMmBLlOPpmklSc9XJBtuvI5Zjuh4z1ETw==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": "*" - } - }, - "node_modules/@angular-eslint/bundled-angular-compiler": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-18.3.1.tgz", - "integrity": "sha512-sikmkjfsXPpPTku1aQkQ1MNNEKGBgGGRvUN/WeNS9dhCJ4dxU3O7dZctt1aQWj+W3nbuUtDiimAWF5fZHGFE2Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/@angular-eslint/eslint-plugin": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-18.3.1.tgz", - "integrity": "sha512-MP4Nm+SHboF8KdnN0KpPEGAaTTzDLPm3+S/4W3Mg8onqWCyadyd4mActh9mK/pvCj8TVlb/SW1zeTtdMYhwonw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@angular-eslint/bundled-angular-compiler": "18.3.1", - "@angular-eslint/utils": "18.3.1" - }, - "peerDependencies": { - "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", - "eslint": "^8.57.0 || ^9.0.0", - "typescript": "*" - } - }, - "node_modules/@angular-eslint/eslint-plugin-template": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-18.3.1.tgz", - "integrity": "sha512-hBJ3+f7VSidvrtYaXH7Vp0sWvblA9jLK2c6uQzhYGWdEDUcTg7g7VI9ThW39WvMbHqkyzNE4PPOynK69cBEDGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@angular-eslint/bundled-angular-compiler": "18.3.1", - "@angular-eslint/utils": "18.3.1", - "aria-query": "5.3.0", - "axobject-query": "4.1.0" - }, - "peerDependencies": { - "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", - "eslint": "^8.57.0 || ^9.0.0", - "typescript": "*" - } - }, - "node_modules/@angular-eslint/template-parser": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-18.3.1.tgz", - "integrity": "sha512-JUUkfWH1G+u/Uk85ZYvJSt/qwN/Ko+jlXFtzBEcknJZsTWTwBcp36v77gPZe5FmKSziJZpyPUd+7Kiy6tuSCTw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@angular-eslint/bundled-angular-compiler": "18.3.1", - "eslint-scope": "^8.0.2" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": "*" - } - }, - "node_modules/@angular-eslint/utils": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-18.3.1.tgz", - "integrity": "sha512-sd9niZI7h9H2FQ7OLiQsLFBhjhRQTASh+Q0+4+hyjv9idbSHBJli8Gsi2fqj9zhtMKpAZFTrWzuLUpubJ9UYbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@angular-eslint/bundled-angular-compiler": "18.3.1" - }, - "peerDependencies": { - "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", - "eslint": "^8.57.0 || ^9.0.0", - "typescript": "*" - } - }, - "node_modules/@angular/animations": { - "version": "18.2.5", - "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-18.2.5.tgz", - "integrity": "sha512-IlXtW/Nj48ZzjHUzH1TykZcSR64ScJx39T3IHnjV2z/bVATzZ36JGoadQHdqpJNKBodYJNgtJCGLCbgAvGWY2g==", - "license": "MIT", - "dependencies": { - "tslib": "^2.3.0" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0" - }, - "peerDependencies": { - "@angular/core": "18.2.5" - } - }, - "node_modules/@angular/cdk": { - "version": "18.2.1", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-18.2.1.tgz", - "integrity": "sha512-6y4MmpEPXze6igUHkLsBUPkxw32F8+rmW0xVXZchkSyGlFgqfh53ueXoryWb0qL4s5enkNY6AzXnKAqHfPNkVQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "tslib": "^2.3.0" - }, - "optionalDependencies": { - "parse5": "^7.1.2" - }, - "peerDependencies": { - "@angular/common": "^18.0.0 || ^19.0.0", - "@angular/core": "^18.0.0 || ^19.0.0", - "rxjs": "^6.5.3 || ^7.4.0" - } - }, - "node_modules/@angular/cli": { - "version": "18.2.5", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-18.2.5.tgz", - "integrity": "sha512-97uNs0HsOdnMaTlNJKFjIBUXw0wz43uYvSSKmIpBt7eq1LaPLju1G/qpDIHx2YwhMClPrXXrW2H/xdvqZiIw+w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@angular-devkit/architect": "0.1802.5", - "@angular-devkit/core": "18.2.5", - "@angular-devkit/schematics": "18.2.5", - "@inquirer/prompts": "5.3.8", - "@listr2/prompt-adapter-inquirer": "2.0.15", - "@schematics/angular": "18.2.5", - "@yarnpkg/lockfile": "1.1.0", - "ini": "4.1.3", - "jsonc-parser": "3.3.1", - "listr2": "8.2.4", - "npm-package-arg": "11.0.3", - "npm-pick-manifest": "9.1.0", - "pacote": "18.0.6", - "resolve": "1.22.8", - "semver": "7.6.3", - "symbol-observable": "4.0.0", - "yargs": "17.7.2" - }, - "bin": { - "ng": "bin/ng.js" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - } - }, - "node_modules/@angular/common": { - "version": "18.2.5", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-18.2.5.tgz", - "integrity": "sha512-m+KJrtbFXTE36jP/po6UAMeUR/enQxRHpVGLCRcIcE7VWVH1ZcOvoW1yqh2A6k+KxWXeajlq/Z04nnMhcoxMRw==", - "dependencies": { - "tslib": "^2.3.0" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0" - }, - "peerDependencies": { - "@angular/core": "18.2.5", - "rxjs": "^6.5.3 || ^7.4.0" - } - }, - "node_modules/@angular/compiler": { - "version": "18.2.5", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-18.2.5.tgz", - "integrity": "sha512-vcqe9x4dGGAnMfPhEpcZyiSVgAiqJeK80LqP1vWoAmBR+HeOqAilSv6SflcLAtuTzwgzMMAvD2T+SMCgUvaqww==", - "license": "MIT", - "dependencies": { - "tslib": "^2.3.0" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0" - }, - "peerDependencies": { - "@angular/core": "18.2.5" - }, - "peerDependenciesMeta": { - "@angular/core": { - "optional": true - } - } - }, - "node_modules/@angular/compiler-cli": { - "version": "18.2.5", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-18.2.5.tgz", - "integrity": "sha512-CCCtZobUTUfId/RTYtuDCw5R1oK0w65hdAUMRP1MdGmd8bb8DKJA86u1QCWwozL3rbXlIIX4ognQ6urQ43k/Gw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "7.25.2", - "@jridgewell/sourcemap-codec": "^1.4.14", - "chokidar": "^3.0.0", - "convert-source-map": "^1.5.1", - "reflect-metadata": "^0.2.0", - "semver": "^7.0.0", - "tslib": "^2.3.0", - "yargs": "^17.2.1" - }, - "bin": { - "ng-xi18n": "bundles/src/bin/ng_xi18n.js", - "ngc": "bundles/src/bin/ngc.js", - "ngcc": "bundles/ngcc/index.js" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0" - }, - "peerDependencies": { - "@angular/compiler": "18.2.5", - "typescript": ">=5.4 <5.6" - } - }, - "node_modules/@angular/core": { - "version": "18.2.5", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-18.2.5.tgz", - "integrity": "sha512-5BLVc5gXxzanQkADNS9WPsor3vNF5nQcyIHBi5VScErwM5vVZ7ATH1iZwaOg1ykDEVTFVhKDwD0X1aaqGDbhmQ==", + "node_modules/@angular/core": { + "version": "19.0.5", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-19.0.5.tgz", + "integrity": "sha512-Ywc6sPO6G/Y1stfk3y/MallV/h0yzQ0vdOHRWueLrk5kD1DTdbolV4X03Cs3PuVvravgcSVE3nnuuHFuH32emQ==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -1030,13 +657,13 @@ }, "peerDependencies": { "rxjs": "^6.5.3 || ^7.4.0", - "zone.js": "~0.14.10" + "zone.js": "~0.15.0" } }, "node_modules/@angular/forms": { - "version": "18.2.5", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-18.2.5.tgz", - "integrity": "sha512-ohKeH+EZCCIyGSiFYlraWLzssGAZc13P92cuYpXB62322PkcA5u0IT72mML9JWGKRqF2zteVsw4koWHVxXM5mA==", + "version": "19.0.5", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-19.0.5.tgz", + "integrity": "sha512-OhNFkfOoguqCDq07vNBV28FFrmTM8S11Z3Cd6PQZJJF9TgAtpV5KtF7A3eXBCN92W4pmqluomPjfK7YyImzIYQ==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -1045,16 +672,16 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/common": "18.2.5", - "@angular/core": "18.2.5", - "@angular/platform-browser": "18.2.5", + "@angular/common": "19.0.5", + "@angular/core": "19.0.5", + "@angular/platform-browser": "19.0.5", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/language-service": { - "version": "18.2.5", - "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-18.2.5.tgz", - "integrity": "sha512-JE6ck4UWXayiG8ptJJtkrKCjy+5Ftktgsoj4QGdQzMhbpia7Wge5XDj28o+bwEFndRnP6ihRtud63IvOz9aKFQ==", + "version": "19.0.5", + "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-19.0.5.tgz", + "integrity": "sha512-E4WFEsCzHuF3DYe4EfOCiMGW1zWmq3UYi5XXOBNLyzWDvwU5xTfdme6ECXGawHMc2kCaWMVNL4DzYpVsUgLG0w==", "dev": true, "license": "MIT", "engines": { @@ -1062,9 +689,9 @@ } }, "node_modules/@angular/platform-browser": { - "version": "18.2.5", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-18.2.5.tgz", - "integrity": "sha512-PoX9idwnOpTJBlujzZ2nFGOsmCnZzOH7uNSWIR7trdoq0b1AFXfrxlCQ36qWamk7bbhJI4H28L8YTmKew/nXDA==", + "version": "19.0.5", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-19.0.5.tgz", + "integrity": "sha512-41+Jo5DEil4Ifvv+UE/p1l9YJtYN+xfhx+/C9cahVgvV5D2q+givyK73d0Mnb6XOfe1q+hoV5lZ+XhQYp21//g==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -1073,9 +700,9 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/animations": "18.2.5", - "@angular/common": "18.2.5", - "@angular/core": "18.2.5" + "@angular/animations": "19.0.5", + "@angular/common": "19.0.5", + "@angular/core": "19.0.5" }, "peerDependenciesMeta": { "@angular/animations": { @@ -1084,9 +711,9 @@ } }, "node_modules/@angular/platform-browser-dynamic": { - "version": "18.2.5", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-18.2.5.tgz", - "integrity": "sha512-5u0IuAt1r5e2u2vSKhp3phnaf6hH89B/q7GErfPse1sdDfNI6wHVppxai28PAfAj9gwooJun6MjFWhJFLzS44A==", + "version": "19.0.5", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-19.0.5.tgz", + "integrity": "sha512-KKFdue/uJVxkWdrntRAXkz+ycp4nD3SuGOH5pPf2svCBxieuHuFlWDi+DYVuFSEpC/ICCmlhrtzIAm44A4qzzQ==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -1095,16 +722,16 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/common": "18.2.5", - "@angular/compiler": "18.2.5", - "@angular/core": "18.2.5", - "@angular/platform-browser": "18.2.5" + "@angular/common": "19.0.5", + "@angular/compiler": "19.0.5", + "@angular/core": "19.0.5", + "@angular/platform-browser": "19.0.5" } }, "node_modules/@angular/router": { - "version": "18.2.5", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-18.2.5.tgz", - "integrity": "sha512-OjZV1PTiSwT0ytmR0ykveLYzs4uQWf0EuIclZmWqM/bb8Q4P+gJl7/sya05nGnZsj6nHGOL0e/LhSZ3N+5p6qg==", + "version": "19.0.5", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-19.0.5.tgz", + "integrity": "sha512-6tNubVVj/rRyTg+OXjQxACfufvCLHAwDQtv9wqt6q/3OYSnysHTik3ho3FaFPwu7fXJ+6p9Rjzkh2VY9QMk4bw==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -1113,16 +740,16 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/common": "18.2.5", - "@angular/core": "18.2.5", - "@angular/platform-browser": "18.2.5", + "@angular/common": "19.0.5", + "@angular/core": "19.0.5", + "@angular/platform-browser": "19.0.5", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/service-worker": { - "version": "18.2.5", - "resolved": "https://registry.npmjs.org/@angular/service-worker/-/service-worker-18.2.5.tgz", - "integrity": "sha512-MoF2n7z/X+yqK89mIRHQutVHIBTyEUo/fDEL8LcuBP4KOZmX9cRoCEt+vqH49BkArsgOM0jNFMYCM8yt0jg7pw==", + "version": "19.0.5", + "resolved": "https://registry.npmjs.org/@angular/service-worker/-/service-worker-19.0.5.tgz", + "integrity": "sha512-qU5lgx1WJ+feCOV/EhkN9m20xFdIslpEQcSZZC+VJnEwcG6VTbofg1dRaHWZ9HAjS1uP7bFoK0HUYu4el0bHGA==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -1134,18 +761,41 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/common": "18.2.5", - "@angular/core": "18.2.5" + "@angular/common": "19.0.5", + "@angular/core": "19.0.5" + } + }, + "node_modules/@awesome-cordova-plugins/core": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/@awesome-cordova-plugins/core/-/core-6.13.0.tgz", + "integrity": "sha512-iqa5TU8HcFns/ND18KFrDVo4rGD4d4Gr5bgACFPwfztNCNmCRp+qkhv/xRBstqZ72qYH2oAJnEE1PTUY+IfX7Q==", + "dependencies": { + "@types/cordova": "latest" + }, + "peerDependencies": { + "rxjs": "^5.5.0 || ^6.5.0 || ^7.3.0" + } + }, + "node_modules/@awesome-cordova-plugins/file-opener": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/@awesome-cordova-plugins/file-opener/-/file-opener-6.13.0.tgz", + "integrity": "sha512-0+caltmjJf3BuZiklqaSHsxviDiS4znnJj3RiBfoERfPCRa0HIyO8dywH2EhkOMhYQ5g8gBobTTOJQpV/yQhmw==", + "dependencies": { + "@types/cordova": "latest" + }, + "peerDependencies": { + "@awesome-cordova-plugins/core": "^6.0.1", + "rxjs": "^5.5.0 || ^6.5.0 || ^7.3.0" } }, "node_modules/@babel/code-frame": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", - "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/highlight": "^7.24.7", + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", "picocolors": "^1.0.0" }, "engines": { @@ -1153,32 +803,30 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.4.tgz", - "integrity": "sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.3.tgz", + "integrity": "sha512-nHIxvKPniQXpmQLb0vhY3VaFb3S0YrTAwpOWJZh1wn3oJPjJk9Asva204PsBdmAE8vpzfHudT8DB0scYvy9q0g==", "dev": true, - "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", - "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz", + "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==", "dev": true, - "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.25.0", - "@babel/helper-compilation-targets": "^7.25.2", - "@babel/helper-module-transforms": "^7.25.2", - "@babel/helpers": "^7.25.0", - "@babel/parser": "^7.25.0", - "@babel/template": "^7.25.0", - "@babel/traverse": "^7.25.2", - "@babel/types": "^7.25.2", + "@babel/code-frame": "^7.26.0", + "@babel/generator": "^7.26.0", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.0", + "@babel/parser": "^7.26.0", + "@babel/template": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.26.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -1197,72 +845,54 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@babel/core/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "license": "ISC", "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/generator": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.0.tgz", - "integrity": "sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw==", + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.2.tgz", + "integrity": "sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/types": "^7.25.0", + "@babel/parser": "^7.26.2", + "@babel/types": "^7.26.0", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^2.5.1" + "jsesc": "^3.0.2" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz", - "integrity": "sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.24.7.tgz", - "integrity": "sha512-xZeCVVdwb4MsDBkkyZ64tReWYrLRHlMN72vP7Bdm3OUOuyFZExhsHUUnuWnm2/XOlAJzR0LfPpB56WXZn0X/lA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", + "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", - "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz", + "integrity": "sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.25.2", - "@babel/helper-validator-option": "^7.24.8", - "browserslist": "^4.23.1", + "@babel/compat-data": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -1275,24 +905,22 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "license": "ISC", "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.4.tgz", - "integrity": "sha512-ro/bFs3/84MDgDmMwbcHgDa8/E6J3QKNTk4xJJnVeFtGE+tL0K26E3pNxhYz2b67fJpt7Aphw5XcploKXuCvCQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.9.tgz", + "integrity": "sha512-UTZQMvt0d/rSz6KI+qdu7GQze5TIajwTS++GUozlw8VBJDEOAqSXwm1WvmYEZwqdqSGQshRocPDqrt4HBZB3fQ==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-member-expression-to-functions": "^7.24.8", - "@babel/helper-optimise-call-expression": "^7.24.7", - "@babel/helper-replace-supers": "^7.25.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", - "@babel/traverse": "^7.25.4", + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-member-expression-to-functions": "^7.25.9", + "@babel/helper-optimise-call-expression": "^7.25.9", + "@babel/helper-replace-supers": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/traverse": "^7.25.9", "semver": "^6.3.1" }, "engines": { @@ -1307,20 +935,18 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "license": "ISC", "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.25.2.tgz", - "integrity": "sha512-+wqVGP+DFmqwFD3EH6TMTfUNeqDehV3E/dl+Sd54eaXqm17tEUNbEIn4sVivVowbvUpOtIGxdo3GoXyDH9N/9g==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.26.3.tgz", + "integrity": "sha512-G7ZRb40uUgdKOQqPLjfD12ZmGA54PzqDFUv2BKImnC9QIfGhIHKvVML0oN8IUiDq4iRqpq74ABpvOaerfWdong==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "regexpu-core": "^5.3.1", + "@babel/helper-annotate-as-pure": "^7.25.9", + "regexpu-core": "^6.2.0", "semver": "^6.3.1" }, "engines": { @@ -1335,17 +961,15 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "license": "ISC", "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz", - "integrity": "sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.3.tgz", + "integrity": "sha512-HK7Bi+Hj6H+VTHA3ZvBis7V/6hu9QuTrnMXNybfUf2iiuU/N97I8VjB+KbhFF8Rld/Lx5MzoCwPCpPjfK+n8Cg==", "dev": true, - "license": "MIT", "dependencies": { "@babel/helper-compilation-targets": "^7.22.6", "@babel/helper-plugin-utils": "^7.22.5", @@ -1358,44 +982,40 @@ } }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.8.tgz", - "integrity": "sha512-LABppdt+Lp/RlBxqrh4qgf1oEH/WxdzQNDJIu5gC/W1GyvPVrOBiItmmM8wan2fm4oYqFuFfkXmlGpLQhPY8CA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz", + "integrity": "sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/traverse": "^7.24.8", - "@babel/types": "^7.24.8" + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", - "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz", - "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", + "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.24.7", - "@babel/helper-simple-access": "^7.24.7", - "@babel/helper-validator-identifier": "^7.24.7", - "@babel/traverse": "^7.25.2" + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1405,38 +1025,35 @@ } }, "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.24.7.tgz", - "integrity": "sha512-jKiTsW2xmWwxT1ixIdfXUZp+P5yURx2suzLZr5Hi64rURpDYdMW0pv+Uf17EYk2Rd428Lx4tLsnjGJzYKDM/6A==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz", + "integrity": "sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/types": "^7.24.7" + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", - "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz", + "integrity": "sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==", "dev": true, - "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.0.tgz", - "integrity": "sha512-NhavI2eWEIz/H9dbrG0TuOicDhNexze43i5z7lEqwYm0WEZVTwnPpA0EafUTP7+6/W79HWIP2cTe3Z5NiSTVpw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.9.tgz", + "integrity": "sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-wrap-function": "^7.25.0", - "@babel/traverse": "^7.25.0" + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-wrap-function": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1446,15 +1063,14 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.25.0.tgz", - "integrity": "sha512-q688zIvQVYtZu+i2PsdIu/uWGRpfxzr5WESsfpShfZECkO+d2o+WROWezCi/Q6kJ0tfPa5+pUGUlfx2HhrA3Bg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.25.9.tgz", + "integrity": "sha512-IiDqTOTBQy0sWyeXyGSC5TBJpGFXBkRynjBeXsvbhQFKj2viwJC76Epz35YLU1fpe/Am6Vppb7W7zM4fPQzLsQ==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.24.8", - "@babel/helper-optimise-call-expression": "^7.24.7", - "@babel/traverse": "^7.25.0" + "@babel/helper-member-expression-to-functions": "^7.25.9", + "@babel/helper-optimise-call-expression": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1463,29 +1079,14 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-simple-access": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", - "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.24.7.tgz", - "integrity": "sha512-IO+DLT3LQUElMbpzlatRASEyQtfhSE0+m465v++3jyyXeBTBUjtVZg28/gHeV5mrTJqvEKhKroBGAvhW+qPHiQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz", + "integrity": "sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1505,88 +1106,66 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", - "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", "dev": true, - "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", - "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", - "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", + "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", "dev": true, - "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-wrap-function": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.0.tgz", - "integrity": "sha512-s6Q1ebqutSiZnEjaofc/UKDyC4SbzV5n5SrA2Gq8UawLycr3i04f1dX4OzoQVnexm6aOCh37SQNYlJ/8Ku+PMQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.9.tgz", + "integrity": "sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/template": "^7.25.0", - "@babel/traverse": "^7.25.0", - "@babel/types": "^7.25.0" + "@babel/template": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.0.tgz", - "integrity": "sha512-MjgLZ42aCm0oGjJj8CtSM3DB8NOOf8h2l7DCTePJs29u+v7yO/RBX9nShlKMgFnRks/Q4tBAe7Hxnov9VkGwLw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.25.0", - "@babel/types": "^7.25.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", - "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz", + "integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.24.7", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.4.tgz", - "integrity": "sha512-nq+eWrOgdtu3jG5Os4TQP3x3cLA8hR8TvJNjD8vnPa20WGycimcparWnLK4jJhElTK6SDyuJo1weMKO/5LpmLA==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.3.tgz", + "integrity": "sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/types": "^7.25.4" + "@babel/types": "^7.26.3" }, "bin": { "parser": "bin/babel-parser.js" @@ -1596,14 +1175,13 @@ } }, "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "version": "7.25.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.3.tgz", - "integrity": "sha512-wUrcsxZg6rqBXG05HG1FPYgsP6EvwF4WpBbxIpWIIYnH8wG0gzx3yZY3dtEHas4sTAOGkbTsc9EGPxwff8lRoA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.9.tgz", + "integrity": "sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/traverse": "^7.25.3" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1613,13 +1191,12 @@ } }, "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.0.tgz", - "integrity": "sha512-Bm4bH2qsX880b/3ziJ8KD711LT7z4u8CFudmjqle65AZj/HNUFhEf90dqYv6O86buWvSBmeQDjv0Tn2aF/bIBA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.9.tgz", + "integrity": "sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1629,13 +1206,12 @@ } }, "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.0.tgz", - "integrity": "sha512-lXwdNZtTmeVOOFtwM/WDe7yg1PL8sYhRk/XH0FzbR2HDQ0xC+EnQ/JHeoMYSavtU115tnUk0q9CDyq8si+LMAA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.9.tgz", + "integrity": "sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1645,15 +1221,14 @@ } }, "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.7.tgz", - "integrity": "sha512-+izXIbke1T33mY4MSNnrqhPXDz01WYhEf3yF5NbnUtkiNnm+XBZJl3kNfoK6NKmYlz/D07+l2GWVK/QfDkNCuQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.25.9.tgz", + "integrity": "sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", - "@babel/plugin-transform-optional-chaining": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/plugin-transform-optional-chaining": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1663,14 +1238,13 @@ } }, "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.0.tgz", - "integrity": "sha512-tggFrk1AIShG/RUQbEwt2Tr/E+ObkfwrPjR6BjbRvsx24+PSjK8zrq0GWPNCjo8qpRx4DuJzlcvWJqlm+0h3kw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.9.tgz", + "integrity": "sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/traverse": "^7.25.0" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1684,49 +1258,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, "engines": { "node": ">=6.9.0" }, @@ -1734,40 +1265,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.7.tgz", - "integrity": "sha512-Ec3NRUMoi8gskrkBe3fNmEQfxDvY8bgfQpz6jlk/41kX9eUjvpyqWU7PBP/pLAvMaSQjbMNKJmvX57jP+M6bPg==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.26.0.tgz", + "integrity": "sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1777,149 +1281,12 @@ } }, "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.7.tgz", - "integrity": "sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz", + "integrity": "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1933,7 +1300,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", "dev": true, - "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.18.6", "@babel/helper-plugin-utils": "^7.18.6" @@ -1946,13 +1312,12 @@ } }, "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.7.tgz", - "integrity": "sha512-Dt9LQs6iEY++gXUwY03DNFat5C2NbO48jj+j/bSAz6b3HgPs39qcPiYt77fDObIcFwj3/C2ICX9YMwGflUoSHQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.25.9.tgz", + "integrity": "sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1962,16 +1327,14 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.0.tgz", - "integrity": "sha512-uaIi2FdqzjpAMvVqvB51S42oC2JEVgh0LDsGfZVDysWE8LrJtQC2jvKmOqEYThKyB7bDEb7BP1GYWDm7tABA0Q==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.9.tgz", + "integrity": "sha512-RXV6QAzTBbhDMO9fWwOmwwTuYaiPbggWQ9INdZqAYeSHyG7FzQ+nOZaUUjNwKv9pV3aE4WFqFm1Hnbci5tBCAw==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/helper-remap-async-to-generator": "^7.25.0", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/traverse": "^7.25.0" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-remap-async-to-generator": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1981,15 +1344,14 @@ } }, "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.7.tgz", - "integrity": "sha512-SQY01PcJfmQ+4Ash7NE+rpbLFbmqA2GPIgqzxfFTL4t1FKRq4zTms/7htKpoCUI9OcFYgzqfmCdH53s6/jn5fA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.25.9.tgz", + "integrity": "sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-remap-async-to-generator": "^7.24.7" + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-remap-async-to-generator": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1999,13 +1361,12 @@ } }, "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.7.tgz", - "integrity": "sha512-yO7RAz6EsVQDaBH18IDJcMB1HnrUn2FJ/Jslc/WtPPWcjhpUJXU/rjbwmluzp7v/ZzWcEhTMXELnnsz8djWDwQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.25.9.tgz", + "integrity": "sha512-toHc9fzab0ZfenFpsyYinOX0J/5dgJVA2fm64xPewu7CoYHWEivIWKxkK2rMi4r3yQqLnVmheMXRdG+k239CgA==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2015,13 +1376,12 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.0.tgz", - "integrity": "sha512-yBQjYoOjXlFv9nlXb3f1casSHOZkWr29NX+zChVanLg5Nc157CrbEX9D7hxxtTpuFy7Q0YzmmWfJxzvps4kXrQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.9.tgz", + "integrity": "sha512-1F05O7AYjymAtqbsFETboN1NvBdcnzMerO+zlMyJBEz6WkMdejvGWw9p05iTSjC85RLlBseHHQpYaM4gzJkBGg==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2031,14 +1391,13 @@ } }, "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.4.tgz", - "integrity": "sha512-nZeZHyCWPfjkdU5pA/uHiTaDAFUEqkpzf1YoQT2NeSynCGYq9rxfyI3XpQbfx/a0hSnFH6TGlEXvae5Vi7GD8g==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.9.tgz", + "integrity": "sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.25.4", - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2048,15 +1407,13 @@ } }, "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.7.tgz", - "integrity": "sha512-HMXK3WbBPpZQufbMG4B46A90PkuuhN9vBCb5T8+VAHqvAqvcLi+2cKoukcpmUYkszLhScU3l1iudhrks3DggRQ==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.26.0.tgz", + "integrity": "sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-class-static-block": "^7.14.5" + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2066,17 +1423,16 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.4.tgz", - "integrity": "sha512-oexUfaQle2pF/b6E0dwsxQtAol9TLSO88kQvym6HHBWFliV2lGdrPieX+WgMRLSJDVzdYywk7jXbLPuO2KLTLg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.9.tgz", + "integrity": "sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-compilation-targets": "^7.25.2", - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/helper-replace-supers": "^7.25.0", - "@babel/traverse": "^7.25.4", + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-replace-supers": "^7.25.9", + "@babel/traverse": "^7.25.9", "globals": "^11.1.0" }, "engines": { @@ -2087,14 +1443,13 @@ } }, "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.7.tgz", - "integrity": "sha512-25cS7v+707Gu6Ds2oY6tCkUwsJ9YIDbggd9+cu9jzzDgiNq7hR/8dkzxWfKWnTic26vsI3EsCXNd4iEB6e8esQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.9.tgz", + "integrity": "sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/template": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/template": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2104,13 +1459,12 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.8.tgz", - "integrity": "sha512-36e87mfY8TnRxc7yc6M9g9gOB7rKgSahqkIKwLpz4Ppk2+zC2Cy1is0uwtuSG6AE4zlTOUa+7JGz9jCJGLqQFQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.25.9.tgz", + "integrity": "sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2120,14 +1474,13 @@ } }, "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.7.tgz", - "integrity": "sha512-ZOA3W+1RRTSWvyqcMJDLqbchh7U4NRGqwRfFSVbOLS/ePIP4vHB5e8T8eXcuqyN1QkgKyj5wuW0lcS85v4CrSw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.25.9.tgz", + "integrity": "sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2137,13 +1490,12 @@ } }, "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.7.tgz", - "integrity": "sha512-JdYfXyCRihAe46jUIliuL2/s0x0wObgwwiGxw/UbgJBr20gQBThrokO4nYKgWkD7uBaqM7+9x5TU7NkExZJyzw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.25.9.tgz", + "integrity": "sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2153,14 +1505,13 @@ } }, "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.0.tgz", - "integrity": "sha512-YLpb4LlYSc3sCUa35un84poXoraOiQucUTTu8X1j18JV+gNa8E0nyUf/CjZ171IRGr4jEguF+vzJU66QZhn29g==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.9.tgz", + "integrity": "sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.0", - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2170,14 +1521,12 @@ } }, "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.7.tgz", - "integrity": "sha512-sc3X26PhZQDb3JhORmakcbvkeInvxz+A8oda99lj7J60QRuPZvNAk9wQlTBS1ZynelDrDmTU4pw1tyc5d5ZMUg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.9.tgz", + "integrity": "sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2187,14 +1536,12 @@ } }, "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.7.tgz", - "integrity": "sha512-Rqe/vSc9OYgDajNIK35u7ot+KeCoetqQYFXM4Epf7M7ez3lWlOjrDjrwMei6caCVhfdw+mIKD4cgdGNy5JQotQ==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.26.3.tgz", + "integrity": "sha512-7CAHcQ58z2chuXPWblnn1K6rLDnDWieghSOEmqQsrBenH0P9InCUtOJYD89pvngljmZlJcz3fcmgYsXFNGa1ZQ==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2204,14 +1551,12 @@ } }, "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.7.tgz", - "integrity": "sha512-v0K9uNYsPL3oXZ/7F9NNIbAj2jv1whUEtyA6aujhekLs56R++JDQuzRcP2/z4WX5Vg/c5lE9uWZA0/iUoFhLTA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.9.tgz", + "integrity": "sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2221,14 +1566,13 @@ } }, "node_modules/@babel/plugin-transform-for-of": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.7.tgz", - "integrity": "sha512-wo9ogrDG1ITTTBsy46oGiN1dS9A7MROBTcYsfS8DtsImMkHk9JXJ3EWQM6X2SUw4x80uGPlwj0o00Uoc6nEE3g==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.25.9.tgz", + "integrity": "sha512-LqHxduHoaGELJl2uhImHwRQudhCM50pT46rIBNvtT/Oql3nqiS3wOwP+5ten7NpYSXrrVLgtZU3DZmPtWZo16A==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2238,15 +1582,14 @@ } }, "node_modules/@babel/plugin-transform-function-name": { - "version": "7.25.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.1.tgz", - "integrity": "sha512-TVVJVdW9RKMNgJJlLtHsKDTydjZAbwIsn6ySBPQaEAUU5+gVvlJt/9nRmqVbsV/IBanRjzWoaAQKLoamWVOUuA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.9.tgz", + "integrity": "sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.24.8", - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/traverse": "^7.25.1" + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2256,14 +1599,12 @@ } }, "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.7.tgz", - "integrity": "sha512-2yFnBGDvRuxAaE/f0vfBKvtnvvqU8tGpMHqMNpTN2oWMKIR3NqFkjaAgGwawhqK/pIN2T3XdjGPdaG0vDhOBGw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.25.9.tgz", + "integrity": "sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-json-strings": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2273,13 +1614,12 @@ } }, "node_modules/@babel/plugin-transform-literals": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.2.tgz", - "integrity": "sha512-HQI+HcTbm9ur3Z2DkO+jgESMAMcYLuN/A7NRw9juzxAezN9AvqvUTnpKP/9kkYANz6u7dFlAyOu44ejuGySlfw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.9.tgz", + "integrity": "sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2289,14 +1629,12 @@ } }, "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.7.tgz", - "integrity": "sha512-4D2tpwlQ1odXmTEIFWy9ELJcZHqrStlzK/dAOWYyxX3zT0iXQB6banjgeOJQXzEc4S0E0a5A+hahxPaEFYftsw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.9.tgz", + "integrity": "sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2306,13 +1644,12 @@ } }, "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.7.tgz", - "integrity": "sha512-T/hRC1uqrzXMKLQ6UCwMT85S3EvqaBXDGf0FaMf4446Qx9vKwlghvee0+uuZcDUCZU5RuNi4781UQ7R308zzBw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.25.9.tgz", + "integrity": "sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2322,14 +1659,13 @@ } }, "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.7.tgz", - "integrity": "sha512-9+pB1qxV3vs/8Hdmz/CulFB8w2tuu6EB94JZFsjdqxQokwGa9Unap7Bo2gGBGIvPmDIVvQrom7r5m/TCDMURhg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.25.9.tgz", + "integrity": "sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2339,15 +1675,13 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.8.tgz", - "integrity": "sha512-WHsk9H8XxRs3JXKWFiqtQebdh9b/pTk4EgueygFzYlTKAg0Ud985mSevdNjdXdFBATSKVJGQXP1tv6aGbssLKA==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.26.3.tgz", + "integrity": "sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.24.8", - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/helper-simple-access": "^7.24.7" + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2357,16 +1691,15 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.0.tgz", - "integrity": "sha512-YPJfjQPDXxyQWg/0+jHKj1llnY5f/R6a0p/vP4lPymxLu7Lvl4k2WMitqi08yxwQcCVUUdG9LCUj4TNEgAp3Jw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.9.tgz", + "integrity": "sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.25.0", - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/helper-validator-identifier": "^7.24.7", - "@babel/traverse": "^7.25.0" + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2376,14 +1709,13 @@ } }, "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.7.tgz", - "integrity": "sha512-3aytQvqJ/h9z4g8AsKPLvD4Zqi2qT+L3j7XoFFu1XBlZWEl2/1kWnhmAbxpLgPrHSY0M6UA02jyTiwUVtiKR6A==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.25.9.tgz", + "integrity": "sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2393,14 +1725,13 @@ } }, "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.24.7.tgz", - "integrity": "sha512-/jr7h/EWeJtk1U/uz2jlsCioHkZk1JJZVcc8oQsJ1dUlaJD83f4/6Zeh2aHt9BIFokHIsSeDfhUmju0+1GPd6g==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.25.9.tgz", + "integrity": "sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2410,13 +1741,12 @@ } }, "node_modules/@babel/plugin-transform-new-target": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.7.tgz", - "integrity": "sha512-RNKwfRIXg4Ls/8mMTza5oPF5RkOW8Wy/WgMAp1/F1yZ8mMbtwXW+HDoJiOsagWrAhI5f57Vncrmr9XeT4CVapA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.25.9.tgz", + "integrity": "sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2426,14 +1756,12 @@ } }, "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.7.tgz", - "integrity": "sha512-Ts7xQVk1OEocqzm8rHMXHlxvsfZ0cEF2yomUqpKENHWMF4zKk175Y4q8H5knJes6PgYad50uuRmt3UJuhBw8pQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.25.9.tgz", + "integrity": "sha512-ENfftpLZw5EItALAD4WsY/KUWvhUlZndm5GC7G3evUsVeSJB6p0pBeLQUnRnBCBx7zV0RKQjR9kCuwrsIrjWog==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2443,14 +1771,12 @@ } }, "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.7.tgz", - "integrity": "sha512-e6q1TiVUzvH9KRvicuxdBTUj4AdKSRwzIyFFnfnezpCfP2/7Qmbb8qbU2j7GODbl4JMkblitCQjKYUaX/qkkwA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.9.tgz", + "integrity": "sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2460,16 +1786,14 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.7.tgz", - "integrity": "sha512-4QrHAr0aXQCEFni2q4DqKLD31n2DL+RxcwnNjDFkSG0eNQ/xCavnRkfCUjsyqGC2OviNJvZOF/mQqZBw7i2C5Q==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.9.tgz", + "integrity": "sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.24.7" + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/plugin-transform-parameters": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2479,14 +1803,13 @@ } }, "node_modules/@babel/plugin-transform-object-super": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.7.tgz", - "integrity": "sha512-A/vVLwN6lBrMFmMDmPPz0jnE6ZGx7Jq7d6sT/Ev4H65RER6pZ+kczlf1DthF5N0qaPHBsI7UXiE8Zy66nmAovg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.25.9.tgz", + "integrity": "sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-replace-supers": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-replace-supers": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2496,14 +1819,12 @@ } }, "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.7.tgz", - "integrity": "sha512-uLEndKqP5BfBbC/5jTwPxLh9kqPWWgzN/f8w6UwAIirAEqiIVJWWY312X72Eub09g5KF9+Zn7+hT7sDxmhRuKA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.9.tgz", + "integrity": "sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2513,15 +1834,13 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.8.tgz", - "integrity": "sha512-5cTOLSMs9eypEy8JUVvIKOu6NgvbJMnpG62VpIHrTmROdQ+L5mDAaI40g25k5vXti55JWNX5jCkq3HZxXBQANw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.9.tgz", + "integrity": "sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2531,13 +1850,12 @@ } }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.7.tgz", - "integrity": "sha512-yGWW5Rr+sQOhK0Ot8hjDJuxU3XLRQGflvT4lhlSY0DFvdb3TwKaY26CJzHtYllU0vT9j58hc37ndFPsqT1SrzA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.25.9.tgz", + "integrity": "sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2547,14 +1865,13 @@ } }, "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.4.tgz", - "integrity": "sha512-ao8BG7E2b/URaUQGqN3Tlsg+M3KlHY6rJ1O1gXAEUnZoyNQnvKyH87Kfg+FoxSeyWUB8ISZZsC91C44ZuBFytw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz", + "integrity": "sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.25.4", - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2564,16 +1881,14 @@ } }, "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.7.tgz", - "integrity": "sha512-9z76mxwnwFxMyxZWEgdgECQglF2Q7cFLm0kMf8pGwt+GSJsY0cONKj/UuO4bOH0w/uAel3ekS4ra5CEAyJRmDA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.9.tgz", + "integrity": "sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-create-class-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2583,13 +1898,12 @@ } }, "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.7.tgz", - "integrity": "sha512-EMi4MLQSHfd2nrCqQEWxFdha2gBCqU4ZcCng4WBGZ5CJL4bBRW0ptdqqDdeirGZcpALazVVNJqRmsO8/+oNCBA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.25.9.tgz", + "integrity": "sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2599,13 +1913,12 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.7.tgz", - "integrity": "sha512-lq3fvXPdimDrlg6LWBoqj+r/DEWgONuwjuOuQCSYgRroXDH/IdM1C0IZf59fL5cHLpjEH/O6opIRBbqv7ELnuA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.25.9.tgz", + "integrity": "sha512-vwDcDNsgMPDGP0nMqzahDWE5/MLcX8sv96+wfX7as7LoF/kr97Bo/7fI00lXY4wUXYfVmwIIyG80fGZ1uvt2qg==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-plugin-utils": "^7.25.9", "regenerator-transform": "^0.15.2" }, "engines": { @@ -2615,14 +1928,29 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.26.0.tgz", + "integrity": "sha512-vN6saax7lrA2yA/Pak3sCxuD6F5InBjn9IcrIKQPjpsLvuHYLVroTxjdlVRHjjBWxKOqIwpTXDkOssYT4BFdRw==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.7.tgz", - "integrity": "sha512-0DUq0pHcPKbjFZCfTss/pGkYMfy3vFWydkUBd9r0GHpIyfs2eCDENvqadMycRS9wZCXR41wucAfJHJmwA0UmoQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.9.tgz", + "integrity": "sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2632,16 +1960,15 @@ } }, "node_modules/@babel/plugin-transform-runtime": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.7.tgz", - "integrity": "sha512-YqXjrk4C+a1kZjewqt+Mmu2UuV1s07y8kqcUf4qYLnoqemhR4gRQikhdAhSVJioMjVTu6Mo6pAbaypEA3jY6fw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.25.9.tgz", + "integrity": "sha512-nZp7GlEl+yULJrClz0SwHPqir3lc0zsPrDHQUcxGspSL7AKrexNSEfTbfqnDNJUO13bgKyfuOLMF8Xqtu8j3YQ==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", "babel-plugin-polyfill-corejs2": "^0.4.10", - "babel-plugin-polyfill-corejs3": "^0.10.1", + "babel-plugin-polyfill-corejs3": "^0.10.6", "babel-plugin-polyfill-regenerator": "^0.6.1", "semver": "^6.3.1" }, @@ -2657,19 +1984,17 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "license": "ISC", "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.7.tgz", - "integrity": "sha512-KsDsevZMDsigzbA09+vacnLpmPH4aWjcZjXdyFKGzpplxhbeB4wYtury3vglQkg6KM/xEPKt73eCjPPf1PgXBA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.25.9.tgz", + "integrity": "sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2679,14 +2004,13 @@ } }, "node_modules/@babel/plugin-transform-spread": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.7.tgz", - "integrity": "sha512-x96oO0I09dgMDxJaANcRyD4ellXFLLiWhuwDxKZX5g2rWP1bTPkBSwCYv96VDXVT1bD9aPj8tppr5ITIh8hBng==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.25.9.tgz", + "integrity": "sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2696,13 +2020,12 @@ } }, "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.7.tgz", - "integrity": "sha512-kHPSIJc9v24zEml5geKg9Mjx5ULpfncj0wRpYtxbvKyTtHCYDkVE3aHQ03FrpEo4gEe2vrJJS1Y9CJTaThA52g==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.25.9.tgz", + "integrity": "sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2712,13 +2035,12 @@ } }, "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.7.tgz", - "integrity": "sha512-AfDTQmClklHCOLxtGoP7HkeMw56k1/bTQjwsfhL6pppo/M4TOBSq+jjBUBLmV/4oeFg4GWMavIl44ZeCtmmZTw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.25.9.tgz", + "integrity": "sha512-o97AE4syN71M/lxrCtQByzphAdlYluKPDBzDVzMmfCobUjjhAryZV0AIpRPrxN0eAkxXO6ZLEScmt+PNhj2OTw==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2728,13 +2050,12 @@ } }, "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.8.tgz", - "integrity": "sha512-adNTUpDCVnmAE58VEqKlAA6ZBlNkMnWD0ZcW76lyNFN3MJniyGFZfNwERVk8Ap56MCnXztmDr19T4mPTztcuaw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.25.9.tgz", + "integrity": "sha512-v61XqUMiueJROUv66BVIOi0Fv/CUuZuZMl5NkRoCVxLAnMexZ0A3kMe7vvZ0nulxMuMp0Mk6S5hNh48yki08ZA==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2744,13 +2065,12 @@ } }, "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.7.tgz", - "integrity": "sha512-U3ap1gm5+4edc2Q/P+9VrBNhGkfnf+8ZqppY71Bo/pzZmXhhLdqgaUl6cuB07O1+AQJtCLfaOmswiNbSQ9ivhw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.9.tgz", + "integrity": "sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2760,14 +2080,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.7.tgz", - "integrity": "sha512-uH2O4OV5M9FZYQrwc7NdVmMxQJOCCzFeYudlZSzUAHRFeOujQefa92E74TQDVskNHCzOXoigEuoyzHDhaEaK5w==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.25.9.tgz", + "integrity": "sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2777,14 +2096,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.7.tgz", - "integrity": "sha512-hlQ96MBZSAXUq7ltkjtu3FJCCSMx/j629ns3hA3pXnBXjanNP0LHi+JpPeA81zaWgVK1VGH95Xuy7u0RyQ8kMg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.25.9.tgz", + "integrity": "sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2794,14 +2112,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.4.tgz", - "integrity": "sha512-qesBxiWkgN1Q+31xUE9RcMk79eOXXDCv6tfyGMRSs4RGlioSg2WVyQAm07k726cSE56pa+Kb0y9epX2qaXzTvA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.9.tgz", + "integrity": "sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.2", - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2811,94 +2128,79 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.25.3", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.25.3.tgz", - "integrity": "sha512-QsYW7UeAaXvLPX9tdVliMJE7MD7M6MLYVTovRTIwhoYQVFHR1rM4wO8wqAezYi3/BpSD+NzVCZ69R6smWiIi8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.25.2", - "@babel/helper-compilation-targets": "^7.25.2", - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/helper-validator-option": "^7.24.8", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.3", - "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.0", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.0", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.7", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.0", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.26.0.tgz", + "integrity": "sha512-H84Fxq0CQJNdPFT2DrfnylZ3cf5K43rGfWK4LJGPpjKHiZlk0/RzwEus3PDDZZg+/Er7lCA03MVacueUuXdzfw==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.26.0", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.9", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.9", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.9", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.25.9", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.9", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.24.7", - "@babel/plugin-syntax-import-attributes": "^7.24.7", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-syntax-import-assertions": "^7.26.0", + "@babel/plugin-syntax-import-attributes": "^7.26.0", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.24.7", - "@babel/plugin-transform-async-generator-functions": "^7.25.0", - "@babel/plugin-transform-async-to-generator": "^7.24.7", - "@babel/plugin-transform-block-scoped-functions": "^7.24.7", - "@babel/plugin-transform-block-scoping": "^7.25.0", - "@babel/plugin-transform-class-properties": "^7.24.7", - "@babel/plugin-transform-class-static-block": "^7.24.7", - "@babel/plugin-transform-classes": "^7.25.0", - "@babel/plugin-transform-computed-properties": "^7.24.7", - "@babel/plugin-transform-destructuring": "^7.24.8", - "@babel/plugin-transform-dotall-regex": "^7.24.7", - "@babel/plugin-transform-duplicate-keys": "^7.24.7", - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.0", - "@babel/plugin-transform-dynamic-import": "^7.24.7", - "@babel/plugin-transform-exponentiation-operator": "^7.24.7", - "@babel/plugin-transform-export-namespace-from": "^7.24.7", - "@babel/plugin-transform-for-of": "^7.24.7", - "@babel/plugin-transform-function-name": "^7.25.1", - "@babel/plugin-transform-json-strings": "^7.24.7", - "@babel/plugin-transform-literals": "^7.25.2", - "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", - "@babel/plugin-transform-member-expression-literals": "^7.24.7", - "@babel/plugin-transform-modules-amd": "^7.24.7", - "@babel/plugin-transform-modules-commonjs": "^7.24.8", - "@babel/plugin-transform-modules-systemjs": "^7.25.0", - "@babel/plugin-transform-modules-umd": "^7.24.7", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", - "@babel/plugin-transform-new-target": "^7.24.7", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", - "@babel/plugin-transform-numeric-separator": "^7.24.7", - "@babel/plugin-transform-object-rest-spread": "^7.24.7", - "@babel/plugin-transform-object-super": "^7.24.7", - "@babel/plugin-transform-optional-catch-binding": "^7.24.7", - "@babel/plugin-transform-optional-chaining": "^7.24.8", - "@babel/plugin-transform-parameters": "^7.24.7", - "@babel/plugin-transform-private-methods": "^7.24.7", - "@babel/plugin-transform-private-property-in-object": "^7.24.7", - "@babel/plugin-transform-property-literals": "^7.24.7", - "@babel/plugin-transform-regenerator": "^7.24.7", - "@babel/plugin-transform-reserved-words": "^7.24.7", - "@babel/plugin-transform-shorthand-properties": "^7.24.7", - "@babel/plugin-transform-spread": "^7.24.7", - "@babel/plugin-transform-sticky-regex": "^7.24.7", - "@babel/plugin-transform-template-literals": "^7.24.7", - "@babel/plugin-transform-typeof-symbol": "^7.24.8", - "@babel/plugin-transform-unicode-escapes": "^7.24.7", - "@babel/plugin-transform-unicode-property-regex": "^7.24.7", - "@babel/plugin-transform-unicode-regex": "^7.24.7", - "@babel/plugin-transform-unicode-sets-regex": "^7.24.7", + "@babel/plugin-transform-arrow-functions": "^7.25.9", + "@babel/plugin-transform-async-generator-functions": "^7.25.9", + "@babel/plugin-transform-async-to-generator": "^7.25.9", + "@babel/plugin-transform-block-scoped-functions": "^7.25.9", + "@babel/plugin-transform-block-scoping": "^7.25.9", + "@babel/plugin-transform-class-properties": "^7.25.9", + "@babel/plugin-transform-class-static-block": "^7.26.0", + "@babel/plugin-transform-classes": "^7.25.9", + "@babel/plugin-transform-computed-properties": "^7.25.9", + "@babel/plugin-transform-destructuring": "^7.25.9", + "@babel/plugin-transform-dotall-regex": "^7.25.9", + "@babel/plugin-transform-duplicate-keys": "^7.25.9", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.9", + "@babel/plugin-transform-dynamic-import": "^7.25.9", + "@babel/plugin-transform-exponentiation-operator": "^7.25.9", + "@babel/plugin-transform-export-namespace-from": "^7.25.9", + "@babel/plugin-transform-for-of": "^7.25.9", + "@babel/plugin-transform-function-name": "^7.25.9", + "@babel/plugin-transform-json-strings": "^7.25.9", + "@babel/plugin-transform-literals": "^7.25.9", + "@babel/plugin-transform-logical-assignment-operators": "^7.25.9", + "@babel/plugin-transform-member-expression-literals": "^7.25.9", + "@babel/plugin-transform-modules-amd": "^7.25.9", + "@babel/plugin-transform-modules-commonjs": "^7.25.9", + "@babel/plugin-transform-modules-systemjs": "^7.25.9", + "@babel/plugin-transform-modules-umd": "^7.25.9", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.25.9", + "@babel/plugin-transform-new-target": "^7.25.9", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.25.9", + "@babel/plugin-transform-numeric-separator": "^7.25.9", + "@babel/plugin-transform-object-rest-spread": "^7.25.9", + "@babel/plugin-transform-object-super": "^7.25.9", + "@babel/plugin-transform-optional-catch-binding": "^7.25.9", + "@babel/plugin-transform-optional-chaining": "^7.25.9", + "@babel/plugin-transform-parameters": "^7.25.9", + "@babel/plugin-transform-private-methods": "^7.25.9", + "@babel/plugin-transform-private-property-in-object": "^7.25.9", + "@babel/plugin-transform-property-literals": "^7.25.9", + "@babel/plugin-transform-regenerator": "^7.25.9", + "@babel/plugin-transform-regexp-modifiers": "^7.26.0", + "@babel/plugin-transform-reserved-words": "^7.25.9", + "@babel/plugin-transform-shorthand-properties": "^7.25.9", + "@babel/plugin-transform-spread": "^7.25.9", + "@babel/plugin-transform-sticky-regex": "^7.25.9", + "@babel/plugin-transform-template-literals": "^7.25.9", + "@babel/plugin-transform-typeof-symbol": "^7.25.9", + "@babel/plugin-transform-unicode-escapes": "^7.25.9", + "@babel/plugin-transform-unicode-property-regex": "^7.25.9", + "@babel/plugin-transform-unicode-regex": "^7.25.9", + "@babel/plugin-transform-unicode-sets-regex": "^7.25.9", "@babel/preset-modules": "0.1.6-no-external-plugins", "babel-plugin-polyfill-corejs2": "^0.4.10", - "babel-plugin-polyfill-corejs3": "^0.10.4", + "babel-plugin-polyfill-corejs3": "^0.10.6", "babel-plugin-polyfill-regenerator": "^0.6.1", - "core-js-compat": "^3.37.1", + "core-js-compat": "^3.38.1", "semver": "^6.3.1" }, "engines": { @@ -2913,7 +2215,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "license": "ISC", "bin": { "semver": "bin/semver.js" } @@ -2923,7 +2224,6 @@ "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", "dev": true, - "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@babel/types": "^7.4.4", @@ -2933,18 +2233,11 @@ "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/@babel/regjsgen": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", - "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", - "dev": true, - "license": "MIT" - }, "node_modules/@babel/runtime": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz", - "integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==", - "license": "MIT", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", + "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", + "dev": true, "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -2953,32 +2246,30 @@ } }, "node_modules/@babel/template": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", - "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", + "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.24.7", - "@babel/parser": "^7.25.0", - "@babel/types": "^7.25.0" + "@babel/code-frame": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.4.tgz", - "integrity": "sha512-VJ4XsrD+nOvlXyLzmLzUs/0qjFS4sK30te5yEFlvbbUNEgKaVb2BHZUpAL+ttLPQAHNrsI3zZisbfha5Cvr8vg==", + "version": "7.26.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.4.tgz", + "integrity": "sha512-fH+b7Y4p3yqvApJALCPJcwb0/XaOSgtK4pzV6WVjPR5GLFQBRI7pfoX2V2iM48NXvX07NUxxm1Vw98YjqTcU5w==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.25.4", - "@babel/parser": "^7.25.4", - "@babel/template": "^7.25.0", - "@babel/types": "^7.25.4", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.3", + "@babel/parser": "^7.26.3", + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.3", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -2987,31 +2278,29 @@ } }, "node_modules/@babel/traverse/node_modules/@babel/generator": { - "version": "7.25.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.5.tgz", - "integrity": "sha512-abd43wyLfbWoxC6ahM8xTkqLpGB2iWBVyuKC9/srhFunCd1SDNrV1s72bBpK4hLj8KLzHBBcOblvLQZBNw9r3w==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.3.tgz", + "integrity": "sha512-6FF/urZvD0sTeO7k6/B15pMLC4CHUv1426lzr3N01aHJTl046uCAh9LXW/fzeXXjPNCJ6iABW5XaWOsIZB93aQ==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/types": "^7.25.4", + "@babel/parser": "^7.26.3", + "@babel/types": "^7.26.3", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^2.5.1" + "jsesc": "^3.0.2" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/types": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.4.tgz", - "integrity": "sha512-zQ1ijeeCXVEh+aNL0RlmkPkG8HUiDcU2pzQQFjtbntgAczRASFzj4H+6+bV+dy1ntKR14I/DypeuRG1uma98iQ==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.3.tgz", + "integrity": "sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.24.8", - "@babel/helper-validator-identifier": "^7.24.7", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -3030,19 +2319,17 @@ } }, "node_modules/@capacitor/android": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/@capacitor/android/-/android-6.1.2.tgz", - "integrity": "sha512-Yh0gQDY1bgRrL25J6ecIlvvs2kF8iNSwIPXjyw6Yz9mnwYxBazF5KZbjpKtGPnJgicJhFkYGsqOkEtxrve0EoQ==", - "license": "MIT", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@capacitor/android/-/android-6.2.0.tgz", + "integrity": "sha512-3YIDPylV0Q2adEQ/H568p496QdYG0jK/XGMdx7OGSqdBZen92ciAsYdyhLtyl91UVsN1lBhDi5H6j3T2KS6aJg==", "peerDependencies": { - "@capacitor/core": "^6.1.0" + "@capacitor/core": "^6.2.0" } }, "node_modules/@capacitor/app": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@capacitor/app/-/app-6.0.1.tgz", - "integrity": "sha512-0kXbOl7LPPMFVcAii3u/7Ps0DvXlr7dtHT97r9J1faDlgdQLQUvtGp48tjvFm48gqHI0aOPRJnTBr5JXW4ETYg==", - "license": "MIT", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@capacitor/app/-/app-6.0.2.tgz", + "integrity": "sha512-SiGTGgslK4TbWJVImCUL1odul7/YFkVfkYtAYS9AAEzQpxBECBeRnuN3FFBcfZ9eiN1XxFBFchhiwpxtx/c7yQ==", "peerDependencies": { "@capacitor/core": "^6.0.0" } @@ -3052,7 +2339,6 @@ "resolved": "https://registry.npmjs.org/@capacitor/assets/-/assets-3.0.5.tgz", "integrity": "sha512-ohz/OUq61Y1Fc6aVSt0uDrUdeOA7oTH4pkWDbv/8I3UrPjH7oPkzYhShuDRUjekNp9RBi198VSFdt0CetpEOzw==", "dev": true, - "license": "MIT", "dependencies": { "@capacitor/cli": "^5.3.0", "@ionic/utils-array": "2.1.6", @@ -3079,7 +2365,6 @@ "resolved": "https://registry.npmjs.org/@capacitor/cli/-/cli-5.7.8.tgz", "integrity": "sha512-qN8LDlREMhrYhOvVXahoJVNkP8LP55/YPRJrzTAFrMqlNJC18L3CzgWYIblFPnuwfbH/RxbfoZT/ydkwgVpMrw==", "dev": true, - "license": "MIT", "dependencies": { "@ionic/cli-framework-output": "^2.2.5", "@ionic/utils-fs": "^3.1.6", @@ -3112,7 +2397,6 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", "dev": true, - "license": "MIT", "engines": { "node": "^12.20.0 || >=14" } @@ -3122,7 +2406,6 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, - "license": "MIT", "dependencies": { "ms": "2.1.2" }, @@ -3140,7 +2423,6 @@ "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } @@ -3150,7 +2432,6 @@ "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", "dev": true, - "license": "MIT", "bin": { "is-docker": "cli.js" }, @@ -3166,7 +2447,6 @@ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", "dev": true, - "license": "MIT", "dependencies": { "is-docker": "^2.0.0" }, @@ -3174,12 +2454,17 @@ "node": ">=8" } }, + "node_modules/@capacitor/assets/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, "node_modules/@capacitor/assets/node_modules/open": { "version": "8.4.2", "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", "dev": true, - "license": "MIT", "dependencies": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", @@ -3196,19 +2481,16 @@ "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true, - "license": "0BSD" + "dev": true }, "node_modules/@capacitor/cli": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/@capacitor/cli/-/cli-6.1.2.tgz", - "integrity": "sha512-HKCNGE0RP8U7aiEF2vg5wTivJROS8BVfu8a3yYJb1mRQvzv+czpmtHNsTWS/WukvwoxUjyjRmsNQSAACHfMTmQ==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@capacitor/cli/-/cli-6.2.0.tgz", + "integrity": "sha512-EWcXG39mZh35zrHhOqzN1ILeSyMRyEqWVtQDXqMGjCXYRH6b6p5TvyvLDN8ZNy26tbhI3i79gfrgirt+mNwuuw==", "dev": true, - "license": "MIT", "dependencies": { "@ionic/cli-framework-output": "^2.2.5", "@ionic/utils-fs": "^3.1.6", - "@ionic/utils-process": "^2.1.11", "@ionic/utils-subprocess": "2.1.11", "@ionic/utils-terminal": "^2.3.3", "commander": "^9.3.0", @@ -3238,7 +2520,6 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", "dev": true, - "license": "MIT", "engines": { "node": "^12.20.0 || >=14" } @@ -3248,7 +2529,6 @@ "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } @@ -3258,7 +2538,6 @@ "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", "dev": true, - "license": "MIT", "bin": { "is-docker": "cli.js" }, @@ -3274,7 +2553,6 @@ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", "dev": true, - "license": "MIT", "dependencies": { "is-docker": "^2.0.0" }, @@ -3287,7 +2565,6 @@ "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", "dev": true, - "license": "MIT", "dependencies": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", @@ -3301,37 +2578,33 @@ } }, "node_modules/@capacitor/core": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/@capacitor/core/-/core-6.1.2.tgz", - "integrity": "sha512-xFy1/4qLFLp5WCIzIhtwUuVNNoz36+V7/BzHmLqgVJcvotc4MMjswW/TshnPQaLLujEOaLkA4h8ZJ0uoK3ImGg==", - "license": "MIT", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@capacitor/core/-/core-6.2.0.tgz", + "integrity": "sha512-B9IlJtDpUqhhYb+T8+cp2Db/3RETX36STgjeU2kQZBs/SLAcFiMama227o+msRjLeo3DO+7HJjWVA1+XlyyPEg==", "dependencies": { "tslib": "^2.1.0" } }, "node_modules/@capacitor/filesystem": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@capacitor/filesystem/-/filesystem-6.0.1.tgz", - "integrity": "sha512-eHhXm6tzBIQhErzFnfOE6eA1U+15DHc2212/COfzzGGRk/dyGympoVV3ct2YPVzvpTSxMEW3xFocORv/xD9gFg==", - "license": "MIT", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@capacitor/filesystem/-/filesystem-6.0.2.tgz", + "integrity": "sha512-OUDjGPljC3/q6wFnCCBUgElfZgAaSaoKeh0ij2eoDygn5KVXta+5CiJ7H7o2/ziTW6WOyvP0++EC3DT0TzR23A==", "peerDependencies": { "@capacitor/core": "^6.0.0" } }, "node_modules/@capacitor/ios": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/@capacitor/ios/-/ios-6.1.2.tgz", - "integrity": "sha512-HaeW68KisBd/7TmavzPDlL2bpoDK5AjR2ZYrqU4TlGwM88GtQfvduBCAlSCj20X0w/4+rWMkseD9dAAkacjiyQ==", - "license": "MIT", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@capacitor/ios/-/ios-6.2.0.tgz", + "integrity": "sha512-gisvZBIrKT1siiumgpLPY63HmJe69Ed/dOmfQQ+U1MIJmOR5gWGWvfO7QSj/FMatVZS4Xt/8jCoUgzDD1U6kSw==", "peerDependencies": { - "@capacitor/core": "^6.1.0" + "@capacitor/core": "^6.2.0" } }, "node_modules/@capacitor/splash-screen": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@capacitor/splash-screen/-/splash-screen-6.0.2.tgz", - "integrity": "sha512-WC0KYZ+ev15up03xs4fTnoTKwBVUSxXsKKQr/8XAncvi/nAG8qrpanW8OlavSC5zF5e1IZZDLsI2GSv0SkZ7VQ==", - "license": "MIT", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@capacitor/splash-screen/-/splash-screen-6.0.3.tgz", + "integrity": "sha512-tpVljeNGSwVCIc8lMQkyiCQFokk2PwgYPdDtPnGjFthqmXW/WhIxW8QYl4MUqyLwwgwTEbp4u3Kcv2zqQu2L6Q==", "peerDependencies": { "@capacitor/core": "^6.0.0" } @@ -3341,7 +2614,6 @@ "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.1.90" } @@ -3351,7 +2623,6 @@ "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", "dev": true, - "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "0.3.9" }, @@ -3364,28 +2635,25 @@ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", "dev": true, - "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "node_modules/@discoveryjs/json-ext": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.6.1.tgz", - "integrity": "sha512-boghen8F0Q8D+0/Q1/1r6DUEieUJ8w2a1gIknExMSHBsJFOr2+0KUfHiVYBvucPwl3+RU5PFBK833FjFCh3BhA==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.6.3.tgz", + "integrity": "sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=14.17.0" } }, "node_modules/@es-joy/jsdoccomment": { - "version": "0.48.0", - "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.48.0.tgz", - "integrity": "sha512-G6QUWIcC+KvSwXNsJyDTHvqUdNoAVJPPgkc3+Uk4WBKqZvoXhlvazOgm9aL0HwihJLQf0l+tOE2UFzXBqCqgDw==", + "version": "0.49.0", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.49.0.tgz", + "integrity": "sha512-xjZTSFgECpb9Ohuk5yMX5RhUEbfeQcuOp8IF60e+wyzWEF0M5xeSgqsfLtvPEX8BIyOX9saZqzuGPmZ8oWc+5Q==", "dev": true, - "license": "MIT", "dependencies": { "comment-parser": "1.4.1", "esquery": "^1.6.0", @@ -3396,9 +2664,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.0.tgz", - "integrity": "sha512-3sG8Zwa5fMcA9bgqB8AfWPQ+HFke6uD3h1s3RIwUNK8EG7a4buxvuFTs3j1IMs2NXAk9F30C/FF4vxRgQCcmoQ==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.0.tgz", + "integrity": "sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==", "cpu": [ "ppc64" ], @@ -3413,9 +2681,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.0.tgz", - "integrity": "sha512-+KuOHTKKyIKgEEqKbGTK8W7mPp+hKinbMBeEnNzjJGyFcWsfrXjSTNluJHCY1RqhxFurdD8uNXQDei7qDlR6+g==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.0.tgz", + "integrity": "sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==", "cpu": [ "arm" ], @@ -3430,9 +2698,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.0.tgz", - "integrity": "sha512-EuHFUYkAVfU4qBdyivULuu03FhJO4IJN9PGuABGrFy4vUuzk91P2d+npxHcFdpUnfYKy0PuV+n6bKIpHOB3prQ==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.0.tgz", + "integrity": "sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==", "cpu": [ "arm64" ], @@ -3447,9 +2715,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.0.tgz", - "integrity": "sha512-WRrmKidLoKDl56LsbBMhzTTBxrsVwTKdNbKDalbEZr0tcsBgCLbEtoNthOW6PX942YiYq8HzEnb4yWQMLQuipQ==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.0.tgz", + "integrity": "sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==", "cpu": [ "x64" ], @@ -3464,9 +2732,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.0.tgz", - "integrity": "sha512-YLntie/IdS31H54Ogdn+v50NuoWF5BDkEUFpiOChVa9UnKpftgwzZRrI4J132ETIi+D8n6xh9IviFV3eXdxfow==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.0.tgz", + "integrity": "sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==", "cpu": [ "arm64" ], @@ -3481,9 +2749,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.0.tgz", - "integrity": "sha512-IMQ6eme4AfznElesHUPDZ+teuGwoRmVuuixu7sv92ZkdQcPbsNHzutd+rAfaBKo8YK3IrBEi9SLLKWJdEvJniQ==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.0.tgz", + "integrity": "sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==", "cpu": [ "x64" ], @@ -3498,9 +2766,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.0.tgz", - "integrity": "sha512-0muYWCng5vqaxobq6LB3YNtevDFSAZGlgtLoAc81PjUfiFz36n4KMpwhtAd4he8ToSI3TGyuhyx5xmiWNYZFyw==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.0.tgz", + "integrity": "sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==", "cpu": [ "arm64" ], @@ -3515,9 +2783,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.0.tgz", - "integrity": "sha512-XKDVu8IsD0/q3foBzsXGt/KjD/yTKBCIwOHE1XwiXmrRwrX6Hbnd5Eqn/WvDekddK21tfszBSrE/WMaZh+1buQ==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.0.tgz", + "integrity": "sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==", "cpu": [ "x64" ], @@ -3532,9 +2800,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.0.tgz", - "integrity": "sha512-SEELSTEtOFu5LPykzA395Mc+54RMg1EUgXP+iw2SJ72+ooMwVsgfuwXo5Fn0wXNgWZsTVHwY2cg4Vi/bOD88qw==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.0.tgz", + "integrity": "sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==", "cpu": [ "arm" ], @@ -3549,9 +2817,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.0.tgz", - "integrity": "sha512-j1t5iG8jE7BhonbsEg5d9qOYcVZv/Rv6tghaXM/Ug9xahM0nX/H2gfu6X6z11QRTMT6+aywOMA8TDkhPo8aCGw==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.0.tgz", + "integrity": "sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==", "cpu": [ "arm64" ], @@ -3566,9 +2834,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.0.tgz", - "integrity": "sha512-P7O5Tkh2NbgIm2R6x1zGJJsnacDzTFcRWZyTTMgFdVit6E98LTxO+v8LCCLWRvPrjdzXHx9FEOA8oAZPyApWUA==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.0.tgz", + "integrity": "sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==", "cpu": [ "ia32" ], @@ -3583,9 +2851,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.0.tgz", - "integrity": "sha512-InQwepswq6urikQiIC/kkx412fqUZudBO4SYKu0N+tGhXRWUqAx+Q+341tFV6QdBifpjYgUndV1hhMq3WeJi7A==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.0.tgz", + "integrity": "sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==", "cpu": [ "loong64" ], @@ -3600,9 +2868,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.0.tgz", - "integrity": "sha512-J9rflLtqdYrxHv2FqXE2i1ELgNjT+JFURt/uDMoPQLcjWQA5wDKgQA4t/dTqGa88ZVECKaD0TctwsUfHbVoi4w==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.0.tgz", + "integrity": "sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==", "cpu": [ "mips64el" ], @@ -3617,9 +2885,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.0.tgz", - "integrity": "sha512-cShCXtEOVc5GxU0fM+dsFD10qZ5UpcQ8AM22bYj0u/yaAykWnqXJDpd77ublcX6vdDsWLuweeuSNZk4yUxZwtw==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.0.tgz", + "integrity": "sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==", "cpu": [ "ppc64" ], @@ -3634,9 +2902,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.0.tgz", - "integrity": "sha512-HEtaN7Y5UB4tZPeQmgz/UhzoEyYftbMXrBCUjINGjh3uil+rB/QzzpMshz3cNUxqXN7Vr93zzVtpIDL99t9aRw==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.0.tgz", + "integrity": "sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==", "cpu": [ "riscv64" ], @@ -3651,9 +2919,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.0.tgz", - "integrity": "sha512-WDi3+NVAuyjg/Wxi+o5KPqRbZY0QhI9TjrEEm+8dmpY9Xir8+HE/HNx2JoLckhKbFopW0RdO2D72w8trZOV+Wg==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.0.tgz", + "integrity": "sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==", "cpu": [ "s390x" ], @@ -3668,9 +2936,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.0.tgz", - "integrity": "sha512-a3pMQhUEJkITgAw6e0bWA+F+vFtCciMjW/LPtoj99MhVt+Mfb6bbL9hu2wmTZgNd994qTAEw+U/r6k3qHWWaOQ==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.0.tgz", + "integrity": "sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==", "cpu": [ "x64" ], @@ -3685,9 +2953,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.0.tgz", - "integrity": "sha512-cRK+YDem7lFTs2Q5nEv/HHc4LnrfBCbH5+JHu6wm2eP+d8OZNoSMYgPZJq78vqQ9g+9+nMuIsAO7skzphRXHyw==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.0.tgz", + "integrity": "sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==", "cpu": [ "x64" ], @@ -3702,9 +2970,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.0.tgz", - "integrity": "sha512-suXjq53gERueVWu0OKxzWqk7NxiUWSUlrxoZK7usiF50C6ipColGR5qie2496iKGYNLhDZkPxBI3erbnYkU0rQ==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.0.tgz", + "integrity": "sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==", "cpu": [ "arm64" ], @@ -3719,9 +2987,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.0.tgz", - "integrity": "sha512-6p3nHpby0DM/v15IFKMjAaayFhqnXV52aEmv1whZHX56pdkK+MEaLoQWj+H42ssFarP1PcomVhbsR4pkz09qBg==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.0.tgz", + "integrity": "sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==", "cpu": [ "x64" ], @@ -3736,9 +3004,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.0.tgz", - "integrity": "sha512-BFelBGfrBwk6LVrmFzCq1u1dZbG4zy/Kp93w2+y83Q5UGYF1d8sCzeLI9NXjKyujjBBniQa8R8PzLFAUrSM9OA==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.0.tgz", + "integrity": "sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==", "cpu": [ "x64" ], @@ -3753,9 +3021,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.0.tgz", - "integrity": "sha512-lY6AC8p4Cnb7xYHuIxQ6iYPe6MfO2CC43XXKo9nBXDb35krYt7KGhQnOkRGar5psxYkircpCqfbNDB4uJbS2jQ==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.0.tgz", + "integrity": "sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==", "cpu": [ "arm64" ], @@ -3770,9 +3038,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.0.tgz", - "integrity": "sha512-7L1bHlOTcO4ByvI7OXVI5pNN6HSu6pUQq9yodga8izeuB1KcT2UkHaH6118QJwopExPn0rMHIseCTx1CRo/uNA==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.0.tgz", + "integrity": "sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==", "cpu": [ "ia32" ], @@ -3787,9 +3055,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.0.tgz", - "integrity": "sha512-Arm+WgUFLUATuoxCJcahGuk6Yj9Pzxd6l11Zb/2aAuv5kWWvvfhLFo2fni4uSK5vzlUdCGZ/BdV5tH8klj8p8g==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.0.tgz", + "integrity": "sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==", "cpu": [ "x64" ], @@ -3804,17 +3072,19 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", + "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", "dev": true, - "license": "MIT", "dependencies": { - "eslint-visitor-keys": "^3.3.0" + "eslint-visitor-keys": "^3.4.3" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, + "funding": { + "url": "https://opencollective.com/eslint" + }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } @@ -3824,7 +3094,6 @@ "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, - "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -3833,26 +3102,72 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", - "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", "dev": true, - "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, + "node_modules/@eslint/config-array": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.1.tgz", + "integrity": "sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==", + "dev": true, + "dependencies": { + "@eslint/object-schema": "^2.1.5", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/core": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.9.1.tgz", + "integrity": "sha512-GuUdqkyyzQI5RMIWkHhvTWLCyLo1jNK3vzkSyaExH5kHPDHcuL2VOpHjmMY+y3+NC69qAKToBqldTBgYeLSr9Q==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "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.2.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", + "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", "dev": true, - "license": "MIT", "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", @@ -3860,7 +3175,7 @@ "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" @@ -3871,7 +3186,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, - "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -3879,63 +3193,27 @@ "uri-js": "^4.2.2" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/eslintrc/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, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@eslint/eslintrc/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==", + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, - "license": "BSD-2-Clause", "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" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, "node_modules/@eslint/eslintrc/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, - "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, "engines": { - "node": ">=8" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -3945,15 +3223,13 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@eslint/eslintrc/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -3961,67 +3237,69 @@ "node": "*" } }, - "node_modules/@eslint/eslintrc/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "node_modules/@eslint/js": { + "version": "9.17.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.17.0.tgz", + "integrity": "sha512-Sxc4hqcs1kTu0iID3kcZDW3JHq2a77HO9P8CP6YEA/FpH3Ll8UXE2r/86Rz9YJLKme39S9vU5OWNjC6Xl0Cr3w==", "dev": true, - "license": "(MIT OR CC0-1.0)", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@eslint/js": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", - "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "node_modules/@eslint/object-schema": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.5.tgz", + "integrity": "sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==", "dev": true, - "license": "MIT", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "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==", - "deprecated": "Use @eslint/config-array instead", + "node_modules/@eslint/plugin-kit": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.4.tgz", + "integrity": "sha512-zSkKow6H5Kdm0ZUQUB2kV5JIXqoG0+uH5YADhaEHswm664N9Db8dXSi0nMJpacpMf+MyyglF1vnZohpEg5yUtg==", "dev": true, - "license": "Apache-2.0", "dependencies": { - "@humanwhocodes/object-schema": "^2.0.2", - "debug": "^4.3.1", - "minimatch": "^3.0.5" + "levn": "^0.4.1" }, "engines": { - "node": ">=10.10.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "engines": { + "node": ">=18.18.0" } }, - "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", "dev": true, - "license": "ISC", "dependencies": { - "brace-expansion": "^1.1.7" + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" }, "engines": { - "node": "*" + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, "node_modules/@humanwhocodes/module-importer": { @@ -4029,7 +3307,6 @@ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, - "license": "Apache-2.0", "engines": { "node": ">=12.22" }, @@ -4038,70 +3315,74 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "deprecated": "Use @eslint/object-schema instead", + "node_modules/@humanwhocodes/retry": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", + "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", "dev": true, - "license": "BSD-3-Clause" + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } }, "node_modules/@hutson/parse-repository-url": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/@hutson/parse-repository-url/-/parse-repository-url-3.0.2.tgz", "integrity": "sha512-H9XAx3hc0BQHY6l+IFSWHDySypcXsvsuLhgYLUGywmJ5pswRVQJUHpOsobnLYp2ZUaUlKiKDrgWWhosOwAEM8Q==", "dev": true, - "license": "Apache-2.0", "engines": { "node": ">=6.9.0" } }, "node_modules/@inquirer/checkbox": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-2.5.0.tgz", - "integrity": "sha512-sMgdETOfi2dUHT8r7TT1BTKOwNvdDGFDXYWtQ2J69SvlYNntk9I/gJe7r5yvMwwsuKnYbuRs3pNhx4tgNck5aA==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.0.3.tgz", + "integrity": "sha512-CEt9B4e8zFOGtc/LYeQx5m8nfqQeG/4oNNv0PUvXGG0mys+wR/WbJ3B4KfSQ4Fcr3AQfpiuFOi3fVvmPfvNbxw==", "dev": true, - "license": "MIT", "dependencies": { - "@inquirer/core": "^9.1.0", - "@inquirer/figures": "^1.0.5", - "@inquirer/type": "^1.5.3", + "@inquirer/core": "^10.1.1", + "@inquirer/figures": "^1.0.8", + "@inquirer/type": "^3.0.1", "ansi-escapes": "^4.3.2", "yoctocolors-cjs": "^2.1.2" }, "engines": { "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" } }, "node_modules/@inquirer/confirm": { - "version": "3.1.22", - "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-3.1.22.tgz", - "integrity": "sha512-gsAKIOWBm2Q87CDfs9fEo7wJT3fwWIJfnDGMn9Qy74gBnNFOACDNfhUzovubbJjWnKLGBln7/NcSmZwj5DuEXg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.0.tgz", + "integrity": "sha512-osaBbIMEqVFjTX5exoqPXs6PilWQdjaLhGtMDXMXg/yxkHXNq43GlxGyTA35lK2HpzUgDN+Cjh/2AmqCN0QJpw==", "dev": true, - "license": "MIT", "dependencies": { - "@inquirer/core": "^9.0.10", - "@inquirer/type": "^1.5.2" + "@inquirer/core": "^10.1.1", + "@inquirer/type": "^3.0.1" }, "engines": { "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" } }, "node_modules/@inquirer/core": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-9.2.1.tgz", - "integrity": "sha512-F2VBt7W/mwqEU4bL0RnHNZmC/OxzNx9cOYxHqnXX3MP6ruYvZUZAW9imgN9+h/uBT/oP8Gh888J2OZSbjSeWcg==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.1.tgz", + "integrity": "sha512-rmZVXy9iZvO3ZStEe/ayuuwIJ23LSF13aPMlLMTQARX6lGUBDHGV8UB5i9MRrfy0+mZwt5/9bdy8llszSD3NQA==", "dev": true, - "license": "MIT", "dependencies": { - "@inquirer/figures": "^1.0.6", - "@inquirer/type": "^2.0.0", - "@types/mute-stream": "^0.0.4", - "@types/node": "^22.5.5", - "@types/wrap-ansi": "^3.0.0", + "@inquirer/figures": "^1.0.8", + "@inquirer/type": "^3.0.1", "ansi-escapes": "^4.3.2", "cli-width": "^4.1.0", - "mute-stream": "^1.0.0", + "mute-stream": "^2.0.0", "signal-exit": "^4.1.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^6.2.0", @@ -4111,267 +3392,232 @@ "node": ">=18" } }, - "node_modules/@inquirer/core/node_modules/@inquirer/type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-2.0.0.tgz", - "integrity": "sha512-XvJRx+2KR3YXyYtPUUy+qd9i7p+GO9Ko6VIIpWlBrpWwXDv8WLFeHTxz35CfQFUiBMLXlGHhGzys7lqit9gWag==", - "dev": true, - "license": "MIT", - "dependencies": { - "mute-stream": "^1.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/core/node_modules/@types/node": { - "version": "22.6.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.6.1.tgz", - "integrity": "sha512-V48tCfcKb/e6cVUigLAaJDAILdMP0fUW6BidkPK4GpGjXcfbnoHasCZDwz3N3yVt5we2RHm4XTQCpv0KJz9zqw==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.19.2" - } - }, "node_modules/@inquirer/editor": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-2.2.0.tgz", - "integrity": "sha512-9KHOpJ+dIL5SZli8lJ6xdaYLPPzB8xB9GZItg39MBybzhxA16vxmszmQFrRwbOA918WA2rvu8xhDEg/p6LXKbw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.0.tgz", + "integrity": "sha512-Z3LeGsD3WlItDqLxTPciZDbGtm0wrz7iJGS/uUxSiQxef33ZrBq7LhsXg30P7xrWz1kZX4iGzxxj5SKZmJ8W+w==", "dev": true, - "license": "MIT", "dependencies": { - "@inquirer/core": "^9.1.0", - "@inquirer/type": "^1.5.3", + "@inquirer/core": "^10.1.1", + "@inquirer/type": "^3.0.1", "external-editor": "^3.1.0" }, "engines": { "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" } }, "node_modules/@inquirer/expand": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-2.3.0.tgz", - "integrity": "sha512-qnJsUcOGCSG1e5DTOErmv2BPQqrtT6uzqn1vI/aYGiPKq+FgslGZmtdnXbhuI7IlT7OByDoEEqdnhUnVR2hhLw==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.3.tgz", + "integrity": "sha512-MDszqW4HYBpVMmAoy/FA9laLrgo899UAga0itEjsYrBthKieDZNc0e16gdn7N3cQ0DSf/6zsTBZMuDYDQU4ktg==", "dev": true, - "license": "MIT", "dependencies": { - "@inquirer/core": "^9.1.0", - "@inquirer/type": "^1.5.3", + "@inquirer/core": "^10.1.1", + "@inquirer/type": "^3.0.1", "yoctocolors-cjs": "^2.1.2" }, "engines": { "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" } }, "node_modules/@inquirer/figures": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.6.tgz", - "integrity": "sha512-yfZzps3Cso2UbM7WlxKwZQh2Hs6plrbjs1QnzQDZhK2DgyCo6D8AaHps9olkNcUFlcYERMqU3uJSp1gmy3s/qQ==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.8.tgz", + "integrity": "sha512-tKd+jsmhq21AP1LhexC0pPwsCxEhGgAkg28byjJAd+xhmIs8LUX8JbUc3vBf3PhLxWiB5EvyBE5X7JSPAqMAqg==", "dev": true, - "license": "MIT", "engines": { "node": ">=18" } }, "node_modules/@inquirer/input": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-2.3.0.tgz", - "integrity": "sha512-XfnpCStx2xgh1LIRqPXrTNEEByqQWoxsWYzNRSEUxJ5c6EQlhMogJ3vHKu8aXuTacebtaZzMAHwEL0kAflKOBw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.1.0.tgz", + "integrity": "sha512-16B8A9hY741yGXzd8UJ9R8su/fuuyO2e+idd7oVLYjP23wKJ6ILRIIHcnXe8/6AoYgwRS2zp4PNsW/u/iZ24yg==", "dev": true, - "license": "MIT", "dependencies": { - "@inquirer/core": "^9.1.0", - "@inquirer/type": "^1.5.3" + "@inquirer/core": "^10.1.1", + "@inquirer/type": "^3.0.1" }, "engines": { "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" } }, "node_modules/@inquirer/number": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-1.1.0.tgz", - "integrity": "sha512-ilUnia/GZUtfSZy3YEErXLJ2Sljo/mf9fiKc08n18DdwdmDbOzRcTv65H1jjDvlsAuvdFXf4Sa/aL7iw/NanVA==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.3.tgz", + "integrity": "sha512-HA/W4YV+5deKCehIutfGBzNxWH1nhvUC67O4fC9ufSijn72yrYnRmzvC61dwFvlXIG1fQaYWi+cqNE9PaB9n6Q==", "dev": true, - "license": "MIT", "dependencies": { - "@inquirer/core": "^9.1.0", - "@inquirer/type": "^1.5.3" + "@inquirer/core": "^10.1.1", + "@inquirer/type": "^3.0.1" }, "engines": { "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" } }, "node_modules/@inquirer/password": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-2.2.0.tgz", - "integrity": "sha512-5otqIpgsPYIshqhgtEwSspBQE40etouR8VIxzpJkv9i0dVHIpyhiivbkH9/dGiMLdyamT54YRdGJLfl8TFnLHg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.3.tgz", + "integrity": "sha512-3qWjk6hS0iabG9xx0U1plwQLDBc/HA/hWzLFFatADpR6XfE62LqPr9GpFXBkLU0KQUaIXZ996bNG+2yUvocH8w==", "dev": true, - "license": "MIT", "dependencies": { - "@inquirer/core": "^9.1.0", - "@inquirer/type": "^1.5.3", + "@inquirer/core": "^10.1.1", + "@inquirer/type": "^3.0.1", "ansi-escapes": "^4.3.2" }, "engines": { "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" } }, "node_modules/@inquirer/prompts": { - "version": "5.3.8", - "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-5.3.8.tgz", - "integrity": "sha512-b2BudQY/Si4Y2a0PdZZL6BeJtl8llgeZa7U2j47aaJSCeAl1e4UI7y8a9bSkO3o/ZbZrgT5muy/34JbsjfIWxA==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.1.0.tgz", + "integrity": "sha512-5U/XiVRH2pp1X6gpNAjWOglMf38/Ys522ncEHIKT1voRUvSj/DQnR22OVxHnwu5S+rCFaUiPQ57JOtMFQayqYA==", "dev": true, - "license": "MIT", "dependencies": { - "@inquirer/checkbox": "^2.4.7", - "@inquirer/confirm": "^3.1.22", - "@inquirer/editor": "^2.1.22", - "@inquirer/expand": "^2.1.22", - "@inquirer/input": "^2.2.9", - "@inquirer/number": "^1.0.10", - "@inquirer/password": "^2.1.22", - "@inquirer/rawlist": "^2.2.4", - "@inquirer/search": "^1.0.7", - "@inquirer/select": "^2.4.7" + "@inquirer/checkbox": "^4.0.2", + "@inquirer/confirm": "^5.0.2", + "@inquirer/editor": "^4.1.0", + "@inquirer/expand": "^4.0.2", + "@inquirer/input": "^4.0.2", + "@inquirer/number": "^3.0.2", + "@inquirer/password": "^4.0.2", + "@inquirer/rawlist": "^4.0.2", + "@inquirer/search": "^3.0.2", + "@inquirer/select": "^4.0.2" }, "engines": { "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" } }, "node_modules/@inquirer/rawlist": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-2.3.0.tgz", - "integrity": "sha512-zzfNuINhFF7OLAtGHfhwOW2TlYJyli7lOUoJUXw/uyklcwalV6WRXBXtFIicN8rTRK1XTiPWB4UY+YuW8dsnLQ==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.0.3.tgz", + "integrity": "sha512-5MhinSzfmOiZlRoPezfbJdfVCZikZs38ja3IOoWe7H1dxL0l3Z2jAUgbBldeyhhOkELdGvPlBfQaNbeLslib1w==", "dev": true, - "license": "MIT", "dependencies": { - "@inquirer/core": "^9.1.0", - "@inquirer/type": "^1.5.3", + "@inquirer/core": "^10.1.1", + "@inquirer/type": "^3.0.1", "yoctocolors-cjs": "^2.1.2" }, "engines": { "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" } }, "node_modules/@inquirer/search": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-1.1.0.tgz", - "integrity": "sha512-h+/5LSj51dx7hp5xOn4QFnUaKeARwUCLs6mIhtkJ0JYPBLmEYjdHSYh7I6GrLg9LwpJ3xeX0FZgAG1q0QdCpVQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.0.3.tgz", + "integrity": "sha512-mQTCbdNolTGvGGVCJSI6afDwiSGTV+fMLPEIMDJgIV6L/s3+RYRpxt6t0DYnqMQmemnZ/Zq0vTIRwoHT1RgcTg==", "dev": true, - "license": "MIT", "dependencies": { - "@inquirer/core": "^9.1.0", - "@inquirer/figures": "^1.0.5", - "@inquirer/type": "^1.5.3", + "@inquirer/core": "^10.1.1", + "@inquirer/figures": "^1.0.8", + "@inquirer/type": "^3.0.1", "yoctocolors-cjs": "^2.1.2" }, "engines": { "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" } }, "node_modules/@inquirer/select": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-2.5.0.tgz", - "integrity": "sha512-YmDobTItPP3WcEI86GvPo+T2sRHkxxOq/kXmsBjHS5BVXUgvgZ5AfJjkvQvZr03T81NnI3KrrRuMzeuYUQRFOA==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.0.3.tgz", + "integrity": "sha512-OZfKDtDE8+J54JYAFTUGZwvKNfC7W/gFCjDkcsO7HnTH/wljsZo9y/FJquOxMy++DY0+9l9o/MOZ8s5s1j5wmw==", "dev": true, - "license": "MIT", "dependencies": { - "@inquirer/core": "^9.1.0", - "@inquirer/figures": "^1.0.5", - "@inquirer/type": "^1.5.3", + "@inquirer/core": "^10.1.1", + "@inquirer/figures": "^1.0.8", + "@inquirer/type": "^3.0.1", "ansi-escapes": "^4.3.2", "yoctocolors-cjs": "^2.1.2" }, "engines": { "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" } }, "node_modules/@inquirer/type": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-1.5.5.tgz", - "integrity": "sha512-MzICLu4yS7V8AA61sANROZ9vT1H3ooca5dSmI1FjZkzq7o/koMsRfQSzRtFo+F3Ao4Sf1C0bpLKejpKB/+j6MA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.1.tgz", + "integrity": "sha512-+ksJMIy92sOAiAccGpcKZUc3bYO07cADnscIxHBknEm3uNts3movSmBofc1908BNy5edKscxYeAdaX1NXkHS6A==", "dev": true, - "license": "MIT", - "dependencies": { - "mute-stream": "^1.0.0" - }, "engines": { "node": ">=18" - } - }, - "node_modules/@ionic-native/core": { - "version": "5.36.0", - "resolved": "https://registry.npmjs.org/@ionic-native/core/-/core-5.36.0.tgz", - "integrity": "sha512-lOrkktadlKYbYf1LrDyAtsu1JnQ0oCCdkOU7iHQ8oXnNOkMwobFfD2m62F1CoOr0u9LIkpYnZSPjng8lZbmbNw==", - "license": "MIT", - "dependencies": { - "@types/cordova": "latest" - }, - "peerDependencies": { - "rxjs": "^5.5.0 || ^6.5.0" - } - }, - "node_modules/@ionic-native/file-opener": { - "version": "5.36.0", - "resolved": "https://registry.npmjs.org/@ionic-native/file-opener/-/file-opener-5.36.0.tgz", - "integrity": "sha512-UKp3pbqvQXsAtLMJ5JE+KcTMxpjSZMFebf6nvy/KJvwy85JGIaCV4ZVM/H9CFUrHJMWBH6wDbY+WPygnsrl4Yg==", - "license": "MIT", - "dependencies": { - "@types/cordova": "latest" }, "peerDependencies": { - "@ionic-native/core": "^5.1.0", - "rxjs": "^5.5.0 || ^6.5.0" + "@types/node": ">=18" } }, "node_modules/@ionic/angular": { - "version": "6.7.5", - "resolved": "https://registry.npmjs.org/@ionic/angular/-/angular-6.7.5.tgz", - "integrity": "sha512-nV8HP7RedjYkIAT8nVr5ifHNT0D3XzA74RPG3/WCCFJKunERNJ9SBiNkCTWhUpSkqsYYwEB4+SOOHz+R5NLk/w==", - "license": "MIT", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@ionic/angular/-/angular-7.8.6.tgz", + "integrity": "sha512-3Qe53hXpyjtx6fFcxt/NTAlauIawsGmCZJPauV5sAnSKVuX8C82C1zMAZTeJt6m2dnd71wythc98BXUXsx/UxQ==", "dependencies": { - "@ionic/core": "6.7.5", - "ionicons": "^6.1.3", + "@ionic/core": "7.8.6", + "ionicons": "^7.0.0", "jsonc-parser": "^3.0.0", - "tslib": "^2.0.0" + "tslib": "^2.3.0" }, "peerDependencies": { - "@angular/core": ">=12.0.0", - "@angular/forms": ">=12.0.0", - "@angular/router": ">=12.0.0", - "rxjs": ">=6.6.0", + "@angular/core": ">=14.0.0", + "@angular/forms": ">=14.0.0", + "@angular/router": ">=14.0.0", + "rxjs": ">=7.5.0", "zone.js": ">=0.11.0" } }, "node_modules/@ionic/angular-toolkit": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/@ionic/angular-toolkit/-/angular-toolkit-11.0.1.tgz", - "integrity": "sha512-dxx2RDbxDYM2nWRPIirKMJySHtqJ1u02T25PGbNb99W2Wlcmu1cza3+2/PQ8ga18yMz/dQqaGyEmPDf3ZSVO0w==", + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/@ionic/angular-toolkit/-/angular-toolkit-12.1.1.tgz", + "integrity": "sha512-VgD9pUg2ViI6a0W+8iUs7sGrQ8/1BQH43/ioAqGu/WWsgcnevy9ddTZp0M1MHbeVWaoauwbmNsG/lgNNEZq1hw==", "dev": true, - "license": "MIT", "dependencies": { - "@angular-devkit/core": "^17.0.0", - "@angular-devkit/schematics": "^17.0.0", - "@schematics/angular": "^17.0.0" + "@angular-devkit/core": "^18.0.0", + "@angular-devkit/schematics": "^18.0.0", + "@schematics/angular": "^18.0.0" } }, "node_modules/@ionic/angular-toolkit/node_modules/@angular-devkit/core": { - "version": "17.3.8", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.3.8.tgz", - "integrity": "sha512-Q8q0voCGudbdCgJ7lXdnyaxKHbNQBARH68zPQV72WT8NWy+Gw/tys870i6L58NWbBaCJEUcIj/kb6KoakSRu+Q==", + "version": "18.2.12", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.12.tgz", + "integrity": "sha512-NtB6ypsaDyPE6/fqWOdfTmACs+yK5RqfH5tStEzWFeeDsIEDYKsJ06ypuRep7qTjYus5Rmttk0Ds+cFgz8JdUQ==", "dev": true, - "license": "MIT", "dependencies": { - "ajv": "8.12.0", - "ajv-formats": "2.1.1", - "jsonc-parser": "3.2.1", - "picomatch": "4.0.1", + "ajv": "8.17.1", + "ajv-formats": "3.0.1", + "jsonc-parser": "3.3.1", + "picomatch": "4.0.2", "rxjs": "7.8.1", "source-map": "0.7.4" }, "engines": { - "node": "^18.13.0 || >=20.9.0", + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" }, @@ -4385,125 +3631,121 @@ } }, "node_modules/@ionic/angular-toolkit/node_modules/@angular-devkit/schematics": { - "version": "17.3.8", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-17.3.8.tgz", - "integrity": "sha512-QRVEYpIfgkprNHc916JlPuNbLzOgrm9DZalHasnLUz4P6g7pR21olb8YCyM2OTJjombNhya9ZpckcADU5Qyvlg==", + "version": "18.2.12", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-18.2.12.tgz", + "integrity": "sha512-mMea9txHbnCX5lXLHlo0RAgfhFHDio45/jMsREM2PA8UtVf2S8ltXz7ZwUrUyMQRv8vaSfn4ijDstF4hDMnRgQ==", "dev": true, - "license": "MIT", "dependencies": { - "@angular-devkit/core": "17.3.8", - "jsonc-parser": "3.2.1", - "magic-string": "0.30.8", + "@angular-devkit/core": "18.2.12", + "jsonc-parser": "3.3.1", + "magic-string": "0.30.11", "ora": "5.4.1", "rxjs": "7.8.1" }, "engines": { - "node": "^18.13.0 || >=20.9.0", + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" } }, "node_modules/@ionic/angular-toolkit/node_modules/@schematics/angular": { - "version": "17.3.8", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-17.3.8.tgz", - "integrity": "sha512-2g4OmSyE9YGq50Uj7fNI26P/TSAFJ7ZuirwTF2O7Xc4XRQ29/tYIIqhezpNlTb6rlYblcQuMcUZBrMfWJHcqJw==", + "version": "18.2.12", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-18.2.12.tgz", + "integrity": "sha512-sIoeipsisK5eTLW3XuNZYcal83AfslBbgI7LnV+3VrXwpasKPGHwo2ZdwhCd2IXAkuJ02Iyu7MyV0aQRM9i/3g==", "dev": true, - "license": "MIT", "dependencies": { - "@angular-devkit/core": "17.3.8", - "@angular-devkit/schematics": "17.3.8", - "jsonc-parser": "3.2.1" + "@angular-devkit/core": "18.2.12", + "@angular-devkit/schematics": "18.2.12", + "jsonc-parser": "3.3.1" }, "engines": { - "node": "^18.13.0 || >=20.9.0", + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" } }, - "node_modules/@ionic/angular-toolkit/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "node_modules/@ionic/angular-toolkit/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, - "license": "MIT", + "optional": true, + "peer": true, "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" } }, - "node_modules/@ionic/angular-toolkit/node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "node_modules/@ionic/angular-toolkit/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, - "license": "MIT", + "optional": true, + "peer": true, "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" + "is-glob": "^4.0.1" }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } + "engines": { + "node": ">= 6" } }, - "node_modules/@ionic/angular-toolkit/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==", + "node_modules/@ionic/angular-toolkit/node_modules/magic-string": { + "version": "0.30.11", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", + "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", "dev": true, - "license": "MIT" + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } }, - "node_modules/@ionic/angular-toolkit/node_modules/magic-string": { - "version": "0.30.8", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz", - "integrity": "sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==", + "node_modules/@ionic/angular-toolkit/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "dev": true, - "license": "MIT", + "optional": true, + "peer": true, "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" + "picomatch": "^2.2.1" }, "engines": { - "node": ">=12" + "node": ">=8.10.0" } }, - "node_modules/@ionic/angular-toolkit/node_modules/picomatch": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.1.tgz", - "integrity": "sha512-xUXwsxNjwTQ8K3GnT4pCJm+xq3RUPQbmkYJTP5aFIfNIvbcc/4MUxgBaaRSZJ6yGJZiGSyYlM6MzwTsRk8SYCg==", + "node_modules/@ionic/angular-toolkit/node_modules/readdirp/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, - "license": "MIT", + "optional": true, + "peer": true, "engines": { - "node": ">=12" + "node": ">=8.6" }, "funding": { "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/@ionic/angular-toolkit/node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" - } - }, "node_modules/@ionic/cli": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@ionic/cli/-/cli-7.2.0.tgz", "integrity": "sha512-IEms9Df8mJOoWPqgvZEXmqKztttHDFAz+9ewDPZGYv8Xx66Cj7zSen13O2Vf4FuLXhl+U95HXT9sAs4lDwFmcQ==", "dev": true, - "license": "MIT", "dependencies": { "@ionic/cli-framework": "6.0.1", "@ionic/cli-framework-output": "2.2.8", @@ -4544,7 +3786,6 @@ "resolved": "https://registry.npmjs.org/@ionic/cli-framework/-/cli-framework-6.0.1.tgz", "integrity": "sha512-Fyix4eQt2HKTV+GoeoiziQGZyqIA8RfoMqjGyAS5XgNXLOYW0P27Ph348hQZh9Mphjf+m0lOYa6dWQTEPzUHiQ==", "dev": true, - "license": "MIT", "dependencies": { "@ionic/cli-framework-output": "2.2.8", "@ionic/utils-array": "2.1.6", @@ -4571,7 +3812,6 @@ "resolved": "https://registry.npmjs.org/@ionic/cli-framework-output/-/cli-framework-output-2.2.8.tgz", "integrity": "sha512-TshtaFQsovB4NWRBydbNFawql6yul7d5bMiW1WYYf17hd99V6xdDdk3vtF51bw6sLkxON3bDQpWsnUc9/hVo3g==", "dev": true, - "license": "MIT", "dependencies": { "@ionic/utils-terminal": "2.3.5", "debug": "^4.0.0", @@ -4586,7 +3826,6 @@ "resolved": "https://registry.npmjs.org/@ionic/cli-framework-prompts/-/cli-framework-prompts-2.1.13.tgz", "integrity": "sha512-Yj1fz6p7OehreQ8C70bd9+M6tYP/rvzLw5JVj8pT/N9s0kQSjqEFRbs96LKr3lfd3TADZaS8OlZrQIqenFIUpg==", "dev": true, - "license": "MIT", "dependencies": { "@ionic/utils-terminal": "2.3.5", "debug": "^4.0.0", @@ -4602,7 +3841,6 @@ "resolved": "https://registry.npmjs.org/@ionic/utils-subprocess/-/utils-subprocess-3.0.1.tgz", "integrity": "sha512-cT4te3AQQPeIM9WCwIg8ohroJ8TjsYaMb2G4ZEgv9YzeDqHZ4JpeIKqG2SoaA3GmVQ3sOfhPM6Ox9sxphV/d1A==", "dev": true, - "license": "MIT", "dependencies": { "@ionic/utils-array": "2.1.6", "@ionic/utils-fs": "3.1.7", @@ -4617,76 +3855,12 @@ "node": ">=16.0.0" } }, - "node_modules/@ionic/cli-framework/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@ionic/cli-framework/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@ionic/cli-framework/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@ionic/cli-framework/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@ionic/cli-framework/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/@ionic/cli-framework/node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, - "license": "ISC", "dependencies": { "glob": "^7.1.3" }, @@ -4697,25 +3871,11 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@ionic/cli-framework/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@ionic/cli/node_modules/@ionic/utils-subprocess": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@ionic/utils-subprocess/-/utils-subprocess-3.0.1.tgz", "integrity": "sha512-cT4te3AQQPeIM9WCwIg8ohroJ8TjsYaMb2G4ZEgv9YzeDqHZ4JpeIKqG2SoaA3GmVQ3sOfhPM6Ox9sxphV/d1A==", "dev": true, - "license": "MIT", "dependencies": { "@ionic/utils-array": "2.1.6", "@ionic/utils-fs": "3.1.7", @@ -4730,75 +3890,11 @@ "node": ">=16.0.0" } }, - "node_modules/@ionic/cli/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@ionic/cli/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@ionic/cli/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@ionic/cli/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@ionic/cli/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/@ionic/cli/node_modules/is-docker": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", "dev": true, - "license": "MIT", "bin": { "is-docker": "cli.js" }, @@ -4814,7 +3910,6 @@ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", "dev": true, - "license": "MIT", "dependencies": { "is-docker": "^2.0.0" }, @@ -4827,7 +3922,6 @@ "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", "dev": true, - "license": "MIT", "dependencies": { "is-docker": "^2.0.0", "is-wsl": "^2.1.1" @@ -4839,27 +3933,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@ionic/cli/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@ionic/core": { - "version": "6.7.5", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.7.5.tgz", - "integrity": "sha512-zRkRn+h/Vs3xt/EVgBdShMKDyeGOM4RU31NPF2icfu3CUTH+VrMV569MUnNjYvd1Lu2xK90pYy4TaicSWmC1Pw==", - "license": "MIT", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.8.6.tgz", + "integrity": "sha512-HAYZdEmeJgOdo2kDlZkcCGHb+zs/vjU6iv4skbVBL7y+OnSv/oC2u83Yee8S3/aY0YAxkyBgu7hLTYH13Zc2Aw==", "dependencies": { - "@stencil/core": "^2.18.0", - "ionicons": "^6.1.3", + "@stencil/core": "^4.12.2", + "ionicons": "^7.2.2", "tslib": "^2.1.0" } }, @@ -4868,7 +3948,6 @@ "resolved": "https://registry.npmjs.org/@ionic/utils-array/-/utils-array-2.1.6.tgz", "integrity": "sha512-0JZ1Zkp3wURnv8oq6Qt7fMPo5MpjbLoUoa9Bu2Q4PJuSDWM8H8gwF3dQO7VTeUj3/0o1IB1wGkFWZZYgUXZMUg==", "dev": true, - "license": "MIT", "dependencies": { "debug": "^4.0.0", "tslib": "^2.0.1" @@ -4882,7 +3961,6 @@ "resolved": "https://registry.npmjs.org/@ionic/utils-fs/-/utils-fs-3.1.7.tgz", "integrity": "sha512-2EknRvMVfhnyhL1VhFkSLa5gOcycK91VnjfrTB0kbqkTFCOXyXgVLI5whzq7SLrgD9t1aqos3lMMQyVzaQ5gVA==", "dev": true, - "license": "MIT", "dependencies": { "@types/fs-extra": "^8.0.0", "debug": "^4.0.0", @@ -4898,7 +3976,6 @@ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", "dev": true, - "license": "MIT", "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", @@ -4914,7 +3991,6 @@ "resolved": "https://registry.npmjs.org/@ionic/utils-network/-/utils-network-2.1.7.tgz", "integrity": "sha512-5Q3NdZtSLiLs7ufuX9X293BvAwo8CxaD93Hkp3ODPgctLYErv3nFibhq3j+eguEqUh2um9WNXEUOuQ8x+Sd1fw==", "dev": true, - "license": "MIT", "dependencies": { "debug": "^4.0.0", "tslib": "^2.0.1" @@ -4928,7 +4004,6 @@ "resolved": "https://registry.npmjs.org/@ionic/utils-object/-/utils-object-2.1.6.tgz", "integrity": "sha512-vCl7sl6JjBHFw99CuAqHljYJpcE88YaH2ZW4ELiC/Zwxl5tiwn4kbdP/gxi2OT3MQb1vOtgAmSNRtusvgxI8ww==", "dev": true, - "license": "MIT", "dependencies": { "debug": "^4.0.0", "tslib": "^2.0.1" @@ -4942,7 +4017,6 @@ "resolved": "https://registry.npmjs.org/@ionic/utils-process/-/utils-process-2.1.12.tgz", "integrity": "sha512-Jqkgyq7zBs/v/J3YvKtQQiIcxfJyplPgECMWgdO0E1fKrrH8EF0QGHNJ9mJCn6PYe2UtHNS8JJf5G21e09DfYg==", "dev": true, - "license": "MIT", "dependencies": { "@ionic/utils-object": "2.1.6", "@ionic/utils-terminal": "2.3.5", @@ -4959,15 +4033,13 @@ "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/@ionic/utils-stream": { "version": "3.1.7", "resolved": "https://registry.npmjs.org/@ionic/utils-stream/-/utils-stream-3.1.7.tgz", "integrity": "sha512-eSELBE7NWNFIHTbTC2jiMvh1ABKGIpGdUIvARsNPMNQhxJB3wpwdiVnoBoTYp+5a6UUIww4Kpg7v6S7iTctH1w==", "dev": true, - "license": "MIT", "dependencies": { "debug": "^4.0.0", "tslib": "^2.0.1" @@ -4981,7 +4053,6 @@ "resolved": "https://registry.npmjs.org/@ionic/utils-subprocess/-/utils-subprocess-2.1.11.tgz", "integrity": "sha512-6zCDixNmZCbMCy5np8klSxOZF85kuDyzZSTTQKQP90ZtYNCcPYmuFSzaqDwApJT4r5L3MY3JrqK1gLkc6xiUPw==", "dev": true, - "license": "MIT", "dependencies": { "@ionic/utils-array": "2.1.5", "@ionic/utils-fs": "3.1.6", @@ -5001,7 +4072,6 @@ "resolved": "https://registry.npmjs.org/@ionic/utils-array/-/utils-array-2.1.5.tgz", "integrity": "sha512-HD72a71IQVBmQckDwmA8RxNVMTbxnaLbgFOl+dO5tbvW9CkkSFCv41h6fUuNsSEVgngfkn0i98HDuZC8mk+lTA==", "dev": true, - "license": "MIT", "dependencies": { "debug": "^4.0.0", "tslib": "^2.0.1" @@ -5015,7 +4085,6 @@ "resolved": "https://registry.npmjs.org/@ionic/utils-fs/-/utils-fs-3.1.6.tgz", "integrity": "sha512-eikrNkK89CfGPmexjTfSWl4EYqsPSBh0Ka7by4F0PLc1hJZYtJxUZV3X4r5ecA8ikjicUmcbU7zJmAjmqutG/w==", "dev": true, - "license": "MIT", "dependencies": { "@types/fs-extra": "^8.0.0", "debug": "^4.0.0", @@ -5031,7 +4100,6 @@ "resolved": "https://registry.npmjs.org/@ionic/utils-object/-/utils-object-2.1.5.tgz", "integrity": "sha512-XnYNSwfewUqxq+yjER1hxTKggftpNjFLJH0s37jcrNDwbzmbpFTQTVAp4ikNK4rd9DOebX/jbeZb8jfD86IYxw==", "dev": true, - "license": "MIT", "dependencies": { "debug": "^4.0.0", "tslib": "^2.0.1" @@ -5045,7 +4113,6 @@ "resolved": "https://registry.npmjs.org/@ionic/utils-process/-/utils-process-2.1.10.tgz", "integrity": "sha512-mZ7JEowcuGQK+SKsJXi0liYTcXd2bNMR3nE0CyTROpMECUpJeAvvaBaPGZf5ERQUPeWBVuwqAqjUmIdxhz5bxw==", "dev": true, - "license": "MIT", "dependencies": { "@ionic/utils-object": "2.1.5", "@ionic/utils-terminal": "2.3.3", @@ -5063,7 +4130,6 @@ "resolved": "https://registry.npmjs.org/@ionic/utils-stream/-/utils-stream-3.1.5.tgz", "integrity": "sha512-hkm46uHvEC05X/8PHgdJi4l4zv9VQDELZTM+Kz69odtO9zZYfnt8DkfXHJqJ+PxmtiE5mk/ehJWLnn/XAczTUw==", "dev": true, - "license": "MIT", "dependencies": { "debug": "^4.0.0", "tslib": "^2.0.1" @@ -5077,7 +4143,6 @@ "resolved": "https://registry.npmjs.org/@ionic/utils-terminal/-/utils-terminal-2.3.3.tgz", "integrity": "sha512-RnuSfNZ5fLEyX3R5mtcMY97cGD1A0NVBbarsSQ6yMMfRJ5YHU7hHVyUfvZeClbqkBC/pAqI/rYJuXKCT9YeMCQ==", "dev": true, - "license": "MIT", "dependencies": { "@types/slice-ansi": "^4.0.0", "debug": "^4.0.0", @@ -5093,48 +4158,11 @@ "node": ">=10.3.0" } }, - "node_modules/@ionic/utils-subprocess/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@ionic/utils-subprocess/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@ionic/utils-subprocess/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, "node_modules/@ionic/utils-subprocess/node_modules/fs-extra": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", "dev": true, - "license": "MIT", "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", @@ -5149,15 +4177,13 @@ "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/@ionic/utils-subprocess/node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, - "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -5175,71 +4201,32 @@ "resolved": "https://registry.npmjs.org/@ionic/utils-terminal/-/utils-terminal-2.3.5.tgz", "integrity": "sha512-3cKScz9Jx2/Pr9ijj1OzGlBDfcmx7OMVBt4+P1uRR0SSW4cm1/y3Mo4OY3lfkuaYifMNBW8Wz6lQHbs1bihr7A==", "dev": true, - "license": "MIT", - "dependencies": { - "@types/slice-ansi": "^4.0.0", - "debug": "^4.0.0", - "signal-exit": "^3.0.3", - "slice-ansi": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "tslib": "^2.0.1", - "untildify": "^4.0.0", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@ionic/utils-terminal/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@ionic/utils-terminal/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "@types/slice-ansi": "^4.0.0", + "debug": "^4.0.0", + "signal-exit": "^3.0.3", + "slice-ansi": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "tslib": "^2.0.1", + "untildify": "^4.0.0", + "wrap-ansi": "^7.0.0" }, "engines": { - "node": ">=7.0.0" + "node": ">=16.0.0" } }, - "node_modules/@ionic/utils-terminal/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, "node_modules/@ionic/utils-terminal/node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/@ionic/utils-terminal/node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, - "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -5257,7 +4244,6 @@ "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "dev": true, - "license": "ISC", "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", @@ -5271,11 +4257,10 @@ } }, "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "dev": true, - "license": "MIT", "engines": { "node": ">=12" }, @@ -5288,7 +4273,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "dev": true, - "license": "MIT", "engines": { "node": ">=12" }, @@ -5300,15 +4284,13 @@ "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@isaacs/cliui/node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, - "license": "MIT", "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", @@ -5326,7 +4308,6 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, - "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" }, @@ -5342,7 +4323,6 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, - "license": "MIT", "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", @@ -5355,22 +4335,32 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@istanbuljs/schema": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } }, "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==", + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", "dev": true, - "license": "MIT", "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", @@ -5385,7 +4375,6 @@ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, - "license": "MIT", "engines": { "node": ">=6.0.0" } @@ -5395,7 +4384,6 @@ "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "dev": true, - "license": "MIT", "engines": { "node": ">=6.0.0" } @@ -5405,7 +4393,6 @@ "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", "dev": true, - "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" @@ -5415,15 +4402,13 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true, - "license": "MIT" + "dev": true }, "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, - "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -5447,9 +4432,9 @@ } }, "node_modules/@jsonjoy.com/json-pack": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.1.0.tgz", - "integrity": "sha512-zlQONA+msXPPwHWZMKFVS78ewFczIll5lXiVPwFPCZUsrOKdxc2AvxU1HoNBmMRhqDZUR9HkC3UOm+6pME6Xsg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.1.1.tgz", + "integrity": "sha512-osjeBqMJ2lb/j/M8NCPjs1ylqWIcTRTycIhVB5pt6LgzgeRSb0YRZ7j9RfA8wIUrsr/medIuhVyonXRZWLyfdw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -5469,56 +4454,310 @@ "tslib": "2" } }, - "node_modules/@jsonjoy.com/util": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.3.0.tgz", - "integrity": "sha512-Cebt4Vk7k1xHy87kHY7KSPLT77A7Ev7IfOblyLZhtYEhrdQ6fX4EoLq3xOQ3O/DRMEh2ok5nyC180E+ABS8Wmw==", + "node_modules/@jsonjoy.com/util": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.5.0.tgz", + "integrity": "sha512-ojoNsrIuPI9g6o8UxhraZQSyF2ByJanAY4cTFbc8Mf2AXEF4aQRGY1dJxyJpuyav8r9FGflEt/Ff3u5Nt6YMPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@kurkle/color": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", + "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==" + }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@listr2/prompt-adapter-inquirer": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/@listr2/prompt-adapter-inquirer/-/prompt-adapter-inquirer-2.0.18.tgz", + "integrity": "sha512-0hz44rAcrphyXcA8IS7EJ2SCoaBZD2u5goE8S/e+q/DL+dOGpqpcLidVOFeLG3VgML62SXmfRLAhWt0zL1oW4Q==", + "dev": true, + "dependencies": { + "@inquirer/type": "^1.5.5" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@inquirer/prompts": ">= 3 < 8" + } + }, + "node_modules/@listr2/prompt-adapter-inquirer/node_modules/@inquirer/type": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-1.5.5.tgz", + "integrity": "sha512-MzICLu4yS7V8AA61sANROZ9vT1H3ooca5dSmI1FjZkzq7o/koMsRfQSzRtFo+F3Ao4Sf1C0bpLKejpKB/+j6MA==", + "dev": true, + "dependencies": { + "mute-stream": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@listr2/prompt-adapter-inquirer/node_modules/mute-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", + "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@lmdb/lmdb-darwin-arm64": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-3.1.5.tgz", + "integrity": "sha512-ue5PSOzHMCIYrfvPP/MRS6hsKKLzqqhcdAvJCO8uFlDdj598EhgnacuOTuqA6uBK5rgiZXfDWyb7DVZSiBKxBA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@lmdb/lmdb-darwin-x64": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-x64/-/lmdb-darwin-x64-3.1.5.tgz", + "integrity": "sha512-CGhsb0R5vE6mMNCoSfxHFD8QTvBHM51gs4DBeigTYHWnYv2V5YpJkC4rMo5qAAFifuUcc0+a8a3SIU0c9NrfNw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@lmdb/lmdb-linux-arm": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm/-/lmdb-linux-arm-3.1.5.tgz", + "integrity": "sha512-3WeW328DN+xB5PZdhSWmqE+t3+44xWXEbqQ+caWJEZfOFdLp9yklBZEbVqVdqzznkoaXJYxTCp996KD6HmANeg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@lmdb/lmdb-linux-arm64": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm64/-/lmdb-linux-arm64-3.1.5.tgz", + "integrity": "sha512-LAjaoOcBHGj6fiYB8ureiqPoph4eygbXu4vcOF+hsxiY74n8ilA7rJMmGUT0K0JOB5lmRQHSmor3mytRjS4qeQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@lmdb/lmdb-linux-x64": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-x64/-/lmdb-linux-x64-3.1.5.tgz", + "integrity": "sha512-k/IklElP70qdCXOQixclSl2GPLFiopynGoKX1FqDd1/H0E3Fo1oPwjY2rEVu+0nS3AOw1sryStdXk8CW3cVIsw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@lmdb/lmdb-win32-x64": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-3.1.5.tgz", + "integrity": "sha512-KYar6W8nraZfSJspcK7Kp7hdj238X/FNauYbZyrqPBrtsXI1hvI4/KcRcRGP50aQoV7fkKDyJERlrQGMGTZUsA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz", + "integrity": "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz", + "integrity": "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz", + "integrity": "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz", + "integrity": "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz", + "integrity": "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz", + "integrity": "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@napi-rs/nice": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice/-/nice-1.0.1.tgz", + "integrity": "sha512-zM0mVWSXE0a0h9aKACLwKmD6nHcRiKrPpCfvaKqG1CqDEyjEawId0ocXxVzPMCAm6kkWr2P025msfxXEnt8UGQ==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "optional": true, "engines": { - "node": ">=10.0" + "node": ">= 10" }, "funding": { "type": "github", - "url": "https://github.com/sponsors/streamich" + "url": "https://github.com/sponsors/Brooooooklyn" }, - "peerDependencies": { - "tslib": "2" - } - }, - "node_modules/@kurkle/color": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", - "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==", - "license": "MIT" - }, - "node_modules/@leichtgewicht/ip-codec": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", - "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", + "optionalDependencies": { + "@napi-rs/nice-android-arm-eabi": "1.0.1", + "@napi-rs/nice-android-arm64": "1.0.1", + "@napi-rs/nice-darwin-arm64": "1.0.1", + "@napi-rs/nice-darwin-x64": "1.0.1", + "@napi-rs/nice-freebsd-x64": "1.0.1", + "@napi-rs/nice-linux-arm-gnueabihf": "1.0.1", + "@napi-rs/nice-linux-arm64-gnu": "1.0.1", + "@napi-rs/nice-linux-arm64-musl": "1.0.1", + "@napi-rs/nice-linux-ppc64-gnu": "1.0.1", + "@napi-rs/nice-linux-riscv64-gnu": "1.0.1", + "@napi-rs/nice-linux-s390x-gnu": "1.0.1", + "@napi-rs/nice-linux-x64-gnu": "1.0.1", + "@napi-rs/nice-linux-x64-musl": "1.0.1", + "@napi-rs/nice-win32-arm64-msvc": "1.0.1", + "@napi-rs/nice-win32-ia32-msvc": "1.0.1", + "@napi-rs/nice-win32-x64-msvc": "1.0.1" + } + }, + "node_modules/@napi-rs/nice-android-arm-eabi": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm-eabi/-/nice-android-arm-eabi-1.0.1.tgz", + "integrity": "sha512-5qpvOu5IGwDo7MEKVqqyAxF90I6aLj4n07OzpARdgDRfz8UbBztTByBp0RC59r3J1Ij8uzYi6jI7r5Lws7nn6w==", + "cpu": [ + "arm" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } }, - "node_modules/@listr2/prompt-adapter-inquirer": { - "version": "2.0.15", - "resolved": "https://registry.npmjs.org/@listr2/prompt-adapter-inquirer/-/prompt-adapter-inquirer-2.0.15.tgz", - "integrity": "sha512-MZrGem/Ujjd4cPTLYDfCZK2iKKeiO/8OX13S6jqxldLs0Prf2aGqVlJ77nMBqMv7fzqgXEgjrNHLXcKR8l9lOg==", + "node_modules/@napi-rs/nice-android-arm64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm64/-/nice-android-arm64-1.0.1.tgz", + "integrity": "sha512-GqvXL0P8fZ+mQqG1g0o4AO9hJjQaeYG84FRfZaYjyJtZZZcMjXW5TwkL8Y8UApheJgyE13TQ4YNUssQaTgTyvA==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@inquirer/type": "^1.5.1" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=18.0.0" - }, - "peerDependencies": { - "@inquirer/prompts": ">= 3 < 6" + "node": ">= 10" } }, - "node_modules/@lmdb/lmdb-darwin-arm64": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-3.0.13.tgz", - "integrity": "sha512-uiKPB0Fv6WEEOZjruu9a6wnW/8jrjzlZbxXscMB8kuCJ1k6kHpcBnuvaAWcqhbI7rqX5GKziwWEdD+wi2gNLfA==", + "node_modules/@napi-rs/nice-darwin-arm64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-arm64/-/nice-darwin-arm64-1.0.1.tgz", + "integrity": "sha512-91k3HEqUl2fsrz/sKkuEkscj6EAj3/eZNCLqzD2AA0TtVbkQi8nqxZCZDMkfklULmxLkMxuUdKe7RvG/T6s2AA==", "cpu": [ "arm64" ], @@ -5527,12 +4766,15 @@ "optional": true, "os": [ "darwin" - ] + ], + "engines": { + "node": ">= 10" + } }, - "node_modules/@lmdb/lmdb-darwin-x64": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-x64/-/lmdb-darwin-x64-3.0.13.tgz", - "integrity": "sha512-bEVIIfK5mSQoG1R19qA+fJOvCB+0wVGGnXHT3smchBVahYBdlPn2OsZZKzlHWfb1E+PhLBmYfqB5zQXFP7hJig==", + "node_modules/@napi-rs/nice-darwin-x64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-x64/-/nice-darwin-x64-1.0.1.tgz", + "integrity": "sha512-jXnMleYSIR/+TAN/p5u+NkCA7yidgswx5ftqzXdD5wgy/hNR92oerTXHc0jrlBisbd7DpzoaGY4cFD7Sm5GlgQ==", "cpu": [ "x64" ], @@ -5541,12 +4783,32 @@ "optional": true, "os": [ "darwin" - ] + ], + "engines": { + "node": ">= 10" + } }, - "node_modules/@lmdb/lmdb-linux-arm": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm/-/lmdb-linux-arm-3.0.13.tgz", - "integrity": "sha512-Yml1KlMzOnXj/tnW7yX8U78iAzTk39aILYvCPbqeewAq1kSzl+w59k/fiVkTBfvDi/oW/5YRxL+Fq+Y1Fr1r2Q==", + "node_modules/@napi-rs/nice-freebsd-x64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-freebsd-x64/-/nice-freebsd-x64-1.0.1.tgz", + "integrity": "sha512-j+iJ/ezONXRQsVIB/FJfwjeQXX7A2tf3gEXs4WUGFrJjpe/z2KB7sOv6zpkm08PofF36C9S7wTNuzHZ/Iiccfw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-arm-gnueabihf": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm-gnueabihf/-/nice-linux-arm-gnueabihf-1.0.1.tgz", + "integrity": "sha512-G8RgJ8FYXYkkSGQwywAUh84m946UTn6l03/vmEXBYNJxQJcD+I3B3k5jmjFG/OPiU8DfvxutOP8bi+F89MCV7Q==", "cpu": [ "arm" ], @@ -5555,12 +4817,15 @@ "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": ">= 10" + } }, - "node_modules/@lmdb/lmdb-linux-arm64": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm64/-/lmdb-linux-arm64-3.0.13.tgz", - "integrity": "sha512-afbVrsMgZ9dUTNUchFpj5VkmJRxvht/u335jUJ7o23YTbNbnpmXif3VKQGCtnjSh+CZaqm6N3CPG8KO3zwyZ1Q==", + "node_modules/@napi-rs/nice-linux-arm64-gnu": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-gnu/-/nice-linux-arm64-gnu-1.0.1.tgz", + "integrity": "sha512-IMDak59/W5JSab1oZvmNbrms3mHqcreaCeClUjwlwDr0m3BoR09ZiN8cKFBzuSlXgRdZ4PNqCYNeGQv7YMTjuA==", "cpu": [ "arm64" ], @@ -5569,54 +4834,83 @@ "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": ">= 10" + } }, - "node_modules/@lmdb/lmdb-linux-x64": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-x64/-/lmdb-linux-x64-3.0.13.tgz", - "integrity": "sha512-vOtxu0xC0SLdQ2WRXg8Qgd8T32ak4SPqk5zjItRszrJk2BdeXqfGxBJbP7o4aOvSPSmSSv46Lr1EP4HXU8v7Kg==", + "node_modules/@napi-rs/nice-linux-arm64-musl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-musl/-/nice-linux-arm64-musl-1.0.1.tgz", + "integrity": "sha512-wG8fa2VKuWM4CfjOjjRX9YLIbysSVV1S3Kgm2Fnc67ap/soHBeYZa6AGMeR5BJAylYRjnoVOzV19Cmkco3QEPw==", "cpu": [ - "x64" + "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": ">= 10" + } }, - "node_modules/@lmdb/lmdb-win32-x64": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-3.0.13.tgz", - "integrity": "sha512-UCrMJQY/gJnOl3XgbWRZZUvGGBuKy6i0YNSptgMzHBjs+QYDYR1Mt/RLTOPy4fzzves65O1EDmlL//OzEqoLlA==", + "node_modules/@napi-rs/nice-linux-ppc64-gnu": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-ppc64-gnu/-/nice-linux-ppc64-gnu-1.0.1.tgz", + "integrity": "sha512-lxQ9WrBf0IlNTCA9oS2jg/iAjQyTI6JHzABV664LLrLA/SIdD+I1i3Mjf7TsnoUbgopBcCuDztVLfJ0q9ubf6Q==", "cpu": [ - "x64" + "ppc64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "win32" - ] + "linux" + ], + "engines": { + "node": ">= 10" + } }, - "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz", - "integrity": "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==", + "node_modules/@napi-rs/nice-linux-riscv64-gnu": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-riscv64-gnu/-/nice-linux-riscv64-gnu-1.0.1.tgz", + "integrity": "sha512-3xs69dO8WSWBb13KBVex+yvxmUeEsdWexxibqskzoKaWx9AIqkMbWmE2npkazJoopPKX2ULKd8Fm9veEn0g4Ig==", "cpu": [ - "arm64" + "riscv64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "darwin" - ] + "linux" + ], + "engines": { + "node": ">= 10" + } }, - "node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz", - "integrity": "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==", + "node_modules/@napi-rs/nice-linux-s390x-gnu": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-s390x-gnu/-/nice-linux-s390x-gnu-1.0.1.tgz", + "integrity": "sha512-lMFI3i9rlW7hgToyAzTaEybQYGbQHDrpRkg+1gJWEpH0PLAQoZ8jiY0IzakLfNWnVda1eTYYlxxFYzW8Rqczkg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-x64-gnu": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-x64-gnu/-/nice-linux-x64-gnu-1.0.1.tgz", + "integrity": "sha512-XQAJs7DRN2GpLN6Fb+ZdGFeYZDdGl2Fn3TmFlqEL5JorgWKrQGRUrpGKbgZ25UeZPILuTKJ+OowG2avN8mThBA==", "cpu": [ "x64" ], @@ -5624,27 +4918,33 @@ "license": "MIT", "optional": true, "os": [ - "darwin" - ] + "linux" + ], + "engines": { + "node": ">= 10" + } }, - "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz", - "integrity": "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==", + "node_modules/@napi-rs/nice-linux-x64-musl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-x64-musl/-/nice-linux-x64-musl-1.0.1.tgz", + "integrity": "sha512-/rodHpRSgiI9o1faq9SZOp/o2QkKQg7T+DK0R5AkbnI/YxvAIEHf2cngjYzLMQSQgUhxym+LFr+UGZx4vK4QdQ==", "cpu": [ - "arm" + "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": ">= 10" + } }, - "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz", - "integrity": "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==", + "node_modules/@napi-rs/nice-win32-arm64-msvc": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-arm64-msvc/-/nice-win32-arm64-msvc-1.0.1.tgz", + "integrity": "sha512-rEcz9vZymaCB3OqEXoHnp9YViLct8ugF+6uO5McifTedjq4QMQs3DHz35xBEGhH3gJWEsXMUbzazkz5KNM5YUg==", "cpu": [ "arm64" ], @@ -5652,27 +4952,33 @@ "license": "MIT", "optional": true, "os": [ - "linux" - ] + "win32" + ], + "engines": { + "node": ">= 10" + } }, - "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz", - "integrity": "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==", + "node_modules/@napi-rs/nice-win32-ia32-msvc": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-ia32-msvc/-/nice-win32-ia32-msvc-1.0.1.tgz", + "integrity": "sha512-t7eBAyPUrWL8su3gDxw9xxxqNwZzAqKo0Szv3IjVQd1GpXXVkb6vBBQUuxfIYaXMzZLwlxRQ7uzM2vdUE9ULGw==", "cpu": [ - "x64" + "ia32" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" - ] + "win32" + ], + "engines": { + "node": ">= 10" + } }, - "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz", - "integrity": "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==", + "node_modules/@napi-rs/nice-win32-x64-msvc": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-x64-msvc/-/nice-win32-x64-msvc-1.0.1.tgz", + "integrity": "sha512-JlF+uDcatt3St2ntBG8H02F1mM45i5SF9W+bIKiReVE6wiy3o16oBP/yxt+RZ+N6LbCImJXJ6bXNO2kn9AXicg==", "cpu": [ "x64" ], @@ -5681,12 +4987,15 @@ "optional": true, "os": [ "win32" - ] + ], + "engines": { + "node": ">= 10" + } }, "node_modules/@ngtools/webpack": { - "version": "18.2.6", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-18.2.6.tgz", - "integrity": "sha512-7HwOPE1EOgcHnpt4brSiT8G2CcXB50G0+CbCBaKGy4LYCG3Y3mrlzF5Fup9HvMJ6Tzqd62RqzpKKYBiGUT7hxg==", + "version": "19.0.6", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-19.0.6.tgz", + "integrity": "sha512-eWrIb0tS1CK6+JvFS4GgTD4fN9TtmApKrlaj3pPQXKXKKd42361ec85fuQQXdb4G8eEEq0vyd/bn4NJllh/3vw==", "dev": true, "license": "MIT", "engines": { @@ -5695,16 +5004,15 @@ "yarn": ">= 1.13.0" }, "peerDependencies": { - "@angular/compiler-cli": "^18.0.0", - "typescript": ">=5.4 <5.6", + "@angular/compiler-cli": "^19.0.0", + "typescript": ">=5.5 <5.7", "webpack": "^5.54.0" } }, "node_modules/@ngx-formly/core": { - "version": "6.3.7", - "resolved": "https://registry.npmjs.org/@ngx-formly/core/-/core-6.3.7.tgz", - "integrity": "sha512-To2mH09YSm3nyThABNHIameIJCPA9C+x3/JFxFtBWek+UbYeW9DYOqNHRCc7P1ToqLqNEuwrmzjB2YSA8pO9Pw==", - "license": "MIT", + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/@ngx-formly/core/-/core-6.3.12.tgz", + "integrity": "sha512-88MOfn9dM1B33t04jl8x0Glh0Ed0lUKMkhYajicRH7ZHTmwIdla1SQjiblp2C+EcCFvsY7XAU2/JUZQdl56aUw==", "dependencies": { "tslib": "^2.0.0" }, @@ -5714,23 +5022,21 @@ } }, "node_modules/@ngx-formly/ionic": { - "version": "6.3.7", - "resolved": "https://registry.npmjs.org/@ngx-formly/ionic/-/ionic-6.3.7.tgz", - "integrity": "sha512-j3jiv51CVNeJGY02bZgizarO24DF5AdzL5HJ7SAtJqBIxJNR++AkQZZvgMYtPYTrvhdOIiZrhkBWh0x+rfQMJQ==", - "license": "MIT", + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/@ngx-formly/ionic/-/ionic-6.3.12.tgz", + "integrity": "sha512-Ohl3bbVZaTwIbcjIuSsjVF1Kig32ftyBBntsD9Lu95rxcFPq67tLUMRNRB/XEPVUAA2ecGvOwhH7RmJKNqyy2Q==", "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@ionic/angular": "^6.0.0 || ^7.0.0", - "@ngx-formly/core": "6.3.7" + "@ngx-formly/core": "6.3.12" } }, "node_modules/@ngx-formly/schematics": { - "version": "6.3.7", - "resolved": "https://registry.npmjs.org/@ngx-formly/schematics/-/schematics-6.3.7.tgz", - "integrity": "sha512-e1Y7RNa6AGK+YEIzNXNX5lvA8MrFQ9UEL/Pj8zwGdotKI8CnNyRVHneQ/1F+QZBo1mLXUtXJnejPoOMkGfo7VQ==", - "license": "MIT", + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/@ngx-formly/schematics/-/schematics-6.3.12.tgz", + "integrity": "sha512-Gzrvo/bRNvTVOTVlJLC/F+j6rTn5h/5GBg7GDF6VipSgfmpXq2xXNvyI0QMdEnfBSLElw5v3ve/HA/xlQPw65A==", "dependencies": { "@angular-devkit/core": "^13.0.3", "@angular-devkit/schematics": "^13.0.3", @@ -5741,7 +5047,6 @@ "version": "13.3.11", "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.11.tgz", "integrity": "sha512-rfqoLMRYhlz0wzKlHx7FfyIyQq8dKTsmbCoIVU1cEIH0gyTMVY7PbVzwRRcO6xp5waY+0hA+0Brriujpuhkm4w==", - "license": "MIT", "dependencies": { "ajv": "8.9.0", "ajv-formats": "2.1.1", @@ -5768,7 +5073,6 @@ "version": "13.3.11", "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-13.3.11.tgz", "integrity": "sha512-ben+EGXpCrClnIVAAnEQmhQdKmnnqFhMp5BqMxgOslSYBAmCutLA6rBu5vsc8kZcGian1wt+lueF7G1Uk5cGBg==", - "license": "MIT", "dependencies": { "@angular-devkit/core": "13.3.11", "jsonc-parser": "3.0.0", @@ -5786,7 +5090,6 @@ "version": "13.3.11", "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-13.3.11.tgz", "integrity": "sha512-imKBnKYEse0SBVELZO/753nkpt3eEgpjrYkB+AFWF9YfO/4RGnYXDHoH8CFkzxPH9QQCgNrmsVFNiYGS+P/S1A==", - "license": "MIT", "dependencies": { "@angular-devkit/core": "13.3.11", "@angular-devkit/schematics": "13.3.11", @@ -5802,7 +5105,6 @@ "version": "8.9.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.9.0.tgz", "integrity": "sha512-qOKJyNj/h+OWx7s5DePL6Zu1KeM9jPZhwBqs+7DzP6bGOvqzVCSf0xueYmVuaC/oQ/VtS2zLMLHdQFbkka+XDQ==", - "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -5818,55 +5120,129 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "license": "MIT", "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/@ngx-formly/schematics/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "optional": true, + "peer": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/@ngx-formly/schematics/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "optional": true, + "peer": true, + "dependencies": { + "is-glob": "^4.0.1" }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } + "engines": { + "node": ">= 6" } }, "node_modules/@ngx-formly/schematics/node_modules/jsonc-parser": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.0.0.tgz", - "integrity": "sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA==", - "license": "MIT" + "integrity": "sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA==" }, "node_modules/@ngx-formly/schematics/node_modules/magic-string": { "version": "0.25.7", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==", - "license": "MIT", "dependencies": { "sourcemap-codec": "^1.4.4" } }, + "node_modules/@ngx-formly/schematics/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "optional": true, + "peer": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@ngx-formly/schematics/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "optional": true, + "peer": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/@ngx-formly/schematics/node_modules/rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" + } + }, "node_modules/@ngx-formly/schematics/node_modules/source-map": { "version": "0.7.3", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "license": "BSD-3-Clause", "engines": { "node": ">= 8" } }, + "node_modules/@ngx-formly/schematics/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, "node_modules/@ngx-translate/core": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/@ngx-translate/core/-/core-15.0.0.tgz", - "integrity": "sha512-Am5uiuR0bOOxyoercDnAA3rJVizo4RRqJHo8N3RqJ+XfzVP/I845yEnMADykOHvM6HkVm4SZSnJBOiz0Anx5BA==", - "license": "SEE LICENSE IN LICENSE", - "engines": { - "node": "^16.13.0 || >=18.10.0" + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@ngx-translate/core/-/core-16.0.4.tgz", + "integrity": "sha512-s8llTL2SJvROhqttxvEs7Cg+6qSf4kvZPFYO+cTOY1d8DWTjlutRkWAleZcPPoeX927Dm7ALfL07G7oYDJ7z6w==", + "dependencies": { + "tslib": "^2.3.0" }, "peerDependencies": { - "@angular/common": ">=16.0.0", - "@angular/core": ">=16.0.0", - "rxjs": "^6.5.5 || ^7.4.0" + "@angular/common": ">=16", + "@angular/core": ">=16" } }, "node_modules/@nodelib/fs.scandir": { @@ -5874,7 +5250,6 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, - "license": "MIT", "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -5888,7 +5263,6 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true, - "license": "MIT", "engines": { "node": ">= 8" } @@ -5898,7 +5272,6 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, - "license": "MIT", "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -5911,17 +5284,24 @@ "version": "0.14.0", "resolved": "https://registry.npmjs.org/@nodro7/angular-mydatepicker/-/angular-mydatepicker-0.14.0.tgz", "integrity": "sha512-NLUqU2Hpy3OQn/2xp5FDIqBlb87o9LCYRShnA9tfbQIPQIKay4sSexK6XPswZ3ccXkvrgRMhFDZpv10JURqahA==", - "license": "MIT", "dependencies": { "tslib": "^2.0.0" } }, + "node_modules/@nolyfill/is-core-module": { + "version": "1.0.39", + "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", + "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==", + "dev": true, + "engines": { + "node": ">=12.4.0" + } + }, "node_modules/@npmcli/agent": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-2.2.2.tgz", - "integrity": "sha512-OrcNPXdpSl9UX7qPVRWbmWMCSXrcDa2M9DvrbOTj7ao1S4PlqVFYv9/yLKMkrJKZ/V5A/kDBC690or307i26Og==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-3.0.0.tgz", + "integrity": "sha512-S79NdEgDQd/NGCay6TCoVzXSj74skRZIKJcpJjC5lOq34SZzyI6MqtiiWoiVWoVrTcGjNeC4ipbh1VIHlpfF5Q==", "dev": true, - "license": "ISC", "dependencies": { "agent-base": "^7.1.0", "http-proxy-agent": "^7.0.0", @@ -5930,48 +5310,45 @@ "socks-proxy-agent": "^8.0.3" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@npmcli/agent/node_modules/lru-cache": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/@npmcli/fs": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.1.tgz", - "integrity": "sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-4.0.0.tgz", + "integrity": "sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q==", "dev": true, - "license": "ISC", "dependencies": { "semver": "^7.3.5" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@npmcli/git": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-5.0.8.tgz", - "integrity": "sha512-liASfw5cqhjNW9UFd+ruwwdEf/lbOAQjLL2XY2dFW/bkJheXDYZgOyul/4gVvEV4BWkTXjYGmDqMw9uegdbJNQ==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-6.0.1.tgz", + "integrity": "sha512-BBWMMxeQzalmKadyimwb2/VVQyJB01PH0HhVSNLHNBDZN/M/h/02P6f8fxedIiFhpMj11SO9Ep5tKTBE7zL2nw==", "dev": true, - "license": "ISC", "dependencies": { - "@npmcli/promise-spawn": "^7.0.0", - "ini": "^4.1.3", + "@npmcli/promise-spawn": "^8.0.0", + "ini": "^5.0.0", "lru-cache": "^10.0.1", - "npm-pick-manifest": "^9.0.0", - "proc-log": "^4.0.0", + "npm-pick-manifest": "^10.0.0", + "proc-log": "^5.0.0", "promise-inflight": "^1.0.1", "promise-retry": "^2.0.1", "semver": "^7.3.5", - "which": "^4.0.0" + "which": "^5.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@npmcli/git/node_modules/isexe": { @@ -5979,7 +5356,6 @@ "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", "dev": true, - "license": "ISC", "engines": { "node": ">=16" } @@ -5988,15 +5364,13 @@ "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/@npmcli/git/node_modules/which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", "dev": true, - "license": "ISC", "dependencies": { "isexe": "^3.1.1" }, @@ -6004,53 +5378,50 @@ "node-which": "bin/which.js" }, "engines": { - "node": "^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@npmcli/installed-package-contents": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-2.1.0.tgz", - "integrity": "sha512-c8UuGLeZpm69BryRykLuKRyKFZYJsZSCT4aVY5ds4omyZqJ172ApzgfKJ5eV/r3HgLdUYgFVe54KSFVjKoe27w==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-3.0.0.tgz", + "integrity": "sha512-fkxoPuFGvxyrH+OQzyTkX2LUEamrF4jZSmxjAtPPHHGO0dqsQ8tTKjnIS8SAnPHdk2I03BDtSMR5K/4loKg79Q==", "dev": true, - "license": "ISC", "dependencies": { - "npm-bundled": "^3.0.0", - "npm-normalize-package-bin": "^3.0.0" + "npm-bundled": "^4.0.0", + "npm-normalize-package-bin": "^4.0.0" }, "bin": { "installed-package-contents": "bin/index.js" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@npmcli/node-gyp": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-3.0.0.tgz", - "integrity": "sha512-gp8pRXC2oOxu0DUE1/M3bYtb1b3/DbJ5aM113+XJBgfXdussRAsX0YOrOhdd8WvnAR6auDBvJomGAkLKA5ydxA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-4.0.0.tgz", + "integrity": "sha512-+t5DZ6mO/QFh78PByMq1fGSAub/agLJZDRfJRMeOSNCt8s9YVlTjmGpIPwPhvXTGUIJk+WszlT0rQa1W33yzNA==", "dev": true, - "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@npmcli/package-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-5.2.0.tgz", - "integrity": "sha512-qe/kiqqkW0AGtvBjL8TJKZk/eBBSpnJkUWvHdQ9jM2lKHXRYYJuyNpJPlJw3c8QjC2ow6NZYiLExhUaeJelbxQ==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-6.1.0.tgz", + "integrity": "sha512-t6G+6ZInT4X+tqj2i+wlLIeCKnKOTuz9/VFYDtj+TGTur5q7sp/OYrQA19LdBbWfXDOi0Y4jtedV6xtB8zQ9ug==", "dev": true, - "license": "ISC", "dependencies": { - "@npmcli/git": "^5.0.0", + "@npmcli/git": "^6.0.0", "glob": "^10.2.2", - "hosted-git-info": "^7.0.0", - "json-parse-even-better-errors": "^3.0.0", - "normalize-package-data": "^6.0.0", - "proc-log": "^4.0.0", + "hosted-git-info": "^8.0.0", + "json-parse-even-better-errors": "^4.0.0", + "normalize-package-data": "^7.0.0", + "proc-log": "^5.0.0", "semver": "^7.5.3" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@npmcli/package-json/node_modules/glob": { @@ -6058,7 +5429,6 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dev": true, - "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", @@ -6071,135 +5441,421 @@ "glob": "dist/esm/bin.mjs" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@npmcli/package-json/node_modules/hosted-git-info": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.0.2.tgz", + "integrity": "sha512-sYKnA7eGln5ov8T8gnYlkSOxFJvywzEx9BueN6xo/GKO8PGiI6uK6xx+DIGe45T3bdVjLAQDQW1aicT8z8JwQg==", + "dev": true, + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/package-json/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, + "node_modules/@npmcli/package-json/node_modules/normalize-package-data": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-7.0.0.tgz", + "integrity": "sha512-k6U0gKRIuNCTkwHGZqblCfLfBRh+w1vI6tBo+IeJwq2M8FUiOqhX7GH+GArQGScA7azd1WfyRCvxoXDO3hQDIA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^8.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/promise-spawn": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-8.0.2.tgz", + "integrity": "sha512-/bNJhjc+o6qL+Dwz/bqfTQClkEO5nTQ1ZEcdCkAQjhkZMHIh22LPG7fNh1enJP1NKWDqYiiABnjFCY7E0zHYtQ==", + "dev": true, + "dependencies": { + "which": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/promise-spawn/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/@npmcli/promise-spawn/node_modules/which": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "dev": true, + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/redact": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-3.0.0.tgz", + "integrity": "sha512-/1uFzjVcfzqrgCeGW7+SZ4hv0qLWmKXVzFahZGJ6QuJBj6Myt9s17+JL86i76NV9YSnJRcGXJYQbAU0rn1YTCQ==", + "dev": true, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/run-script": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-9.0.2.tgz", + "integrity": "sha512-cJXiUlycdizQwvqE1iaAb4VRUM3RX09/8q46zjvy+ct9GhfZRWd7jXYVc1tn/CfRlGPVkX/u4sstRlepsm7hfw==", + "dev": true, + "dependencies": { + "@npmcli/node-gyp": "^4.0.0", + "@npmcli/package-json": "^6.0.0", + "@npmcli/promise-spawn": "^8.0.0", + "node-gyp": "^11.0.0", + "proc-log": "^5.0.0", + "which": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/run-script/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/@npmcli/run-script/node_modules/which": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "dev": true, + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.0.tgz", + "integrity": "sha512-i0GV1yJnm2n3Yq1qw6QrUrd/LI9bE8WEBOTtOkpCXHHdyN3TAGgqAK/DAT05z4fq2x04cARXt2pDmjWjL92iTQ==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "dependencies": { + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.0", + "@parcel/watcher-darwin-arm64": "2.5.0", + "@parcel/watcher-darwin-x64": "2.5.0", + "@parcel/watcher-freebsd-x64": "2.5.0", + "@parcel/watcher-linux-arm-glibc": "2.5.0", + "@parcel/watcher-linux-arm-musl": "2.5.0", + "@parcel/watcher-linux-arm64-glibc": "2.5.0", + "@parcel/watcher-linux-arm64-musl": "2.5.0", + "@parcel/watcher-linux-x64-glibc": "2.5.0", + "@parcel/watcher-linux-x64-musl": "2.5.0", + "@parcel/watcher-win32-arm64": "2.5.0", + "@parcel/watcher-win32-ia32": "2.5.0", + "@parcel/watcher-win32-x64": "2.5.0" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.0.tgz", + "integrity": "sha512-qlX4eS28bUcQCdribHkg/herLe+0A9RyYC+mm2PXpncit8z5b3nSqGVzMNR3CmtAOgRutiZ02eIJJgP/b1iEFQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.0.tgz", + "integrity": "sha512-hyZ3TANnzGfLpRA2s/4U1kbw2ZI4qGxaRJbBH2DCSREFfubMswheh8TeiC1sGZ3z2jUf3s37P0BBlrD3sjVTUw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.0.tgz", + "integrity": "sha512-9rhlwd78saKf18fT869/poydQK8YqlU26TMiNg7AIu7eBp9adqbJZqmdFOsbZ5cnLp5XvRo9wcFmNHgHdWaGYA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.0.tgz", + "integrity": "sha512-syvfhZzyM8kErg3VF0xpV8dixJ+RzbUaaGaeb7uDuz0D3FK97/mZ5AJQ3XNnDsXX7KkFNtyQyFrXZzQIcN49Tw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@npmcli/package-json/node_modules/hosted-git-info": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", - "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.0.tgz", + "integrity": "sha512-0VQY1K35DQET3dVYWpOaPFecqOT9dbuCfzjxoQyif1Wc574t3kOSkKevULddcR9znz1TcklCE7Ht6NIxjvTqLA==", + "cpu": [ + "arm" + ], "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^10.0.1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@npmcli/package-json/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/@npmcli/package-json/node_modules/normalize-package-data": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", - "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.0.tgz", + "integrity": "sha512-6uHywSIzz8+vi2lAzFeltnYbdHsDm3iIB57d4g5oaB9vKwjb6N6dRIgZMujw4nm5r6v9/BQH0noq6DzHrqr2pA==", + "cpu": [ + "arm" + ], "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^7.0.0", - "semver": "^7.3.5", - "validate-npm-package-license": "^3.0.4" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@npmcli/promise-spawn": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-7.0.2.tgz", - "integrity": "sha512-xhfYPXoV5Dy4UkY0D+v2KkwvnDfiA/8Mt3sWCGI/hM03NsYIH8ZaG6QzS9x7pje5vHZBZJ2v6VRFVTWACnqcmQ==", + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.0.tgz", + "integrity": "sha512-BfNjXwZKxBy4WibDb/LDCriWSKLz+jJRL3cM/DllnHH5QUyoiUNEp3GmL80ZqxeumoADfCCP19+qiYiC8gUBjA==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "ISC", - "dependencies": { - "which": "^4.0.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@npmcli/promise-spawn/node_modules/isexe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", - "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.0.tgz", + "integrity": "sha512-S1qARKOphxfiBEkwLUbHjCY9BWPdWnW9j7f7Hb2jPplu8UZ3nes7zpPOW9bkLbHRvWM0WDTsjdOTUgW0xLBN1Q==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "ISC", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=16" + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@npmcli/promise-spawn/node_modules/which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.0.tgz", + "integrity": "sha512-d9AOkusyXARkFD66S6zlGXyzx5RvY+chTP9Jp0ypSTC9d4lzyRs9ovGf/80VCxjKddcUvnsGwCHWuF2EoPgWjw==", + "cpu": [ + "x64" + ], "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^3.1.1" - }, - "bin": { - "node-which": "bin/which.js" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^16.13.0 || >=18.0.0" + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@npmcli/redact": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-2.0.1.tgz", - "integrity": "sha512-YgsR5jCQZhVmTJvjduTOIHph0L73pK8xwMVaDY0PatySqVM9AZj93jpoXYSJqfHFxFkN9dmqTw6OiqExsS3LPw==", + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.0.tgz", + "integrity": "sha512-iqOC+GoTDoFyk/VYSFHwjHhYrk8bljW6zOhPuhi5t9ulqiYq1togGJB5e3PwYVFFfeVgc6pbz3JdQyDoBszVaA==", + "cpu": [ + "x64" + ], "dev": true, - "license": "ISC", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@npmcli/run-script": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-8.1.0.tgz", - "integrity": "sha512-y7efHHwghQfk28G2z3tlZ67pLG0XdfYbcVG26r7YIXALRsrVQcTq4/tdenSmdOrEsNahIYA/eh8aEVROWGFUDg==", + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.0.tgz", + "integrity": "sha512-twtft1d+JRNkM5YbmexfcH/N4znDtjgysFaV9zvZmmJezQsKpkfLYJ+JFV3uygugK6AtIM2oADPkB2AdhBrNig==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "ISC", - "dependencies": { - "@npmcli/node-gyp": "^3.0.0", - "@npmcli/package-json": "^5.0.0", - "@npmcli/promise-spawn": "^7.0.0", - "node-gyp": "^10.0.0", - "proc-log": "^4.0.0", - "which": "^4.0.0" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@npmcli/run-script/node_modules/isexe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", - "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.0.tgz", + "integrity": "sha512-+rgpsNRKwo8A53elqbbHXdOMtY/tAtTzManTWShB5Kk54N8Q9mzNWV7tV+IbGueCbcj826MfWGU3mprWtuf1TA==", + "cpu": [ + "ia32" + ], "dev": true, - "license": "ISC", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=16" + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@npmcli/run-script/node_modules/which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.0.tgz", + "integrity": "sha512-lPrxve92zEHdgeff3aiu4gDOIt4u7sJYha6wbdEZDCDUhtjTsOMiaJzG5lMY4GkWH8p0fMmO2Ppq5G5XXG+DQw==", + "cpu": [ + "x64" + ], "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^3.1.1" - }, - "bin": { - "node-which": "bin/which.js" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": "^16.13.0 || >=18.0.0" + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, "node_modules/@pkgjs/parseargs": { @@ -6207,7 +5863,6 @@ "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", "dev": true, - "license": "MIT", "optional": true, "engines": { "node": ">=14" @@ -6218,7 +5873,6 @@ "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", "dev": true, - "license": "MIT", "engines": { "node": "^12.20.0 || ^14.18.0 || >=16.0.0" }, @@ -6231,16 +5885,15 @@ "resolved": "https://registry.npmjs.org/@prettier/plugin-xml/-/plugin-xml-2.2.0.tgz", "integrity": "sha512-UWRmygBsyj4bVXvDiqSccwT1kmsorcwQwaIy30yVh8T+Gspx4OlC0shX1y+ZuwXZvgnafmpRYKks0bAu9urJew==", "dev": true, - "license": "MIT", "dependencies": { "@xml-tools/parser": "^1.0.11", "prettier": ">=2.4.0" } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.23.0.tgz", - "integrity": "sha512-8OR+Ok3SGEMsAZispLx8jruuXw0HVF16k+ub2eNXKHDmdxL4cf9NlNpAzhlOhNyXzKDEJuFeq0nZm+XlNb1IFw==", + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.26.0.tgz", + "integrity": "sha512-gJNwtPDGEaOEgejbaseY6xMFu+CPltsc8/T+diUTTbOQLqD+bnrJq9ulH6WD69TqwqWmrfRAtUv30cCFZlbGTQ==", "cpu": [ "arm" ], @@ -6252,9 +5905,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.23.0.tgz", - "integrity": "sha512-rEFtX1nP8gqmLmPZsXRMoLVNB5JBwOzIAk/XAcEPuKrPa2nPJ+DuGGpfQUR0XjRm8KjHfTZLpWbKXkA5BoFL3w==", + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.26.0.tgz", + "integrity": "sha512-YJa5Gy8mEZgz5JquFruhJODMq3lTHWLm1fOy+HIANquLzfIOzE9RA5ie3JjCdVb9r46qfAQY/l947V0zfGJ0OQ==", "cpu": [ "arm64" ], @@ -6266,9 +5919,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.23.0.tgz", - "integrity": "sha512-ZbqlMkJRMMPeapfaU4drYHns7Q5MIxjM/QeOO62qQZGPh9XWziap+NF9fsqPHT0KzEL6HaPspC7sOwpgyA3J9g==", + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.26.0.tgz", + "integrity": "sha512-ErTASs8YKbqTBoPLp/kA1B1Um5YSom8QAc4rKhg7b9tyyVqDBlQxy7Bf2wW7yIlPGPg2UODDQcbkTlruPzDosw==", "cpu": [ "arm64" ], @@ -6280,9 +5933,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.23.0.tgz", - "integrity": "sha512-PfmgQp78xx5rBCgn2oYPQ1rQTtOaQCna0kRaBlc5w7RlA3TDGGo7m3XaptgitUZ54US9915i7KeVPHoy3/W8tA==", + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.26.0.tgz", + "integrity": "sha512-wbgkYDHcdWW+NqP2mnf2NOuEbOLzDblalrOWcPyY6+BRbVhliavon15UploG7PpBRQ2bZJnbmh8o3yLoBvDIHA==", "cpu": [ "x64" ], @@ -6293,10 +5946,38 @@ "darwin" ] }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.26.0.tgz", + "integrity": "sha512-Y9vpjfp9CDkAG4q/uwuhZk96LP11fBz/bYdyg9oaHYhtGZp7NrbkQrj/66DYMMP2Yo/QPAsVHkV891KyO52fhg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.26.0.tgz", + "integrity": "sha512-A/jvfCZ55EYPsqeaAt/yDAG4q5tt1ZboWMHEvKAH9Zl92DWvMIbnZe/f/eOXze65aJaaKbL+YeM0Hz4kLQvdwg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.23.0.tgz", - "integrity": "sha512-WAeZfAAPus56eQgBioezXRRzArAjWJGjNo/M+BHZygUcs9EePIuGI1Wfc6U/Ki+tMW17FFGvhCfYnfcKPh18SA==", + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.26.0.tgz", + "integrity": "sha512-paHF1bMXKDuizaMODm2bBTjRiHxESWiIyIdMugKeLnjuS1TCS54MF5+Y5Dx8Ui/1RBPVRE09i5OUlaLnv8OGnA==", "cpu": [ "arm" ], @@ -6308,9 +5989,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.23.0.tgz", - "integrity": "sha512-v7PGcp1O5XKZxKX8phTXtmJDVpE20Ub1eF6w9iMmI3qrrPak6yR9/5eeq7ziLMrMTjppkkskXyxnmm00HdtXjA==", + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.26.0.tgz", + "integrity": "sha512-cwxiHZU1GAs+TMxvgPfUDtVZjdBdTsQwVnNlzRXC5QzIJ6nhfB4I1ahKoe9yPmoaA/Vhf7m9dB1chGPpDRdGXg==", "cpu": [ "arm" ], @@ -6322,9 +6003,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.23.0.tgz", - "integrity": "sha512-nAbWsDZ9UkU6xQiXEyXBNHAKbzSAi95H3gTStJq9UGiS1v+YVXwRHcQOQEF/3CHuhX5BVhShKoeOf6Q/1M+Zhg==", + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.26.0.tgz", + "integrity": "sha512-4daeEUQutGRCW/9zEo8JtdAgtJ1q2g5oHaoQaZbMSKaIWKDQwQ3Yx0/3jJNmpzrsScIPtx/V+1AfibLisb3AMQ==", "cpu": [ "arm64" ], @@ -6336,9 +6017,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.23.0.tgz", - "integrity": "sha512-5QT/Di5FbGNPaVw8hHO1wETunwkPuZBIu6W+5GNArlKHD9fkMHy7vS8zGHJk38oObXfWdsuLMogD4sBySLJ54g==", + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.26.0.tgz", + "integrity": "sha512-eGkX7zzkNxvvS05ROzJ/cO/AKqNvR/7t1jA3VZDi2vRniLKwAWxUr85fH3NsvtxU5vnUUKFHKh8flIBdlo2b3Q==", "cpu": [ "arm64" ], @@ -6350,9 +6031,9 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.23.0.tgz", - "integrity": "sha512-Sefl6vPyn5axzCsO13r1sHLcmPuiSOrKIImnq34CBurntcJ+lkQgAaTt/9JkgGmaZJ+OkaHmAJl4Bfd0DmdtOQ==", + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.26.0.tgz", + "integrity": "sha512-Odp/lgHbW/mAqw/pU21goo5ruWsytP7/HCC/liOt0zcGG0llYWKrd10k9Fj0pdj3prQ63N5yQLCLiE7HTX+MYw==", "cpu": [ "ppc64" ], @@ -6364,9 +6045,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.23.0.tgz", - "integrity": "sha512-o4QI2KU/QbP7ZExMse6ULotdV3oJUYMrdx3rBZCgUF3ur3gJPfe8Fuasn6tia16c5kZBBw0aTmaUygad6VB/hQ==", + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.26.0.tgz", + "integrity": "sha512-MBR2ZhCTzUgVD0OJdTzNeF4+zsVogIR1U/FsyuFerwcqjZGvg2nYe24SAHp8O5sN8ZkRVbHwlYeHqcSQ8tcYew==", "cpu": [ "riscv64" ], @@ -6378,9 +6059,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.23.0.tgz", - "integrity": "sha512-+bxqx+V/D4FGrpXzPGKp/SEZIZ8cIW3K7wOtcJAoCrmXvzRtmdUhYNbgd+RztLzfDEfA2WtKj5F4tcbNPuqgeg==", + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.26.0.tgz", + "integrity": "sha512-YYcg8MkbN17fMbRMZuxwmxWqsmQufh3ZJFxFGoHjrE7bv0X+T6l3glcdzd7IKLiwhT+PZOJCblpnNlz1/C3kGQ==", "cpu": [ "s390x" ], @@ -6392,9 +6073,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.23.0.tgz", - "integrity": "sha512-I/eXsdVoCKtSgK9OwyQKPAfricWKUMNCwJKtatRYMmDo5N859tbO3UsBw5kT3dU1n6ZcM1JDzPRSGhAUkxfLxw==", + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.26.0.tgz", + "integrity": "sha512-ZuwpfjCwjPkAOxpjAEjabg6LRSfL7cAJb6gSQGZYjGhadlzKKywDkCUnJ+KEfrNY1jH5EEoSIKLCb572jSiglA==", "cpu": [ "x64" ], @@ -6406,9 +6087,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.23.0.tgz", - "integrity": "sha512-4ZoDZy5ShLbbe1KPSafbFh1vbl0asTVfkABC7eWqIs01+66ncM82YJxV2VtV3YVJTqq2P8HMx3DCoRSWB/N3rw==", + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.26.0.tgz", + "integrity": "sha512-+HJD2lFS86qkeF8kNu0kALtifMpPCZU80HvwztIKnYwym3KnA1os6nsX4BGSTLtS2QVAGG1P3guRgsYyMA0Yhg==", "cpu": [ "x64" ], @@ -6420,9 +6101,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.23.0.tgz", - "integrity": "sha512-+5Ky8dhft4STaOEbZu3/NU4QIyYssKO+r1cD3FzuusA0vO5gso15on7qGzKdNXnc1gOrsgCqZjRw1w+zL4y4hQ==", + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.26.0.tgz", + "integrity": "sha512-WUQzVFWPSw2uJzX4j6YEbMAiLbs0BUysgysh8s817doAYhR5ybqTI1wtKARQKo6cGop3pHnrUJPFCsXdoFaimQ==", "cpu": [ "arm64" ], @@ -6434,9 +6115,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.23.0.tgz", - "integrity": "sha512-0SPJk4cPZQhq9qA1UhIRumSE3+JJIBBjtlGl5PNC///BoaByckNZd53rOYD0glpTkYFBQSt7AkMeLVPfx65+BQ==", + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.26.0.tgz", + "integrity": "sha512-D4CxkazFKBfN1akAIY6ieyOqzoOoBV1OICxgUblWxff/pSjCA2khXlASUx7mK6W1oP4McqhgcCsu6QaLj3WMWg==", "cpu": [ "ia32" ], @@ -6448,9 +6129,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.23.0.tgz", - "integrity": "sha512-lqCK5GQC8fNo0+JvTSxcG7YB1UKYp8yrNLhsArlvPWN+16ovSZgoehlVHg6X0sSWPUkpjRBR5TuR12ZugowZ4g==", + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.26.0.tgz", + "integrity": "sha512-2x8MO1rm4PGEP0xWbubJW5RtbNLk3puzAMaLQd3B3JHVw4KcHlmXcO+Wewx9zCoo7EUFiMlu/aZbCJ7VjMzAag==", "cpu": [ "x64" ], @@ -6465,18 +6146,16 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@schematics/angular": { - "version": "18.2.5", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-18.2.5.tgz", - "integrity": "sha512-tBXhk9OGT4U6VsBNbuCNl2ITDOF3NYdGrEieIHU+lHSkpJNGZUIGxCgXCETXkmXDq1pe4wFZSKelWjeqYDfX0g==", + "version": "19.0.6", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-19.0.6.tgz", + "integrity": "sha512-HicclmbW/+mlljU7a4PzbyIWG+7tognoL5LsgMFJQUDzJXHNjRt1riL0vk57o8Pcprnz9FheeWZXO1KRhXkQuw==", "dev": true, - "license": "MIT", "dependencies": { - "@angular-devkit/core": "18.2.5", - "@angular-devkit/schematics": "18.2.5", + "@angular-devkit/core": "19.0.6", + "@angular-devkit/schematics": "19.0.6", "jsonc-parser": "3.3.1" }, "engines": { @@ -6486,26 +6165,24 @@ } }, "node_modules/@sigstore/bundle": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-2.3.2.tgz", - "integrity": "sha512-wueKWDk70QixNLB363yHc2D2ItTgYiMTdPwK8D9dKQMR3ZQ0c35IxP5xnwQ8cNLoCgCRcHf14kE+CLIvNX1zmA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-3.0.0.tgz", + "integrity": "sha512-XDUYX56iMPAn/cdgh/DTJxz5RWmqKV4pwvUAEKEWJl+HzKdCd/24wUa9JYNMlDSCb7SUHAdtksxYX779Nne/Zg==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@sigstore/protobuf-specs": "^0.3.2" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@sigstore/core": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-1.1.0.tgz", - "integrity": "sha512-JzBqdVIyqm2FRQCulY6nbQzMpJJpSiJ8XXWMhtOX9eKgaXXpfNOF53lzQEjIydlStnd/eFtuC1dW4VYdD93oRg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-2.0.0.tgz", + "integrity": "sha512-nYxaSb/MtlSI+JWcwTHQxyNmWeWrUXJJ/G4liLrGG7+tS4vAz6LF3xRXqLH6wPIVUoZQel2Fs4ddLx4NCpiIYg==", "dev": true, - "license": "Apache-2.0", "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@sigstore/protobuf-specs": { @@ -6513,56 +6190,52 @@ "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.3.2.tgz", "integrity": "sha512-c6B0ehIWxMI8wiS/bj6rHMPqeFvngFV7cDU/MY+B16P9Z3Mp9k8L93eYZ7BYzSickzuqAQqAq0V956b3Ju6mLw==", "dev": true, - "license": "Apache-2.0", "engines": { "node": "^16.14.0 || >=18.0.0" } }, "node_modules/@sigstore/sign": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-2.3.2.tgz", - "integrity": "sha512-5Vz5dPVuunIIvC5vBb0APwo7qKA4G9yM48kPWJT+OEERs40md5GoUR1yedwpekWZ4m0Hhw44m6zU+ObsON+iDA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-3.0.0.tgz", + "integrity": "sha512-UjhDMQOkyDoktpXoc5YPJpJK6IooF2gayAr5LvXI4EL7O0vd58okgfRcxuaH+YTdhvb5aa1Q9f+WJ0c2sVuYIw==", "dev": true, - "license": "Apache-2.0", "dependencies": { - "@sigstore/bundle": "^2.3.2", - "@sigstore/core": "^1.0.0", + "@sigstore/bundle": "^3.0.0", + "@sigstore/core": "^2.0.0", "@sigstore/protobuf-specs": "^0.3.2", - "make-fetch-happen": "^13.0.1", - "proc-log": "^4.2.0", + "make-fetch-happen": "^14.0.1", + "proc-log": "^5.0.0", "promise-retry": "^2.0.1" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@sigstore/tuf": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-2.3.4.tgz", - "integrity": "sha512-44vtsveTPUpqhm9NCrbU8CWLe3Vck2HO1PNLw7RIajbB7xhtn5RBPm1VNSCMwqGYHhDsBJG8gDF0q4lgydsJvw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-3.0.0.tgz", + "integrity": "sha512-9Xxy/8U5OFJu7s+OsHzI96IX/OzjF/zj0BSSaWhgJgTqtlBhQIV2xdrQI5qxLD7+CWWDepadnXAxzaZ3u9cvRw==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@sigstore/protobuf-specs": "^0.3.2", - "tuf-js": "^2.2.1" + "tuf-js": "^3.0.1" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@sigstore/verify": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-1.2.1.tgz", - "integrity": "sha512-8iKx79/F73DKbGfRf7+t4dqrc0bRr0thdPrxAtCKWRm/F0tG71i6O1rvlnScncJLLBZHn3h8M3c1BSUAb9yu8g==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-2.0.0.tgz", + "integrity": "sha512-Ggtq2GsJuxFNUvQzLoXqRwS4ceRfLAJnrIHUDrzAD0GgnOhwujJkKkxM/s5Bako07c3WtAs/sZo5PJq7VHjeDg==", "dev": true, - "license": "Apache-2.0", "dependencies": { - "@sigstore/bundle": "^2.3.2", - "@sigstore/core": "^1.1.0", + "@sigstore/bundle": "^3.0.0", + "@sigstore/core": "^2.0.0", "@sigstore/protobuf-specs": "^0.3.2" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@sindresorhus/merge-streams": { @@ -6570,7 +6243,6 @@ "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", "dev": true, - "license": "MIT", "engines": { "node": ">=18" }, @@ -6582,31 +6254,29 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@stencil/core": { - "version": "2.22.3", - "resolved": "https://registry.npmjs.org/@stencil/core/-/core-2.22.3.tgz", - "integrity": "sha512-kmVA0M/HojwsfkeHsifvHVIYe4l5tin7J5+DLgtl8h6WWfiMClND5K3ifCXXI2ETDNKiEk21p6jql3Fx9o2rng==", - "license": "MIT", + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.23.0.tgz", + "integrity": "sha512-9vI8ywhoqlaDEmrcntejrZ9X76Tddv2JyWakjVpqJAdDNXgf520sYkyjzGT0p8VJfPtSaKm6pMtl5sAH1r9cFg==", "bin": { "stencil": "bin/stencil" }, "engines": { - "node": ">=12.10.0", - "npm": ">=6.0.0" + "node": ">=16.0.0", + "npm": ">=7.10.0" } }, "node_modules/@stylistic/eslint-plugin": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-2.8.0.tgz", - "integrity": "sha512-Ufvk7hP+bf+pD35R/QfunF793XlSRIC7USr3/EdgduK9j13i2JjmsM0LUz3/foS+jDYp2fzyWZA9N44CPur0Ow==", + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-2.12.1.tgz", + "integrity": "sha512-fubZKIHSPuo07FgRTn6S4Nl0uXPRPYVNpyZzIDGfp7Fny6JjNus6kReLD7NI380JXi4HtUTSOZ34LBuNPO1XLQ==", "dev": true, "dependencies": { - "@typescript-eslint/utils": "^8.4.0", - "eslint-visitor-keys": "^4.0.0", - "espree": "^10.1.0", + "@typescript-eslint/utils": "^8.13.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", "estraverse": "^5.3.0", "picomatch": "^4.0.2" }, @@ -6621,30 +6291,26 @@ "version": "0.23.0", "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@trapezedev/gradle-parse": { - "version": "7.0.10", - "resolved": "https://registry.npmjs.org/@trapezedev/gradle-parse/-/gradle-parse-7.0.10.tgz", - "integrity": "sha512-k822Is3jGroqOTKF0gAFm80LmhFJWBAyZvNtyuXq6uQUzDDe2fj/gHwixP6VFzlpaWKLP7IuR609Xv8gwJCXyg==", - "dev": true, - "license": "SEE LICENSE" + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@trapezedev/gradle-parse/-/gradle-parse-7.1.3.tgz", + "integrity": "sha512-WQVF5pEJ5o/mUyvfGTG9nBKx9Te/ilKM3r2IT69GlbaooItT5ao7RyF1MUTBNjHLPk/xpGUY3c6PyVnjDlz0Vw==", + "dev": true }, "node_modules/@trapezedev/project": { - "version": "7.0.10", - "resolved": "https://registry.npmjs.org/@trapezedev/project/-/project-7.0.10.tgz", - "integrity": "sha512-UjwsStjhHq/+D1bWREmFDoyKql+qFIgJX93zQLg7R6CyWZUdtlGP2hU3l7tsVRtjJBVXpVu5mj8tdwJJoABO3A==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@trapezedev/project/-/project-7.1.3.tgz", + "integrity": "sha512-GANh8Ey73MechZrryfJoILY9hBnWqzS6AdB53zuWBCBbaiImyblXT41fWdN6pB2f5+cNI2FAUxGfVhl+LeEVbQ==", "dev": true, - "license": "SEE LICENSE", "dependencies": { "@ionic/utils-fs": "^3.1.5", "@ionic/utils-subprocess": "^2.1.8", "@prettier/plugin-xml": "^2.2.0", - "@trapezedev/gradle-parse": "7.0.10", + "@trapezedev/gradle-parse": "7.1.3", "@xmldom/xmldom": "^0.7.5", "conventional-changelog": "^3.1.4", - "cross-fetch": "^3.1.5", "cross-spawn": "^7.0.3", "diff": "^5.1.0", "env-paths": "^3.0.0", @@ -6653,7 +6319,6 @@ "kleur": "^4.1.5", "lodash": "^4.17.21", "mergexml": "^1.2.3", - "npm-watch": "^0.9.0", "plist": "^3.0.4", "prettier": "^2.7.1", "prompts": "^2.4.2", @@ -6672,7 +6337,6 @@ "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", "dev": true, - "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" } @@ -6682,7 +6346,6 @@ "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==", "dev": true, - "license": "MIT", "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, @@ -6695,7 +6358,6 @@ "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", "dev": true, - "license": "ISC", "engines": { "node": ">=10" } @@ -6704,52 +6366,46 @@ "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@tsconfig/node12": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@tsconfig/node14": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@tsconfig/node16": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@tufjs/canonical-json": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-2.0.0.tgz", "integrity": "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==", "dev": true, - "license": "MIT", "engines": { "node": "^16.14.0 || >=18.0.0" } }, "node_modules/@tufjs/models": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-2.0.1.tgz", - "integrity": "sha512-92F7/SFyufn4DXsha9+QfKnN03JGqtMFMXgSHbZOo8JG59WkTni7UzAouNQDf7AuP9OAMxVOPQcqG3sB7w+kkg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-3.0.1.tgz", + "integrity": "sha512-UUYHISyhCU3ZgN8yaear3cGATHb3SMuKHsQ/nVbHXcmnBf+LzQ/cQfhNG+rfaSHgqGKNEm2cOCLVLELStUQ1JA==", "dev": true, - "license": "MIT", "dependencies": { "@tufjs/canonical-json": "2.0.0", - "minimatch": "^9.0.4" + "minimatch": "^9.0.5" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@types/body-parser": { @@ -6798,31 +6454,47 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@types/cordova": { "version": "11.0.3", "resolved": "https://registry.npmjs.org/@types/cordova/-/cordova-11.0.3.tgz", - "integrity": "sha512-kyuRQ40/NWQVhqGIHq78Ehu2Bf9Mlg0LhmSmis6ZFJK7z933FRfYi8tHe/k/0fB+PGfCf95rJC6TO7dopaFvAg==", - "license": "MIT" + "integrity": "sha512-kyuRQ40/NWQVhqGIHq78Ehu2Bf9Mlg0LhmSmis6ZFJK7z933FRfYi8tHe/k/0fB+PGfCf95rJC6TO7dopaFvAg==" }, "node_modules/@types/cors": { "version": "2.8.17", "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", "dev": true, - "license": "MIT", "dependencies": { "@types/node": "*" } }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "dev": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@types/express": { "version": "4.17.21", @@ -6838,9 +6510,9 @@ } }, "node_modules/@types/express-serve-static-core": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.0.tgz", - "integrity": "sha512-AbXMTZGt40T+KON9/Fdxx0B2WK5hsgxcfXJLr5bFpZ7b4JCex2WyQPTEKdXqfHiY5nKKBScZ7yCoO6Pvgxfvnw==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.3.tgz", + "integrity": "sha512-JEhMNwUJt7bw728CydvYzntD0XJeTmDnvwLlbfbAhE7Tbslm/ax6bdIiUwTgeVlZTsJQPwZwKpAkyDtIjsvx3g==", "dev": true, "license": "MIT", "dependencies": { @@ -6868,11 +6540,15 @@ "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.1.5.tgz", "integrity": "sha512-0dzKcwO+S8s2kuF5Z9oUWatQJj5Uq/iqphEtE3GQJVRRYm/tD1LglU2UnXi2A8jLq5umkGouOXOR9y0n613ZwQ==", "dev": true, - "license": "MIT", "dependencies": { "@types/node": "*" } }, + "node_modules/@types/hammerjs": { + "version": "2.0.46", + "resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.46.tgz", + "integrity": "sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw==" + }, "node_modules/@types/http-errors": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", @@ -6885,24 +6561,21 @@ "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.15.tgz", "integrity": "sha512-25g5atgiVNTIv0LBDTg1H74Hvayx0ajtJPLLcYE3whFv75J0pWNtOBzaXJQgDTmrX1bx5U9YC2w/n65BN1HwRQ==", "dev": true, - "license": "MIT", "dependencies": { "@types/node": "*" } }, "node_modules/@types/jasmine": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-4.3.6.tgz", - "integrity": "sha512-3N0FpQTeiWjm+Oo1WUYWguUS7E6JLceiGTriFrG8k5PU7zRLJCzLcWURU3wjMbZGS//a2/LgjsnO3QxIlwxt9g==", - "dev": true, - "license": "MIT" + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-5.1.5.tgz", + "integrity": "sha512-SaCZ3kM5NjOiJqMRYwHpLbTfUC2Dyk1KS3QanNFsUYPGTk70CWVK/J9ueun6zNhw/UkgV7xl8V4ZLQZNRbfnNw==", + "dev": true }, "node_modules/@types/jasminewd2": { "version": "2.0.13", "resolved": "https://registry.npmjs.org/@types/jasminewd2/-/jasminewd2-2.0.13.tgz", "integrity": "sha512-aJ3wj8tXMpBrzQ5ghIaqMisD8C3FIrcO6sDKHqFbuqAsI7yOxj0fA7MrRCPLZHIVUjERIwsMmGn/vB0UQ9u0Hg==", "dev": true, - "license": "MIT", "dependencies": { "@types/jasmine": "*" } @@ -6911,46 +6584,31 @@ "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@types/minimist": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz", "integrity": "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/mute-stream": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/@types/mute-stream/-/mute-stream-0.0.4.tgz", - "integrity": "sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } + "dev": true }, "node_modules/@types/node": { - "version": "20.16.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.1.tgz", - "integrity": "sha512-zJDo7wEadFtSyNz5QITDfRcrhqDvQI1xQNQ0VoizPjM/dVAODqqIUWbJPkvsxmTI0MYRGRikcdjMPhOssnPejQ==", + "version": "20.17.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.10.tgz", + "integrity": "sha512-/jrvh5h6NXhEauFFexRin69nA0uHJ5gwk4iDivp/DeoEua3uwCUto6PC86IpRITBOs4+6i2I56K5x5b6WYGXHA==", "dev": true, - "license": "MIT", "dependencies": { "undici-types": "~6.19.2" } @@ -6969,29 +6627,19 @@ "version": "2.4.4", "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/q": { - "version": "0.0.32", - "resolved": "https://registry.npmjs.org/@types/q/-/q-0.0.32.tgz", - "integrity": "sha512-qYi3YV9inU/REEfxwVcGZzbS3KG/Xs90lv0Pr+lDtuVjBPGd1A+eciXzVSaRvLify132BfcvhvEjeVahrUl0Ug==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@types/qs": { - "version": "6.9.16", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.16.tgz", - "integrity": "sha512-7i+zxXdPD0T4cKDuxCUXJ4wHcsJLwENa6Z3dCu8cfCK743OGy5Nu1RmAGqDPsoTDINVEcdXKRvR/zre+P2Ku1A==", - "dev": true, - "license": "MIT" + "version": "6.9.17", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.17.tgz", + "integrity": "sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==", + "dev": true }, "node_modules/@types/range-parser": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@types/retry": { "version": "0.12.2", @@ -7000,522 +6648,171 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/selenium-webdriver": { - "version": "3.0.26", - "resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-3.0.26.tgz", - "integrity": "sha512-dyIGFKXfUFiwkMfNGn1+F6b80ZjR3uSYv1j6xVJSDlft5waZ2cwkHW4e7zNzvq7hiEackcgvBpmnXZrI1GltPg==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/send": { "version": "0.17.4", "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", "dev": true, - "license": "MIT", - "dependencies": { - "@types/mime": "^1", - "@types/node": "*" - } - }, - "node_modules/@types/serve-index": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", - "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/express": "*" - } - }, - "node_modules/@types/serve-static": { - "version": "1.15.7", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", - "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/http-errors": "*", - "@types/node": "*", - "@types/send": "*" - } - }, - "node_modules/@types/slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-+OpjSaq85gvlZAYINyzKpLeiFkSC4EsC6IIiT6v6TLSU5k5U83fHGj9Lel8oKEXM0HqgrMVCjXPDPVICtxF7EQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/sockjs": { - "version": "0.3.36", - "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", - "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/wrap-ansi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz", - "integrity": "sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/ws": { - "version": "8.5.12", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz", - "integrity": "sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz", - "integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "7.18.0", - "@typescript-eslint/type-utils": "7.18.0", - "@typescript-eslint/utils": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0", - "graphemer": "^1.4.0", - "ignore": "^5.3.1", - "natural-compare": "^1.4.0", - "ts-api-utils": "^1.3.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^7.0.0", - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", - "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", - "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/typescript-estree": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", - "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz", - "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "7.18.0", - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/typescript-estree": "7.18.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", - "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "7.18.0", - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/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, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz", - "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", - "dev": true, - "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/scope-manager": "7.18.0", - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/typescript-estree": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0", - "debug": "^4.3.4" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "@types/mime": "^1", + "@types/node": "*" } }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", - "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==", + "node_modules/@types/serve-index": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", + "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "@types/express": "*" } }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", - "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", + "node_modules/@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", "dev": true, "license": "MIT", - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", - "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", - "dev": true, - "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" } }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", - "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", + "node_modules/@types/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-+OpjSaq85gvlZAYINyzKpLeiFkSC4EsC6IIiT6v6TLSU5k5U83fHGj9Lel8oKEXM0HqgrMVCjXPDPVICtxF7EQ==", + "dev": true + }, + "node_modules/@types/sockjs": { + "version": "0.3.36", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", + "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.18.0", - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "@types/node": "*" } }, - "node_modules/@typescript-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, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } + "node_modules/@types/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", + "dev": true }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.7.0.tgz", - "integrity": "sha512-87rC0k3ZlDOuz82zzXRtQ7Akv3GKhHs0ti4YcbAJtaomllXoSO8hi7Ix3ccEvCd824dy9aIX+j3d2UMAfCtVpg==", + "node_modules/@types/ws": { + "version": "8.5.13", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.13.tgz", + "integrity": "sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.7.0", - "@typescript-eslint/visitor-keys": "8.7.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "@types/node": "*" } }, - "node_modules/@typescript-eslint/type-utils": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz", - "integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==", + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.19.0.tgz", + "integrity": "sha512-NggSaEZCdSrFddbctrVjkVZvFC6KGfKfNK0CU7mNK/iKHGKbzT4Wmgm08dKpcZECBu9f5FypndoMyRHkdqfT1Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "7.18.0", - "@typescript-eslint/utils": "7.18.0", - "debug": "^4.3.4", + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.19.0", + "@typescript-eslint/type-utils": "8.19.0", + "@typescript-eslint/utils": "8.19.0", + "@typescript-eslint/visitor-keys": "8.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/scope-manager": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", - "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", - "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", + "node_modules/@typescript-eslint/parser": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.19.0.tgz", + "integrity": "sha512-6M8taKyOETY1TKHp0x8ndycipTVgmp4xtg5QpEZzXxDhNvvHOJi5rLRkLr8SK3jTgD5l4fTlvBiRdfsuWydxBw==", "dev": true, "license": "MIT", - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", - "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", - "dev": true, - "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" + "@typescript-eslint/scope-manager": "8.19.0", + "@typescript-eslint/types": "8.19.0", + "@typescript-eslint/typescript-estree": "8.19.0", + "@typescript-eslint/visitor-keys": "8.19.0", + "debug": "^4.3.4" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/utils": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz", - "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==", + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.19.0.tgz", + "integrity": "sha512-hkoJiKQS3GQ13TSMEiuNmSCvhz7ujyqD1x3ShbaETATHrck+9RaDdUbt+osXaUuns9OFwrDTTrjtwsU8gJyyRA==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "7.18.0", - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/typescript-estree": "7.18.0" + "@typescript-eslint/types": "8.19.0", + "@typescript-eslint/visitor-keys": "8.19.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" } }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", - "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", + "node_modules/@typescript-eslint/type-utils": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.19.0.tgz", + "integrity": "sha512-TZs0I0OSbd5Aza4qAMpp1cdCYVnER94IziudE3JU328YUHgWu9gwiwhag+fuLeJ2LkWLXI+F/182TbG+JaBdTg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.18.0", - "eslint-visitor-keys": "^3.4.3" + "@typescript-eslint/typescript-estree": "8.19.0", + "@typescript-eslint/utils": "8.19.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-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, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, - "funding": { - "url": "https://opencollective.com/eslint" + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.7.0.tgz", - "integrity": "sha512-LLt4BLHFwSfASHSF2K29SZ+ZCsbQOM+LuarPjRUuHm+Qd09hSe3GCeaQbcCr+Mik+0QFRmep/FyZBO6fJ64U3w==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.19.0.tgz", + "integrity": "sha512-8XQ4Ss7G9WX8oaYvD4OOLCjIQYgRQxO+qCiR2V2s2GxI9AUpo7riNwo6jDhKtTcaJjT8PY54j2Yb33kWtSJsmA==", "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -7526,14 +6823,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.7.0.tgz", - "integrity": "sha512-MC8nmcGHsmfAKxwnluTQpNqceniT8SteVwd2voYlmiSWGOtjvGXdPl17dYu2797GVscK30Z04WRM28CrKS9WOg==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.19.0.tgz", + "integrity": "sha512-WW9PpDaLIFW9LCbucMSdYUuGeFUz1OkWYS/5fwZwTA+l2RwlWFdJvReQqMUMBw4yJWJOfqd7An9uwut2Oj8sLw==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.7.0", - "@typescript-eslint/visitor-keys": "8.7.0", + "@typescript-eslint/types": "8.19.0", + "@typescript-eslint/visitor-keys": "8.19.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -7548,22 +6845,21 @@ "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "peerDependencies": { + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/utils": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.7.0.tgz", - "integrity": "sha512-ZbdUdwsl2X/s3CiyAu3gOlfQzpbuG3nTWKPoIvAu1pu5r8viiJvv2NPN2AqArL35NCYtw/lrPPfM4gxrMLNLPw==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.19.0.tgz", + "integrity": "sha512-PTBG+0oEMPH9jCZlfg07LCB2nYI0I317yyvXGfxnvGvw4SHIOuRnQ3kadyyXY6tGdChusIHIbM5zfIbp4M6tCg==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.7.0", - "@typescript-eslint/types": "8.7.0", - "@typescript-eslint/typescript-estree": "8.7.0" + "@typescript-eslint/scope-manager": "8.19.0", + "@typescript-eslint/types": "8.19.0", + "@typescript-eslint/typescript-estree": "8.19.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -7573,18 +6869,19 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.7.0.tgz", - "integrity": "sha512-b1tx0orFCCh/THWPQa2ZwWzvOeyzzp36vkJYOpVg0u8UVOIsfVrnuC9FqAw9gRKn+rG2VmWQ/zDJZzkxUnj/XQ==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.19.0.tgz", + "integrity": "sha512-mCFtBbFBJDCNCWUl5y6sZSCHXw1DEFEk3c/M3nRK2a4XUB8StGFtmcEMizdjKuBzB6e/smJAAWYug3VrdLMr1w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.7.0", - "eslint-visitor-keys": "^3.4.3" + "@typescript-eslint/types": "8.19.0", + "eslint-visitor-keys": "^4.2.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -7594,26 +6891,6 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/visitor-keys/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, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "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, - "license": "ISC" - }, "node_modules/@vitejs/plugin-basic-ssl": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-1.1.0.tgz", @@ -7628,163 +6905,148 @@ } }, "node_modules/@webassemblyjs/ast": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", - "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", "dev": true, - "license": "MIT", "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" } }, "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", - "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", - "dev": true, - "license": "MIT" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "dev": true }, "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", - "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", - "dev": true, - "license": "MIT" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "dev": true }, "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", - "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", - "dev": true, - "license": "MIT" + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "dev": true }, "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", - "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", "dev": true, - "license": "MIT", "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", - "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", - "dev": true, - "license": "MIT" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "dev": true }, "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", - "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", "dev": true, - "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" } }, "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", - "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", "dev": true, - "license": "MIT", "dependencies": { "@xtuc/ieee754": "^1.2.0" } }, "node_modules/@webassemblyjs/leb128": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", - "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/utf8": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", - "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", - "dev": true, - "license": "MIT" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "dev": true }, "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", - "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", "dev": true, - "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/helper-wasm-section": "1.12.1", - "@webassemblyjs/wasm-gen": "1.12.1", - "@webassemblyjs/wasm-opt": "1.12.1", - "@webassemblyjs/wasm-parser": "1.12.1", - "@webassemblyjs/wast-printer": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" } }, "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", - "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", "dev": true, - "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", - "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", "dev": true, - "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/wasm-gen": "1.12.1", - "@webassemblyjs/wasm-parser": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" } }, "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", - "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", "dev": true, - "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-api-error": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, "node_modules/@webassemblyjs/wast-printer": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", - "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", "dev": true, - "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" } }, @@ -7793,7 +7055,6 @@ "resolved": "https://registry.npmjs.org/@xml-tools/parser/-/parser-1.0.11.tgz", "integrity": "sha512-aKqQ077XnR+oQtHJlrAflaZaL7qZsulWc/i/ZEooar5JiWj1eLt0+Wg28cpa+XLney107wXqneC+oG1IZvxkTA==", "dev": true, - "license": "Apache-2.0", "dependencies": { "chevrotain": "7.1.1" } @@ -7802,8 +7063,8 @@ "version": "0.7.13", "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.13.tgz", "integrity": "sha512-lm2GW5PkosIzccsaZIz7tp8cPADSIlIHWDFTR1N0SzfinhhYgeIQjFMz4rYzanCScr3DqQLeomUDArp6MWKm+g==", + "deprecated": "this version is no longer supported, please update to at least 0.8.*", "dev": true, - "license": "MIT", "engines": { "node": ">=10.0.0" } @@ -7812,29 +7073,25 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true, - "license": "BSD-3-Clause" + "dev": true }, "node_modules/@xtuc/long": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true, - "license": "Apache-2.0" + "dev": true }, "node_modules/@yarnpkg/lockfile": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", - "dev": true, - "license": "BSD-2-Clause" + "dev": true }, "node_modules/abbrev": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", "dev": true, - "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -7844,7 +7101,6 @@ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "dev": true, - "license": "MIT", "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" @@ -7853,12 +7109,20 @@ "node": ">= 0.6" } }, + "node_modules/accepts/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/acorn": { - "version": "8.12.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", - "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", "dev": true, - "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -7866,32 +7130,20 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-import-attributes": { - "version": "1.9.5", - "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", - "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^8" - } - }, "node_modules/acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, - "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "node_modules/acorn-walk": { - "version": "8.3.3", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.3.tgz", - "integrity": "sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==", + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", "dev": true, - "license": "MIT", "dependencies": { "acorn": "^8.11.0" }, @@ -7903,15 +7155,13 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/add-stream/-/add-stream-1.0.0.tgz", "integrity": "sha512-qQLMr+8o0WC4FZGQTcJiKBVC59JylcPSrTtk6usvmIDFUOCKegapy1VHQwRbFMOFyb/inzUVqHs+eMYKDM1YeQ==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/adjust-sourcemap-loader": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz", "integrity": "sha512-OXwN5b9pCUXNQHJpwwD2qP40byEmSgzj8B4ydSN0uMNYWiFmJ6x6KwUllMmfk8Rwu/HJDFR7U8ubsWBoN0Xp0A==", "dev": true, - "license": "MIT", "dependencies": { "loader-utils": "^2.0.0", "regex-parser": "^2.2.11" @@ -7925,7 +7175,6 @@ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", "dev": true, - "license": "MIT", "dependencies": { "big.js": "^5.2.2", "emojis-list": "^3.0.0", @@ -7935,25 +7184,11 @@ "node": ">=8.9.0" } }, - "node_modules/adm-zip": { - "version": "0.5.15", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.15.tgz", - "integrity": "sha512-jYPWSeOA8EFoZnucrKCNihqBjoEGQSU4HKgHYQgKNEQ0pQF9a/DYuo/+fAxY76k4qe75LUlLWpAM1QWcBMTOKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0" - } - }, "node_modules/agent-base": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", - "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.3.4" - }, "engines": { "node": ">= 14" } @@ -7963,7 +7198,6 @@ "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", "dev": true, - "license": "MIT", "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" @@ -7993,7 +7227,6 @@ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", "dev": true, - "license": "MIT", "dependencies": { "ajv": "^8.0.0" }, @@ -8011,7 +7244,6 @@ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "dev": true, - "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3" }, @@ -8024,7 +7256,6 @@ "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", "dev": true, - "license": "MIT", "engines": { "node": ">=6" } @@ -8034,7 +7265,6 @@ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", "dev": true, - "license": "MIT", "dependencies": { "type-fest": "^0.21.3" }, @@ -8062,22 +7292,22 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "license": "MIT", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dependencies": { - "color-convert": "^1.9.0" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=4" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/anymatch": { @@ -8085,7 +7315,6 @@ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "devOptional": true, - "license": "ISC", "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -8099,7 +7328,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "devOptional": true, - "license": "MIT", "engines": { "node": ">=8.6" }, @@ -8112,7 +7340,6 @@ "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz", "integrity": "sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==", "dev": true, - "license": "MIT", "engines": { "node": ">=14" } @@ -8121,24 +7348,21 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" + "dev": true }, "node_modules/aria-query": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", - "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "dequal": "^2.0.3" + "engines": { + "node": ">= 0.4" } }, "node_modules/array-buffer-byte-length": { @@ -8146,7 +7370,6 @@ "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", "dev": true, - "license": "MIT", "dependencies": { "call-bind": "^1.0.5", "is-array-buffer": "^3.0.4" @@ -8169,15 +7392,13 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/array-includes": { "version": "3.1.8", "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", "dev": true, - "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", @@ -8198,27 +7419,15 @@ "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/array.prototype.findlastindex": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", "dev": true, - "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", @@ -8235,16 +7444,15 @@ } }, "node_modules/array.prototype.flat": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", - "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", "dev": true, - "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0" + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -8254,16 +7462,15 @@ } }, "node_modules/array.prototype.flatmap": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", - "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", "dev": true, - "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0" + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -8273,20 +7480,18 @@ } }, "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", - "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", "dev": true, - "license": "MIT", "dependencies": { "array-buffer-byte-length": "^1.0.1", - "call-bind": "^1.0.5", + "call-bind": "^1.0.8", "define-properties": "^1.2.1", - "es-abstract": "^1.22.3", - "es-errors": "^1.2.1", - "get-intrinsic": "^1.2.3", - "is-array-buffer": "^3.0.4", - "is-shared-array-buffer": "^1.0.2" + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" }, "engines": { "node": ">= 0.4" @@ -8300,7 +7505,6 @@ "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -8309,35 +7513,13 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "dev": true, - "license": "MIT" - }, - "node_modules/asn1": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", - "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": "~2.1.0" - } - }, - "node_modules/assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8" - } + "dev": true }, "node_modules/ast-types": { "version": "0.13.4", "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", "dev": true, - "license": "MIT", "dependencies": { "tslib": "^2.0.1" }, @@ -8350,7 +7532,6 @@ "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } @@ -8359,15 +7540,13 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/at-least-node": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", "dev": true, - "license": "ISC", "engines": { "node": ">= 4.0.0" } @@ -8391,7 +7570,6 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { "browserslist": "^4.23.3", "caniuse-lite": "^1.0.30001646", @@ -8415,7 +7593,6 @@ "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", "dev": true, - "license": "MIT", "dependencies": { "possible-typed-array-names": "^1.0.0" }, @@ -8426,46 +7603,26 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "*" - } - }, - "node_modules/aws4": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.1.tgz", - "integrity": "sha512-u5w79Rd7SU4JaIlA/zFqG+gOiuq25q5VLyZ8E+ijJeILuTxVzZgp2CaGw/UTw6pXYN9XMO9yiqj/nEHmhTG5CA==", - "dev": true, - "license": "MIT" - }, "node_modules/axobject-query": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", "dev": true, - "license": "Apache-2.0", "engines": { "node": ">= 0.4" } }, "node_modules/b4a": { - "version": "1.6.6", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.6.tgz", - "integrity": "sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==", - "dev": true, - "license": "Apache-2.0" + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", + "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==", + "dev": true }, "node_modules/babel-loader": { - "version": "9.1.3", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.3.tgz", - "integrity": "sha512-xG3ST4DglodGf8qSwv0MdeWLhrDsw/32QMdTO5T1ZIp9gQur0HkCyFs7Awskr10JKXFXwpAhiCuYX5oGXnRGbw==", + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.2.1.tgz", + "integrity": "sha512-fqe8naHt46e0yIdkjUZYqddSXfej3AHajX+CSO5X7oy0EmPc6o5Xh+RClNoHjnieWz9AW4kZxW9yyFMhVB1QLA==", "dev": true, - "license": "MIT", "dependencies": { "find-cache-dir": "^4.0.0", "schema-utils": "^4.0.0" @@ -8479,14 +7636,13 @@ } }, "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.11", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz", - "integrity": "sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q==", + "version": "0.4.12", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.12.tgz", + "integrity": "sha512-CPWT6BwvhrTO2d8QVorhTCQw9Y43zOu7G9HigcfxvepOU6b8o3tcWad6oVgZIsZCTt42FFv97aA7ZJsbM4+8og==", "dev": true, - "license": "MIT", "dependencies": { "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.6.2", + "@babel/helper-define-polyfill-provider": "^0.6.3", "semver": "^6.3.1" }, "peerDependencies": { @@ -8498,7 +7654,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "license": "ISC", "bin": { "semver": "bin/semver.js" } @@ -8508,7 +7663,6 @@ "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz", "integrity": "sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==", "dev": true, - "license": "MIT", "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.2", "core-js-compat": "^3.38.0" @@ -8518,13 +7672,12 @@ } }, "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.2.tgz", - "integrity": "sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.3.tgz", + "integrity": "sha512-LiWSbl4CRSIa5x/JAU6jZiG9eit9w6mz+yVMFwDE83LAWvt0AfGBoZ7HS/mkhrKuh2ZlzfVZYKoLjXdqw6Yt7Q==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.2" + "@babel/helper-define-polyfill-provider": "^0.6.3" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -8534,23 +7687,20 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/bare-events": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.4.2.tgz", - "integrity": "sha512-qMKFd2qG/36aA4GwvKq8MxnPgCQAmBWmSyLWsJcbn8v03wvIPQ/hG1Ms8bPzndZxMDoHpxez5VOS+gC9Yi24/Q==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.5.0.tgz", + "integrity": "sha512-/E8dDe9dsbLyh2qrZ64PEPadOQ0F4gbl1sUJOrmph7xOiIxfY8vwab/4bFLh4Y88/Hk/ujKcrQKc+ps0mv873A==", "dev": true, - "license": "Apache-2.0", "optional": true }, "node_modules/bare-fs": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-2.3.1.tgz", - "integrity": "sha512-W/Hfxc/6VehXlsgFtbB5B4xFcsCl+pAh30cYhoFyXErf6oGrwjh8SwiPAdHgpmWonKuYpZgGywN0SXt7dgsADA==", + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-2.3.5.tgz", + "integrity": "sha512-SlE9eTxifPDJrT6YgemQ1WGFleevzwY+XAP1Xqgl56HtcrisC2CHCZ2tq6dBpcH2TnNxwUEUGhweo+lrQtYuiw==", "dev": true, - "license": "Apache-2.0", "optional": true, "dependencies": { "bare-events": "^2.0.0", @@ -8559,11 +7709,10 @@ } }, "node_modules/bare-os": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-2.4.0.tgz", - "integrity": "sha512-v8DTT08AS/G0F9xrhyLtepoo9EJBJ85FRSMbu1pQUlAf6A8T0tEEQGMVObWeqpjhSPXsE0VGlluFBJu2fdoTNg==", + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-2.4.4.tgz", + "integrity": "sha512-z3UiI2yi1mK0sXeRdc4O1Kk8aOa/e+FNWZcTiPB/dfTWyLypuE99LibgRaQki914Jq//yAWylcAt+mknKdixRQ==", "dev": true, - "license": "Apache-2.0", "optional": true }, "node_modules/bare-path": { @@ -8571,21 +7720,19 @@ "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-2.1.3.tgz", "integrity": "sha512-lh/eITfU8hrj9Ru5quUp0Io1kJWIk1bTjzo7JH1P5dWmQ2EL4hFUlfI8FonAhSlgIfhn63p84CDY/x+PisgcXA==", "dev": true, - "license": "Apache-2.0", "optional": true, "dependencies": { "bare-os": "^2.1.0" } }, "node_modules/bare-stream": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.1.3.tgz", - "integrity": "sha512-tiDAH9H/kP+tvNO5sczyn9ZAA7utrSMobyDchsnyyXBuUe2FSQWbxhtuHB8jwpHYYevVo2UJpcmvvjrbHboUUQ==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.6.1.tgz", + "integrity": "sha512-eVZbtKM+4uehzrsj49KtCy3Pbg7kO1pJ3SKZ1SFrIH/0pnj9scuGGgUlNDf/7qS8WKtGdiJY5Kyhs/ivYPTB/g==", "dev": true, - "license": "Apache-2.0", "optional": true, "dependencies": { - "streamx": "^2.18.0" + "streamx": "^2.21.0" } }, "node_modules/base64-js": { @@ -8605,15 +7752,13 @@ "type": "consulting", "url": "https://feross.org/support" } - ], - "license": "MIT" + ] }, "node_modules/base64id": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", "dev": true, - "license": "MIT", "engines": { "node": "^4.5.0 || >= 5.9" } @@ -8623,7 +7768,6 @@ "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", "dev": true, - "license": "MIT", "engines": { "node": ">=10.0.0" } @@ -8635,14 +7779,97 @@ "dev": true, "license": "MIT" }, - "node_modules/bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "node_modules/beasties": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/beasties/-/beasties-0.1.0.tgz", + "integrity": "sha512-+Ssscd2gVG24qRNC+E2g88D+xsQW4xwakWtKAiGEQ3Pw54/FGdyo9RrfxhGhEv6ilFVbB7r3Lgx+QnAxnSpECw==", "dev": true, - "license": "BSD-3-Clause", + "license": "Apache-2.0", + "dependencies": { + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "htmlparser2": "^9.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-media-query-parser": "^0.2.3" + } + }, + "node_modules/beasties/node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/beasties/node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/beasties/node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/beasties/node_modules/domutils": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.1.tgz", + "integrity": "sha512-xWXmuRnN9OMP6ptPd2+H0cCbcYBULa5YDTbMm/2lvkWvNA3O4wcW+GvzooqBuNM8yy6pl3VIAeJTUUWUbfI5Fw==", + "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "tweetnacl": "^0.14.3" + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/beasties/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" } }, "node_modules/big-integer": { @@ -8650,7 +7877,6 @@ "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", "dev": true, - "license": "Unlicense", "engines": { "node": ">=0.6" } @@ -8660,7 +7886,6 @@ "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", "dev": true, - "license": "MIT", "engines": { "node": "*" } @@ -8670,7 +7895,6 @@ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", "devOptional": true, - "license": "MIT", "engines": { "node": ">=8" }, @@ -8682,29 +7906,12 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "license": "MIT", "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, - "node_modules/blocking-proxy": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/blocking-proxy/-/blocking-proxy-1.0.1.tgz", - "integrity": "sha512-KE8NFMZr3mN2E0HcvCgRtX7DjhiIQrwle+nSVJVC/yqFb9+xznHl2ZcoBp2L9qzkI4t4cBFJ1efXF8Dwi132RA==", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "blocking-proxy": "built/lib/bin.js" - }, - "engines": { - "node": ">=6.9.x" - } - }, "node_modules/body-parser": { "version": "1.20.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", @@ -8734,7 +7941,6 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, - "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -8744,7 +7950,6 @@ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "dev": true, - "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3" }, @@ -8756,13 +7961,12 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/bonjour-service": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.2.1.tgz", - "integrity": "sha512-oSzCS2zV14bh2kji6vNe7vrpJYCHGvcZnlffFQ1MEoX/WOeQ/teD8SYWKR942OI3INjq8OMNJlbPK5LLLUxFDw==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz", + "integrity": "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==", "dev": true, "license": "MIT", "dependencies": { @@ -8774,15 +7978,13 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/bplist-creator": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/bplist-creator/-/bplist-creator-0.1.0.tgz", "integrity": "sha512-sXaHZicyEEmY86WyueLTQesbeoH/mquvarJaQNbjuOQO+7gbFcDEWqKmcWA4cOTLzFlfgvkiVxolk1k5bBIpmg==", "dev": true, - "license": "MIT", "dependencies": { "stream-buffers": "2.2.x" } @@ -8792,7 +7994,6 @@ "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.3.2.tgz", "integrity": "sha512-apC2+fspHGI3mMKj+dGevkGo/tCqVB8jMb6i+OX+E29p0Iposz07fABkRIfVUPNd5A5VbuOz1bZbnmkKLYF+wQ==", "dev": true, - "license": "MIT", "dependencies": { "big-integer": "1.6.x" }, @@ -8805,7 +8006,6 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, - "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -8815,7 +8015,6 @@ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "devOptional": true, - "license": "MIT", "dependencies": { "fill-range": "^7.1.1" }, @@ -8824,9 +8023,9 @@ } }, "node_modules/browserslist": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", - "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.3.tgz", + "integrity": "sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA==", "dev": true, "funding": [ { @@ -8842,12 +8041,11 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001646", - "electron-to-chromium": "^1.5.4", - "node-releases": "^2.0.18", - "update-browserslist-db": "^1.1.0" + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.1" }, "bin": { "browserslist": "cli.js" @@ -8856,53 +8054,6 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/browserstack": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/browserstack/-/browserstack-1.6.1.tgz", - "integrity": "sha512-GxtFjpIaKdbAyzHfFDKixKO8IBT7wR3NjbzrGc78nNs/Ciys9wU3/nBtsqsWv5nDSrdI5tz0peKuzCPuNXNUiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "https-proxy-agent": "^2.2.1" - } - }, - "node_modules/browserstack/node_modules/agent-base": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", - "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", - "dev": true, - "license": "MIT", - "dependencies": { - "es6-promisify": "^5.0.0" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/browserstack/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/browserstack/node_modules/https-proxy-agent": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", - "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" - }, - "engines": { - "node": ">= 4.5.0" - } - }, "node_modules/buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", @@ -8921,7 +8072,6 @@ "url": "https://feross.org/support" } ], - "license": "MIT", "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" @@ -8932,7 +8082,6 @@ "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", "dev": true, - "license": "MIT", "engines": { "node": "*" } @@ -8941,8 +8090,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/bundle-name": { "version": "4.1.0", @@ -8965,19 +8113,17 @@ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/cacache": { - "version": "18.0.4", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.4.tgz", - "integrity": "sha512-B+L5iIa9mgcjLbliir2th36yEwPftrzteHYujzsx3dFP/31GCHcIeS8f5MGd80odLOjaOvSpU3EEAmRQptkxLQ==", + "version": "19.0.1", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-19.0.1.tgz", + "integrity": "sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ==", "dev": true, - "license": "ISC", "dependencies": { - "@npmcli/fs": "^3.1.0", + "@npmcli/fs": "^4.0.0", "fs-minipass": "^3.0.0", "glob": "^10.2.2", "lru-cache": "^10.0.1", @@ -8985,13 +8131,22 @@ "minipass-collect": "^2.0.1", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", - "p-map": "^4.0.0", - "ssri": "^10.0.0", - "tar": "^6.1.11", - "unique-filename": "^3.0.0" + "p-map": "^7.0.2", + "ssri": "^12.0.0", + "tar": "^7.4.3", + "unique-filename": "^4.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/cacache/node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "engines": { + "node": ">=18" } }, "node_modules/cacache/node_modules/glob": { @@ -8999,7 +8154,6 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dev": true, - "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", @@ -9019,21 +8173,88 @@ "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, + "node_modules/cacache/node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", "dev": true, - "license": "ISC" + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cacache/node_modules/tar": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", + "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", + "dev": true, + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cacache/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "engines": { + "node": ">=18" + } }, "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", "dev": true, - "license": "MIT", "dependencies": { + "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", + "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", + "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", + "dev": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "get-intrinsic": "^1.2.6" }, "engines": { "node": ">= 0.4" @@ -9047,7 +8268,6 @@ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=6" } @@ -9057,7 +8277,6 @@ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true, - "license": "MIT", "engines": { "node": ">=6" } @@ -9067,7 +8286,6 @@ "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", "dev": true, - "license": "MIT", "dependencies": { "camelcase": "^5.3.1", "map-obj": "^4.0.0", @@ -9081,9 +8299,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001653", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001653.tgz", - "integrity": "sha512-XGWQVB8wFQ2+9NZwZ10GxTYC5hk0Fa+q8cSkr0tgvMhYhMHP/QC+WTgrePMDBWiWc/pV+1ik82Al20XOK25Gcw==", + "version": "1.0.30001689", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001689.tgz", + "integrity": "sha512-CmeR2VBycfa+5/jOfnp/NpWPGd06nf1XYiefUvhXFfZE4GkRc9jv+eGPS4nT558WS/8lYCzV8SlANCIPvbWP1g==", "dev": true, "funding": [ { @@ -9098,24 +8316,21 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ], - "license": "CC-BY-4.0" + ] }, "node_modules/capacitor-blob-writer": { - "version": "1.1.17", - "resolved": "https://registry.npmjs.org/capacitor-blob-writer/-/capacitor-blob-writer-1.1.17.tgz", - "integrity": "sha512-IixJbl0k4NQ+aPo+UbDvHJxztnaGymVMCkYoHKTjj3F3KDTsMEl5iK7fEZkQ5jhQabe8YJKW0wTGu6Qm/RmXrw==", - "license": "MIT", + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/capacitor-blob-writer/-/capacitor-blob-writer-1.1.19.tgz", + "integrity": "sha512-2uZjL/y6F2yblA6lc5SP0JE+rQhxZcinmsLQKwGuSIDQySAdrcDkCAoQ07nqQV9qMtaeWsVTD6n08wo4R2sSjA==", "peerDependencies": { "@capacitor/core": ">=3.0.0", "@capacitor/filesystem": ">=1.0.0" } }, "node_modules/capacitor-ios-autofill-save-password": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/capacitor-ios-autofill-save-password/-/capacitor-ios-autofill-save-password-3.0.0.tgz", - "integrity": "sha512-FGWralgZ47FtpRcIaJP+IcNDZfmoaCUmkeGBKo84V3nYMKQwQbpN9wLIzXa+TtHj8h1SenRuSR2eTsQBrRewAQ==", - "license": "MIT", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/capacitor-ios-autofill-save-password/-/capacitor-ios-autofill-save-password-4.0.0.tgz", + "integrity": "sha512-CNswfilVSy5gLLjoWd4h0HUSJCExn6DDjWLQF6xucmLmdP3CKoqVNPieJOJBLqoIm4d1xfOkHFpolha2cr2ZWA==", "peerDependencies": { "@capacitor/core": "^6.0.0" } @@ -9124,45 +8339,35 @@ "version": "0.10.0", "resolved": "https://registry.npmjs.org/capacitor-secure-storage-plugin/-/capacitor-secure-storage-plugin-0.10.0.tgz", "integrity": "sha512-dV4E+HTZAJWC3gef7sBXaAkkb6wvcZHyXjJIHXNb3yz9gRQ/5VMLqCxa0khqpwgWh5oIbo4XFxg3g5tEkfaNMg==", - "license": "MIT", "peerDependencies": { "@capacitor/core": "^6.0.0" } }, - "node_modules/caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", - "dev": true, - "license": "Apache-2.0" - }, "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "license": "MIT", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, "node_modules/chardet": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/chart.js": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.4.tgz", - "integrity": "sha512-emICKGBABnxhMjUjlYRR12PmOXhJ2eJjEHL2/dZlWjxRAZT1D8xplLFq5M0tMQK8ja+wBS/tuVEJB5C6r7VxJA==", - "license": "MIT", + "version": "4.4.7", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.7.tgz", + "integrity": "sha512-pwkcKfdzTMAU/+jNosKhNL2bHtJc/sSmYgVbuGTEDhzkrhmyihmP7vUc/5ZK9WopidMDHNe3Wm7jOd/WhuHWuw==", "dependencies": { "@kurkle/color": "^0.3.0" }, @@ -9174,17 +8379,15 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/chartjs-adapter-date-fns/-/chartjs-adapter-date-fns-3.0.0.tgz", "integrity": "sha512-Rs3iEB3Q5pJ973J93OBTpnP7qoGwvq3nUnoMdtxO+9aoJof7UFcRbWcIDteXuYd1fgAvct/32T9qaLyLuZVwCg==", - "license": "MIT", "peerDependencies": { "chart.js": ">=2.8.0", "date-fns": ">=2.0.0" } }, "node_modules/chartjs-plugin-annotation": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/chartjs-plugin-annotation/-/chartjs-plugin-annotation-3.0.1.tgz", - "integrity": "sha512-hlIrXXKqSDgb+ZjVYHefmlZUXK8KbkCPiynSVrTb/HjTMkT62cOInaT1NTQCKtxKKOm9oHp958DY3RTAFKtkHg==", - "license": "MIT", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/chartjs-plugin-annotation/-/chartjs-plugin-annotation-3.1.0.tgz", + "integrity": "sha512-EkAed6/ycXD/7n0ShrlT1T2Hm3acnbFhgkIEJLa0X+M6S16x0zwj1Fv4suv/2bwayCT3jGPdAtI9uLcAMToaQQ==", "peerDependencies": { "chart.js": ">=4.0.0" } @@ -9193,17 +8396,16 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/chartjs-plugin-datalabels/-/chartjs-plugin-datalabels-2.2.0.tgz", "integrity": "sha512-14ZU30lH7n89oq+A4bWaJPnAG8a7ZTk7dKf48YAzMvJjQtjrgg5Dpk9f+LbjCF6bpx3RAGTeL13IXpKQYyRvlw==", - "license": "MIT", "peerDependencies": { "chart.js": ">=3.0.0" } }, "node_modules/chartjs-plugin-zoom": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/chartjs-plugin-zoom/-/chartjs-plugin-zoom-2.0.1.tgz", - "integrity": "sha512-ogOmLu6e+Q7E1XWOCOz9YwybMslz9qNfGV2a+qjfmqJYpsw5ZMoRHZBUyW+NGhkpQ5PwwPA/+rikHpBZb7PZuA==", - "license": "MIT", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/chartjs-plugin-zoom/-/chartjs-plugin-zoom-2.2.0.tgz", + "integrity": "sha512-in6kcdiTlP6npIVLMd4zXZ08PDUXC52gZ4FAy5oyjk1zX3gKarXMAof7B9eFiisf9WOC3bh2saHg+J5WtLXZeA==", "dependencies": { + "@types/hammerjs": "^2.0.45", "hammerjs": "^2.0.8" }, "peerDependencies": { @@ -9215,34 +8417,23 @@ "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-7.1.1.tgz", "integrity": "sha512-wy3mC1x4ye+O+QkEinVJkPf5u2vsrDIYW9G7ZuwFl6v/Yu0LwUuT2POsb+NUWApebyxfkQq6+yDfRExbnI5rcw==", "dev": true, - "license": "Apache-2.0", "dependencies": { "regexp-to-ast": "0.5.0" } }, "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "devOptional": true, - "license": "MIT", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.2.tgz", + "integrity": "sha512-/b57FK+bblSU+dfewfFe0rT1YjVDfOmeLQwCAuC+vwvgLkXboATqqmy+Ipux6JrF6L5joe5CBnFOw+gLWH6yKg==", + "dev": true, "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" + "readdirp": "^4.0.1" }, "engines": { - "node": ">= 8.10.0" + "node": ">= 14.16.0" }, "funding": { "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" } }, "node_modules/chownr": { @@ -9250,7 +8441,6 @@ "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", "dev": true, - "license": "ISC", "engines": { "node": ">=10" } @@ -9260,7 +8450,6 @@ "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=6.0" } @@ -9268,15 +8457,13 @@ "node_modules/classlist.js": { "version": "1.1.20150312", "resolved": "https://registry.npmjs.org/classlist.js/-/classlist.js-1.1.20150312.tgz", - "integrity": "sha512-eR8yB970+yGslcTnJnROX2icsMa8v/KVLv/sgv3NhSvZSHgam64XNSF2TyJnKIfsnTFJBcTdrIneYqUIrvxLpg==", - "license": "Dedicated to the public domain" + "integrity": "sha512-eR8yB970+yGslcTnJnROX2icsMa8v/KVLv/sgv3NhSvZSHgam64XNSF2TyJnKIfsnTFJBcTdrIneYqUIrvxLpg==" }, "node_modules/clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", "dev": true, - "license": "MIT", "engines": { "node": ">=6" } @@ -9285,7 +8472,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "license": "MIT", "dependencies": { "restore-cursor": "^3.1.0" }, @@ -9297,7 +8483,6 @@ "version": "2.9.2", "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", - "license": "MIT", "engines": { "node": ">=6" }, @@ -9310,7 +8495,6 @@ "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", "dev": true, - "license": "MIT", "dependencies": { "slice-ansi": "^5.0.0", "string-width": "^7.0.0" @@ -9327,7 +8511,6 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "dev": true, - "license": "MIT", "engines": { "node": ">=12" }, @@ -9340,7 +8523,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "dev": true, - "license": "MIT", "engines": { "node": ">=12" }, @@ -9352,15 +8534,13 @@ "version": "10.4.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/cli-truncate/node_modules/is-fullwidth-code-point": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=12" }, @@ -9373,7 +8553,6 @@ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", "dev": true, - "license": "MIT", "dependencies": { "ansi-styles": "^6.0.0", "is-fullwidth-code-point": "^4.0.0" @@ -9390,7 +8569,6 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "dev": true, - "license": "MIT", "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", @@ -9408,7 +8586,6 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, - "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" }, @@ -9424,7 +8601,6 @@ "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", "dev": true, - "license": "ISC", "engines": { "node": ">= 12" } @@ -9434,7 +8610,6 @@ "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, - "license": "ISC", "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", @@ -9444,48 +8619,11 @@ "node": ">=12" } }, - "node_modules/cliui/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/cliui/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/cliui/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, "node_modules/cliui/node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, - "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -9502,7 +8640,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", - "license": "MIT", "engines": { "node": ">=0.8" } @@ -9512,7 +8649,6 @@ "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", "dev": true, - "license": "MIT", "dependencies": { "is-plain-object": "^2.0.4", "kind-of": "^6.0.2", @@ -9522,54 +8658,35 @@ "node": ">=6" } }, - "node_modules/color": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", - "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1", - "color-string": "^1.9.0" - }, - "engines": { - "node": ">=12.5.0" - } - }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "node_modules/clone-deep/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "dev": true, - "license": "MIT" + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } }, - "node_modules/color-string": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", - "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", "dev": true, - "license": "MIT", "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" } }, - "node_modules/color/node_modules/color-convert": { + "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -9577,26 +8694,32 @@ "node": ">=7.0.0" } }, - "node_modules/color/node_modules/color-name": { + "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", "dev": true, - "license": "MIT" + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } }, "node_modules/colorette": { "version": "2.0.20", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/colors": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.1.90" } @@ -9606,7 +8729,6 @@ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "dev": true, - "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" }, @@ -9619,7 +8741,6 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", "dev": true, - "license": "MIT", "engines": { "node": ">= 12" } @@ -9629,7 +8750,6 @@ "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz", "integrity": "sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==", "dev": true, - "license": "MIT", "engines": { "node": ">= 12.0.0" } @@ -9638,15 +8758,13 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/compare-func": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", "dev": true, - "license": "MIT", "dependencies": { "array-ify": "^1.0.0", "dot-prop": "^5.1.0" @@ -9655,15 +8773,13 @@ "node_modules/compare-versions": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.1.tgz", - "integrity": "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==", - "license": "MIT" + "integrity": "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==" }, "node_modules/component-emitter": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", "dev": true, - "license": "MIT", "funding": { "url": "https://github.com/sponsors/sindresorhus" } @@ -9682,34 +8798,24 @@ } }, "node_modules/compression": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", - "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.5.tgz", + "integrity": "sha512-bQJ0YRck5ak3LgtnpKkiabX5pNF7tMUh1BSy2ZBOTh0Dim0BUu6aPPwByIns6/A5Prh8PufSPerMDUklpzes2Q==", "dev": true, "license": "MIT", "dependencies": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", + "bytes": "3.1.2", + "compressible": "~2.0.18", "debug": "2.6.9", + "negotiator": "~0.6.4", "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", + "safe-buffer": "5.2.1", "vary": "~1.1.2" }, "engines": { "node": ">= 0.8.0" } }, - "node_modules/compression/node_modules/bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/compression/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -9727,26 +8833,27 @@ "dev": true, "license": "MIT" }, - "node_modules/compression/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/compression/node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">= 0.6" + } }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/connect": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", "dev": true, - "license": "MIT", "dependencies": { "debug": "2.6.9", "finalhandler": "1.1.2", @@ -9772,7 +8879,6 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, - "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -9781,8 +8887,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/content-disposition": { "version": "0.5.4", @@ -9802,7 +8907,6 @@ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.6" } @@ -9812,7 +8916,6 @@ "resolved": "https://registry.npmjs.org/conventional-changelog/-/conventional-changelog-3.1.25.tgz", "integrity": "sha512-ryhi3fd1mKf3fSjbLXOfK2D06YwKNic1nC9mWqybBHdObPd8KJ2vjaXZfYj1U23t+V8T8n0d7gwnc9XbIdFbyQ==", "dev": true, - "license": "MIT", "dependencies": { "conventional-changelog-angular": "^5.0.12", "conventional-changelog-atom": "^2.0.8", @@ -9835,7 +8938,6 @@ "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.13.tgz", "integrity": "sha512-i/gipMxs7s8L/QeuavPF2hLnJgH6pEZAttySB6aiQLWcX3puWDL3ACVmvBhJGxnAy52Qc15ua26BufY6KpmrVA==", "dev": true, - "license": "ISC", "dependencies": { "compare-func": "^2.0.0", "q": "^1.5.1" @@ -9849,7 +8951,6 @@ "resolved": "https://registry.npmjs.org/conventional-changelog-atom/-/conventional-changelog-atom-2.0.8.tgz", "integrity": "sha512-xo6v46icsFTK3bb7dY/8m2qvc8sZemRgdqLb/bjpBsH2UyOS8rKNTgcb5025Hri6IpANPApbXMg15QLb1LJpBw==", "dev": true, - "license": "ISC", "dependencies": { "q": "^1.5.1" }, @@ -9862,7 +8963,6 @@ "resolved": "https://registry.npmjs.org/conventional-changelog-codemirror/-/conventional-changelog-codemirror-2.0.8.tgz", "integrity": "sha512-z5DAsn3uj1Vfp7po3gpt2Boc+Bdwmw2++ZHa5Ak9k0UKsYAO5mH1UBTN0qSCuJZREIhX6WU4E1p3IW2oRCNzQw==", "dev": true, - "license": "ISC", "dependencies": { "q": "^1.5.1" }, @@ -9875,7 +8975,6 @@ "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-4.6.3.tgz", "integrity": "sha512-LTTQV4fwOM4oLPad317V/QNQ1FY4Hju5qeBIM1uTHbrnCE+Eg4CdRZ3gO2pUeR+tzWdp80M2j3qFFEDWVqOV4g==", "dev": true, - "license": "ISC", "dependencies": { "compare-func": "^2.0.0", "lodash": "^4.17.15", @@ -9890,7 +8989,6 @@ "resolved": "https://registry.npmjs.org/conventional-changelog-core/-/conventional-changelog-core-4.2.4.tgz", "integrity": "sha512-gDVS+zVJHE2v4SLc6B0sLsPiloR0ygU7HaDW14aNJE1v4SlqJPILPl/aJC7YdtRE4CybBf8gDwObBvKha8Xlyg==", "dev": true, - "license": "MIT", "dependencies": { "add-stream": "^1.0.0", "conventional-changelog-writer": "^5.0.0", @@ -9916,7 +9014,6 @@ "resolved": "https://registry.npmjs.org/conventional-changelog-ember/-/conventional-changelog-ember-2.0.9.tgz", "integrity": "sha512-ulzIReoZEvZCBDhcNYfDIsLTHzYHc7awh+eI44ZtV5cx6LVxLlVtEmcO+2/kGIHGtw+qVabJYjdI5cJOQgXh1A==", "dev": true, - "license": "ISC", "dependencies": { "q": "^1.5.1" }, @@ -9929,7 +9026,6 @@ "resolved": "https://registry.npmjs.org/conventional-changelog-eslint/-/conventional-changelog-eslint-3.0.9.tgz", "integrity": "sha512-6NpUCMgU8qmWmyAMSZO5NrRd7rTgErjrm4VASam2u5jrZS0n38V7Y9CzTtLT2qwz5xEChDR4BduoWIr8TfwvXA==", "dev": true, - "license": "ISC", "dependencies": { "q": "^1.5.1" }, @@ -9942,7 +9038,6 @@ "resolved": "https://registry.npmjs.org/conventional-changelog-express/-/conventional-changelog-express-2.0.6.tgz", "integrity": "sha512-SDez2f3iVJw6V563O3pRtNwXtQaSmEfTCaTBPCqn0oG0mfkq0rX4hHBq5P7De2MncoRixrALj3u3oQsNK+Q0pQ==", "dev": true, - "license": "ISC", "dependencies": { "q": "^1.5.1" }, @@ -9955,7 +9050,6 @@ "resolved": "https://registry.npmjs.org/conventional-changelog-jquery/-/conventional-changelog-jquery-3.0.11.tgz", "integrity": "sha512-x8AWz5/Td55F7+o/9LQ6cQIPwrCjfJQ5Zmfqi8thwUEKHstEn4kTIofXub7plf1xvFA2TqhZlq7fy5OmV6BOMw==", "dev": true, - "license": "ISC", "dependencies": { "q": "^1.5.1" }, @@ -9968,7 +9062,6 @@ "resolved": "https://registry.npmjs.org/conventional-changelog-jshint/-/conventional-changelog-jshint-2.0.9.tgz", "integrity": "sha512-wMLdaIzq6TNnMHMy31hql02OEQ8nCQfExw1SE0hYL5KvU+JCTuPaDO+7JiogGT2gJAxiUGATdtYYfh+nT+6riA==", "dev": true, - "license": "ISC", "dependencies": { "compare-func": "^2.0.0", "q": "^1.5.1" @@ -9982,7 +9075,6 @@ "resolved": "https://registry.npmjs.org/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-2.3.4.tgz", "integrity": "sha512-GEKRWkrSAZeTq5+YjUZOYxdHq+ci4dNwHvpaBC3+ENalzFWuCWa9EZXSuZBpkr72sMdKB+1fyDV4takK1Lf58g==", "dev": true, - "license": "MIT", "engines": { "node": ">=10" } @@ -9992,7 +9084,6 @@ "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-5.0.1.tgz", "integrity": "sha512-5WsuKUfxW7suLblAbFnxAcrvf6r+0b7GvNaWUwUIk0bXMnENP/PEieGKVUQrjPqwPT4o3EPAASBXiY6iHooLOQ==", "dev": true, - "license": "MIT", "dependencies": { "conventional-commits-filter": "^2.0.7", "dateformat": "^3.0.0", @@ -10016,7 +9107,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "license": "ISC", "bin": { "semver": "bin/semver.js" } @@ -10026,7 +9116,6 @@ "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-2.0.7.tgz", "integrity": "sha512-ASS9SamOP4TbCClsRHxIHXRfcGCnIoQqkvAzCSbZzTFLfcTqJVugB0agRgsEELsqaeWgsXv513eS116wnlSSPA==", "dev": true, - "license": "MIT", "dependencies": { "lodash.ismatch": "^4.4.0", "modify-values": "^1.0.0" @@ -10040,7 +9129,6 @@ "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-3.2.4.tgz", "integrity": "sha512-nK7sAtfi+QXbxHCYfhpZsfRtaitZLIA6889kFIouLvz6repszQDgxBu7wf2WbU+Dco7sAnNCJYERCwt54WPC2Q==", "dev": true, - "license": "MIT", "dependencies": { "is-text-path": "^1.0.1", "JSONStream": "^1.0.4", @@ -10060,15 +9148,13 @@ "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.6" } @@ -10084,15 +9170,13 @@ "version": "2.1.4", "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/copy-anything": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.6.tgz", "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==", "dev": true, - "license": "MIT", "dependencies": { "is-what": "^3.14.1" }, @@ -10105,7 +9189,6 @@ "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-12.0.2.tgz", "integrity": "sha512-SNwdBeHyII+rWvee/bTnAYyO8vfVdcSTud4EIb6jcZ8inLeWucJE0DnxXQBjlQ5zlteuuvooGQy3LIyGxhvlOA==", "dev": true, - "license": "MIT", "dependencies": { "fast-glob": "^3.3.2", "glob-parent": "^6.0.1", @@ -10125,74 +9208,13 @@ "webpack": "^5.1.0" } }, - "node_modules/copy-webpack-plugin/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/copy-webpack-plugin/node_modules/globby": { - "version": "14.0.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.2.tgz", - "integrity": "sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sindresorhus/merge-streams": "^2.1.0", - "fast-glob": "^3.3.2", - "ignore": "^5.2.4", - "path-type": "^5.0.0", - "slash": "^5.1.0", - "unicorn-magic": "^0.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/copy-webpack-plugin/node_modules/path-type": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", - "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/copy-webpack-plugin/node_modules/slash": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", - "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/core-js-compat": { - "version": "3.38.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.38.1.tgz", - "integrity": "sha512-JRH6gfXxGmrzF3tZ57lFx97YARxCXPaMzPo6jELZhv88pBH5VXpQ+y0znKGlFnzuaihqhLbefxSJxWJMPtfDzw==", + "version": "3.39.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.39.0.tgz", + "integrity": "sha512-VgEUx3VwlExr5no0tXlBt+silBvhTryPwCXRI2Id1PN8WTKu7MreethvddqOubrYxkFdv/RnYrqlv1sFNAUelw==", "dev": true, - "license": "MIT", "dependencies": { - "browserslist": "^4.23.3" + "browserslist": "^4.24.2" }, "funding": { "type": "opencollective", @@ -10200,18 +9222,16 @@ } }, "node_modules/core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", - "dev": true, - "license": "MIT" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true }, "node_modules/cors": { "version": "2.8.5", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", "dev": true, - "license": "MIT", "dependencies": { "object-assign": "^4", "vary": "^1" @@ -10225,7 +9245,6 @@ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", "dev": true, - "license": "MIT", "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", @@ -10238,130 +9257,26 @@ "funding": { "url": "https://github.com/sponsors/d-fischer" }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/critters": { - "version": "0.0.24", - "resolved": "https://registry.npmjs.org/critters/-/critters-0.0.24.tgz", - "integrity": "sha512-Oyqew0FGM0wYUSNqR0L6AteO5MpMoUU0rhKRieXeiKs+PmRTxiJMyaunYB2KF6fQ3dzChXKCpbFOEJx3OQ1v/Q==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "chalk": "^4.1.0", - "css-select": "^5.1.0", - "dom-serializer": "^2.0.0", - "domhandler": "^5.0.2", - "htmlparser2": "^8.0.2", - "postcss": "^8.4.23", - "postcss-media-query-parser": "^0.2.3" - } - }, - "node_modules/critters/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/critters/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/critters/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/critters/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/critters/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/critters/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" + "peerDependencies": { + "typescript": ">=4.9.5" }, - "engines": { - "node": ">=8" + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/cross-fetch": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz", - "integrity": "sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==", - "dev": true, - "license": "MIT", - "dependencies": { - "node-fetch": "^2.6.12" - } + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, - "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -10376,7 +9291,6 @@ "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } @@ -10386,7 +9300,6 @@ "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.2.tgz", "integrity": "sha512-6WvYYn7l/XEGN8Xu2vWFt9nVzrCn39vKyTEFf/ExEyoksJjjSZV/0/35XPlMbpnr6VGhZIUg5yJrL8tGfes/FA==", "dev": true, - "license": "MIT", "dependencies": { "icss-utils": "^5.1.0", "postcss": "^8.4.33", @@ -10418,16 +9331,15 @@ } }, "node_modules/css-select": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", - "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "boolbase": "^1.0.0", - "css-what": "^6.1.0", - "domhandler": "^5.0.2", - "domutils": "^3.0.1", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", "nth-check": "^2.0.1" }, "funding": { @@ -10439,7 +9351,6 @@ "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", "dev": true, - "license": "BSD-2-Clause", "engines": { "node": ">= 6" }, @@ -10452,7 +9363,6 @@ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "dev": true, - "license": "MIT", "bin": { "cssesc": "bin/cssesc" }, @@ -10464,14 +9374,12 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", "integrity": "sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/d3": { "version": "7.9.0", "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", - "license": "ISC", "dependencies": { "d3-array": "3", "d3-axis": "3", @@ -10512,7 +9420,6 @@ "version": "3.2.4", "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", - "license": "ISC", "dependencies": { "internmap": "1 - 2" }, @@ -10524,7 +9431,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", - "license": "ISC", "engines": { "node": ">=12" } @@ -10533,7 +9439,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", - "license": "ISC", "dependencies": { "d3-dispatch": "1 - 3", "d3-drag": "2 - 3", @@ -10549,7 +9454,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", - "license": "ISC", "dependencies": { "d3-path": "1 - 3" }, @@ -10561,7 +9465,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", - "license": "ISC", "engines": { "node": ">=12" } @@ -10570,7 +9473,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", - "license": "ISC", "dependencies": { "d3-array": "^3.2.0" }, @@ -10582,7 +9484,6 @@ "version": "6.0.4", "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", - "license": "ISC", "dependencies": { "delaunator": "5" }, @@ -10594,7 +9495,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", - "license": "ISC", "engines": { "node": ">=12" } @@ -10603,7 +9503,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", - "license": "ISC", "dependencies": { "d3-dispatch": "1 - 3", "d3-selection": "3" @@ -10616,7 +9515,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", - "license": "ISC", "dependencies": { "commander": "7", "iconv-lite": "0.6", @@ -10641,7 +9539,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "license": "MIT", "engines": { "node": ">= 10" } @@ -10650,7 +9547,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", - "license": "BSD-3-Clause", "engines": { "node": ">=12" } @@ -10659,7 +9555,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", - "license": "ISC", "dependencies": { "d3-dsv": "1 - 3" }, @@ -10671,7 +9566,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", - "license": "ISC", "dependencies": { "d3-dispatch": "1 - 3", "d3-quadtree": "1 - 3", @@ -10685,7 +9579,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", - "license": "ISC", "engines": { "node": ">=12" } @@ -10694,7 +9587,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", - "license": "ISC", "dependencies": { "d3-array": "2.5.0 - 3" }, @@ -10706,7 +9598,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", - "license": "ISC", "engines": { "node": ">=12" } @@ -10715,7 +9606,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", - "license": "ISC", "dependencies": { "d3-color": "1 - 3" }, @@ -10727,7 +9617,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", - "license": "ISC", "engines": { "node": ">=12" } @@ -10736,7 +9625,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", - "license": "ISC", "engines": { "node": ">=12" } @@ -10745,7 +9633,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", - "license": "ISC", "engines": { "node": ">=12" } @@ -10754,7 +9641,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", - "license": "ISC", "engines": { "node": ">=12" } @@ -10763,7 +9649,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", - "license": "ISC", "dependencies": { "d3-array": "2.10.0 - 3", "d3-format": "1 - 3", @@ -10779,7 +9664,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", - "license": "ISC", "dependencies": { "d3-color": "1 - 3", "d3-interpolate": "1 - 3" @@ -10792,7 +9676,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", - "license": "ISC", "engines": { "node": ">=12" } @@ -10801,7 +9684,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", - "license": "ISC", "dependencies": { "d3-path": "^3.1.0" }, @@ -10813,7 +9695,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", - "license": "ISC", "dependencies": { "d3-array": "2 - 3" }, @@ -10825,7 +9706,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", - "license": "ISC", "dependencies": { "d3-time": "1 - 3" }, @@ -10837,7 +9717,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", - "license": "ISC", "engines": { "node": ">=12" } @@ -10846,7 +9725,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", - "license": "ISC", "dependencies": { "d3-color": "1 - 3", "d3-dispatch": "1 - 3", @@ -10865,7 +9743,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", - "license": "ISC", "dependencies": { "d3-dispatch": "1 - 3", "d3-drag": "2 - 3", @@ -10882,30 +9759,15 @@ "resolved": "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz", "integrity": "sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", - "dev": true, - "license": "MIT", - "dependencies": { - "assert-plus": "^1.0.0" - }, - "engines": { - "node": ">=0.10" - } - }, "node_modules/data-uri-to-buffer": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", "dev": true, - "license": "MIT", "engines": { "node": ">= 14" } @@ -10915,7 +9777,6 @@ "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", "dev": true, - "license": "MIT", "dependencies": { "call-bind": "^1.0.6", "es-errors": "^1.3.0", @@ -10933,7 +9794,6 @@ "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", "dev": true, - "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "es-errors": "^1.3.0", @@ -10951,7 +9811,6 @@ "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", "dev": true, - "license": "MIT", "dependencies": { "call-bind": "^1.0.6", "es-errors": "^1.3.0", @@ -10965,19 +9824,12 @@ } }, "node_modules/date-fns": { - "version": "2.30.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", - "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.21.0" - }, - "engines": { - "node": ">=0.11" - }, + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", "funding": { - "type": "opencollective", - "url": "https://opencollective.com/date-fns" + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" } }, "node_modules/date-format": { @@ -10985,7 +9837,6 @@ "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz", "integrity": "sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==", "dev": true, - "license": "MIT", "engines": { "node": ">=4.0" } @@ -10995,19 +9846,17 @@ "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==", "dev": true, - "license": "MIT", "engines": { "node": "*" } }, "node_modules/debug": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", - "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "dev": true, - "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -11023,7 +9872,6 @@ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -11033,7 +9881,6 @@ "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", "dev": true, - "license": "MIT", "dependencies": { "decamelize": "^1.1.0", "map-obj": "^1.0.0" @@ -11050,7 +9897,6 @@ "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -11060,7 +9906,6 @@ "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", "dev": true, - "license": "MIT", "dependencies": { "mimic-response": "^3.1.0" }, @@ -11076,7 +9921,6 @@ "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "dev": true, - "license": "MIT", "engines": { "node": ">=4.0.0" } @@ -11085,8 +9929,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/default-browser": { "version": "5.2.1", @@ -11118,78 +9961,10 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/default-gateway": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", - "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "execa": "^5.0.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/default-gateway/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/default-gateway/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-gateway/node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/default-gateway/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" - }, "node_modules/defaults": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", - "license": "MIT", "dependencies": { "clone": "^1.0.2" }, @@ -11202,7 +9977,6 @@ "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "dev": true, - "license": "MIT", "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -11233,7 +10007,6 @@ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", "dev": true, - "license": "MIT", "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", @@ -11251,7 +10024,6 @@ "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", "dev": true, - "license": "MIT", "dependencies": { "ast-types": "^0.13.4", "escodegen": "^2.1.0", @@ -11266,7 +10038,6 @@ "resolved": "https://registry.npmjs.org/del/-/del-6.1.1.tgz", "integrity": "sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg==", "dev": true, - "license": "MIT", "dependencies": { "globby": "^11.0.1", "graceful-fs": "^4.2.4", @@ -11284,13 +10055,47 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/del/node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/del/node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/del/node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, - "license": "ISC", "dependencies": { "glob": "^7.1.3" }, @@ -11301,11 +10106,19 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/del/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/delaunator": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", - "license": "ISC", "dependencies": { "robust-predicates": "^3.0.2" } @@ -11315,7 +10128,6 @@ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.4.0" } @@ -11325,40 +10137,31 @@ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.8" } }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/destroy": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.8", "npm": "1.2.8000 || >= 1.4.16" } }, "node_modules/detect-libc": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", - "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", "dev": true, - "license": "Apache-2.0", + "optional": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, "engines": { - "node": ">=8" + "node": ">=0.10" } }, "node_modules/detect-node": { @@ -11373,7 +10176,6 @@ "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", "dev": true, - "license": "ISC", "dependencies": { "asap": "^2.0.0", "wrappy": "1" @@ -11383,15 +10185,13 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", "integrity": "sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true, - "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" } @@ -11401,7 +10201,6 @@ "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", "dev": true, - "license": "MIT", "dependencies": { "path-type": "^4.0.0" }, @@ -11409,6 +10208,15 @@ "node": ">=8" } }, + "node_modules/dir-glob/node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/dns-packet": { "version": "5.6.1", "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", @@ -11423,16 +10231,15 @@ } }, "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, - "license": "Apache-2.0", "dependencies": { "esutils": "^2.0.2" }, "engines": { - "node": ">=6.0.0" + "node": ">=0.10.0" } }, "node_modules/dom-serialize": { @@ -11440,7 +10247,6 @@ "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", "integrity": "sha512-Yra4DbvoW7/Z6LBN560ZwXMjoNOSAN2wRsKFGc4iBeso+mpIA6qj1vfdf9HpMaKAqG6wXTy+1SYEzmNpKXOSsQ==", "dev": true, - "license": "MIT", "dependencies": { "custom-event": "~1.0.0", "ent": "~2.2.0", @@ -11449,15 +10255,14 @@ } }, "node_modules/dom-serializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", "dev": true, - "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" }, "funding": { "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" @@ -11473,17 +10278,15 @@ "type": "github", "url": "https://github.com/sponsors/fb55" } - ], - "license": "BSD-2-Clause" + ] }, "node_modules/domhandler": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { - "domelementtype": "^2.3.0" + "domelementtype": "^2.2.0" }, "engines": { "node": ">= 4" @@ -11493,15 +10296,14 @@ } }, "node_modules/domutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", - "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3" + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" }, "funding": { "url": "https://github.com/fb55/domutils?sponsor=1" @@ -11512,7 +10314,6 @@ "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", "dev": true, - "license": "MIT", "dependencies": { "is-obj": "^2.0.0" }, @@ -11520,12 +10321,25 @@ "node": ">=8" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/duplexer2": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", "dev": true, - "license": "BSD-3-Clause", "dependencies": { "readable-stream": "^2.0.2" } @@ -11534,15 +10348,13 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/duplexer2/node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dev": true, - "license": "MIT", "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -11557,15 +10369,13 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/duplexer2/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==", "dev": true, - "license": "MIT", "dependencies": { "safe-buffer": "~5.1.0" } @@ -11574,47 +10384,25 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, - "license": "MIT" - }, - "node_modules/ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", - "dev": true, - "license": "MIT", - "dependencies": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "node_modules/ecc-jsbn/node_modules/jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.5.13", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.13.tgz", - "integrity": "sha512-lbBcvtIJ4J6sS4tb5TLp1b4LyfCdMkwStzXPyAgVgTRAsep4bvrAGaBOP7ZJtQMNJpSQ9SqG4brWOroNaQtm7Q==", - "dev": true, - "license": "ISC" + "version": "1.5.74", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.74.tgz", + "integrity": "sha512-ck3//9RC+6oss/1Bh9tiAVFy5vfSKbRHAFh7Z3/eTRkEqJeWgymloShB17Vg3Z4nmDNp35vAd1BZ6CMW4Wt6Iw==", + "dev": true }, "node_modules/elementtree": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/elementtree/-/elementtree-0.1.7.tgz", "integrity": "sha512-wkgGT6kugeQk/P6VZ/f4T+4HB41BVgNBq5CDIZVbQ02nvTVqAiVTbskxxu3eA/X96lMlfYOwnLQpN2v5E1zDEg==", "dev": true, - "license": "Apache-2.0", "dependencies": { "sax": "1.1.4" }, @@ -11626,15 +10414,13 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/emojis-list": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", "dev": true, - "license": "MIT", "engines": { "node": ">= 4" } @@ -11644,7 +10430,6 @@ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.8" } @@ -11654,7 +10439,6 @@ "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", "dev": true, - "license": "MIT", "optional": true, "dependencies": { "iconv-lite": "^0.6.2" @@ -11665,24 +10449,22 @@ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", "dev": true, - "license": "MIT", "dependencies": { "once": "^1.4.0" } }, "node_modules/engine.io": { - "version": "6.5.5", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.5.tgz", - "integrity": "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==", + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.2.tgz", + "integrity": "sha512-gmNvsYi9C8iErnZdVcJnvCpSKbWTt1E8+JZo8b+daLninywUWi5NQ5STSHZ9rFjFO7imNcvb8Pc5pe/wMR5xEw==", "dev": true, - "license": "MIT", "dependencies": { "@types/cookie": "^0.4.1", "@types/cors": "^2.8.12", "@types/node": ">=10.0.0", "accepts": "~1.3.4", "base64id": "2.0.0", - "cookie": "~0.4.1", + "cookie": "~0.7.2", "cors": "~2.8.5", "debug": "~4.3.1", "engine.io-parser": "~5.2.1", @@ -11697,17 +10479,32 @@ "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", "dev": true, - "license": "MIT", "engines": { "node": ">=10.0.0" } }, + "node_modules/engine.io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/enhanced-resolve": { "version": "5.17.1", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", "dev": true, - "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -11717,27 +10514,25 @@ } }, "node_modules/ent": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.1.tgz", - "integrity": "sha512-QHuXVeZx9d+tIQAz/XztU0ZwZf2Agg9CcXcgE1rurqvdBeDBrpSwjl8/6XUqMg7tw2Y7uAdKb2sRv+bSEFqQ5A==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.2.tgz", + "integrity": "sha512-kKvD1tO6BM+oK9HzCPpUdRb4vKFQY/FPTFmurMvh6LlN68VMrdj77w8yp51/kDbpkFOS9J8w5W6zIzgM2H8/hw==", "dev": true, - "license": "MIT", "dependencies": { - "punycode": "^1.4.1" + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "punycode": "^1.4.1", + "safe-regex-test": "^1.1.0" }, "engines": { "node": ">= 0.4" } }, "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "devOptional": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, "funding": { "url": "https://github.com/fb55/entities?sponsor=1" } @@ -11747,7 +10542,6 @@ "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", "dev": true, - "license": "MIT", "engines": { "node": ">=6" } @@ -11757,7 +10551,6 @@ "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", "dev": true, - "license": "MIT", "engines": { "node": ">=18" }, @@ -11769,15 +10562,13 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/errno": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", "dev": true, - "license": "MIT", "optional": true, "dependencies": { "prr": "~1.0.1" @@ -11791,64 +10582,64 @@ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", "dev": true, - "license": "MIT", "dependencies": { "is-arrayish": "^0.2.1" } }, "node_modules/es-abstract": { - "version": "1.23.3", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", - "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", + "version": "1.23.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.6.tgz", + "integrity": "sha512-Ifco6n3yj2tMZDWNLyloZrytt9lqqlwvS83P3HtaETR0NUOYnIULGGHpktqYGObGy+8wc1okO25p8TjemhImvA==", "dev": true, - "license": "MIT", "dependencies": { "array-buffer-byte-length": "^1.0.1", - "arraybuffer.prototype.slice": "^1.0.3", + "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", "data-view-buffer": "^1.0.1", "data-view-byte-length": "^1.0.1", "data-view-byte-offset": "^1.0.0", - "es-define-property": "^1.0.0", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-set-tostringtag": "^2.0.3", - "es-to-primitive": "^1.2.1", - "function.prototype.name": "^1.1.6", - "get-intrinsic": "^1.2.4", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.7", + "get-intrinsic": "^1.2.6", "get-symbol-description": "^1.0.2", - "globalthis": "^1.0.3", - "gopd": "^1.0.1", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", - "has-proto": "^1.0.3", - "has-symbols": "^1.0.3", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", "hasown": "^2.0.2", - "internal-slot": "^1.0.7", + "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.4", "is-callable": "^1.2.7", - "is-data-view": "^1.0.1", + "is-data-view": "^1.0.2", "is-negative-zero": "^2.0.3", - "is-regex": "^1.1.4", + "is-regex": "^1.2.1", "is-shared-array-buffer": "^1.0.3", - "is-string": "^1.0.7", + "is-string": "^1.1.1", "is-typed-array": "^1.1.13", - "is-weakref": "^1.0.2", - "object-inspect": "^1.13.1", + "is-weakref": "^1.1.0", + "math-intrinsics": "^1.0.0", + "object-inspect": "^1.13.3", "object-keys": "^1.1.1", "object.assign": "^4.1.5", - "regexp.prototype.flags": "^1.5.2", - "safe-array-concat": "^1.1.2", - "safe-regex-test": "^1.0.3", - "string.prototype.trim": "^1.2.9", - "string.prototype.trimend": "^1.0.8", + "regexp.prototype.flags": "^1.5.3", + "safe-array-concat": "^1.1.3", + "safe-regex-test": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.2", "typed-array-byte-length": "^1.0.1", - "typed-array-byte-offset": "^1.0.2", - "typed-array-length": "^1.0.6", + "typed-array-byte-offset": "^1.0.3", + "typed-array-length": "^1.0.7", "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.15" + "which-typed-array": "^1.1.16" }, "engines": { "node": ">= 0.4" @@ -11858,14 +10649,10 @@ } }, "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==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "dev": true, - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.4" - }, "engines": { "node": ">= 0.4" } @@ -11875,7 +10662,6 @@ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.4" } @@ -11884,15 +10670,13 @@ "version": "1.5.4", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/es-object-atoms": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", "dev": true, - "license": "MIT", "dependencies": { "es-errors": "^1.3.0" }, @@ -11905,7 +10689,6 @@ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", "dev": true, - "license": "MIT", "dependencies": { "get-intrinsic": "^1.2.4", "has-tostringtag": "^1.0.2", @@ -11920,21 +10703,19 @@ "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", "dev": true, - "license": "MIT", "dependencies": { "hasown": "^2.0.0" } }, "node_modules/es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", "dev": true, - "license": "MIT", "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" }, "engines": { "node": ">= 0.4" @@ -11943,27 +10724,10 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", - "dev": true, - "license": "MIT" - }, - "node_modules/es6-promisify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", - "integrity": "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "es6-promise": "^4.0.3" - } - }, "node_modules/esbuild": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.0.tgz", - "integrity": "sha512-1lvV17H2bMYda/WaFb2jLPeHU3zml2k4/yagNMG8Q/YtfMjCwEUZa2eXXMgZTVSL5q1n4H7sQ0X6CdJDqqeCFA==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.0.tgz", + "integrity": "sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -11974,38 +10738,37 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.23.0", - "@esbuild/android-arm": "0.23.0", - "@esbuild/android-arm64": "0.23.0", - "@esbuild/android-x64": "0.23.0", - "@esbuild/darwin-arm64": "0.23.0", - "@esbuild/darwin-x64": "0.23.0", - "@esbuild/freebsd-arm64": "0.23.0", - "@esbuild/freebsd-x64": "0.23.0", - "@esbuild/linux-arm": "0.23.0", - "@esbuild/linux-arm64": "0.23.0", - "@esbuild/linux-ia32": "0.23.0", - "@esbuild/linux-loong64": "0.23.0", - "@esbuild/linux-mips64el": "0.23.0", - "@esbuild/linux-ppc64": "0.23.0", - "@esbuild/linux-riscv64": "0.23.0", - "@esbuild/linux-s390x": "0.23.0", - "@esbuild/linux-x64": "0.23.0", - "@esbuild/netbsd-x64": "0.23.0", - "@esbuild/openbsd-arm64": "0.23.0", - "@esbuild/openbsd-x64": "0.23.0", - "@esbuild/sunos-x64": "0.23.0", - "@esbuild/win32-arm64": "0.23.0", - "@esbuild/win32-ia32": "0.23.0", - "@esbuild/win32-x64": "0.23.0" + "@esbuild/aix-ppc64": "0.24.0", + "@esbuild/android-arm": "0.24.0", + "@esbuild/android-arm64": "0.24.0", + "@esbuild/android-x64": "0.24.0", + "@esbuild/darwin-arm64": "0.24.0", + "@esbuild/darwin-x64": "0.24.0", + "@esbuild/freebsd-arm64": "0.24.0", + "@esbuild/freebsd-x64": "0.24.0", + "@esbuild/linux-arm": "0.24.0", + "@esbuild/linux-arm64": "0.24.0", + "@esbuild/linux-ia32": "0.24.0", + "@esbuild/linux-loong64": "0.24.0", + "@esbuild/linux-mips64el": "0.24.0", + "@esbuild/linux-ppc64": "0.24.0", + "@esbuild/linux-riscv64": "0.24.0", + "@esbuild/linux-s390x": "0.24.0", + "@esbuild/linux-x64": "0.24.0", + "@esbuild/netbsd-x64": "0.24.0", + "@esbuild/openbsd-arm64": "0.24.0", + "@esbuild/openbsd-x64": "0.24.0", + "@esbuild/sunos-x64": "0.24.0", + "@esbuild/win32-arm64": "0.24.0", + "@esbuild/win32-ia32": "0.24.0", + "@esbuild/win32-x64": "0.24.0" } }, "node_modules/esbuild-wasm": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.23.0.tgz", - "integrity": "sha512-6jP8UmWy6R6TUUV8bMuC3ZyZ6lZKI56x0tkxyCIqWwRRJ/DgeQKneh/Oid5EoGoPFLrGNkz47ZEtWAYuiY/u9g==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.24.0.tgz", + "integrity": "sha512-xhNn5tL1AhkPg4ft59yXT6FkwKXiPSYyz1IeinJHUJpjvOHOIPvdmFQc0pGdjxlKSbzZc2mNmtVOWAR1EF/JAg==", "dev": true, - "license": "MIT", "bin": { "esbuild": "bin/esbuild" }, @@ -12014,11 +10777,10 @@ } }, "node_modules/escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, - "license": "MIT", "engines": { "node": ">=6" } @@ -12027,17 +10789,18 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, - "license": "MIT", "engines": { - "node": ">=0.8.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/escodegen": { @@ -12045,7 +10808,6 @@ "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "esprima": "^4.0.1", "estraverse": "^5.2.0", @@ -12067,66 +10829,68 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, - "license": "BSD-3-Clause", "optional": true, "engines": { "node": ">=0.10.0" } }, "node_modules/eslint": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", - "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "version": "9.17.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.17.0.tgz", + "integrity": "sha512-evtlNcpJg+cZLcnVKwsai8fExnqjGPicK7gnUtlNuzu+Fv9bI0aLpND5T44VLQtoMEnI57LoXO9XAkIXwohKrA==", "dev": true, - "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.0", - "@humanwhocodes/config-array": "^0.11.14", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.19.0", + "@eslint/core": "^0.9.0", + "@eslint/eslintrc": "^3.2.0", + "@eslint/js": "9.17.0", + "@eslint/plugin-kit": "^0.2.3", + "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", + "@humanwhocodes/retry": "^0.4.1", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", + "cross-spawn": "^7.0.6", "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.2.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.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", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" + "optionator": "^0.9.3" }, "bin": { "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" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } } }, "node_modules/eslint-import-resolver-node": { @@ -12134,7 +10898,6 @@ "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", "dev": true, - "license": "MIT", "dependencies": { "debug": "^3.2.7", "is-core-module": "^2.13.0", @@ -12146,17 +10909,50 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, - "license": "MIT", "dependencies": { "ms": "^2.1.1" } }, + "node_modules/eslint-import-resolver-typescript": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.7.0.tgz", + "integrity": "sha512-Vrwyi8HHxY97K5ebydMtffsWAn1SCR9eol49eCd5fJS4O1WV7PaAjbcjmbfJJSMz/t4Mal212Uz/fQZrOB8mow==", + "dev": true, + "dependencies": { + "@nolyfill/is-core-module": "1.0.39", + "debug": "^4.3.7", + "enhanced-resolve": "^5.15.0", + "fast-glob": "^3.3.2", + "get-tsconfig": "^4.7.5", + "is-bun-module": "^1.0.2", + "is-glob": "^4.0.3", + "stable-hash": "^0.0.4" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts/projects/eslint-import-resolver-ts" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*", + "eslint-plugin-import-x": "*" + }, + "peerDependenciesMeta": { + "eslint-plugin-import": { + "optional": true + }, + "eslint-plugin-import-x": { + "optional": true + } + } + }, "node_modules/eslint-module-utils": { - "version": "2.11.1", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.11.1.tgz", - "integrity": "sha512-EwcbfLOhwVMAfatfqLecR2yv3dE5+kQ8kx+Rrt0DvDXEVwW86KQ/xbMDQhtp5l42VXukD5SOF8mQQHbaNtO0CQ==", + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz", + "integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==", "dev": true, - "license": "MIT", "dependencies": { "debug": "^3.2.7" }, @@ -12174,17 +10970,35 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, - "license": "MIT", "dependencies": { "ms": "^2.1.1" } }, + "node_modules/eslint-plugin-check-file": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-check-file/-/eslint-plugin-check-file-2.8.0.tgz", + "integrity": "sha512-FvvafMTam2WJYH9uj+FuMxQ1y+7jY3Z6P9T4j2214cH0FBxNzTcmeCiGTj1Lxp3mI6kbbgsXvmgewvf+llKYyw==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3", + "micromatch": "^4.0.5" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "ko_fi", + "url": "https://ko-fi.com/huanluo" + }, + "peerDependencies": { + "eslint": ">=7.28.0" + } + }, "node_modules/eslint-plugin-import": { - "version": "2.30.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.30.0.tgz", - "integrity": "sha512-/mHNE9jINJfiD2EKkg1BKyPyUk4zdnT54YgbOgfjSakWT5oyX/qQLVNTkehyfpcMxZXMy1zyonZ2v7hZTX43Yw==", + "version": "2.31.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz", + "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", "dev": true, - "license": "MIT", "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.8", @@ -12194,7 +11008,7 @@ "debug": "^3.2.7", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.9", - "eslint-module-utils": "^2.9.0", + "eslint-module-utils": "^2.12.0", "hasown": "^2.0.2", "is-core-module": "^2.15.1", "is-glob": "^4.0.3", @@ -12203,13 +11017,14 @@ "object.groupby": "^1.0.3", "object.values": "^1.2.0", "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.8", "tsconfig-paths": "^3.15.0" }, "engines": { "node": ">=4" }, "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" } }, "node_modules/eslint-plugin-import/node_modules/brace-expansion": { @@ -12217,7 +11032,6 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, - "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -12228,30 +11042,15 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, - "license": "MIT", "dependencies": { "ms": "^2.1.1" } }, - "node_modules/eslint-plugin-import/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/eslint-plugin-import/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -12264,19 +11063,17 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "license": "ISC", "bin": { "semver": "bin/semver.js" } }, "node_modules/eslint-plugin-jsdoc": { - "version": "50.2.4", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-50.2.4.tgz", - "integrity": "sha512-020jA+dXaXdb+TML3ZJBvpPmzwbNROjnYuTYi/g6A5QEmEjhptz4oPJDKkOGMIByNxsPpdTLzSU1HYVqebOX1w==", + "version": "50.6.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-50.6.1.tgz", + "integrity": "sha512-UWyaYi6iURdSfdVVqvfOs2vdCVz0J40O/z/HTsv2sFjdjmdlUI/qlKLOTmwbPQ2tAfQnE5F9vqx+B+poF71DBQ==", "dev": true, - "license": "BSD-3-Clause", "dependencies": { - "@es-joy/jsdoccomment": "~0.48.0", + "@es-joy/jsdoccomment": "~0.49.0", "are-docs-informative": "^0.0.2", "comment-parser": "1.4.1", "debug": "^4.3.6", @@ -12289,23 +11086,10 @@ "synckit": "^0.9.1" }, "engines": { - "node": ">=18" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" - } - }, - "node_modules/eslint-plugin-jsdoc/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" + "node": ">=18" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" } }, "node_modules/eslint-plugin-prefer-arrow": { @@ -12313,7 +11097,6 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-prefer-arrow/-/eslint-plugin-prefer-arrow-1.2.3.tgz", "integrity": "sha512-J9I5PKCOJretVuiZRGvPQxCbllxGAV/viI20JO3LYblAodofBxyMnZAJ+WGeClHgANnSJberTNoFWWjrWKBuXQ==", "dev": true, - "license": "MIT", "peerDependencies": { "eslint": ">=2.0.0" } @@ -12334,11 +11117,10 @@ } }, "node_modules/eslint-scope": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.2.tgz", - "integrity": "sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", + "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -12351,11 +11133,10 @@ } }, "node_modules/eslint-visitor-keys": { - "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==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", "dev": true, - "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -12368,7 +11149,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, - "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -12380,183 +11160,27 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/eslint/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/eslint/node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, - "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, - "node_modules/eslint/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/eslint/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/eslint/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/eslint/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/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, - "license": "BSD-2-Clause", - "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/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==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/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==", - "dev": true, - "license": "BSD-2-Clause", - "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/eslint/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/eslint/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, - "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/eslint/node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/eslint/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -12564,42 +11188,15 @@ "node": "*" } }, - "node_modules/eslint/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/espree": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz", - "integrity": "sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.12.0", + "acorn": "^8.14.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.0.0" + "eslint-visitor-keys": "^4.2.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -12613,7 +11210,6 @@ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true, - "license": "BSD-2-Clause", "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" @@ -12627,7 +11223,6 @@ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", "dev": true, - "license": "BSD-3-Clause", "dependencies": { "estraverse": "^5.1.0" }, @@ -12640,7 +11235,6 @@ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" }, @@ -12653,7 +11247,6 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, - "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } @@ -12663,7 +11256,6 @@ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, - "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" } @@ -12682,15 +11274,13 @@ "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.8.x" } @@ -12700,7 +11290,6 @@ "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", "dev": true, - "license": "MIT", "dependencies": { "cross-spawn": "^7.0.0", "get-stream": "^5.0.0", @@ -12723,24 +11312,13 @@ "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } + "dev": true }, "node_modules/expand-template": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", "dev": true, - "license": "(MIT OR WTFPL)", "engines": { "node": ">=6" } @@ -12749,13 +11327,12 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.1.tgz", "integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==", - "dev": true, - "license": "Apache-2.0" + "dev": true }, "node_modules/express": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", - "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "dev": true, "license": "MIT", "dependencies": { @@ -12764,7 +11341,7 @@ "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.6.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -12778,7 +11355,7 @@ "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.10", + "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", @@ -12793,12 +11370,16 @@ }, "engines": { "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/express/node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", "dev": true, "license": "MIT", "engines": { @@ -12865,15 +11446,13 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/external-editor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", "dev": true, - "license": "MIT", "dependencies": { "chardet": "^0.7.0", "iconv-lite": "^0.4.24", @@ -12888,7 +11467,6 @@ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "dev": true, - "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3" }, @@ -12901,7 +11479,6 @@ "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", "dev": true, - "license": "MIT", "dependencies": { "os-tmpdir": "~1.0.2" }, @@ -12909,35 +11486,22 @@ "node": ">=0.6.0" } }, - "node_modules/extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", - "dev": true, - "engines": [ - "node >=0.6.0" - ], - "license": "MIT" - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "license": "MIT" + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "node_modules/fast-fifo": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/fast-glob": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "dev": true, - "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -12949,30 +11513,39 @@ "node": ">=8.6.0" } }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "license": "MIT" + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/fast-safe-stringify": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/fast-uri": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.1.tgz", - "integrity": "sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.3.tgz", + "integrity": "sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw==", "dev": true }, "node_modules/fastq": { @@ -12980,7 +11553,6 @@ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", "dev": true, - "license": "ISC", "dependencies": { "reusify": "^1.0.4" } @@ -13003,7 +11575,6 @@ "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", "dev": true, - "license": "MIT", "dependencies": { "pend": "~1.2.0" } @@ -13013,7 +11584,6 @@ "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", "dev": true, - "license": "MIT", "dependencies": { "escape-string-regexp": "^1.0.5" }, @@ -13024,31 +11594,37 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/figures/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, "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, - "license": "MIT", "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/file-saver-es": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/file-saver-es/-/file-saver-es-2.0.5.tgz", - "integrity": "sha512-Kg0lt+is9nOyi/VDms9miScNGot25jVFbjFccXuCL/shd2Q+rt70MALxHVkXllsX83JEBLiHQNjDPGd/6FIOoQ==", - "license": "MIT" + "integrity": "sha512-Kg0lt+is9nOyi/VDms9miScNGot25jVFbjFccXuCL/shd2Q+rt70MALxHVkXllsX83JEBLiHQNjDPGd/6FIOoQ==" }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "devOptional": true, - "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -13061,7 +11637,6 @@ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", "dev": true, - "license": "MIT", "dependencies": { "debug": "2.6.9", "encodeurl": "~1.0.2", @@ -13080,7 +11655,6 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, - "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -13089,15 +11663,13 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/finalhandler/node_modules/on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", "dev": true, - "license": "MIT", "dependencies": { "ee-first": "1.1.1" }, @@ -13110,7 +11682,6 @@ "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-4.0.0.tgz", "integrity": "sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==", "dev": true, - "license": "MIT", "dependencies": { "common-path-prefix": "^3.0.0", "pkg-dir": "^7.0.0" @@ -13127,7 +11698,6 @@ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, - "license": "MIT", "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -13144,54 +11714,33 @@ "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", "dev": true, - "license": "BSD-3-Clause", "bin": { "flat": "cli.js" } }, "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, - "license": "MIT", "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_modules/flat-cache/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=16" } }, "node_modules/flatted": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", - "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", - "dev": true, - "license": "ISC" + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz", + "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==", + "dev": true }, "node_modules/follow-redirects": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", "dev": true, "funding": [ { @@ -13199,7 +11748,6 @@ "url": "https://github.com/sponsors/RubenVerborgh" } ], - "license": "MIT", "engines": { "node": ">=4.0" }, @@ -13214,7 +11762,6 @@ "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", "dev": true, - "license": "MIT", "dependencies": { "is-callable": "^1.1.3" } @@ -13224,7 +11771,6 @@ "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", "dev": true, - "license": "ISC", "dependencies": { "cross-spawn": "^7.0.0", "signal-exit": "^4.0.1" @@ -13236,22 +11782,11 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "*" - } - }, "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", "dev": true, - "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -13262,14 +11797,13 @@ } }, "node_modules/formidable": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.1.tgz", - "integrity": "sha512-WJWKelbRHN41m5dumb0/k8TeAx7Id/y3a+Z7QfhxP/htI9Js5zYaEDtG8uMgG0vM0lOlqnmjE99/kfpOYi/0Og==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.2.tgz", + "integrity": "sha512-Jqc1btCy3QzRbJaICGwKcBfGWuLADRerLzDqi2NwSt/UkXLsHJw2TVResiaoBufHVHy9aSgClOHCeJsSsFLTbg==", "dev": true, - "license": "MIT", "dependencies": { "dezalgo": "^1.0.4", - "hexoid": "^1.0.0", + "hexoid": "^2.0.0", "once": "^1.4.0" }, "funding": { @@ -13291,7 +11825,6 @@ "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", "dev": true, - "license": "MIT", "engines": { "node": "*" }, @@ -13314,15 +11847,13 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/fs-extra": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "dev": true, - "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -13337,7 +11868,6 @@ "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", "dev": true, - "license": "ISC", "dependencies": { "minipass": "^7.0.3" }, @@ -13349,16 +11879,13 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, - "license": "MIT", "optional": true, "os": [ "darwin" @@ -13372,22 +11899,21 @@ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "dev": true, - "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/function.prototype.name": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", - "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.7.tgz", + "integrity": "sha512-2g4x+HqTJKM9zcJqBSpjoRmdcPFtJM60J3xJisTQSXBWka5XqyBN/2tNUgma1mztTXyDuUsEtYe5qcs7xYzYQA==", "dev": true, - "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "functions-have-names": "^1.2.3" + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" }, "engines": { "node": ">= 0.4" @@ -13401,7 +11927,6 @@ "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", "dev": true, - "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -13411,7 +11936,6 @@ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true, - "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -13421,17 +11945,15 @@ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, - "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" } }, "node_modules/get-east-asian-width": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.2.0.tgz", - "integrity": "sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", + "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=18" }, @@ -13440,17 +11962,21 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz", + "integrity": "sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==", "dev": true, - "license": "MIT", "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "dunder-proto": "^1.0.0", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -13464,7 +11990,6 @@ "resolved": "https://registry.npmjs.org/get-pkg-repo/-/get-pkg-repo-4.2.1.tgz", "integrity": "sha512-2+QbHjFRfGB74v/pYWjd5OhU3TDIC2Gv/YKUTk/tCvAz0pkn/Mz6P3uByuBimLOcPvN2jYdScl3xGFSrx0jEcA==", "dev": true, - "license": "MIT", "dependencies": { "@hutson/parse-repository-url": "^3.0.0", "hosted-git-info": "^4.0.0", @@ -13478,67 +12003,28 @@ "node": ">=6.9.0" } }, - "node_modules/get-pkg-repo/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/get-pkg-repo/node_modules/cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dev": true, - "license": "ISC", "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^7.0.0" } }, - "node_modules/get-pkg-repo/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/get-pkg-repo/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, "node_modules/get-pkg-repo/node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/get-pkg-repo/node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dev": true, - "license": "MIT", "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -13553,15 +12039,13 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/get-pkg-repo/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==", "dev": true, - "license": "MIT", "dependencies": { "safe-buffer": "~5.1.0" } @@ -13571,7 +12055,6 @@ "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", "dev": true, - "license": "MIT", "dependencies": { "readable-stream": "~2.3.6", "xtend": "~4.0.1" @@ -13582,7 +12065,6 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, - "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -13600,7 +12082,6 @@ "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "dev": true, - "license": "MIT", "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", @@ -13619,7 +12100,6 @@ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", "dev": true, - "license": "MIT", "dependencies": { "pump": "^3.0.0" }, @@ -13635,7 +12115,6 @@ "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", "dev": true, - "license": "MIT", "dependencies": { "call-bind": "^1.0.5", "es-errors": "^1.3.0", @@ -13648,45 +12127,30 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-uri": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.3.tgz", - "integrity": "sha512-BzUrJBS9EcUb4cFol8r4W3v1cPsSyajLSthNkz5BxbpDcHN5tIrM10E2eNvfnvBn3DaT3DUgx0OpsBKkaOpanw==", + "node_modules/get-tsconfig": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.1.tgz", + "integrity": "sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==", "dev": true, - "license": "MIT", "dependencies": { - "basic-ftp": "^5.0.2", - "data-uri-to-buffer": "^6.0.2", - "debug": "^4.3.4", - "fs-extra": "^11.2.0" + "resolve-pkg-maps": "^1.0.0" }, - "engines": { - "node": ">= 14" + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, - "node_modules/get-uri/node_modules/fs-extra": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", - "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "node_modules/get-uri": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.4.tgz", + "integrity": "sha512-E1b1lFFLvLgak2whF2xDBcOy6NLVGZBqqjJjsIhvopKfWWEi64pLVTWWehV8KlLerZkfNTA95sTe2OdJKm1OzQ==", "dev": true, - "license": "MIT", "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4" }, "engines": { - "node": ">=14.14" - } - }, - "node_modules/getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", - "dev": true, - "license": "MIT", - "dependencies": { - "assert-plus": "^1.0.0" + "node": ">= 14" } }, "node_modules/git-raw-commits": { @@ -13694,7 +12158,6 @@ "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-2.0.11.tgz", "integrity": "sha512-VnctFhw+xfj8Va1xtfEqCUD2XDrbAPSJx+hSrE5K7fGdjZruW7XV+QOrN7LF/RJyvspRiD2I0asWsxFp0ya26A==", "dev": true, - "license": "MIT", "dependencies": { "dargs": "^7.0.0", "lodash": "^4.17.15", @@ -13714,7 +12177,6 @@ "resolved": "https://registry.npmjs.org/git-remote-origin-url/-/git-remote-origin-url-2.0.0.tgz", "integrity": "sha512-eU+GGrZgccNJcsDH5LkXR3PB9M958hxc7sbA8DFJjrv9j4L2P/eZfKhM+QD6wyzpiv+b1BpK0XrYCxkovtjSLw==", "dev": true, - "license": "MIT", "dependencies": { "gitconfiglocal": "^1.0.0", "pify": "^2.3.0" @@ -13728,7 +12190,6 @@ "resolved": "https://registry.npmjs.org/git-semver-tags/-/git-semver-tags-4.1.1.tgz", "integrity": "sha512-OWyMt5zBe7xFs8vglMmhM9lRQzCWL3WjHtxNNfJTMngGym7pC1kh8sP6jevfydJ6LP3ZvGxfb6ABYgPUM0mtsA==", "dev": true, - "license": "MIT", "dependencies": { "meow": "^8.0.0", "semver": "^6.0.0" @@ -13745,7 +12206,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "license": "ISC", "bin": { "semver": "bin/semver.js" } @@ -13755,7 +12215,6 @@ "resolved": "https://registry.npmjs.org/gitconfiglocal/-/gitconfiglocal-1.0.0.tgz", "integrity": "sha512-spLUXeTAVHxDtKsJc8FkFVgFtMdEN9qPGpL23VfSHx4fP4+Ds097IXLvymbnDH8FnmxX5Nr9bPw3A+AQ6mWEaQ==", "dev": true, - "license": "BSD", "dependencies": { "ini": "^1.3.2" } @@ -13764,15 +12223,13 @@ "version": "1.3.8", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/glob": { "version": "7.2.3", @@ -13780,7 +12237,6 @@ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, - "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -13797,31 +12253,28 @@ } }, "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "devOptional": true, - "license": "ISC", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, "dependencies": { - "is-glob": "^4.0.1" + "is-glob": "^4.0.3" }, "engines": { - "node": ">= 6" + "node": ">=10.13.0" } }, "node_modules/glob-to-regexp": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true, - "license": "BSD-2-Clause" + "dev": true }, "node_modules/glob/node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, - "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -13832,7 +12285,6 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -13845,7 +12297,6 @@ "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true, - "license": "MIT", "engines": { "node": ">=4" } @@ -13855,7 +12306,6 @@ "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", "dev": true, - "license": "MIT", "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" @@ -13868,34 +12318,32 @@ } }, "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.2.tgz", + "integrity": "sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==", "dev": true, - "license": "MIT", "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" + "@sindresorhus/merge-streams": "^2.1.0", + "fast-glob": "^3.3.2", + "ignore": "^5.2.4", + "path-type": "^5.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.1.0" }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "dev": true, - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.1.3" + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -13905,15 +12353,13 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/gradle-to-js": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/gradle-to-js/-/gradle-to-js-2.0.1.tgz", "integrity": "sha512-is3hDn9zb8XXnjbEeAEIqxTpLHUiGBqjegLmXPuyMBfKAggpadWFku4/AP8iYAGBX6qR9/5UIUIp47V0XI3aMw==", "dev": true, - "license": "Apache-2.0", "dependencies": { "lodash.merge": "^4.6.2" }, @@ -13925,14 +12371,12 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/hammerjs": { "version": "2.0.8", "resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz", "integrity": "sha512-tSQXBXS/MWQOn/RKckawJ61vvsDpCom87JgxiYdGwHdOa0ht0vzUWDlfioofFCRU0L+6NGDt6XzbgoJvZkMeRQ==", - "license": "MIT", "engines": { "node": ">=0.8.0" } @@ -13949,7 +12393,6 @@ "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", "dev": true, - "license": "MIT", "dependencies": { "minimist": "^1.2.5", "neo-async": "^2.6.2", @@ -13957,125 +12400,48 @@ "wordwrap": "^1.0.0" }, "bin": { - "handlebars": "bin/handlebars" - }, - "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" - } - }, - "node_modules/handlebars/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=4" - } - }, - "node_modules/har-validator": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", - "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", - "deprecated": "this library is no longer supported", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.3", - "har-schema": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/har-validator/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "handlebars": "bin/handlebars" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" } }, - "node_modules/har-validator/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "node_modules/handlebars/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, - "license": "MIT" + "engines": { + "node": ">=0.10.0" + } }, "node_modules/hard-rejection": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", "dev": true, - "license": "MIT", "engines": { "node": ">=6" } }, - "node_modules/has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-ansi/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/has-bigints": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", "dev": true, - "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "license": "MIT", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "engines": { - "node": ">=4" + "node": ">=8" } }, "node_modules/has-property-descriptors": { @@ -14083,7 +12449,6 @@ "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dev": true, - "license": "MIT", "dependencies": { "es-define-property": "^1.0.0" }, @@ -14092,11 +12457,13 @@ } }, "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", "dev": true, - "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, "engines": { "node": ">= 0.4" }, @@ -14105,11 +12472,10 @@ } }, "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -14122,7 +12488,6 @@ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dev": true, - "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" }, @@ -14138,7 +12503,6 @@ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dev": true, - "license": "MIT", "dependencies": { "function-bind": "^1.1.2" }, @@ -14151,17 +12515,15 @@ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true, - "license": "MIT", "bin": { "he": "bin/he" } }, "node_modules/hexoid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", - "integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-2.0.0.tgz", + "integrity": "sha512-qlspKUK7IlSQv2o+5I7yhUd7TxlOG2Vr5LTa3ve2XSNVKAL/n/u/7KLvKmFNimomDIKvZFXWHv0T12mv7rT8Aw==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } @@ -14171,7 +12533,6 @@ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", "dev": true, - "license": "ISC", "dependencies": { "lru-cache": "^6.0.0" }, @@ -14184,7 +12545,6 @@ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, - "license": "ISC", "dependencies": { "yallist": "^4.0.0" }, @@ -14196,8 +12556,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/hpack.js": { "version": "2.1.6", @@ -14273,13 +12632,12 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/htmlparser2": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", - "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz", + "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==", "dev": true, "funding": [ "https://github.com/fb55/htmlparser2?sponsor=1", @@ -14292,16 +12650,74 @@ "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", - "domutils": "^3.0.1", - "entities": "^4.4.0" + "domutils": "^3.1.0", + "entities": "^4.5.0" + } + }, + "node_modules/htmlparser2/node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/htmlparser2/node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/htmlparser2/node_modules/domutils": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.1.tgz", + "integrity": "sha512-xWXmuRnN9OMP6ptPd2+H0cCbcYBULa5YDTbMm/2lvkWvNA3O4wcW+GvzooqBuNM8yy6pl3VIAeJTUUWUbfI5Fw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" } }, "node_modules/http-cache-semantics": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", - "dev": true, - "license": "BSD-2-Clause" + "dev": true }, "node_modules/http-deceiver": { "version": "1.2.7", @@ -14315,7 +12731,6 @@ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "dev": true, - "license": "MIT", "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", @@ -14332,7 +12747,6 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.8" } @@ -14349,7 +12763,6 @@ "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", "dev": true, - "license": "MIT", "dependencies": { "eventemitter3": "^4.0.0", "follow-redirects": "^1.0.0", @@ -14364,7 +12777,6 @@ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, - "license": "MIT", "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" @@ -14374,47 +12786,29 @@ } }, "node_modules/http-proxy-middleware": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-3.0.0.tgz", - "integrity": "sha512-36AV1fIaI2cWRzHo+rbcxhe3M3jUDCNzc4D5zRl57sEWRAxdXYtw7FSQKYY6PDKssiAKjLYypbssHk+xs/kMXw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-3.0.3.tgz", + "integrity": "sha512-usY0HG5nyDUwtqpiZdETNbmKtw3QQ1jwYFZ9wi5iHzX2BcILwQKtYDJPo7XHTsu5Z0B2Hj3W9NNnbd+AjFWjqg==", "dev": true, - "license": "MIT", "dependencies": { - "@types/http-proxy": "^1.17.10", - "debug": "^4.3.4", + "@types/http-proxy": "^1.17.15", + "debug": "^4.3.6", "http-proxy": "^1.18.1", - "is-glob": "^4.0.1", - "is-plain-obj": "^3.0.0", - "micromatch": "^4.0.5" + "is-glob": "^4.0.3", + "is-plain-object": "^5.0.0", + "micromatch": "^4.0.8" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - }, - "engines": { - "node": ">=0.8", - "npm": ">=1.3.7" - } - }, "node_modules/https-proxy-agent": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", - "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "dev": true, - "license": "MIT", "dependencies": { - "agent-base": "^7.0.2", + "agent-base": "^7.1.2", "debug": "4" }, "engines": { @@ -14426,7 +12820,6 @@ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", "dev": true, - "license": "Apache-2.0", "engines": { "node": ">=8.12.0" } @@ -14445,7 +12838,6 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -14458,7 +12850,6 @@ "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", "dev": true, - "license": "ISC", "engines": { "node": "^10 || ^12 || >= 14" }, @@ -14483,37 +12874,27 @@ "type": "consulting", "url": "https://feross.org/support" } - ], - "license": "BSD-3-Clause" + ] }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, - "license": "MIT", "engines": { "node": ">= 4" } }, - "node_modules/ignore-by-default": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", - "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", - "dev": true, - "license": "ISC" - }, "node_modules/ignore-walk": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-6.0.5.tgz", - "integrity": "sha512-VuuG0wCnjhnylG1ABXT3dAuIpTNDs/G8jlpmwXY03fXoXy/8ZK8/T+hMzt8L4WnrLCJgdybqgPagnF/f97cg3A==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-7.0.0.tgz", + "integrity": "sha512-T4gbf83A4NH95zvhVYZc+qWocBBGlpzUXLPGurJggw/WIOwicfXJChLDP/iBZnN5WqROSu5Bm3hhle4z8a8YGQ==", "dev": true, - "license": "ISC", "dependencies": { "minimatch": "^9.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/image-size": { @@ -14521,7 +12902,6 @@ "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", "dev": true, - "license": "MIT", "optional": true, "bin": { "image-size": "bin/image-size.js" @@ -14530,26 +12910,17 @@ "node": ">=0.10.0" } }, - "node_modules/immediate": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", - "dev": true, - "license": "MIT" - }, "node_modules/immutable": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz", - "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==", - "dev": true, - "license": "MIT" + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.0.3.tgz", + "integrity": "sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw==", + "dev": true }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, - "license": "MIT", "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -14566,7 +12937,6 @@ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.8.19" } @@ -14576,7 +12946,6 @@ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } @@ -14587,7 +12956,6 @@ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dev": true, - "license": "ISC", "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -14596,17 +12964,15 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/ini": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz", - "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-5.0.0.tgz", + "integrity": "sha512-+N0ngpO3e7cRUWOJAS7qw0IZIVc6XPrW4MlFBdD066F2L4k1L6ker3hLqSq7iXxU5tgS4WGkIUElWn5vogAEnw==", "dev": true, - "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/inquirer": { @@ -14614,7 +12980,6 @@ "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", "dev": true, - "license": "MIT", "dependencies": { "ansi-escapes": "^4.2.1", "chalk": "^4.1.0", @@ -14634,109 +12999,48 @@ "node": ">=8.0.0" } }, - "node_modules/inquirer/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/inquirer/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/inquirer/node_modules/cli-width": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", "dev": true, - "license": "ISC", "engines": { "node": ">= 10" } }, - "node_modules/inquirer/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/inquirer/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/inquirer/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/inquirer/node_modules/mute-stream": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, + "node_modules/inquirer/node_modules/rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", "dev": true, - "license": "ISC" - }, - "node_modules/inquirer/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "tslib": "^1.9.0" }, "engines": { - "node": ">=8" + "npm": ">=2.0.0" } }, + "node_modules/inquirer/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, "node_modules/internal-slot": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", - "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", "dev": true, - "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "hasown": "^2.0.0", - "side-channel": "^1.0.4" + "hasown": "^2.0.2", + "side-channel": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -14746,18 +13050,16 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", - "license": "ISC", "engines": { "node": ">=12" } }, "node_modules/ionicons": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/ionicons/-/ionicons-6.1.3.tgz", - "integrity": "sha512-ptzz38dd/Yq+PgjhXegh7yhb/SLIk1bvL9vQDtLv1aoSc7alO6mX2DIMgcKYzt9vrNWkRu1f9Jr78zIFFyOXqw==", - "license": "MIT", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/ionicons/-/ionicons-7.4.0.tgz", + "integrity": "sha512-ZK94MMqgzMCPPMhmk8Ouu6goyVHFIlw/ACP6oe3FrikcI0N7CX0xcwVaEbUc0G/v3W0shI93vo+9ve/KpvcNhQ==", "dependencies": { - "@stencil/core": "^2.18.0" + "@stencil/core": "^4.0.3" } }, "node_modules/ip-address": { @@ -14765,7 +13067,6 @@ "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", "dev": true, - "license": "MIT", "dependencies": { "jsbn": "1.1.0", "sprintf-js": "^1.1.3" @@ -14785,14 +13086,14 @@ } }, "node_modules/is-array-buffer": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", - "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", "dev": true, - "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1" + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" }, "engines": { "node": ">= 0.4" @@ -14805,17 +13106,33 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-async-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", + "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==", "dev": true, - "license": "MIT" + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", "dev": true, - "license": "MIT", "dependencies": { - "has-bigints": "^1.0.1" + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -14826,7 +13143,6 @@ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "devOptional": true, - "license": "MIT", "dependencies": { "binary-extensions": "^2.0.0" }, @@ -14835,14 +13151,13 @@ } }, "node_modules/is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.1.tgz", + "integrity": "sha512-l9qO6eFlUETHtuihLcYOaLKByJ1f+N4kthcU9YjHy3N+B3hWv0y/2Nd0mu/7lTFnRQHTrSdXF50HQ3bl5fEnng==", "dev": true, - "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -14851,12 +13166,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-bun-module": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-1.3.0.tgz", + "integrity": "sha512-DgXeu5UWI0IsMQundYb5UAOzm6G2eVnarJ0byP6Tm55iZNKceD59LNPA2L4VvsScTtHcw0yEkVwSf7PC+QoLSA==", + "dev": true, + "dependencies": { + "semver": "^7.6.3" + } + }, "node_modules/is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -14865,11 +13188,10 @@ } }, "node_modules/is-core-module": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", - "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.0.tgz", + "integrity": "sha512-urTSINYfAYgcbLb0yDQ6egFm6h3Mo1DcF9EkyXSRjjzdHbsulg01qhwWuXdOoUBuTkbQ80KDboXa0vFJ+BDH+g==", "dev": true, - "license": "MIT", "dependencies": { "hasown": "^2.0.2" }, @@ -14881,12 +13203,13 @@ } }, "node_modules/is-data-view": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", - "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", "dev": true, - "license": "MIT", "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", "is-typed-array": "^1.1.13" }, "engines": { @@ -14897,13 +13220,13 @@ } }, "node_modules/is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", "dev": true, - "license": "MIT", "dependencies": { - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -14933,27 +13256,54 @@ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "devOptional": true, - "license": "MIT", "engines": { "node": ">=0.10.0" } }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "devOptional": true, - "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" }, @@ -14984,24 +13334,27 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", - "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/is-lambda": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", - "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", "dev": true, - "license": "MIT" + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/is-negative-zero": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -15027,19 +13380,18 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "devOptional": true, - "license": "MIT", "engines": { "node": ">=0.12.0" } }, "node_modules/is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", "dev": true, - "license": "MIT", "dependencies": { - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -15053,7 +13405,6 @@ "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } @@ -15063,83 +13414,60 @@ "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=6" } }, - "node_modules/is-path-in-cwd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", - "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-path-inside": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-path-in-cwd/node_modules/is-path-inside": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", - "integrity": "sha512-qhsCR/Esx4U4hg/9I19OVUAJkGWtjRYHMRgUMZE2TDdj+Ag+kttZanLupfddNyglzz50cUlmWzUaI37GDfNx/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-is-inside": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-path-inside": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/is-plain-obj": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", - "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", "dev": true, - "license": "MIT", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=0.10.0" } }, "node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", "dev": true, - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, "engines": { "node": ">=0.10.0" } }, "node_modules/is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", "dev": true, - "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, "engines": { "node": ">= 0.4" }, @@ -15152,7 +13480,6 @@ "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", "dev": true, - "license": "MIT", "dependencies": { "call-bind": "^1.0.7" }, @@ -15168,7 +13495,6 @@ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" }, @@ -15177,13 +13503,13 @@ } }, "node_modules/is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", "dev": true, - "license": "MIT", "dependencies": { - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -15193,13 +13519,14 @@ } }, "node_modules/is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", "dev": true, - "license": "MIT", "dependencies": { - "has-symbols": "^1.0.2" + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -15213,7 +13540,6 @@ "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-1.0.1.tgz", "integrity": "sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w==", "dev": true, - "license": "MIT", "dependencies": { "text-extensions": "^1.0.0" }, @@ -15226,7 +13552,6 @@ "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", "dev": true, - "license": "MIT", "dependencies": { "which-typed-array": "^1.1.14" }, @@ -15241,14 +13566,12 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/is-unicode-supported": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "license": "MIT", "engines": { "node": ">=10" }, @@ -15256,14 +13579,44 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.0.tgz", + "integrity": "sha512-SXM8Nwyys6nT5WP6pltOwKytLV7FqQ4UiibxVmW+EIosHcmCqkkjViTb5SNssDlkCiEYRP1/pdWUKVvZBmsR2Q==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", "dev": true, - "license": "MIT", "dependencies": { - "call-bind": "^1.0.2" + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -15273,8 +13626,7 @@ "version": "3.14.1", "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz", "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/is-wsl": { "version": "3.1.0", @@ -15296,15 +13648,13 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/isbinaryfile": { "version": "4.0.10", "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", "dev": true, - "license": "MIT", "engines": { "node": ">= 8.0.0" }, @@ -15316,32 +13666,22 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", - "dev": true, - "license": "MIT" - }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", "dev": true, - "license": "BSD-3-Clause", "engines": { "node": ">=8" } @@ -15368,7 +13708,6 @@ "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, - "license": "BSD-3-Clause", "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^4.0.0", @@ -15378,35 +13717,11 @@ "node": ">=10" } }, - "node_modules/istanbul-lib-report/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/istanbul-lib-source-maps": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", "dev": true, - "license": "BSD-3-Clause", "dependencies": { "debug": "^4.1.1", "istanbul-lib-coverage": "^3.0.0", @@ -15421,7 +13736,6 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, - "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -15431,7 +13745,6 @@ "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", "dev": true, - "license": "BSD-3-Clause", "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" @@ -15445,72 +13758,36 @@ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, - "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" }, "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/jasmine": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-2.8.0.tgz", - "integrity": "sha512-KbdGQTf5jbZgltoHs31XGiChAPumMSY64OZMWLNYnEnMfG5uwGBhffePwuskexjT+/Jea/gU3qAU8344hNohSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "exit": "^0.1.2", - "glob": "^7.0.6", - "jasmine-core": "~2.8.0" + "url": "https://github.com/sponsors/isaacs" }, - "bin": { - "jasmine": "bin/jasmine.js" + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" } }, "node_modules/jasmine-core": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.3.0.tgz", - "integrity": "sha512-zsOmeBKESky4toybvWEikRiZ0jHoBEu79wNArLfMdSnlLMZx3Xcp6CSm2sUcYyoJC+Uyj8LBJap/MUbVSfJ27g==", - "dev": true, - "license": "MIT" + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.5.0.tgz", + "integrity": "sha512-NHOvoPO6o9gVR6pwqEACTEpbgcH+JJ6QDypyymGbSUIFIFsMMbBJ/xsFNud8MSClfnWclXd7RQlAZBz7yVo5TQ==", + "dev": true }, "node_modules/jasmine-spec-reporter": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/jasmine-spec-reporter/-/jasmine-spec-reporter-7.0.0.tgz", "integrity": "sha512-OtC7JRasiTcjsaCBPtMO0Tl8glCejM4J4/dNuOJdA8lBjz4PmWjYQ6pzb0uzpBNAWJMDudYuj9OdXJWqM2QTJg==", "dev": true, - "license": "Apache-2.0", "dependencies": { "colors": "1.4.0" } }, - "node_modules/jasmine/node_modules/jasmine-core": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.8.0.tgz", - "integrity": "sha512-SNkOkS+/jMZvLhuSx1fjhcNWUC/KG6oVyFUGkSBEr9n1axSNduWU8GlI7suaHXr4yxjet6KjrUZxUTE5WzzWwQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/jasminewd2": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/jasminewd2/-/jasminewd2-2.2.0.tgz", - "integrity": "sha512-Rn0nZe4rfDhzA63Al3ZGh0E+JTmM6ESZYXJGKuqKGZObsAB9fwXPD03GjtIEvJBDOhN94T5MzbwZSqzFHSQPzg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6.9.x" - } - }, "node_modules/jest-worker": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", "dev": true, - "license": "MIT", "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -15520,22 +13797,11 @@ "node": ">= 10.13.0" } }, - "node_modules/jest-worker/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/jest-worker/node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, - "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -15551,7 +13817,6 @@ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", "dev": true, - "license": "MIT", "bin": { "jiti": "bin/jiti.js" } @@ -15560,15 +13825,13 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, - "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -15580,89 +13843,72 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/jsdoc-type-pratt-parser": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.1.0.tgz", "integrity": "sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==", "dev": true, - "license": "MIT", "engines": { "node": ">=12.0.0" } }, "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", "dev": true, - "license": "MIT", "bin": { "jsesc": "bin/jsesc" }, "engines": { - "node": ">=4" + "node": ">=6" } }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/json-parse-even-better-errors": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", - "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-4.0.0.tgz", + "integrity": "sha512-lR4MXjGNgkJc7tkQ97kb2nuEMnNCyU//XYVH0MKTGcXEiSudQ5MKGKen3C5QubYy0vmq+JGitUg92uuywGEwIA==", "dev": true, - "license": "MIT", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "dev": true, - "license": "(AFL-2.1 OR BSD-3-Clause)" - }, "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT" + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, - "license": "MIT", "bin": { "json5": "lib/cli.js" }, @@ -15673,15 +13919,13 @@ "node_modules/jsonc-parser": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", - "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", - "license": "MIT" + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==" }, "node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "dev": true, - "license": "MIT", "dependencies": { "universalify": "^2.0.0" }, @@ -15696,15 +13940,13 @@ "dev": true, "engines": [ "node >= 0.2.0" - ], - "license": "MIT" + ] }, "node_modules/JSONStream": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", "dev": true, - "license": "(MIT OR Apache-2.0)", "dependencies": { "jsonparse": "^1.2.0", "through": ">=2.2.7 <3" @@ -15716,81 +13958,11 @@ "node": "*" } }, - "node_modules/jsprim": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", - "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", - "dev": true, - "license": "MIT", - "dependencies": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.4.0", - "verror": "1.10.0" - }, - "engines": { - "node": ">=0.6.0" - } - }, - "node_modules/jszip": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", - "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", - "dev": true, - "license": "(MIT OR GPL-3.0-or-later)", - "dependencies": { - "lie": "~3.3.0", - "pako": "~1.0.2", - "readable-stream": "~2.3.6", - "setimmediate": "^1.0.5" - } - }, - "node_modules/jszip/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/jszip/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dev": true, - "license": "MIT", - "dependencies": { - "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/jszip/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==", - "dev": true, - "license": "MIT" - }, - "node_modules/jszip/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==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, "node_modules/karma": { "version": "6.4.4", "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.4.tgz", "integrity": "sha512-LrtUxbdvt1gOpo3gxG+VAJlJAEMhbWlM4YrFQgql98FwF7+K8K12LYO4hnDdUkNjeztYrOXEMqgTajSWgmtI/w==", "dev": true, - "license": "MIT", "dependencies": { "@colors/colors": "1.5.0", "body-parser": "^1.19.0", @@ -15829,7 +14001,6 @@ "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-3.2.0.tgz", "integrity": "sha512-rE9RkUPI7I9mAxByQWkGJFXfFD6lE4gC5nPuZdobf/QdTEJI6EU4yIay/cfU/xV4ZxlM5JiTv7zWYgA64NpS5Q==", "dev": true, - "license": "MIT", "dependencies": { "which": "^1.2.1" } @@ -15839,7 +14010,6 @@ "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "dev": true, - "license": "ISC", "dependencies": { "isexe": "^2.0.0" }, @@ -15852,7 +14022,6 @@ "resolved": "https://registry.npmjs.org/karma-coverage/-/karma-coverage-2.2.1.tgz", "integrity": "sha512-yj7hbequkQP2qOSb20GuNSIyE//PgJWHwC2IydLE6XRtsnaflv+/OSGNssPjobYUlhVVagy99TQpqUt3vAUG7A==", "dev": true, - "license": "MIT", "dependencies": { "istanbul-lib-coverage": "^3.2.0", "istanbul-lib-instrument": "^5.1.0", @@ -15870,7 +14039,6 @@ "resolved": "https://registry.npmjs.org/karma-coverage-istanbul-reporter/-/karma-coverage-istanbul-reporter-3.0.3.tgz", "integrity": "sha512-wE4VFhG/QZv2Y4CdAYWDbMmcAHeS926ZIji4z+FkB2aF/EposRb6DP6G5ncT/wXhqUfAb/d7kZrNKPonbvsATw==", "dev": true, - "license": "MIT", "dependencies": { "istanbul-lib-coverage": "^3.0.0", "istanbul-lib-report": "^3.0.0", @@ -15887,7 +14055,6 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, - "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -15898,7 +14065,6 @@ "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz", "integrity": "sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==", "dev": true, - "license": "BSD-3-Clause", "dependencies": { "debug": "^4.1.1", "istanbul-lib-coverage": "^2.0.5", @@ -15915,7 +14081,6 @@ "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", "dev": true, - "license": "BSD-3-Clause", "engines": { "node": ">=6" } @@ -15925,7 +14090,6 @@ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", "dev": true, - "license": "MIT", "dependencies": { "pify": "^4.0.1", "semver": "^5.6.0" @@ -15939,7 +14103,6 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -15952,7 +14115,6 @@ "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", "dev": true, - "license": "MIT", "engines": { "node": ">=6" } @@ -15963,7 +14125,6 @@ "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, - "license": "ISC", "dependencies": { "glob": "^7.1.3" }, @@ -15976,7 +14137,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, - "license": "ISC", "bin": { "semver": "bin/semver" } @@ -15986,7 +14146,6 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, - "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -15996,7 +14155,6 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, - "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -16007,7 +14165,6 @@ "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", "dev": true, - "license": "BSD-3-Clause", "dependencies": { "@babel/core": "^7.12.3", "@babel/parser": "^7.14.7", @@ -16024,7 +14181,6 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -16037,7 +14193,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "license": "ISC", "bin": { "semver": "bin/semver.js" } @@ -16047,7 +14202,6 @@ "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-5.1.0.tgz", "integrity": "sha512-i/zQLFrfEpRyQoJF9fsCdTMOF5c2dK7C7OmsuKg2D0YSsuZSfQDiLuaiktbuio6F2wiCsZSnSnieIQ0ant/uzQ==", "dev": true, - "license": "MIT", "dependencies": { "jasmine-core": "^4.1.0" }, @@ -16063,7 +14217,6 @@ "resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-2.1.0.tgz", "integrity": "sha512-sPQE1+nlsn6Hwb5t+HHwyy0A1FNCVKuL1192b+XNauMYWThz2kweiBVW1DqloRpVvZIJkIoHVB7XRpK78n1xbQ==", "dev": true, - "license": "MIT", "peerDependencies": { "jasmine-core": "^4.0.0 || ^5.0.0", "karma": "^6.0.0", @@ -16074,84 +14227,79 @@ "version": "4.6.1", "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-4.6.1.tgz", "integrity": "sha512-VYz/BjjmC3klLJlLwA4Kw8ytk0zDSmbbDLNs794VnWmkcCB7I9aAL/D48VNQtmITyPvea2C3jdUMfc3kAoy0PQ==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/karma-source-map-support": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/karma-source-map-support/-/karma-source-map-support-1.4.0.tgz", "integrity": "sha512-RsBECncGO17KAoJCYXjv+ckIz+Ii9NCi+9enk+rq6XC81ezYkb4/RHE6CTXdA7IOJqoF3wcaLfVG0CPmE5ca6A==", "dev": true, - "license": "MIT", "dependencies": { "source-map-support": "^0.5.5" } }, - "node_modules/karma/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/karma/node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, - "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, + "node_modules/karma/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, "node_modules/karma/node_modules/cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dev": true, - "license": "ISC", "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^7.0.0" } }, - "node_modules/karma/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/karma/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, - "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "is-glob": "^4.0.1" }, "engines": { - "node": ">=7.0.0" + "node": ">= 6" } }, - "node_modules/karma/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, "node_modules/karma/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -16159,13 +14307,36 @@ "node": "*" } }, + "node_modules/karma/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/karma/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, "node_modules/karma/node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, - "license": "ISC", "dependencies": { "glob": "^7.1.3" }, @@ -16181,7 +14352,6 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, - "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -16191,7 +14361,6 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, - "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -16209,7 +14378,6 @@ "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "dev": true, - "license": "MIT", "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", @@ -16228,7 +14396,6 @@ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, - "license": "MIT", "dependencies": { "json-buffer": "3.0.1" } @@ -16238,7 +14405,6 @@ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -16248,7 +14414,6 @@ "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=6" } @@ -16269,7 +14434,6 @@ "resolved": "https://registry.npmjs.org/leek/-/leek-0.0.24.tgz", "integrity": "sha512-6PVFIYXxlYF0o6hrAsHtGpTmi06otkwNrMcmQ0K96SeSRHPREPa9J3nJZ1frliVH7XT0XFswoJFQoXsDukzGNQ==", "dev": true, - "license": "MIT", "dependencies": { "debug": "^2.1.0", "lodash.assign": "^3.2.0", @@ -16281,7 +14445,6 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, - "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -16290,15 +14453,13 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/less": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/less/-/less-4.2.0.tgz", "integrity": "sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==", "dev": true, - "license": "Apache-2.0", "dependencies": { "copy-anything": "^2.0.1", "parse-node-version": "^1.0.1", @@ -16325,7 +14486,6 @@ "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-12.2.0.tgz", "integrity": "sha512-MYUxjSQSBUQmowc0l5nPieOYwMzGPUaTzB6inNW/bdPEG9zOL3eAAD1Qw5ZxSPk7we5dMojHwNODYMV1hq4EVg==", "dev": true, - "license": "MIT", "engines": { "node": ">= 18.12.0" }, @@ -16352,7 +14512,6 @@ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", "dev": true, - "license": "MIT", "optional": true, "dependencies": { "pify": "^4.0.1", @@ -16367,7 +14526,6 @@ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", "dev": true, - "license": "MIT", "optional": true, "bin": { "mime": "cli.js" @@ -16381,7 +14539,6 @@ "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", "dev": true, - "license": "MIT", "optional": true, "engines": { "node": ">=6" @@ -16392,7 +14549,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, - "license": "ISC", "optional": true, "bin": { "semver": "bin/semver" @@ -16403,7 +14559,6 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, - "license": "BSD-3-Clause", "optional": true, "engines": { "node": ">=0.10.0" @@ -16414,7 +14569,6 @@ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, - "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -16428,7 +14582,6 @@ "resolved": "https://registry.npmjs.org/license-webpack-plugin/-/license-webpack-plugin-4.0.2.tgz", "integrity": "sha512-771TFWFD70G1wLTC4oU2Cw4qvtmNrIw+wRvBtn+okgHl7slJVi7zfNcdmqDL72BojM30VNJ2UHylr1o77U37Jw==", "dev": true, - "license": "ISC", "dependencies": { "webpack-sources": "^3.0.0" }, @@ -16441,29 +14594,17 @@ } } }, - "node_modules/lie": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", - "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "immediate": "~3.0.5" - } - }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/listr2": { - "version": "8.2.4", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.4.tgz", - "integrity": "sha512-opevsywziHd3zHCVQGAj8zu+Z3yHNkkoYhWIGnq54RrCVwLz0MozotJEDnKsIBLvkfLGN6BLOyAeRrYI0pKA4g==", + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.5.tgz", + "integrity": "sha512-iyAZCeyD+c1gPyE9qpFu8af0Y+MRtmKOncdGoA2S5EY8iFq99dmmvkNnHiWo+pj0s7yH7l3KPIgee77tKpXPWQ==", "dev": true, - "license": "MIT", "dependencies": { "cli-truncate": "^4.0.0", "colorette": "^2.0.20", @@ -16481,7 +14622,6 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "dev": true, - "license": "MIT", "engines": { "node": ">=12" }, @@ -16494,7 +14634,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "dev": true, - "license": "MIT", "engines": { "node": ">=12" }, @@ -16506,22 +14645,19 @@ "version": "10.4.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/listr2/node_modules/eventemitter3": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/listr2/node_modules/string-width": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "dev": true, - "license": "MIT", "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", @@ -16539,7 +14675,6 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, - "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" }, @@ -16555,7 +14690,6 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", "dev": true, - "license": "MIT", "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", @@ -16569,29 +14703,30 @@ } }, "node_modules/lmdb": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/lmdb/-/lmdb-3.0.13.tgz", - "integrity": "sha512-UGe+BbaSUQtAMZobTb4nHvFMrmvuAQKSeaqAX2meTEQjfsbpl5sxdHD8T72OnwD4GU9uwNhYXIVe4QGs8N9Zyw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/lmdb/-/lmdb-3.1.5.tgz", + "integrity": "sha512-46Mch5Drq+A93Ss3gtbg+Xuvf5BOgIuvhKDWoGa3HcPHI6BL2NCOkRdSx1D4VfzwrxhnsjbyIVsLRlQHu6URvw==", "dev": true, "hasInstallScript": true, "license": "MIT", + "optional": true, "dependencies": { - "msgpackr": "^1.10.2", + "msgpackr": "^1.11.2", "node-addon-api": "^6.1.0", "node-gyp-build-optional-packages": "5.2.2", - "ordered-binary": "^1.4.1", + "ordered-binary": "^1.5.3", "weak-lru-cache": "^1.2.2" }, "bin": { "download-lmdb-prebuilds": "bin/download-prebuilds.js" }, "optionalDependencies": { - "@lmdb/lmdb-darwin-arm64": "3.0.13", - "@lmdb/lmdb-darwin-x64": "3.0.13", - "@lmdb/lmdb-linux-arm": "3.0.13", - "@lmdb/lmdb-linux-arm64": "3.0.13", - "@lmdb/lmdb-linux-x64": "3.0.13", - "@lmdb/lmdb-win32-x64": "3.0.13" + "@lmdb/lmdb-darwin-arm64": "3.1.5", + "@lmdb/lmdb-darwin-x64": "3.1.5", + "@lmdb/lmdb-linux-arm": "3.1.5", + "@lmdb/lmdb-linux-arm64": "3.1.5", + "@lmdb/lmdb-linux-x64": "3.1.5", + "@lmdb/lmdb-win32-x64": "3.1.5" } }, "node_modules/lmdb/node_modules/node-addon-api": { @@ -16599,14 +14734,14 @@ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/load-json-file": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", "dev": true, - "license": "MIT", "dependencies": { "graceful-fs": "^4.1.2", "parse-json": "^4.0.0", @@ -16622,7 +14757,6 @@ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", "dev": true, - "license": "MIT", "dependencies": { "error-ex": "^1.3.1", "json-parse-better-errors": "^1.0.1" @@ -16636,7 +14770,6 @@ "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", "dev": true, - "license": "MIT", "engines": { "node": ">=4" } @@ -16646,7 +14779,6 @@ "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", "dev": true, - "license": "MIT", "engines": { "node": ">=6.11.5" } @@ -16656,7 +14788,6 @@ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.3.1.tgz", "integrity": "sha512-FMJTLMXfCLMLfJxcX9PFqX5qD88Z5MRGaZCVzfuqeZSPsyiBzs+pahDQjbIWz2QIzPZz0NX9Zy4FX3lmK6YHIg==", "dev": true, - "license": "MIT", "engines": { "node": ">= 12.13.0" } @@ -16666,7 +14797,6 @@ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, - "license": "MIT", "dependencies": { "p-locate": "^5.0.0" }, @@ -16681,21 +14811,18 @@ "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/lodash-es": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", - "license": "MIT" + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" }, "node_modules/lodash._baseassign": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", "integrity": "sha512-t3N26QR2IdSN+gqSy9Ds9pBu/J1EAFEshKlUHpJG3rvyJOYgcELIxcIeKKfZk7sjOz11cFfzJRsyFry/JyabJQ==", "dev": true, - "license": "MIT", "dependencies": { "lodash._basecopy": "^3.0.0", "lodash.keys": "^3.0.0" @@ -16705,22 +14832,19 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", "integrity": "sha512-rFR6Vpm4HeCK1WPGvjZSJ+7yik8d8PVUdCJx5rT2pogG4Ve/2ZS7kfmO5l5T2o5V2mqlNIfSF5MZlr1+xOoYQQ==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/lodash._bindcallback": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz", "integrity": "sha512-2wlI0JRAGX8WEf4Gm1p/mv/SZ+jLijpj0jyaE/AXeuQphzCgD8ZQW4oSpoN8JAopujOFGU3KMuq7qfHBWlGpjQ==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/lodash._createassigner": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/lodash._createassigner/-/lodash._createassigner-3.1.1.tgz", "integrity": "sha512-LziVL7IDnJjQeeV95Wvhw6G28Z8Q6da87LWKOPWmzBLv4u6FAT/x5v00pyGW0u38UoogNF2JnD3bGgZZDaNEBw==", "dev": true, - "license": "MIT", "dependencies": { "lodash._bindcallback": "^3.0.0", "lodash._isiterateecall": "^3.0.0", @@ -16731,22 +14855,19 @@ "version": "3.9.1", "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", "integrity": "sha512-RrL9VxMEPyDMHOd9uFbvMe8X55X16/cGM5IgOKgRElQZutpX89iS6vwl64duTV1/16w5JY7tuFNXqoekmh1EmA==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/lodash._isiterateecall": { "version": "3.0.9", "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", "integrity": "sha512-De+ZbrMu6eThFti/CSzhRvTKMgQToLxbij58LMfM8JnYDNSOjkjTCIaa8ixglOeGh2nyPlakbt5bJWJ7gvpYlQ==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/lodash.assign": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-3.2.0.tgz", "integrity": "sha512-/VVxzgGBmbphasTg51FrztxQJ/VgAUpol6zmJuSVSGcNg4g7FA4z7rQV8Ovr9V3vFBNWZhvKWHfpAytjTVUfFA==", "dev": true, - "license": "MIT", "dependencies": { "lodash._baseassign": "^3.0.0", "lodash._createassigner": "^3.0.0", @@ -16757,36 +14878,31 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/lodash.isarguments": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/lodash.isarray": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", "integrity": "sha512-JwObCrNJuT0Nnbuecmqr5DgtuBppuCvGD9lxjFpAzwnVtdGoDQ1zig+5W8k5/6Gcn0gZ3936HDAlGd28i7sOGQ==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/lodash.ismatch": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz", "integrity": "sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/lodash.keys": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", "integrity": "sha512-CuBsapFjcubOGMn3VD+24HOAPxM79tH+V6ivJL3CHYjtrawauDJHUk//Yew9Hvc6e9rbCrURGk8z6PC+8WJBfQ==", "dev": true, - "license": "MIT", "dependencies": { "lodash._getnative": "^3.0.0", "lodash.isarguments": "^3.0.0", @@ -16797,21 +14913,18 @@ "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/lodash.restparam": { "version": "3.6.1", "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", "integrity": "sha512-L4/arjjuq4noiUJpt3yS6KIKDtJwNe2fIYgMqyYYKoeIfV1iEqvPwhCx23o+R9dzouGihDAPN1dTIRWa7zk8tw==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "license": "MIT", "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" @@ -16823,82 +14936,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/log-symbols/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/log-symbols/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/log-symbols/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/log-symbols/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "license": "MIT" - }, - "node_modules/log-symbols/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/log-symbols/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/log-update": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", "dev": true, - "license": "MIT", "dependencies": { "ansi-escapes": "^7.0.0", "cli-cursor": "^5.0.0", @@ -16918,7 +14960,6 @@ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", "integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==", "dev": true, - "license": "MIT", "dependencies": { "environment": "^1.0.0" }, @@ -16934,7 +14975,6 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "dev": true, - "license": "MIT", "engines": { "node": ">=12" }, @@ -16947,7 +14987,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "dev": true, - "license": "MIT", "engines": { "node": ">=12" }, @@ -16960,7 +14999,6 @@ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", "dev": true, - "license": "MIT", "dependencies": { "restore-cursor": "^5.0.0" }, @@ -16975,15 +15013,13 @@ "version": "10.4.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/log-update/node_modules/is-fullwidth-code-point": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz", "integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==", "dev": true, - "license": "MIT", "dependencies": { "get-east-asian-width": "^1.0.0" }, @@ -16999,7 +15035,6 @@ "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", "dev": true, - "license": "MIT", "dependencies": { "mimic-function": "^5.0.0" }, @@ -17015,7 +15050,6 @@ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", "dev": true, - "license": "MIT", "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" @@ -17032,7 +15066,6 @@ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz", "integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==", "dev": true, - "license": "MIT", "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" @@ -17049,7 +15082,6 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "dev": true, - "license": "MIT", "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", @@ -17067,7 +15099,6 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, - "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" }, @@ -17083,7 +15114,6 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", "dev": true, - "license": "MIT", "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", @@ -17101,7 +15131,6 @@ "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.9.1.tgz", "integrity": "sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g==", "dev": true, - "license": "Apache-2.0", "dependencies": { "date-format": "^4.0.14", "debug": "^4.3.4", @@ -17118,7 +15147,6 @@ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, - "license": "ISC", "dependencies": { "yallist": "^3.0.2" } @@ -17128,7 +15156,6 @@ "resolved": "https://registry.npmjs.org/macos-release/-/macos-release-2.5.1.tgz", "integrity": "sha512-DXqXhEM7gW59OjZO8NIjBCz9AQ1BEMrfiOAl4AYByHCtVHRF4KoGNO8mqQeM8lRCtQe/UnJ4imO/d2HdkKsd+A==", "dev": true, - "license": "MIT", "engines": { "node": ">=6" }, @@ -17137,9 +15164,9 @@ } }, "node_modules/magic-string": { - "version": "0.30.11", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", - "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", + "version": "0.30.12", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.12.tgz", + "integrity": "sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==", "dev": true, "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" @@ -17150,7 +15177,6 @@ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, - "license": "MIT", "dependencies": { "semver": "^7.5.3" }, @@ -17165,31 +15191,28 @@ "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/make-fetch-happen": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-13.0.1.tgz", - "integrity": "sha512-cKTUFc/rbKUd/9meOvgrpJ2WrNzymt6jfRDdwg5UCnVzv9dTpEj9JS5m3wtziXVCjluIXyL8pcaukYqezIzZQA==", + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-14.0.3.tgz", + "integrity": "sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ==", "dev": true, - "license": "ISC", "dependencies": { - "@npmcli/agent": "^2.0.0", - "cacache": "^18.0.0", + "@npmcli/agent": "^3.0.0", + "cacache": "^19.0.1", "http-cache-semantics": "^4.1.1", - "is-lambda": "^1.0.1", "minipass": "^7.0.2", - "minipass-fetch": "^3.0.0", + "minipass-fetch": "^4.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", - "proc-log": "^4.2.0", + "negotiator": "^1.0.0", + "proc-log": "^5.0.0", "promise-retry": "^2.0.1", - "ssri": "^10.0.0" + "ssri": "^12.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/map-obj": { @@ -17197,7 +15220,6 @@ "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" }, @@ -17205,20 +15227,28 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/math-intrinsics": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.0.0.tgz", + "integrity": "sha512-4MqMiKP90ybymYvsut0CH2g4XWbfLtmlCkXmtmdcDCxNB+mQcu1w/1+L/VD7vi/PSv7X2JYV7SCcR+jiPXnQtA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/memfs": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.12.0.tgz", - "integrity": "sha512-74wDsex5tQDSClVkeK1vtxqYCAgCoXxx+K4NSHzgU/muYVYByFqa+0RnrPO9NM6naWm1+G9JmZ0p6QHhXmeYfA==", + "version": "4.15.2", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.15.2.tgz", + "integrity": "sha512-n8/qP8AT6CtY6kxCPYgYVusT5rS6axaT66dD3tYi2lm+l1iMH7YYpmW8H/qL5bfV4YvInCCgUDAWIRvrNS7kbQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -17240,7 +15270,6 @@ "resolved": "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz", "integrity": "sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==", "dev": true, - "license": "MIT", "dependencies": { "@types/minimist": "^1.2.0", "camelcase-keys": "^6.2.2", @@ -17266,7 +15295,6 @@ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, - "license": "MIT", "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -17279,15 +15307,13 @@ "version": "2.8.9", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/meow/node_modules/locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, - "license": "MIT", "dependencies": { "p-locate": "^4.1.0" }, @@ -17300,7 +15326,6 @@ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, - "license": "MIT", "dependencies": { "p-try": "^2.0.0" }, @@ -17316,7 +15341,6 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, - "license": "MIT", "dependencies": { "p-limit": "^2.2.0" }, @@ -17329,7 +15353,6 @@ "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", "dev": true, - "license": "MIT", "dependencies": { "@types/normalize-package-data": "^2.4.0", "normalize-package-data": "^2.5.0", @@ -17345,7 +15368,6 @@ "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", "dev": true, - "license": "MIT", "dependencies": { "find-up": "^4.1.0", "read-pkg": "^5.2.0", @@ -17363,7 +15385,6 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", "dev": true, - "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=8" } @@ -17373,7 +15394,6 @@ "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "hosted-git-info": "^2.1.4", "resolve": "^1.10.0", @@ -17386,7 +15406,6 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", "dev": true, - "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=8" } @@ -17396,7 +15415,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, - "license": "ISC", "bin": { "semver": "bin/semver" } @@ -17406,7 +15424,6 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", "dev": true, - "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" }, @@ -17428,15 +15445,13 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true, - "license": "MIT", "engines": { "node": ">= 8" } @@ -17446,7 +15461,6 @@ "resolved": "https://registry.npmjs.org/mergexml/-/mergexml-1.2.4.tgz", "integrity": "sha512-yiOlDqcVCz7AG1eSboonc18FTlfqDEKYfGoAV3Lul98u6YRV/s0kjtf4bjk47t0hLTFJR0BSYMd6BpmX3xDjNQ==", "dev": true, - "license": "ISC", "dependencies": { "@xmldom/xmldom": "^0.7.0", "formidable": "^3.5.1", @@ -17458,7 +15472,6 @@ "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.27.tgz", "integrity": "sha512-fg03WRxtkCV6ohClePNAECYsmpKKTv5L8y/X3Dn1hQrec3POx2jHZ/0P2qQ6HvsrU1BmeqXcof3NGGueG6LxwQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.6.0" } @@ -17468,7 +15481,6 @@ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.6" } @@ -17478,7 +15490,6 @@ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, - "license": "MIT", "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" @@ -17492,7 +15503,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, - "license": "MIT", "engines": { "node": ">=8.6" }, @@ -17505,7 +15515,6 @@ "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", "dev": true, - "license": "MIT", "bin": { "mime": "cli.js" }, @@ -17518,7 +15527,6 @@ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.6" } @@ -17528,7 +15536,6 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, - "license": "MIT", "dependencies": { "mime-db": "1.52.0" }, @@ -17540,7 +15547,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "license": "MIT", "engines": { "node": ">=6" } @@ -17550,7 +15556,6 @@ "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", "dev": true, - "license": "MIT", "engines": { "node": ">=18" }, @@ -17563,7 +15568,6 @@ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=10" }, @@ -17576,17 +15580,15 @@ "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", "dev": true, - "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/mini-css-extract-plugin": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.0.tgz", - "integrity": "sha512-Zs1YsZVfemekSZG+44vBsYTLQORkPMwnlv+aehcxK/NLKC+EGhDB39/YePYYqx/sTk6NnYpuqikhSn7+JIevTA==", + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.2.tgz", + "integrity": "sha512-GJuACcS//jtq4kCtd5ii/M0SZf7OZRH+BxdqXZHaJfb8TJiVl+NgQRPwiYt2EuqeSkNydn/7vP+bcE27C5mb9w==", "dev": true, - "license": "MIT", "dependencies": { "schema-utils": "^4.0.0", "tapable": "^2.2.1" @@ -17614,7 +15616,6 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, - "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -17630,7 +15631,6 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "dev": true, - "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -17640,7 +15640,6 @@ "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", "dev": true, - "license": "MIT", "dependencies": { "arrify": "^1.0.1", "is-plain-obj": "^1.1.0", @@ -17650,22 +15649,11 @@ "node": ">= 6" } }, - "node_modules/minimist-options/node_modules/is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/minipass": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true, - "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" } @@ -17675,7 +15663,6 @@ "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", "dev": true, - "license": "ISC", "dependencies": { "minipass": "^7.0.3" }, @@ -17684,18 +15671,17 @@ } }, "node_modules/minipass-fetch": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.5.tgz", - "integrity": "sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-4.0.0.tgz", + "integrity": "sha512-2v6aXUXwLP1Epd/gc32HAMIWoczx+fZwEPRHm/VwtrJzRGwR1qGZXEYV3Zp8ZjjbwaZhMrM6uHV4KVkk+XCc2w==", "dev": true, - "license": "MIT", "dependencies": { "minipass": "^7.0.3", "minipass-sized": "^1.0.3", - "minizlib": "^2.1.2" + "minizlib": "^3.0.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" }, "optionalDependencies": { "encoding": "^0.1.13" @@ -17706,7 +15692,6 @@ "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", "dev": true, - "license": "ISC", "dependencies": { "minipass": "^3.0.0" }, @@ -17719,7 +15704,6 @@ "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, - "license": "ISC", "dependencies": { "yallist": "^4.0.0" }, @@ -17731,15 +15715,13 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/minipass-pipeline": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", "dev": true, - "license": "ISC", "dependencies": { "minipass": "^3.0.0" }, @@ -17752,7 +15734,6 @@ "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, - "license": "ISC", "dependencies": { "yallist": "^4.0.0" }, @@ -17764,15 +15745,13 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/minipass-sized": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", "dev": true, - "license": "ISC", "dependencies": { "minipass": "^3.0.0" }, @@ -17785,7 +15764,6 @@ "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, - "license": "ISC", "dependencies": { "yallist": "^4.0.0" }, @@ -17797,49 +15775,61 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.1.tgz", + "integrity": "sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg==", "dev": true, - "license": "MIT", "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" + "minipass": "^7.0.4", + "rimraf": "^5.0.5" }, "engines": { - "node": ">= 8" + "node": ">= 18" } }, - "node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "node_modules/minizlib/node_modules/glob": { + "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": "^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" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minizlib/node_modules/rimraf": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", "dev": true, - "license": "ISC", "dependencies": { - "yallist": "^4.0.0" + "glob": "^10.3.7" }, - "engines": { - "node": ">=8" + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/minizlib/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, - "license": "ISC" - }, "node_modules/mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dev": true, - "license": "MIT", "dependencies": { "minimist": "^1.2.6" }, @@ -17851,15 +15841,13 @@ "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/modify-values": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz", "integrity": "sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -17875,18 +15863,18 @@ } }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, - "license": "MIT" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true }, "node_modules/msgpackr": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.0.tgz", - "integrity": "sha512-I8qXuuALqJe5laEBYoFykChhSXLikZmUhccjGsPuSJ/7uPip2TJ7lwdIQwWSAi0jGZDXv4WOP8Qg65QZRuXxXw==", + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.2.tgz", + "integrity": "sha512-F9UngXRlPyWCDEASDpTf6c9uNhGPTqnTeLVt7bN+bU1eajoR/8V9ys2BRaV5C/e5ihE6sJ9uPIKaYt6bFuO32g==", "dev": true, "license": "MIT", + "optional": true, "optionalDependencies": { "msgpackr-extract": "^3.0.2" } @@ -17929,19 +15917,18 @@ } }, "node_modules/mute-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", - "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", "dev": true, - "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", "dev": true, "funding": [ { @@ -17949,7 +15936,6 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -17961,15 +15947,13 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/native-run": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/native-run/-/native-run-2.0.1.tgz", "integrity": "sha512-XfG1FBZLM50J10xH9361whJRC9SHZ0Bub4iNRhhI61C8Jv0e1ud19muex6sNKB51ibQNUJNuYn25MuYET/rE6w==", "dev": true, - "license": "MIT", "dependencies": { "@ionic/utils-fs": "^3.1.7", "@ionic/utils-terminal": "^2.3.4", @@ -17990,12 +15974,20 @@ "node": ">=16.0.0" } }, + "node_modules/native-run/node_modules/ini": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz", + "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/native-run/node_modules/split2": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", "dev": true, - "license": "ISC", "engines": { "node": ">= 10.x" } @@ -18004,15 +15996,13 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/needle": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/needle/-/needle-3.3.1.tgz", "integrity": "sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==", "dev": true, - "license": "MIT", "optional": true, "dependencies": { "iconv-lite": "^0.6.3", @@ -18030,15 +16020,13 @@ "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", "dev": true, - "license": "ISC", "optional": true }, "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.6" } @@ -18047,67 +16035,62 @@ "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/netmask": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.4.0" } }, "node_modules/ng2-charts": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ng2-charts/-/ng2-charts-4.1.1.tgz", - "integrity": "sha512-iHwXDbmX86lfeH8VRcsaW2tJATsuAZo4kvvC/Yk2l35zOHjevja1qBvO6BAibiDazi9r9aS6ZRJOqWPsz1pP2w==", - "license": "ISC", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/ng2-charts/-/ng2-charts-7.0.0.tgz", + "integrity": "sha512-HofyPPkz7yOX6Dr9JfV/SDddzmmqCFYCKbn71jeDiyWPRjrj99yTBCyqYtjzzNrnlTfWwbdvynYZ4GAhu/lbgQ==", "dependencies": { "lodash-es": "^4.17.15", "tslib": "^2.3.0" }, "peerDependencies": { - "@angular/cdk": ">=14.0.0", - "@angular/common": ">=14.0.0", - "@angular/core": ">=14.0.0", + "@angular/cdk": ">=18.0.0", + "@angular/common": ">=18.0.0", + "@angular/core": ">=18.0.0", + "@angular/platform-browser": ">=18.0.0", "chart.js": "^3.4.0 || ^4.0.0", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/ngx-cookie-service": { - "version": "18.0.0", - "resolved": "https://registry.npmjs.org/ngx-cookie-service/-/ngx-cookie-service-18.0.0.tgz", - "integrity": "sha512-hkkUckzZTXXWtFgvVkT2hg6mwYMLXioXDZWBsVCOy9gYkADjsj0N5VViO7eo2izQ0VcMPd/Etog1trf/T4oZMQ==", - "license": "MIT", + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/ngx-cookie-service/-/ngx-cookie-service-19.0.0.tgz", + "integrity": "sha512-itxGY1BlIRoEjEtDsSsRKnJuiQteTMLKPNHrykiH06tjUQ1bi3orE7YKU1D210VBqVy1jNrB7hKuGOOIQtQJDA==", "dependencies": { - "tslib": "^2.6.2" + "tslib": "^2.8.0" }, "peerDependencies": { - "@angular/common": "^18.0.0-rc.0", - "@angular/core": "^18.0.0-rc.0" + "@angular/common": "^19.0.0", + "@angular/core": "^19.0.0" } }, "node_modules/ngx-device-detector": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/ngx-device-detector/-/ngx-device-detector-8.0.0.tgz", - "integrity": "sha512-ik6EwUKnlN+xwoWHzyJp5+V+QRWYrmpTqAvRwa16xBnAVd7/i3jElN7MZjs/InwcYz7AW3XcSNeu+XRvtHgb9w==", - "license": "MIT", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/ngx-device-detector/-/ngx-device-detector-9.0.0.tgz", + "integrity": "sha512-zpio/wqH1GnxIpWCdA7cp5fmWf7YLycgzfXzQHmB9vaS7eAcqpBF5mxDS65IhE7TzNTJziDrYJCT+9tVqBcsDg==", "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { - "@angular/common": "^18.0.0", - "@angular/core": "^18.0.0" + "@angular/common": "^19.0.0", + "@angular/core": "^19.0.0" } }, "node_modules/ngx-spinner": { - "version": "16.0.2", - "resolved": "https://registry.npmjs.org/ngx-spinner/-/ngx-spinner-16.0.2.tgz", - "integrity": "sha512-MZpOHb3dvSqD6xiEdR+EtOfPY2r1kfA7t5Hv5IVwi7gkbTwVgnqxDWdOYJ/HW1pSZ5iEGhqlJqWg6CysLmNfHQ==", - "license": "MIT", + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/ngx-spinner/-/ngx-spinner-17.0.0.tgz", + "integrity": "sha512-VWDSvLlCnaWqu0W1L+ybQIRHTbd+GffkX1sWs++iMPXMGVJ2ZCuzW32FHnu+p0regMUHU8r1/rvUcFD0YooJxQ==", "dependencies": { "tslib": "^2.3.0" }, @@ -18117,28 +16100,11 @@ "@angular/core": ">=15.0.0" } }, - "node_modules/nice-napi": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz", - "integrity": "sha512-px/KnJAJZf5RuBGcfD+Sp2pAKq0ytz8j+1NehvgIGFkvtvFrDM3T8E4x/JJODXK9WZow8RRGrbA9QQ3hs+pDhA==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "!win32" - ], - "dependencies": { - "node-addon-api": "^3.0.0", - "node-gyp-build": "^4.2.2" - } - }, "node_modules/node-abi": { - "version": "3.67.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.67.0.tgz", - "integrity": "sha512-bLn/fU/ALVBE9wj+p4Y21ZJWYFjUXLXPi/IewyLZkx3ApxKDNBWCKdReeKOtD8dWpOdDCeMyLh6ZewzcLsG2Nw==", + "version": "3.71.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.71.0.tgz", + "integrity": "sha512-SZ40vRiy/+wRTf21hxkkEjPJZpARzUMVcJoQse2EF8qkUWbbO2z7vd5oA/H6bVH6SZQ5STGcu0KRDS7biNRfxw==", "dev": true, - "license": "MIT", "dependencies": { "semver": "^7.3.5" }, @@ -18147,11 +16113,10 @@ } }, "node_modules/node-addon-api": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", - "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", "dev": true, - "license": "MIT", "optional": true }, "node_modules/node-fetch": { @@ -18159,7 +16124,6 @@ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", "dev": true, - "license": "MIT", "dependencies": { "whatwg-url": "^5.0.0" }, @@ -18186,41 +16150,27 @@ } }, "node_modules/node-gyp": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-10.2.0.tgz", - "integrity": "sha512-sp3FonBAaFe4aYTcFdZUn2NYkbP7xroPGYvQmP4Nl5PxamznItBnNCgjrVTKrEfQynInMsJvZrdmqUnysCJ8rw==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-11.0.0.tgz", + "integrity": "sha512-zQS+9MTTeCMgY0F3cWPyJyRFAkVltQ1uXm+xXu/ES6KFgC6Czo1Seb9vQW2wNxSX2OrDTiqL0ojtkFxBQ0ypIw==", "dev": true, - "license": "MIT", "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", "glob": "^10.3.10", "graceful-fs": "^4.2.6", - "make-fetch-happen": "^13.0.0", - "nopt": "^7.0.0", - "proc-log": "^4.1.0", + "make-fetch-happen": "^14.0.3", + "nopt": "^8.0.0", + "proc-log": "^5.0.0", "semver": "^7.3.5", - "tar": "^6.2.1", - "which": "^4.0.0" + "tar": "^7.4.3", + "which": "^5.0.0" }, "bin": { "node-gyp": "bin/node-gyp.js" }, "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/node-gyp-build": { - "version": "4.8.2", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.2.tgz", - "integrity": "sha512-IRUxE4BVsHWXkV/SFOut4qTlagw2aM8T5/vnTsmrHJvVoKueJHRc/JaFND7QDDc61kLYUJ6qlZM3sqTSyx2dTw==", - "dev": true, - "license": "MIT", - "optional": true, - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/node-gyp-build-optional-packages": { @@ -18229,6 +16179,7 @@ "integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { "detect-libc": "^2.0.1" }, @@ -18238,12 +16189,31 @@ "node-gyp-build-optional-packages-test": "build-test.js" } }, + "node_modules/node-gyp-build-optional-packages/node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/node-gyp/node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "engines": { + "node": ">=18" + } + }, "node_modules/node-gyp/node_modules/glob": { "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dev": true, - "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", @@ -18264,197 +16234,87 @@ "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", "dev": true, - "license": "ISC", "engines": { "node": ">=16" } }, - "node_modules/node-gyp/node_modules/which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "node_modules/node-gyp/node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^3.1.1" - }, "bin": { - "node-which": "bin/which.js" + "mkdirp": "dist/cjs/src/bin.js" }, "engines": { - "node": "^16.13.0 || >=18.0.0" - } - }, - "node_modules/node-html-parser": { - "version": "5.4.2", - "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-5.4.2.tgz", - "integrity": "sha512-RaBPP3+51hPne/OolXxcz89iYvQvKOydaqoePpOgXcrOKZhjVIzmpKZz+Hd/RBO2/zN2q6CNJhQzucVz+u3Jyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "css-select": "^4.2.1", - "he": "1.2.0" - } - }, - "node_modules/node-html-parser/node_modules/css-select": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", - "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.0.1", - "domhandler": "^4.3.1", - "domutils": "^2.8.0", - "nth-check": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/node-html-parser/node_modules/dom-serializer": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", - "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", - "dev": true, - "license": "MIT", - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" + "node": ">=10" }, "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/node-html-parser/node_modules/domhandler": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", - "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "node_modules/node-gyp/node_modules/tar": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", + "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { - "domelementtype": "^2.2.0" + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" }, "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/node-html-parser/node_modules/domutils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", - "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/node-html-parser/node_modules/entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", - "dev": true, - "license": "BSD-2-Clause", - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" + "node": ">=18" } }, - "node_modules/node-releases": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", - "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", - "dev": true, - "license": "MIT" - }, - "node_modules/nodemon": { - "version": "2.0.22", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.22.tgz", - "integrity": "sha512-B8YqaKMmyuCO7BowF1Z1/mkPqLk6cs/l63Ojtd6otKjMx47Dq1utxfRxcavH1I7VSaL8n5BUaoutadnsX3AAVQ==", + "node_modules/node-gyp/node_modules/which": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", "dev": true, - "license": "MIT", "dependencies": { - "chokidar": "^3.5.2", - "debug": "^3.2.7", - "ignore-by-default": "^1.0.1", - "minimatch": "^3.1.2", - "pstree.remy": "^1.1.8", - "semver": "^5.7.1", - "simple-update-notifier": "^1.0.7", - "supports-color": "^5.5.0", - "touch": "^3.1.0", - "undefsafe": "^2.0.5" + "isexe": "^3.1.1" }, "bin": { - "nodemon": "bin/nodemon.js" + "node-which": "bin/which.js" }, "engines": { - "node": ">=8.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/nodemon" - } - }, - "node_modules/nodemon/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/nodemon/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/nodemon/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "node_modules/node-gyp/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, "engines": { - "node": "*" + "node": ">=18" } }, - "node_modules/nodemon/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "node_modules/node-html-parser": { + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-5.4.2.tgz", + "integrity": "sha512-RaBPP3+51hPne/OolXxcz89iYvQvKOydaqoePpOgXcrOKZhjVIzmpKZz+Hd/RBO2/zN2q6CNJhQzucVz+u3Jyw==", "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" + "dependencies": { + "css-select": "^4.2.1", + "he": "1.2.0" } }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true + }, "node_modules/nopt": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", - "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-8.0.0.tgz", + "integrity": "sha512-1L/fTJ4UmV/lUxT2Uf006pfZKTvAgCF+chz+0OgBHO8u2Z67pE7AaAUUj7CJy0lXqHmymUvGFt6NE9R3HER0yw==", "dev": true, - "license": "ISC", "dependencies": { "abbrev": "^2.0.0" }, @@ -18462,7 +16322,7 @@ "nopt": "bin/nopt.js" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/normalize-package-data": { @@ -18470,7 +16330,6 @@ "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "hosted-git-info": "^4.0.1", "is-core-module": "^2.5.0", @@ -18486,7 +16345,6 @@ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "devOptional": true, - "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -18496,130 +16354,120 @@ "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/npm-bundled": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-3.0.1.tgz", - "integrity": "sha512-+AvaheE/ww1JEwRHOrn4WHNzOxGtVp+adrg2AeZS/7KuxGUYFuBta98wYpfHBbJp6Tg6j1NKSEVHNcfZzJHQwQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-4.0.0.tgz", + "integrity": "sha512-IxaQZDMsqfQ2Lz37VvyyEtKLe8FsRZuysmedy/N06TU1RyVppYKXrO4xIhR0F+7ubIBox6Q7nir6fQI3ej39iA==", "dev": true, - "license": "ISC", "dependencies": { - "npm-normalize-package-bin": "^3.0.0" + "npm-normalize-package-bin": "^4.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/npm-install-checks": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.3.0.tgz", - "integrity": "sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-7.1.1.tgz", + "integrity": "sha512-u6DCwbow5ynAX5BdiHQ9qvexme4U3qHW3MWe5NqH+NeBm0LbiH6zvGjNNew1fY+AZZUtVHbOPF3j7mJxbUzpXg==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "semver": "^7.1.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/npm-normalize-package-bin": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", - "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-4.0.0.tgz", + "integrity": "sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w==", "dev": true, - "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/npm-package-arg": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.3.tgz", - "integrity": "sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw==", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-12.0.0.tgz", + "integrity": "sha512-ZTE0hbwSdTNL+Stx2zxSqdu2KZfNDcrtrLdIk7XGnQFYBWYDho/ORvXtn5XEePcL3tFpGjHCV3X3xrtDh7eZ+A==", "dev": true, - "license": "ISC", "dependencies": { - "hosted-git-info": "^7.0.0", - "proc-log": "^4.0.0", + "hosted-git-info": "^8.0.0", + "proc-log": "^5.0.0", "semver": "^7.3.5", - "validate-npm-package-name": "^5.0.0" + "validate-npm-package-name": "^6.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/npm-package-arg/node_modules/hosted-git-info": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", - "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.0.2.tgz", + "integrity": "sha512-sYKnA7eGln5ov8T8gnYlkSOxFJvywzEx9BueN6xo/GKO8PGiI6uK6xx+DIGe45T3bdVjLAQDQW1aicT8z8JwQg==", "dev": true, - "license": "ISC", "dependencies": { "lru-cache": "^10.0.1" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/npm-package-arg/node_modules/lru-cache": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/npm-packlist": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-8.0.2.tgz", - "integrity": "sha512-shYrPFIS/JLP4oQmAwDyk5HcyysKW8/JLTEA32S0Z5TzvpaeeX2yMFfoK1fjEBnCBvVyIB/Jj/GBFdm0wsgzbA==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-9.0.0.tgz", + "integrity": "sha512-8qSayfmHJQTx3nJWYbbUmflpyarbLMBc6LCAjYsiGtXxDB68HaZpb8re6zeaLGxZzDuMdhsg70jryJe+RrItVQ==", "dev": true, - "license": "ISC", "dependencies": { - "ignore-walk": "^6.0.4" + "ignore-walk": "^7.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/npm-pick-manifest": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-9.1.0.tgz", - "integrity": "sha512-nkc+3pIIhqHVQr085X9d2JzPzLyjzQS96zbruppqC9aZRm/x8xx6xhI98gHtsfELP2bE+loHq8ZaHFHhe+NauA==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-10.0.0.tgz", + "integrity": "sha512-r4fFa4FqYY8xaM7fHecQ9Z2nE9hgNfJR+EmoKv0+chvzWkBcORX3r0FpTByP+CbOVJDladMXnPQGVN8PBLGuTQ==", "dev": true, - "license": "ISC", "dependencies": { - "npm-install-checks": "^6.0.0", - "npm-normalize-package-bin": "^3.0.0", - "npm-package-arg": "^11.0.0", + "npm-install-checks": "^7.1.0", + "npm-normalize-package-bin": "^4.0.0", + "npm-package-arg": "^12.0.0", "semver": "^7.3.5" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/npm-registry-fetch": { - "version": "17.1.0", - "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-17.1.0.tgz", - "integrity": "sha512-5+bKQRH0J1xG1uZ1zMNvxW0VEyoNWgJpY9UDuluPFLKDfJ9u2JmmjmTJV1srBGQOROfdBMiVvnH2Zvpbm+xkVA==", + "version": "18.0.2", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-18.0.2.tgz", + "integrity": "sha512-LeVMZBBVy+oQb5R6FDV9OlJCcWDU+al10oKpe+nsvcHnG24Z3uM3SvJYKfGJlfGjVU8v9liejCrUR/M5HO5NEQ==", "dev": true, - "license": "ISC", "dependencies": { - "@npmcli/redact": "^2.0.0", + "@npmcli/redact": "^3.0.0", "jsonparse": "^1.3.1", - "make-fetch-happen": "^13.0.0", + "make-fetch-happen": "^14.0.0", "minipass": "^7.0.2", - "minipass-fetch": "^3.0.0", - "minizlib": "^2.1.2", - "npm-package-arg": "^11.0.0", - "proc-log": "^4.0.0" + "minipass-fetch": "^4.0.0", + "minizlib": "^3.0.1", + "npm-package-arg": "^12.0.0", + "proc-log": "^5.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/npm-run-path": { @@ -18627,7 +16475,6 @@ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, - "license": "MIT", "dependencies": { "path-key": "^3.0.0" }, @@ -18635,26 +16482,11 @@ "node": ">=8" } }, - "node_modules/npm-watch": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/npm-watch/-/npm-watch-0.9.0.tgz", - "integrity": "sha512-C5Rgh5+jvY33K1EH8Qjr1hfpH9Nhasc90QJ0W+JyKg2ogE0LOCZI4xirC8QmywW7XinyBpynwxlrN6aPfjc3Hw==", - "dev": true, - "license": "MIT", - "dependencies": { - "nodemon": "^2.0.7", - "through2": "^4.0.2" - }, - "bin": { - "npm-watch": "cli.js" - } - }, "node_modules/nth-check": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "boolbase": "^1.0.0" }, @@ -18662,32 +16494,20 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, - "node_modules/oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "*" - } - }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/object-inspect": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", - "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", + "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -18700,7 +16520,6 @@ "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.4" } @@ -18710,7 +16529,6 @@ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", "dev": true, - "license": "MIT", "dependencies": { "call-bind": "^1.0.5", "define-properties": "^1.2.1", @@ -18729,7 +16547,6 @@ "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", "dev": true, - "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", @@ -18748,7 +16565,6 @@ "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", "dev": true, - "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", @@ -18763,7 +16579,6 @@ "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", "dev": true, - "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", @@ -18788,7 +16603,6 @@ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "dev": true, - "license": "MIT", "dependencies": { "ee-first": "1.1.1" }, @@ -18811,7 +16625,6 @@ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dev": true, - "license": "ISC", "dependencies": { "wrappy": "1" } @@ -18820,7 +16633,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "license": "MIT", "dependencies": { "mimic-fn": "^2.1.0" }, @@ -18855,7 +16667,6 @@ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, - "license": "MIT", "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", @@ -18872,7 +16683,6 @@ "version": "5.4.1", "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", - "license": "MIT", "dependencies": { "bl": "^4.1.0", "chalk": "^4.1.0", @@ -18891,89 +16701,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ora/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/ora/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/ora/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/ora/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "license": "MIT" - }, - "node_modules/ora/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ora/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/ordered-binary": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.5.2.tgz", - "integrity": "sha512-JTo+4+4Fw7FreyAvlSLjb1BBVaxEQAacmjD3jjuyPZclpbEghTvQZbXBb2qPd2LeIMxiHwXBZUcpmG2Gl/mDEA==", + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.5.3.tgz", + "integrity": "sha512-oGFr3T+pYdTGJ+YFEILMpS3es+GiIbs9h/XQrclBXUtd44ey7XwfsMzM31f64I1SQOawDoDr/D823kNCADI8TA==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/os-name": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/os-name/-/os-name-4.0.1.tgz", "integrity": "sha512-xl9MAoU97MH1Xt5K9ERft2YfCAoaO6msy1OBA0ozxEC0x0TmIoE6K3QvgJMMZA9yKGLmHXNY/YZoDbiGDj4zYw==", "dev": true, - "license": "MIT", "dependencies": { "macos-release": "^2.5.0", "windows-release": "^4.0.0" @@ -18990,7 +16730,6 @@ "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -19000,7 +16739,6 @@ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, - "license": "MIT", "dependencies": { "yocto-queue": "^0.1.0" }, @@ -19016,7 +16754,6 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, - "license": "MIT", "dependencies": { "p-limit": "^3.0.2" }, @@ -19028,25 +16765,21 @@ } }, "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.3.tgz", + "integrity": "sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==", "dev": true, - "license": "MIT", - "dependencies": { - "aggregate-error": "^3.0.0" - }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-retry": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.0.tgz", - "integrity": "sha512-JA6nkq6hKyWLLasXQXUrO4z8BUZGUt/LjlJxx8Gb2+2ntodU/SS63YZ8b0LUTbQ8ZB9iwOfhEPhg4ykKnn2KsA==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.1.tgz", + "integrity": "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==", "dev": true, "license": "MIT", "dependencies": { @@ -19076,26 +16809,24 @@ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/pac-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.0.2.tgz", - "integrity": "sha512-BFi3vZnO9X5Qt6NRz7ZOaPja3ic0PhlsmCRYLOpN11+mWBCR6XJDqW5RF3j8jm4WGGQZtBA+bTfxYzeKW73eHg==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.1.0.tgz", + "integrity": "sha512-Z5FnLVVZSnX7WjBg0mhDtydeRZ1xMcATZThjySQUHqr+0ksP8kqaw23fNKkaaN/Z8gwLUs/W7xdl0I75eP2Xyw==", "dev": true, - "license": "MIT", "dependencies": { "@tootallnate/quickjs-emscripten": "^0.23.0", - "agent-base": "^7.0.2", + "agent-base": "^7.1.2", "debug": "^4.3.4", "get-uri": "^6.0.1", "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.5", + "https-proxy-agent": "^7.0.6", "pac-resolver": "^7.0.1", - "socks-proxy-agent": "^8.0.4" + "socks-proxy-agent": "^8.0.5" }, "engines": { "node": ">= 14" @@ -19106,7 +16837,6 @@ "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", "dev": true, - "license": "MIT", "dependencies": { "degenerator": "^5.0.0", "netmask": "^2.0.2" @@ -19116,57 +16846,47 @@ } }, "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, - "license": "BlueOak-1.0.0" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true }, "node_modules/pacote": { - "version": "18.0.6", - "resolved": "https://registry.npmjs.org/pacote/-/pacote-18.0.6.tgz", - "integrity": "sha512-+eK3G27SMwsB8kLIuj4h1FUhHtwiEUo21Tw8wNjmvdlpOEr613edv+8FUsTj/4F/VN5ywGE19X18N7CC2EJk6A==", + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-20.0.0.tgz", + "integrity": "sha512-pRjC5UFwZCgx9kUFDVM9YEahv4guZ1nSLqwmWiLUnDbGsjs+U5w7z6Uc8HNR1a6x8qnu5y9xtGE6D1uAuYz+0A==", "dev": true, - "license": "ISC", "dependencies": { - "@npmcli/git": "^5.0.0", - "@npmcli/installed-package-contents": "^2.0.1", - "@npmcli/package-json": "^5.1.0", - "@npmcli/promise-spawn": "^7.0.0", - "@npmcli/run-script": "^8.0.0", - "cacache": "^18.0.0", + "@npmcli/git": "^6.0.0", + "@npmcli/installed-package-contents": "^3.0.0", + "@npmcli/package-json": "^6.0.0", + "@npmcli/promise-spawn": "^8.0.0", + "@npmcli/run-script": "^9.0.0", + "cacache": "^19.0.0", "fs-minipass": "^3.0.0", "minipass": "^7.0.2", - "npm-package-arg": "^11.0.0", - "npm-packlist": "^8.0.0", - "npm-pick-manifest": "^9.0.0", - "npm-registry-fetch": "^17.0.0", - "proc-log": "^4.0.0", + "npm-package-arg": "^12.0.0", + "npm-packlist": "^9.0.0", + "npm-pick-manifest": "^10.0.0", + "npm-registry-fetch": "^18.0.0", + "proc-log": "^5.0.0", "promise-retry": "^2.0.1", - "sigstore": "^2.2.0", - "ssri": "^10.0.0", + "sigstore": "^3.0.0", + "ssri": "^12.0.0", "tar": "^6.1.11" }, "bin": { "pacote": "bin/index.js" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "dev": true, - "license": "(MIT AND Zlib)" - }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, - "license": "MIT", "dependencies": { "callsites": "^3.0.0" }, @@ -19175,11 +16895,10 @@ } }, "node_modules/parse-imports": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/parse-imports/-/parse-imports-2.1.1.tgz", - "integrity": "sha512-TDT4HqzUiTMO1wJRwg/t/hYk8Wdp3iF/ToMIlAoVQfL1Xs/sTxq1dKWSMjMbQmIarfWKymOyly40+zmPHXMqCA==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/parse-imports/-/parse-imports-2.2.1.tgz", + "integrity": "sha512-OL/zLggRp8mFhKL0rNORUTR4yBYujK/uU+xZL+/0Rgm2QE4nLO9v8PzEweSJEbMGKmDRjJE4R3IMJlL2di4JeQ==", "dev": true, - "license": "Apache-2.0", "dependencies": { "es-module-lexer": "^1.5.3", "slashes": "^3.0.12" @@ -19193,7 +16912,6 @@ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "dev": true, - "license": "MIT", "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -19211,27 +16929,24 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/parse-node-version": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.10" } }, "node_modules/parse5": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", - "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", + "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", "devOptional": true, - "license": "MIT", "dependencies": { - "entities": "^4.4.0" + "entities": "^4.5.0" }, "funding": { "url": "https://github.com/inikulin/parse5?sponsor=1" @@ -19252,6 +16967,19 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, + "node_modules/parse5-html-rewriting-stream/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/parse5-sax-parser": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/parse5-sax-parser/-/parse5-sax-parser-7.0.0.tgz", @@ -19265,12 +16993,23 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, + "node_modules/parse5/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "devOptional": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.8" } @@ -19280,7 +17019,6 @@ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } @@ -19290,24 +17028,15 @@ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==", - "dev": true, - "license": "(WTFPL OR MIT)" - }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } @@ -19316,15 +17045,13 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/path-scurry": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, - "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" @@ -19340,44 +17067,37 @@ "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/path-to-regexp": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", - "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==", + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "dev": true, "license": "MIT" }, "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", + "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==", "dev": true, - "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "dev": true, - "license": "MIT" - }, - "node_modules/performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/picocolors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", - "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true }, "node_modules/picomatch": { @@ -19385,7 +17105,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", "dev": true, - "license": "MIT", "engines": { "node": ">=12" }, @@ -19398,42 +17117,18 @@ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "pinkie": "^2.0.0" - }, "engines": { "node": ">=0.10.0" } }, "node_modules/piscina": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/piscina/-/piscina-4.6.1.tgz", - "integrity": "sha512-z30AwWGtQE+Apr+2WBZensP2lIvwoaMcOPkQlIEmSGMJNUvaYACylPYrQM6wSdUNJlnDVMSpLv7xTMJqlVshOA==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/piscina/-/piscina-4.7.0.tgz", + "integrity": "sha512-b8hvkpp9zS0zsfa939b/jXbe64Z2gZv0Ha7FYPNUiDIB1y2AtxcOZdfP8xN8HFjUaqQiT9gRlfjAsoL8vdJ1Iw==", "dev": true, "license": "MIT", "optionalDependencies": { - "nice-napi": "^1.0.2" + "@napi-rs/nice": "^1.0.1" } }, "node_modules/pkg-dir": { @@ -19441,7 +17136,6 @@ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==", "dev": true, - "license": "MIT", "dependencies": { "find-up": "^6.3.0" }, @@ -19457,7 +17151,6 @@ "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", "dev": true, - "license": "MIT", "dependencies": { "locate-path": "^7.1.0", "path-exists": "^5.0.0" @@ -19474,7 +17167,6 @@ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", "dev": true, - "license": "MIT", "dependencies": { "p-locate": "^6.0.0" }, @@ -19490,7 +17182,6 @@ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", "dev": true, - "license": "MIT", "dependencies": { "yocto-queue": "^1.0.0" }, @@ -19506,7 +17197,6 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", "dev": true, - "license": "MIT", "dependencies": { "p-limit": "^4.0.0" }, @@ -19522,7 +17212,6 @@ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", "dev": true, - "license": "MIT", "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } @@ -19532,7 +17221,6 @@ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.1.1.tgz", "integrity": "sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==", "dev": true, - "license": "MIT", "engines": { "node": ">=12.20" }, @@ -19545,7 +17233,6 @@ "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz", "integrity": "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==", "dev": true, - "license": "MIT", "dependencies": { "@xmldom/xmldom": "^0.8.8", "base64-js": "^1.5.1", @@ -19560,7 +17247,6 @@ "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", "integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==", "dev": true, - "license": "MIT", "engines": { "node": ">=10.0.0" } @@ -19570,15 +17256,14 @@ "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.4" } }, "node_modules/postcss": { - "version": "8.4.41", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz", - "integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==", + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", "dev": true, "funding": [ { @@ -19594,11 +17279,10 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.0.1", - "source-map-js": "^1.2.0" + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" @@ -19609,7 +17293,6 @@ "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-8.1.1.tgz", "integrity": "sha512-0IeqyAsG6tYiDRCYKQJLAmgQr47DX6N7sFSWvQxt6AcupX8DIdmykuk/o/tx0Lze3ErGHJEp5OSRxrelC6+NdQ==", "dev": true, - "license": "MIT", "dependencies": { "cosmiconfig": "^9.0.0", "jiti": "^1.20.0", @@ -19648,7 +17331,6 @@ "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", "dev": true, - "license": "ISC", "engines": { "node": "^10 || ^12 || >= 14" }, @@ -19657,14 +17339,13 @@ } }, "node_modules/postcss-modules-local-by-default": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.5.tgz", - "integrity": "sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", + "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", "dev": true, - "license": "MIT", "dependencies": { "icss-utils": "^5.0.0", - "postcss-selector-parser": "^6.0.2", + "postcss-selector-parser": "^7.0.0", "postcss-value-parser": "^4.1.0" }, "engines": { @@ -19675,13 +17356,12 @@ } }, "node_modules/postcss-modules-scope": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.0.tgz", - "integrity": "sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", + "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", "dev": true, - "license": "ISC", "dependencies": { - "postcss-selector-parser": "^6.0.4" + "postcss-selector-parser": "^7.0.0" }, "engines": { "node": "^10 || ^12 || >= 14" @@ -19695,7 +17375,6 @@ "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", "dev": true, - "license": "ISC", "dependencies": { "icss-utils": "^5.0.0" }, @@ -19707,11 +17386,10 @@ } }, "node_modules/postcss-selector-parser": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", - "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", "dev": true, - "license": "MIT", "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -19724,15 +17402,13 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/prebuild-install": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.2.tgz", "integrity": "sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==", "dev": true, - "license": "MIT", "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", @@ -19758,15 +17434,22 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, + "node_modules/prebuild-install/node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", "dev": true, - "license": "ISC" + "engines": { + "node": ">=8" + } }, "node_modules/prebuild-install/node_modules/tar-fs": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", "dev": true, - "license": "MIT", "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", @@ -19779,7 +17462,6 @@ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "dev": true, - "license": "MIT", "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", @@ -19793,347 +17475,80 @@ }, "node_modules/prelude-ls": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", - "dev": true, - "license": "MIT", - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/proc-log": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", - "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true, - "license": "MIT" - }, - "node_modules/promise-inflight": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", - "dev": true, - "license": "ISC" - }, - "node_modules/promise-retry": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", - "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "err-code": "^2.0.2", - "retry": "^0.12.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/prompts/node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/protractor": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/protractor/-/protractor-7.0.0.tgz", - "integrity": "sha512-UqkFjivi4GcvUQYzqGYNe0mLzfn5jiLmO8w9nMhQoJRLhy2grJonpga2IWhI6yJO30LibWXJJtA4MOIZD2GgZw==", - "deprecated": "We have news to share - Protractor is deprecated and will reach end-of-life by Summer 2023. To learn more and find out about other options please refer to this post on the Angular blog. Thank you for using and contributing to Protractor. https://goo.gle/state-of-e2e-in-angular", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/q": "^0.0.32", - "@types/selenium-webdriver": "^3.0.0", - "blocking-proxy": "^1.0.0", - "browserstack": "^1.5.1", - "chalk": "^1.1.3", - "glob": "^7.0.3", - "jasmine": "2.8.0", - "jasminewd2": "^2.1.0", - "q": "1.4.1", - "saucelabs": "^1.5.0", - "selenium-webdriver": "3.6.0", - "source-map-support": "~0.4.0", - "webdriver-js-extender": "2.1.0", - "webdriver-manager": "^12.1.7", - "yargs": "^15.3.1" - }, - "bin": { - "protractor": "bin/protractor", - "webdriver-manager": "bin/webdriver-manager" - }, - "engines": { - "node": ">=10.13.x" - } - }, - "node_modules/protractor/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/protractor/node_modules/ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/protractor/node_modules/chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/protractor/node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "node_modules/protractor/node_modules/cliui/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/protractor/node_modules/cliui/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/protractor/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/protractor/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/protractor/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/protractor/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/protractor/node_modules/q": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.4.1.tgz", - "integrity": "sha512-/CdEdaw49VZVmyIDGUQKDDT53c7qBkO6g5CefWz91Ae+l4+cRtcDYwMTXh6me4O8TMldeGHG3N2Bl84V78Ywbg==", - "deprecated": "You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other.\n\n(For a CapTP with native promises, see @endo/eventual-send and @endo/captp)", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.6.0", - "teleport": ">=0.2.0" - } - }, - "node_modules/protractor/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/protractor/node_modules/source-map-support": { - "version": "0.4.18", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", - "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, - "license": "MIT", - "dependencies": { - "source-map": "^0.5.6" + "engines": { + "node": ">= 0.8.0" } }, - "node_modules/protractor/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^2.0.0" + "bin": { + "prettier": "bin-prettier.js" }, "engines": { - "node": ">=0.10.0" + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "node_modules/protractor/node_modules/supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", + "node_modules/proc-log": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-5.0.0.tgz", + "integrity": "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==", "dev": true, - "license": "MIT", "engines": { - "node": ">=0.8.0" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/protractor/node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true, - "license": "ISC" + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true }, - "node_modules/protractor/node_modules/yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "node_modules/promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "dev": true + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", "dev": true, - "license": "MIT", "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" + "err-code": "^2.0.2", + "retry": "^0.12.0" }, "engines": { - "node": ">=8" + "node": ">=10" } }, - "node_modules/protractor/node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", "dev": true, - "license": "ISC", "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/prompts/node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, "engines": { "node": ">=6" } @@ -20163,20 +17578,19 @@ } }, "node_modules/proxy-agent": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", - "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", + "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", "dev": true, - "license": "MIT", "dependencies": { - "agent-base": "^7.0.2", + "agent-base": "^7.1.2", "debug": "^4.3.4", "http-proxy-agent": "^7.0.1", - "https-proxy-agent": "^7.0.3", + "https-proxy-agent": "^7.0.6", "lru-cache": "^7.14.1", - "pac-proxy-agent": "^7.0.1", + "pac-proxy-agent": "^7.1.0", "proxy-from-env": "^1.1.0", - "socks-proxy-agent": "^8.0.2" + "socks-proxy-agent": "^8.0.5" }, "engines": { "node": ">= 14" @@ -20187,7 +17601,6 @@ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", "dev": true, - "license": "ISC", "engines": { "node": ">=12" } @@ -20196,37 +17609,20 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", "dev": true, - "license": "MIT", "optional": true }, - "node_modules/psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", - "dev": true, - "license": "MIT" - }, - "node_modules/pstree.remy": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", - "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", - "dev": true, - "license": "MIT" - }, "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", + "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", "dev": true, - "license": "MIT", "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -20236,8 +17632,7 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/q": { "version": "1.5.1", @@ -20245,7 +17640,6 @@ "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", "deprecated": "You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other.\n\n(For a CapTP with native promises, see @endo/eventual-send and @endo/captp)", "dev": true, - "license": "MIT", "engines": { "node": ">=0.6.0", "teleport": ">=0.2.0" @@ -20256,7 +17650,6 @@ "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.9" } @@ -20294,22 +17687,19 @@ "type": "consulting", "url": "https://feross.org/support" } - ], - "license": "MIT" + ] }, "node_modules/queue-tick": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/quick-lru": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } @@ -20319,7 +17709,6 @@ "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "dev": true, - "license": "MIT", "dependencies": { "safe-buffer": "^5.1.0" } @@ -20329,7 +17718,6 @@ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.6" } @@ -20339,7 +17727,6 @@ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "dev": true, - "license": "MIT", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -20355,7 +17742,6 @@ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "dev": true, - "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3" }, @@ -20368,7 +17754,6 @@ "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "dev": true, - "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", @@ -20383,15 +17768,13 @@ "version": "1.3.8", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/rc/node_modules/strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -20401,7 +17784,6 @@ "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", "integrity": "sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==", "dev": true, - "license": "MIT", "dependencies": { "load-json-file": "^4.0.0", "normalize-package-data": "^2.3.2", @@ -20416,7 +17798,6 @@ "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", "integrity": "sha512-YFzFrVvpC6frF1sz8psoHDBGF7fLPc+llq/8NB43oagqWkx8ar5zYtsTORtOjw9W2RHLpWP+zTWwBvf1bCmcSw==", "dev": true, - "license": "MIT", "dependencies": { "find-up": "^2.0.0", "read-pkg": "^3.0.0" @@ -20430,7 +17811,6 @@ "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", "dev": true, - "license": "MIT", "dependencies": { "locate-path": "^2.0.0" }, @@ -20443,7 +17823,6 @@ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", "dev": true, - "license": "MIT", "dependencies": { "p-locate": "^2.0.0", "path-exists": "^3.0.0" @@ -20457,7 +17836,6 @@ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", "dev": true, - "license": "MIT", "dependencies": { "p-try": "^1.0.0" }, @@ -20470,7 +17848,6 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", "dev": true, - "license": "MIT", "dependencies": { "p-limit": "^1.1.0" }, @@ -20483,7 +17860,6 @@ "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", "dev": true, - "license": "MIT", "engines": { "node": ">=4" } @@ -20493,7 +17869,6 @@ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=4" } @@ -20502,15 +17877,13 @@ "version": "2.8.9", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/read-pkg/node_modules/normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "hosted-git-info": "^2.1.4", "resolve": "^1.10.0", @@ -20523,7 +17896,6 @@ "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", "dev": true, - "license": "MIT", "dependencies": { "pify": "^3.0.0" }, @@ -20536,7 +17908,6 @@ "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", "dev": true, - "license": "MIT", "engines": { "node": ">=4" } @@ -20546,7 +17917,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, - "license": "ISC", "bin": { "semver": "bin/semver" } @@ -20555,7 +17925,6 @@ "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -20566,29 +17935,16 @@ } }, "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/readdirp/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "devOptional": true, - "license": "MIT", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz", + "integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==", + "dev": true, "engines": { - "node": ">=8.6" + "node": ">= 14.16.0" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, "node_modules/redent": { @@ -20596,7 +17952,6 @@ "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", "dev": true, - "license": "MIT", "dependencies": { "indent-string": "^4.0.0", "strip-indent": "^3.0.0" @@ -20612,19 +17967,39 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.8.tgz", + "integrity": "sha512-B5dj6usc5dkk8uFliwjwDHM8To5/QwdKz9JcBZ8Ic4G1f0YmeeJTtE/ZTdgRFPAfxZFiUaPhZ1Jcs4qeagItGQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "dunder-proto": "^1.0.0", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "gopd": "^1.2.0", + "which-builtin-type": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/regenerate-unicode-properties": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz", - "integrity": "sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", + "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", "dev": true, - "license": "MIT", "dependencies": { "regenerate": "^1.4.2" }, @@ -20636,14 +18011,13 @@ "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", - "license": "MIT" + "dev": true }, "node_modules/regenerator-transform": { "version": "0.15.2", "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", "dev": true, - "license": "MIT", "dependencies": { "@babel/runtime": "^7.8.4" } @@ -20652,27 +18026,24 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.3.0.tgz", "integrity": "sha512-TVILVSz2jY5D47F4mA4MppkBrafEaiUWJO/TcZHEIuI13AqoZMkK1WMA4Om1YkYbTx+9Ki1/tSUXbceyr9saRg==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/regexp-to-ast": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/regexp-to-ast/-/regexp-to-ast-0.5.0.tgz", "integrity": "sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/regexp.prototype.flags": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", - "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz", + "integrity": "sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==", "dev": true, - "license": "MIT", "dependencies": { - "call-bind": "^1.0.6", + "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-errors": "^1.3.0", - "set-function-name": "^2.0.1" + "set-function-name": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -20682,16 +18053,15 @@ } }, "node_modules/regexpu-core": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", - "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz", + "integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/regjsgen": "^0.8.0", "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.1.0", - "regjsparser": "^0.9.1", + "regenerate-unicode-properties": "^10.2.0", + "regjsgen": "^0.8.0", + "regjsparser": "^0.12.0", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.1.0" }, @@ -20699,26 +18069,34 @@ "node": ">=4" } }, + "node_modules/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", + "dev": true + }, "node_modules/regjsparser": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", - "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz", + "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { - "jsesc": "~0.5.0" + "jsesc": "~3.0.2" }, "bin": { "regjsparser": "bin/parser" } }, "node_modules/regjsparser/node_modules/jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", "dev": true, "bin": { "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" } }, "node_modules/replace": { @@ -20726,7 +18104,6 @@ "resolved": "https://registry.npmjs.org/replace/-/replace-1.2.2.tgz", "integrity": "sha512-C4EDifm22XZM2b2JOYe6Mhn+lBsLBAvLbK8drfUQLTfD1KYl/n3VaW/CDju0Ny4w3xTtegBpg8YNSpFJPUDSjA==", "dev": true, - "license": "MIT", "dependencies": { "chalk": "2.4.2", "minimatch": "3.0.5", @@ -20740,27 +18117,75 @@ "node": ">= 6" } }, + "node_modules/replace/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/replace/node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, - "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, + "node_modules/replace/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/replace/node_modules/cliui": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/replace/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/replace/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/replace/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" } }, "node_modules/replace/node_modules/find-up": { @@ -20768,7 +18193,6 @@ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, - "license": "MIT", "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -20777,12 +18201,20 @@ "node": ">=8" } }, + "node_modules/replace/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/replace/node_modules/locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, - "license": "MIT", "dependencies": { "p-locate": "^4.1.0" }, @@ -20795,7 +18227,6 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.5.tgz", "integrity": "sha512-tUpxzX0VAzJHjLu0xUfFv1gwVp9ba3IOuRAVH2EGuRW8a5emA2FlACLqiT/lDVtS1W+TGNwqz3sWaNyLgDJWuw==", "dev": true, - "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -20808,7 +18239,6 @@ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, - "license": "MIT", "dependencies": { "p-try": "^2.0.0" }, @@ -20824,7 +18254,6 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, - "license": "MIT", "dependencies": { "p-limit": "^2.2.0" }, @@ -20832,19 +18261,29 @@ "node": ">=8" } }, + "node_modules/replace/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/replace/node_modules/y18n": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/replace/node_modules/yargs": { "version": "15.4.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", "dev": true, - "license": "MIT", "dependencies": { "cliui": "^6.0.0", "decamelize": "^1.2.0", @@ -20867,7 +18306,6 @@ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", "dev": true, - "license": "ISC", "dependencies": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" @@ -20876,81 +18314,11 @@ "node": ">=6" } }, - "node_modules/request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/request/node_modules/form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 0.12" - } - }, - "node_modules/request/node_modules/qs": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", - "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/request/node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", - "dev": true, - "license": "MIT", - "bin": { - "uuid": "bin/uuid" - } - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -20959,7 +18327,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -20968,22 +18335,19 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", "dev": true, - "license": "MIT", "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", @@ -21001,17 +18365,24 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, - "license": "MIT", "engines": { "node": ">=4" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/resolve-url-loader": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-5.0.0.tgz", "integrity": "sha512-uZtduh8/8srhBoMx//5bwqjQ+rfYOUq8zC9NrMUGtjBiGTtFJM42s58/36+hTqeqINcnYe08Nj3LkK9lW4N8Xg==", "dev": true, - "license": "MIT", "dependencies": { "adjust-sourcemap-loader": "^4.0.0", "convert-source-map": "^1.7.0", @@ -21028,7 +18399,6 @@ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", "dev": true, - "license": "MIT", "dependencies": { "big.js": "^5.2.2", "emojis-list": "^3.0.0", @@ -21043,7 +18413,6 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, - "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -21052,7 +18421,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "license": "MIT", "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" @@ -21064,15 +18432,13 @@ "node_modules/restore-cursor/node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "license": "ISC" + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, "node_modules/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", "dev": true, - "license": "MIT", "engines": { "node": ">= 4" } @@ -21082,7 +18448,6 @@ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", "dev": true, - "license": "MIT", "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" @@ -21092,15 +18457,13 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/rimraf": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-4.4.1.tgz", "integrity": "sha512-Gk8NlF062+T9CqNGn6h4tls3k6T1+/nXdOcSZVikNVtlRdYpA7wRJJMoXmuvOnLW844rPjdQ7JgXCYM6PPC/og==", "dev": true, - "license": "ISC", "dependencies": { "glob": "^9.2.0" }, @@ -21119,7 +18482,6 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz", "integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==", "dev": true, - "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "minimatch": "^8.0.2", @@ -21138,7 +18500,6 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.4.tgz", "integrity": "sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==", "dev": true, - "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -21154,7 +18515,6 @@ "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", "dev": true, - "license": "ISC", "engines": { "node": ">=8" } @@ -21162,19 +18522,17 @@ "node_modules/roboto-fontface": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/roboto-fontface/-/roboto-fontface-0.10.0.tgz", - "integrity": "sha512-OlwfYEgA2RdboZohpldlvJ1xngOins5d7ejqnIBWr9KaMxsnBqotpptRXTyfNRLnFpqzX6sTDt+X+a+6udnU8g==", - "license": "Apache-2.0" + "integrity": "sha512-OlwfYEgA2RdboZohpldlvJ1xngOins5d7ejqnIBWr9KaMxsnBqotpptRXTyfNRLnFpqzX6sTDt+X+a+6udnU8g==" }, "node_modules/robust-predicates": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", - "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", - "license": "Unlicense" + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==" }, "node_modules/rollup": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.23.0.tgz", - "integrity": "sha512-vXB4IT9/KLDrS2WRXmY22sVB2wTsTwkpxjB8Q3mnakTENcYw3FRmfdYDy/acNmls+lHmDazgrRjK/yQ6hQAtwA==", + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.26.0.tgz", + "integrity": "sha512-ilcl12hnWonG8f+NxU6BlgysVA0gvY2l8N0R84S1HcINbW20bvwuCngJkkInV6LXhwRpucsW5k1ovDwEdBVrNg==", "dev": true, "license": "MIT", "dependencies": { @@ -21188,22 +18546,24 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.23.0", - "@rollup/rollup-android-arm64": "4.23.0", - "@rollup/rollup-darwin-arm64": "4.23.0", - "@rollup/rollup-darwin-x64": "4.23.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.23.0", - "@rollup/rollup-linux-arm-musleabihf": "4.23.0", - "@rollup/rollup-linux-arm64-gnu": "4.23.0", - "@rollup/rollup-linux-arm64-musl": "4.23.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.23.0", - "@rollup/rollup-linux-riscv64-gnu": "4.23.0", - "@rollup/rollup-linux-s390x-gnu": "4.23.0", - "@rollup/rollup-linux-x64-gnu": "4.23.0", - "@rollup/rollup-linux-x64-musl": "4.23.0", - "@rollup/rollup-win32-arm64-msvc": "4.23.0", - "@rollup/rollup-win32-ia32-msvc": "4.23.0", - "@rollup/rollup-win32-x64-msvc": "4.23.0", + "@rollup/rollup-android-arm-eabi": "4.26.0", + "@rollup/rollup-android-arm64": "4.26.0", + "@rollup/rollup-darwin-arm64": "4.26.0", + "@rollup/rollup-darwin-x64": "4.26.0", + "@rollup/rollup-freebsd-arm64": "4.26.0", + "@rollup/rollup-freebsd-x64": "4.26.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.26.0", + "@rollup/rollup-linux-arm-musleabihf": "4.26.0", + "@rollup/rollup-linux-arm64-gnu": "4.26.0", + "@rollup/rollup-linux-arm64-musl": "4.26.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.26.0", + "@rollup/rollup-linux-riscv64-gnu": "4.26.0", + "@rollup/rollup-linux-s390x-gnu": "4.26.0", + "@rollup/rollup-linux-x64-gnu": "4.26.0", + "@rollup/rollup-linux-x64-musl": "4.26.0", + "@rollup/rollup-win32-arm64-msvc": "4.26.0", + "@rollup/rollup-win32-ia32-msvc": "4.26.0", + "@rollup/rollup-win32-x64-msvc": "4.26.0", "fsevents": "~2.3.2" } }, @@ -21212,7 +18572,6 @@ "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-3.6.2.tgz", "integrity": "sha512-OfWGQTb9vnwRjwtA2QwpG2ICclHC3pgXZO5xt8H2EfgDquO0qVdSb5T88L4qJVAEugbS56pAuV4XZM58UX8ulw==", "dev": true, - "license": "MIT", "engines": { "node": "0.12.* || 4.* || 6.* || >= 7.*" } @@ -21235,7 +18594,6 @@ "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -21259,7 +18617,6 @@ "url": "https://feross.org/support" } ], - "license": "MIT", "dependencies": { "queue-microtask": "^1.2.2" } @@ -21267,37 +18624,26 @@ "node_modules/rw": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", - "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", - "license": "BSD-3-Clause" + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" }, "node_modules/rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "license": "Apache-2.0", + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "npm": ">=2.0.0" + "tslib": "^2.1.0" } }, - "node_modules/rxjs/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "license": "0BSD" - }, "node_modules/safe-array-concat": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", - "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", "dev": true, - "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", - "get-intrinsic": "^1.2.4", - "has-symbols": "^1.0.3", + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", "isarray": "^2.0.5" }, "engines": { @@ -21324,19 +18670,17 @@ "type": "consulting", "url": "https://feross.org/support" } - ], - "license": "MIT" + ] }, "node_modules/safe-regex-test": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", - "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", "dev": true, - "license": "MIT", "dependencies": { - "call-bind": "^1.0.6", + "call-bound": "^1.0.2", "es-errors": "^1.3.0", - "is-regex": "^1.1.4" + "is-regex": "^1.2.1" }, "engines": { "node": ">= 0.4" @@ -21348,18 +18692,16 @@ "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/sass": { - "version": "1.77.6", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.6.tgz", - "integrity": "sha512-ByXE1oLD79GVq9Ht1PeHWCPMPB8XHpBuz1r85oByKHjZY6qV6rWnQovQzXJXuQ/XyE1Oj3iPk3lo28uzaRA2/Q==", + "version": "1.80.7", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.80.7.tgz", + "integrity": "sha512-MVWvN0u5meytrSjsU7AWsbhoXi1sc58zADXFllfZzbsBT1GHjjar6JwBINYPRrkx/zqnQ6uqbQuHgE95O+C+eQ==", "dev": true, - "license": "MIT", "dependencies": { - "chokidar": ">=3.0.0 <4.0.0", - "immutable": "^4.0.0", + "chokidar": "^4.0.0", + "immutable": "^5.0.2", "source-map-js": ">=0.6.2 <2.0.0" }, "bin": { @@ -21367,14 +18709,16 @@ }, "engines": { "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" } }, "node_modules/sass-loader": { - "version": "16.0.0", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.0.tgz", - "integrity": "sha512-n13Z+3rU9A177dk4888czcVFiC8CL9dii4qpXWUg3YIIgZEvi9TCFKjOQcbK0kJM7DJu9VucrZFddvNfYCPwtw==", + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.3.tgz", + "integrity": "sha512-gosNorT1RCkuCMyihv6FBRR7BMV06oKRAs+l4UMp1mlcVg9rWN6KMmUj3igjQwmYys4mDP3etEYJgiHRbgHCHA==", "dev": true, - "license": "MIT", "dependencies": { "neo-async": "^2.6.2" }, @@ -21410,68 +18754,17 @@ } } }, - "node_modules/saucelabs": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/saucelabs/-/saucelabs-1.5.0.tgz", - "integrity": "sha512-jlX3FGdWvYf4Q3LFfFWS1QvPg3IGCGWxIc8QBFdPTbpTJnt/v17FHXYVAn7C8sHf1yUXo2c7yIM0isDryfYtHQ==", - "dev": true, - "dependencies": { - "https-proxy-agent": "^2.2.1" - }, - "engines": { - "node": "*" - } - }, - "node_modules/saucelabs/node_modules/agent-base": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", - "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", - "dev": true, - "license": "MIT", - "dependencies": { - "es6-promisify": "^5.0.0" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/saucelabs/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/saucelabs/node_modules/https-proxy-agent": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", - "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" - }, - "engines": { - "node": ">= 4.5.0" - } - }, "node_modules/sax": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.1.4.tgz", "integrity": "sha512-5f3k2PbGGp+YtKJjOItpg3P99IMD84E4HOvcfleTb5joCHNXYLsR9yWFPOYGgaeMPDubQILTCMdsFb2OMeOjtg==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/schema-utils": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", - "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz", + "integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==", "dev": true, - "license": "MIT", "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", @@ -21479,7 +18772,7 @@ "ajv-keywords": "^5.1.0" }, "engines": { - "node": ">= 12.13.0" + "node": ">= 10.13.0" }, "funding": { "type": "opencollective", @@ -21491,7 +18784,6 @@ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", "dev": true, - "license": "MIT", "dependencies": { "ajv": "^8.0.0" }, @@ -21511,73 +18803,6 @@ "dev": true, "license": "MIT" }, - "node_modules/selenium-webdriver": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-3.6.0.tgz", - "integrity": "sha512-WH7Aldse+2P5bbFBO4Gle/nuQOdVwpHMTL6raL3uuBj/vPG07k6uzt3aiahu352ONBr5xXh0hDlM3LhtXPOC4Q==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "jszip": "^3.1.3", - "rimraf": "^2.5.4", - "tmp": "0.0.30", - "xml2js": "^0.4.17" - }, - "engines": { - "node": ">= 6.9.0" - } - }, - "node_modules/selenium-webdriver/node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/selenium-webdriver/node_modules/tmp": { - "version": "0.0.30", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.30.tgz", - "integrity": "sha512-HXdTB7lvMwcb55XFfrTM8CPr/IYREk4hVBFaQ4b/6nInrluSL86hfHm7vu0luYKCfyBZp2trCjpc8caC3vVM3w==", - "dev": true, - "license": "MIT", - "dependencies": { - "os-tmpdir": "~1.0.1" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/selenium-webdriver/node_modules/xml2js": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", - "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", - "dev": true, - "license": "MIT", - "dependencies": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/selenium-webdriver/node_modules/xmlbuilder": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", - "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4.0" - } - }, "node_modules/selfsigned": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", @@ -21597,7 +18822,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, - "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -21660,13 +18884,6 @@ "node": ">=4" } }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, "node_modules/send/node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -21682,7 +18899,6 @@ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "dev": true, - "license": "BSD-3-Clause", "dependencies": { "randombytes": "^2.1.0" } @@ -21793,15 +19009,13 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/set-function-length": { "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==", "dev": true, - "license": "MIT", "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", @@ -21819,7 +19033,6 @@ "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", "dev": true, - "license": "MIT", "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", @@ -21830,26 +19043,17 @@ "node": ">= 0.4" } }, - "node_modules/setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", - "dev": true, - "license": "MIT" - }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", "dev": true, - "license": "MIT", "dependencies": { "kind-of": "^6.0.2" }, @@ -21863,7 +19067,6 @@ "integrity": "sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w==", "dev": true, "hasInstallScript": true, - "license": "Apache-2.0", "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.2", @@ -21881,19 +19084,26 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/sharp/node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/sharp/node_modules/node-addon-api": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, - "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" }, @@ -21906,32 +19116,87 @@ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/shell-quote": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", - "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.2.tgz", + "integrity": "sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==", "dev": true, "license": "MIT", + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/side-channel": { - "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==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "dev": true, - "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -21945,7 +19210,6 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, - "license": "ISC", "engines": { "node": ">=14" }, @@ -21954,21 +19218,20 @@ } }, "node_modules/sigstore": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-2.3.1.tgz", - "integrity": "sha512-8G+/XDU8wNsJOQS5ysDVO0Etg9/2uA5gR9l4ZwijjlwxBcrU6RPfwi2+jJmbP+Ap1Hlp/nVAaEO4Fj22/SL2gQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-3.0.0.tgz", + "integrity": "sha512-PHMifhh3EN4loMcHCz6l3v/luzgT3za+9f8subGgeMNjbJjzH4Ij/YoX3Gvu+kaouJRIlVdTHHCREADYf+ZteA==", "dev": true, - "license": "Apache-2.0", "dependencies": { - "@sigstore/bundle": "^2.3.2", - "@sigstore/core": "^1.0.0", + "@sigstore/bundle": "^3.0.0", + "@sigstore/core": "^2.0.0", "@sigstore/protobuf-specs": "^0.3.2", - "@sigstore/sign": "^2.3.2", - "@sigstore/tuf": "^2.3.4", - "@sigstore/verify": "^1.2.1" + "@sigstore/sign": "^3.0.0", + "@sigstore/tuf": "^3.0.0", + "@sigstore/verify": "^2.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/simple-concat": { @@ -21989,8 +19252,7 @@ "type": "consulting", "url": "https://feross.org/support" } - ], - "license": "MIT" + ] }, "node_modules/simple-get": { "version": "4.0.1", @@ -22011,7 +19273,6 @@ "url": "https://feross.org/support" } ], - "license": "MIT", "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", @@ -22023,7 +19284,6 @@ "resolved": "https://registry.npmjs.org/simple-plist/-/simple-plist-1.3.1.tgz", "integrity": "sha512-iMSw5i0XseMnrhtIzRb7XpQEXepa9xhWxGUojHBL43SIpQuDQkh3Wpy67ZbDzZVr6EKxvwVChnVpdl8hEVLDiw==", "dev": true, - "license": "MIT", "dependencies": { "bplist-creator": "0.1.0", "bplist-parser": "0.3.1", @@ -22035,7 +19295,6 @@ "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.3.1.tgz", "integrity": "sha512-PyJxiNtA5T2PlLIeBot4lbp7rj4OadzjnMZD/G5zuBNt8ei/yCU7+wW0h2bag9vr8c+/WuRWmSxbqAl9hL1rBA==", "dev": true, - "license": "MIT", "dependencies": { "big-integer": "1.6.x" }, @@ -22048,7 +19307,6 @@ "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", "dev": true, - "license": "MIT", "dependencies": { "is-arrayish": "^0.3.1" } @@ -22057,62 +19315,37 @@ "version": "0.3.2", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/simple-update-notifier": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz", - "integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "~7.0.0" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/simple-update-notifier/node_modules/semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } + "dev": true }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", "dev": true, - "license": "MIT", "engines": { - "node": ">=8" + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/slashes": { "version": "3.0.12", "resolved": "https://registry.npmjs.org/slashes/-/slashes-3.0.12.tgz", "integrity": "sha512-Q9VME8WyGkc7pJf6QEkj3wE+2CnvZMI+XJhwdTPR8Z/kWQRXi7boAWLDibRPyHRTUTPx5FaU7MsyrjI3yLB4HA==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/slice-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", "dev": true, - "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", @@ -22125,65 +19358,27 @@ "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, - "node_modules/slice-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/slice-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/slice-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, "node_modules/smart-buffer": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", "dev": true, - "license": "MIT", "engines": { "node": ">= 6.0.0", "npm": ">= 3.0.0" } }, "node_modules/socket.io": { - "version": "4.7.5", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.5.tgz", - "integrity": "sha512-DmeAkF6cwM9jSfmp6Dr/5/mfMwb5Z5qRrSXLpo3Fq5SqyU8CMF15jIN4ZhfSwu35ksM1qmHZDQ/DK5XTccSTvA==", + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", + "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", "dev": true, - "license": "MIT", "dependencies": { "accepts": "~1.3.4", "base64id": "~2.0.0", "cors": "~2.8.5", "debug": "~4.3.2", - "engine.io": "~6.5.2", + "engine.io": "~6.6.0", "socket.io-adapter": "~2.5.2", "socket.io-parser": "~4.2.4" }, @@ -22196,18 +19391,33 @@ "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", "dev": true, - "license": "MIT", "dependencies": { "debug": "~4.3.4", "ws": "~8.17.1" } }, + "node_modules/socket.io-adapter/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/socket.io-parser": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", "dev": true, - "license": "MIT", "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1" @@ -22216,6 +19426,40 @@ "node": ">=10.0.0" } }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/sockjs": { "version": "0.3.24", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", @@ -22243,7 +19487,6 @@ "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", "dev": true, - "license": "MIT", "dependencies": { "ip-address": "^9.0.5", "smart-buffer": "^4.2.0" @@ -22254,13 +19497,12 @@ } }, "node_modules/socks-proxy-agent": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.4.tgz", - "integrity": "sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw==", + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", "dev": true, - "license": "MIT", "dependencies": { - "agent-base": "^7.1.1", + "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" }, @@ -22273,7 +19515,6 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", "dev": true, - "license": "BSD-3-Clause", "engines": { "node": ">= 8" } @@ -22292,7 +19533,6 @@ "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-5.0.0.tgz", "integrity": "sha512-k2Dur7CbSLcAH73sBcIkV5xjPV4SzqO1NJ7+XaQl8if3VODDUj3FNchNGpqgJSKbvUfJuhVdv8K2Eu8/TNl2eA==", "dev": true, - "license": "MIT", "dependencies": { "iconv-lite": "^0.6.3", "source-map-js": "^1.0.2" @@ -22313,7 +19553,6 @@ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, - "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -22324,7 +19563,6 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, - "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -22333,15 +19571,13 @@ "version": "1.4.8", "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "deprecated": "Please use @jridgewell/sourcemap-codec instead", - "license": "MIT" + "deprecated": "Please use @jridgewell/sourcemap-codec instead" }, "node_modules/spdx-correct": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", "dev": true, - "license": "Apache-2.0", "dependencies": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" @@ -22352,7 +19588,6 @@ "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", "dev": true, - "license": "MIT", "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" @@ -22362,15 +19597,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", - "dev": true, - "license": "CC-BY-3.0" + "dev": true }, "node_modules/spdx-expression-parse": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz", "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==", "dev": true, - "license": "MIT", "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" @@ -22380,8 +19613,7 @@ "version": "3.0.20", "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.20.tgz", "integrity": "sha512-jg25NiDV/1fLtSgEgyvVyDunvaNHbuwF9lfNV17gSmPFAlYzdfNBlLtLzXTevwkPj7DhGbmN9VnmJIgLnhvaBw==", - "dev": true, - "license": "CC0-1.0" + "dev": true }, "node_modules/spdy": { "version": "4.0.2", @@ -22420,7 +19652,6 @@ "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", "dev": true, - "license": "MIT", "dependencies": { "through": "2" }, @@ -22433,7 +19664,6 @@ "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", "dev": true, - "license": "ISC", "dependencies": { "readable-stream": "^3.0.0" } @@ -22442,68 +19672,37 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", - "dev": true, - "license": "BSD-3-Clause" + "dev": true }, "node_modules/ssh-config": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/ssh-config/-/ssh-config-1.1.6.tgz", "integrity": "sha512-ZPO9rECxzs5JIQ6G/2EfL1I9ho/BVZkx9HRKn8+0af7QgwAmumQ7XBFP1ggMyPMo+/tUbmv0HFdv4qifdO/9JA==", - "dev": true, - "license": "MIT" - }, - "node_modules/sshpk": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", - "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - }, - "bin": { - "sshpk-conv": "bin/sshpk-conv", - "sshpk-sign": "bin/sshpk-sign", - "sshpk-verify": "bin/sshpk-verify" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sshpk/node_modules/jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/ssri": { - "version": "10.0.6", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz", - "integrity": "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-12.0.0.tgz", + "integrity": "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ==", "dev": true, - "license": "ISC", "dependencies": { "minipass": "^7.0.3" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, + "node_modules/stable-hash": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.4.tgz", + "integrity": "sha512-LjdcbuBeLcdETCrPn9i8AYAZ1eCtu4ECAWtP7UleOiZ9LzVxRzzUZEoZ8zB24nhkQnDWyET0I+3sWokSDS3E7g==", + "dev": true + }, "node_modules/statuses": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.6" } @@ -22513,7 +19712,6 @@ "resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-2.2.0.tgz", "integrity": "sha512-uyQK/mx5QjHun80FLJTfaWE7JtwfRMKBLkMne6udYOmvH0CawotVa7TfgYHzAnpphn4+TweIx1QKMnRIbipmUg==", "dev": true, - "license": "Unlicense", "engines": { "node": ">= 0.10.0" } @@ -22523,7 +19721,6 @@ "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", "integrity": "sha512-3PnJbYgS56AeWgtKF5jtJRT6uFJe56Z0Hc5Ngg/6sI6rIt8iiMBTa9cvdyFfpMQjaVHr8dusbNeFGIIonxOvKw==", "dev": true, - "license": "MIT", "dependencies": { "duplexer2": "~0.1.0", "readable-stream": "^2.0.2" @@ -22533,15 +19730,13 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/stream-combiner2/node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dev": true, - "license": "MIT", "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -22556,15 +19751,13 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/stream-combiner2/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==", "dev": true, - "license": "MIT", "dependencies": { "safe-buffer": "~5.1.0" } @@ -22574,7 +19767,6 @@ "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.5.tgz", "integrity": "sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==", "dev": true, - "license": "MIT", "dependencies": { "date-format": "^4.0.14", "debug": "^4.3.4", @@ -22589,7 +19781,6 @@ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "dev": true, - "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", @@ -22604,7 +19795,6 @@ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", "dev": true, - "license": "MIT", "optionalDependencies": { "graceful-fs": "^4.1.6" } @@ -22614,17 +19804,15 @@ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", "dev": true, - "license": "MIT", "engines": { "node": ">= 4.0.0" } }, "node_modules/streamx": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.19.0.tgz", - "integrity": "sha512-5z6CNR4gtkPbwlxyEqoDGDmWIzoNJqCBt4Eac1ICP9YaIT08ct712cFj0u1rx4F8luAuL+3Qc+RFIdI4OX00kg==", + "version": "2.21.1", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.21.1.tgz", + "integrity": "sha512-PhP9wUnFLa+91CPy3N6tiQsK+gnYyUNuk15S3YG/zjYE7RuPeCjJngqnzpC31ow0lzBHQ+QGO4cNJnd0djYUsw==", "dev": true, - "license": "MIT", "dependencies": { "fast-fifo": "^1.3.2", "queue-tick": "^1.0.1", @@ -22638,7 +19826,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "license": "MIT", "dependencies": { "safe-buffer": "~5.2.0" } @@ -22648,7 +19835,6 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, - "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -22664,7 +19850,6 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, - "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -22675,16 +19860,18 @@ } }, "node_modules/string.prototype.trim": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", - "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", "dev": true, - "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", "define-properties": "^1.2.1", - "es-abstract": "^1.23.0", - "es-object-atoms": "^1.0.0" + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -22694,16 +19881,19 @@ } }, "node_modules/string.prototype.trimend": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", - "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", "dev": true, - "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -22713,7 +19903,6 @@ "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", "dev": true, - "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", @@ -22730,7 +19919,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -22744,7 +19932,6 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, - "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -22757,7 +19944,6 @@ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", "dev": true, - "license": "MIT", "engines": { "node": ">=4" } @@ -22767,7 +19953,6 @@ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", "dev": true, - "license": "MIT", "engines": { "node": ">=6" } @@ -22777,7 +19962,6 @@ "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", "dev": true, - "license": "MIT", "dependencies": { "min-indent": "^1.0.0" }, @@ -22790,7 +19974,6 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" }, @@ -22804,7 +19987,6 @@ "integrity": "sha512-6WTxW1EB6yCxV5VFOIPQruWGHqc3yI7hEmZK6h+pyk69Lk/Ut7rLUY6W/ONF2MjBuGjvmMiIpsrVJ2vjrHlslA==", "deprecated": "Please upgrade to v9.0.0+ as we have fixed a public vulnerability with formidable dependency. Note that v9.0.0+ requires Node.js v14.18.0+. See https://github.com/ladjs/superagent/pull/1800 for insight. This project is supported and maintained by the team at Forward Email @ https://forwardemail.net", "dev": true, - "license": "MIT", "dependencies": { "component-emitter": "^1.3.0", "cookiejar": "^2.1.4", @@ -22826,7 +20008,6 @@ "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.2.tgz", "integrity": "sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g==", "dev": true, - "license": "MIT", "dependencies": { "dezalgo": "^1.0.4", "hexoid": "^1.0.0", @@ -22837,17 +20018,24 @@ "url": "https://ko-fi.com/tunnckoCore/commissions" } }, + "node_modules/superagent/node_modules/hexoid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", + "integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "license": "MIT", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dependencies": { - "has-flag": "^3.0.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=4" + "node": ">=8" } }, "node_modules/supports-preserve-symlinks-flag": { @@ -22855,7 +20043,6 @@ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -22864,9 +20051,9 @@ } }, "node_modules/swiper": { - "version": "11.1.14", - "resolved": "https://registry.npmjs.org/swiper/-/swiper-11.1.14.tgz", - "integrity": "sha512-VbQLQXC04io6AoAjIUWuZwW4MSYozkcP9KjLdrsG/00Q/yiwvhz9RQyt0nHXV10hi9NVnDNy1/wv7Dzq1lkOCQ==", + "version": "11.1.15", + "resolved": "https://registry.npmjs.org/swiper/-/swiper-11.1.15.tgz", + "integrity": "sha512-IzWeU34WwC7gbhjKsjkImTuCRf+lRbO6cnxMGs88iVNKDwV+xQpBCJxZ4bNH6gSrIbbyVJ1kuGzo3JTtz//CBw==", "funding": [ { "type": "patreon", @@ -22886,17 +20073,15 @@ "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", "integrity": "sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.10" } }, "node_modules/synckit": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.1.tgz", - "integrity": "sha512-7gr8p9TQP6RAHusBOSLs46F4564ZrjV8xFmw5zCmgmhGUcw2hxsShhJ6CEiHQMgPDwAQ1fWHPM0ypc4RMAig4A==", + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz", + "integrity": "sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==", "dev": true, - "license": "MIT", "dependencies": { "@pkgr/core": "^0.1.0", "tslib": "^2.6.2" @@ -22913,7 +20098,6 @@ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=6" } @@ -22923,7 +20107,6 @@ "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", "dev": true, - "license": "ISC", "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", @@ -22941,7 +20124,6 @@ "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", "integrity": "sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==", "dev": true, - "license": "MIT", "dependencies": { "pump": "^3.0.0", "tar-stream": "^3.1.5" @@ -22956,7 +20138,6 @@ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", "dev": true, - "license": "MIT", "dependencies": { "b4a": "^1.6.4", "fast-fifo": "^1.2.0", @@ -22968,7 +20149,6 @@ "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", "dev": true, - "license": "ISC", "dependencies": { "minipass": "^3.0.0" }, @@ -22981,7 +20161,6 @@ "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, - "license": "ISC", "dependencies": { "yallist": "^4.0.0" }, @@ -22994,7 +20173,31 @@ "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", "dev": true, - "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/tar/node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/tar/node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, "engines": { "node": ">=8" } @@ -23004,7 +20207,6 @@ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "dev": true, - "license": "MIT", "bin": { "mkdirp": "bin/cmd.js" }, @@ -23016,15 +20218,13 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/temp-dir": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } @@ -23034,7 +20234,6 @@ "resolved": "https://registry.npmjs.org/tempy/-/tempy-1.0.1.tgz", "integrity": "sha512-biM9brNqxSc04Ee71hzFbryD11nX7VPhQQY32AdDmjFvodsRFz/3ufeoTZ6uYkRFfGo188tENcASNs3vTdsM0w==", "dev": true, - "license": "MIT", "dependencies": { "del": "^6.0.0", "is-stream": "^2.0.0", @@ -23054,7 +20253,6 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz", "integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==", "dev": true, - "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" }, @@ -23063,11 +20261,10 @@ } }, "node_modules/terser": { - "version": "5.31.6", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.6.tgz", - "integrity": "sha512-PQ4DAriWzKj+qgehQ7LK5bQqCFNMmlhjR2PFFLuqGCpuCAauxemVBWwWOxo3UIwWQx8+Pr61Df++r76wDmkQBg==", + "version": "5.36.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.36.0.tgz", + "integrity": "sha512-IYV9eNMuFAV4THUspIRXkLakHnV6XO7FEdtKjf/mDyrnqUg9LnlOn6/RwRvM9SZjR4GUq8Nk8zj67FzVARr74w==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.8.2", @@ -23082,17 +20279,16 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.10", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", - "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", + "version": "5.3.11", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.11.tgz", + "integrity": "sha512-RVCsMfuD0+cTt3EwX8hSl2Ks56EbFHWmhluwcqoPKtBnfjiT6olaq7PRIRfhyU8nnC2MrnDrBLfrD/RGE+cVXQ==", "dev": true, - "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.20", + "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.1", - "terser": "^5.26.0" + "schema-utils": "^4.3.0", + "serialize-javascript": "^6.0.2", + "terser": "^5.31.1" }, "engines": { "node": ">= 10.13.0" @@ -23116,72 +20312,17 @@ } } }, - "node_modules/terser-webpack-plugin/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/terser-webpack-plugin/node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/terser-webpack-plugin/node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, "node_modules/terser/node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/text-decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.1.1.tgz", - "integrity": "sha512-8zll7REEv4GDD3x4/0pW+ppIxSNs7H1J10IKFZsuOMscumCdM2a+toDGLPA3T+1+fLBql4zbt5z83GEQGGV5VA==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", + "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", "dev": true, - "license": "Apache-2.0", "dependencies": { "b4a": "^1.6.4" } @@ -23191,18 +20332,10 @@ "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz", "integrity": "sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.10" } }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true, - "license": "MIT" - }, "node_modules/thingies": { "version": "1.21.0", "resolved": "https://registry.npmjs.org/thingies/-/thingies-1.21.0.tgz", @@ -23220,15 +20353,13 @@ "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/through2": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", "dev": true, - "license": "MIT", "dependencies": { "readable-stream": "3" } @@ -23245,27 +20376,15 @@ "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", "dev": true, - "license": "MIT", "engines": { "node": ">=14.14" } }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "devOptional": true, - "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -23278,51 +20397,15 @@ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.6" } }, - "node_modules/touch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", - "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", - "dev": true, - "license": "ISC", - "bin": { - "nodetouch": "bin/nodetouch.js" - } - }, - "node_modules/tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/tough-cookie/node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/tree-dump": { "version": "1.0.2", @@ -23346,7 +20429,6 @@ "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", "dev": true, - "license": "MIT", "bin": { "tree-kill": "cli.js" } @@ -23356,17 +20438,15 @@ "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/ts-api-utils": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", - "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", + "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", "dev": true, - "license": "MIT", "engines": { "node": ">=16" }, @@ -23379,7 +20459,6 @@ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, - "license": "MIT", "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -23423,7 +20502,6 @@ "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", "dev": true, - "license": "MIT", "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.2", @@ -23436,7 +20514,6 @@ "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, - "license": "MIT", "dependencies": { "minimist": "^1.2.0" }, @@ -23445,24 +20522,22 @@ } }, "node_modules/tslib": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", - "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", - "license": "0BSD" + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" }, "node_modules/tuf-js": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-2.2.1.tgz", - "integrity": "sha512-GwIJau9XaA8nLVbUXsN3IlFi7WmQ48gBUrl3FTkkL/XLu/POhBzfmX9hd33FNMX1qAsfl6ozO1iMmW9NC8YniA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-3.0.1.tgz", + "integrity": "sha512-+68OP1ZzSF84rTckf3FA95vJ1Zlx/uaXyiiKyPd1pA4rZNkpEvDAKmsu1xUSmbF/chCRYgZ6UZkDwC7PmzmAyA==", "dev": true, - "license": "MIT", "dependencies": { - "@tufjs/models": "2.0.1", - "debug": "^4.3.4", - "make-fetch-happen": "^13.0.1" + "@tufjs/models": "3.0.1", + "debug": "^4.3.6", + "make-fetch-happen": "^14.0.1" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/tunnel-agent": { @@ -23470,7 +20545,6 @@ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", "dev": true, - "license": "Apache-2.0", "dependencies": { "safe-buffer": "^5.0.1" }, @@ -23478,19 +20552,11 @@ "node": "*" } }, - "node_modules/tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", - "dev": true, - "license": "Unlicense" - }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, - "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1" }, @@ -23503,7 +20569,6 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "dev": true, - "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" }, @@ -23516,7 +20581,6 @@ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", "dev": true, - "license": "MIT", "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" @@ -23530,7 +20594,6 @@ "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", "dev": true, - "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "es-errors": "^1.3.0", @@ -23545,7 +20608,6 @@ "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", "dev": true, - "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "for-each": "^0.3.3", @@ -23561,18 +20623,18 @@ } }, "node_modules/typed-array-byte-offset": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", - "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.3.tgz", + "integrity": "sha512-GsvTyUHTriq6o/bHcTd0vM7OQ9JEdlvluu9YISaA7+KzDzPaIzEeDFNkTfhdE3MYcNhNi0vq/LlegYgIs5yPAw==", "dev": true, - "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13" + "is-typed-array": "^1.1.13", + "reflect.getprototypeof": "^1.0.6" }, "engines": { "node": ">= 0.4" @@ -23582,18 +20644,17 @@ } }, "node_modules/typed-array-length": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", - "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", "dev": true, - "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", - "has-proto": "^1.0.3", "is-typed-array": "^1.1.13", - "possible-typed-array-names": "^1.0.0" + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" }, "engines": { "node": ">= 0.4" @@ -23606,25 +20667,22 @@ "version": "1.0.9", "resolved": "https://registry.npmjs.org/typed-assert/-/typed-assert-1.0.9.tgz", "integrity": "sha512-KNNZtayBCtmnNmbo5mG47p1XsCyrx6iVqomjcZnec/1Y5GGARaxPs6r49RnSPeUP3YjNYiU9sQHAtY4BBvnZwg==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/typedarray-to-buffer": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", "dev": true, - "license": "MIT", "dependencies": { "is-typedarray": "^1.0.0" } }, "node_modules/typescript": { - "version": "5.4.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", - "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", "dev": true, - "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -23638,7 +20696,6 @@ "resolved": "https://registry.npmjs.org/typescript-strict-plugin/-/typescript-strict-plugin-2.4.4.tgz", "integrity": "sha512-OXcWHQk+pW9gqEL/Mb1eTgj/Yiqk1oHBERr9v4VInPOYN++p+cXejmQK/h/VlUPGD++FXQ8pgiqVMyEtxU4T6A==", "dev": true, - "license": "MIT", "dependencies": { "chalk": "^3.0.0", "execa": "^4.0.0", @@ -23651,89 +20708,28 @@ "update-strict-comments": "dist/cli/update-strict-comments/index.js" } }, - "node_modules/typescript-strict-plugin/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/typescript-strict-plugin/node_modules/chalk": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", "dev": true, - "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/typescript-strict-plugin/node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/typescript-strict-plugin/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/typescript-strict-plugin/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/typescript-strict-plugin/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", + }, "engines": { "node": ">=8" } }, - "node_modules/typescript-strict-plugin/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/typescript-strict-plugin/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dev": true, - "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" } }, "node_modules/typescript-strict-plugin/node_modules/wrap-ansi": { @@ -23741,7 +20737,6 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, - "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -23759,7 +20754,6 @@ "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "dev": true, - "license": "MIT", "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", @@ -23774,9 +20768,9 @@ } }, "node_modules/ua-parser-js": { - "version": "0.7.38", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.38.tgz", - "integrity": "sha512-fYmIy7fKTSFAhG3fuPlubeGaMoAd6r0rSnfEsO5nEY55i26KSLt9EH7PLQiiqPUhNqYIJvSkTy1oArIcXAbPbA==", + "version": "0.7.39", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.39.tgz", + "integrity": "sha512-IZ6acm6RhQHNibSt7+c09hhvsKy9WUr4DVbeq9U8o71qxyYtJpQeDxQnMrVqnIFMLcQjHO0I9wgfO2vIahht4w==", "dev": true, "funding": [ { @@ -23792,17 +20786,18 @@ "url": "https://github.com/sponsors/faisalman" } ], - "license": "MIT", + "bin": { + "ua-parser-js": "script/cli.js" + }, "engines": { "node": "*" } }, "node_modules/uglify-js": { - "version": "3.19.2", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.2.tgz", - "integrity": "sha512-S8KA6DDI47nQXJSi2ctQ629YzwOVs+bQML6DAtvy0wgNdpi+0ySpQK0g2pxBq2xfF2z3YCscu7NNA8nXT9PlIQ==", + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", "dev": true, - "license": "BSD-2-Clause", "optional": true, "bin": { "uglifyjs": "bin/uglifyjs" @@ -23812,41 +20807,34 @@ } }, "node_modules/unbox-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", - "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", "dev": true, - "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", + "call-bound": "^1.0.3", "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/undefsafe": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", - "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", - "dev": true, - "license": "MIT" - }, "node_modules/undici-types": { "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", - "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", "dev": true, - "license": "MIT", "engines": { "node": ">=4" } @@ -23856,7 +20844,6 @@ "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", "dev": true, - "license": "MIT", "dependencies": { "unicode-canonical-property-names-ecmascript": "^2.0.0", "unicode-property-aliases-ecmascript": "^2.0.0" @@ -23866,11 +20853,10 @@ } }, "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", - "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", + "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", "dev": true, - "license": "MIT", "engines": { "node": ">=4" } @@ -23880,7 +20866,6 @@ "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", "dev": true, - "license": "MIT", "engines": { "node": ">=4" } @@ -23890,7 +20875,6 @@ "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=18" }, @@ -23899,29 +20883,27 @@ } }, "node_modules/unique-filename": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", - "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-4.0.0.tgz", + "integrity": "sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ==", "dev": true, - "license": "ISC", "dependencies": { - "unique-slug": "^4.0.0" + "unique-slug": "^5.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/unique-slug": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", - "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-5.0.0.tgz", + "integrity": "sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg==", "dev": true, - "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/unique-string": { @@ -23929,7 +20911,6 @@ "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", "dev": true, - "license": "MIT", "dependencies": { "crypto-random-string": "^2.0.0" }, @@ -23942,7 +20923,6 @@ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, - "license": "MIT", "engines": { "node": ">= 10.0.0" } @@ -23952,7 +20932,6 @@ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.8" } @@ -23962,15 +20941,14 @@ "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/update-browserslist-db": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", - "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", "dev": true, "funding": [ { @@ -23986,10 +20964,9 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { - "escalade": "^3.1.2", - "picocolors": "^1.0.1" + "escalade": "^3.2.0", + "picocolors": "^1.1.0" }, "bin": { "update-browserslist-db": "cli.js" @@ -24002,7 +20979,6 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" } @@ -24011,7 +20987,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "license": "MIT", "engines": { "node": ">=6" } @@ -24019,45 +20994,40 @@ "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "license": "MIT" + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.4.0" } }, "node_modules/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.3.tgz", + "integrity": "sha512-d0z310fCWv5dJwnX1Y/MncBAqGMKEzlBb1AOf7z9K8ALnd0utBX/msg/fA0+sbyN1ihbMsLhrBlnl1ak7Wa0rg==", "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" ], - "license": "MIT", "bin": { - "uuid": "dist/bin/uuid" + "uuid": "dist/esm/bin/uuid" } }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", "dev": true, - "license": "Apache-2.0", "dependencies": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" @@ -24068,20 +21038,18 @@ "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", "dev": true, - "license": "MIT", "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" } }, "node_modules/validate-npm-package-name": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", - "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-6.0.0.tgz", + "integrity": "sha512-d7KLgL1LD3U3fgnvWEY1cQXoO/q6EQ1BSz48Sa149V/5zVTAbgmZIpyI8TRi6U9/JNyeYLlTKsEMPtLC27RFUg==", "dev": true, - "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/vary": { @@ -24089,30 +21057,14 @@ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.8" } }, - "node_modules/verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", - "dev": true, - "engines": [ - "node >=0.6.0" - ], - "license": "MIT", - "dependencies": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, "node_modules/vite": { - "version": "5.4.6", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.6.tgz", - "integrity": "sha512-IeL5f8OO5nylsgzd9tq4qD2QqI0k2CQLGrWD0rCN0EQJZpBK5vJAx0I+GDkMOXxQX/OfFHMuLIx6ddAxGX/k+Q==", + "version": "5.4.11", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz", + "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==", "dev": true, "license": "MIT", "dependencies": { @@ -24599,51 +21551,20 @@ "@esbuild/win32-x64": "0.21.5" } }, - "node_modules/vite/node_modules/postcss": { - "version": "8.4.47", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", - "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.1.0", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, "node_modules/void-elements": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", "integrity": "sha512-qZKX4RnBzH2ugr8Lxa7x+0V6XD9Sb/ouARtiasEQCHB1EVU4NXtmHsDDrx1dO4ne5fc3J6EW05BP1Dl0z0iung==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/watchpack": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.1.tgz", - "integrity": "sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", + "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", "dev": true, - "license": "MIT", "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -24666,7 +21587,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", - "license": "MIT", "dependencies": { "defaults": "^1.0.3" } @@ -24676,244 +21596,28 @@ "resolved": "https://registry.npmjs.org/weak-lru-cache/-/weak-lru-cache-1.2.2.tgz", "integrity": "sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw==", "dev": true, - "license": "MIT" - }, - "node_modules/webdriver-js-extender": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/webdriver-js-extender/-/webdriver-js-extender-2.1.0.tgz", - "integrity": "sha512-lcUKrjbBfCK6MNsh7xaY2UAUmZwe+/ib03AjVOpFobX4O7+83BUveSrLfU0Qsyb1DaKJdQRbuU+kM9aZ6QUhiQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/selenium-webdriver": "^3.0.0", - "selenium-webdriver": "^3.0.1" - }, - "engines": { - "node": ">=6.9.x" - } - }, - "node_modules/webdriver-manager": { - "version": "12.1.9", - "resolved": "https://registry.npmjs.org/webdriver-manager/-/webdriver-manager-12.1.9.tgz", - "integrity": "sha512-Yl113uKm8z4m/KMUVWHq1Sjtla2uxEBtx2Ue3AmIlnlPAKloDn/Lvmy6pqWCUersVISpdMeVpAaGbNnvMuT2LQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "adm-zip": "^0.5.2", - "chalk": "^1.1.1", - "del": "^2.2.0", - "glob": "^7.0.3", - "ini": "^1.3.4", - "minimist": "^1.2.0", - "q": "^1.4.1", - "request": "^2.87.0", - "rimraf": "^2.5.2", - "semver": "^5.3.0", - "xml2js": "^0.4.17" - }, - "bin": { - "webdriver-manager": "bin/webdriver-manager" - }, - "engines": { - "node": ">=6.9.x" - } - }, - "node_modules/webdriver-manager/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webdriver-manager/node_modules/ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webdriver-manager/node_modules/array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-uniq": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webdriver-manager/node_modules/chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webdriver-manager/node_modules/del": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", - "integrity": "sha512-Z4fzpbIRjOu7lO5jCETSWoqUDVe0IPOlfugBsF6suen2LKDlVb4QZpKEM9P+buNJ4KI1eN7I083w/pbKUpsrWQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "globby": "^5.0.0", - "is-path-cwd": "^1.0.0", - "is-path-in-cwd": "^1.0.0", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "rimraf": "^2.2.8" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webdriver-manager/node_modules/globby": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", - "integrity": "sha512-HJRTIH2EeH44ka+LWig+EqT2ONSYpVlNfx6pyd592/VF1TbfljJ7elwie7oSwcViLGqOdWocSdu2txwBF9bjmQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-union": "^1.0.1", - "arrify": "^1.0.0", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webdriver-manager/node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true, - "license": "ISC" - }, - "node_modules/webdriver-manager/node_modules/is-path-cwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", - "integrity": "sha512-cnS56eR9SPAscL77ik76ATVqoPARTqPIVkMDVxRaWH06zT+6+CzIroYRJ0VVvm0Z1zfAvxvz9i/D3Ppjaqt5Nw==", - "dev": true, "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webdriver-manager/node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/webdriver-manager/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/webdriver-manager/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webdriver-manager/node_modules/supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/webdriver-manager/node_modules/xml2js": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", - "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", - "dev": true, - "license": "MIT", - "dependencies": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/webdriver-manager/node_modules/xmlbuilder": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", - "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4.0" - } + "optional": true }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "dev": true, - "license": "BSD-2-Clause" + "dev": true }, "node_modules/webpack": { - "version": "5.94.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", - "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", + "version": "5.96.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.96.1.tgz", + "integrity": "sha512-l2LlBSvVZGhL4ZrPwyr8+37AunkcYj5qh8o6u2/2rzoPc8gxFJkLj1WxNgooi9pnoc06jh0BjuXnamM4qlujZA==", "dev": true, - "license": "MIT", "dependencies": { - "@types/estree": "^1.0.5", + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.6", "@webassemblyjs/ast": "^1.12.1", "@webassemblyjs/wasm-edit": "^1.12.1", "@webassemblyjs/wasm-parser": "^1.12.1", - "acorn": "^8.7.1", - "acorn-import-attributes": "^1.9.5", - "browserslist": "^4.21.10", + "acorn": "^8.14.0", + "browserslist": "^4.24.0", "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^5.17.1", "es-module-lexer": "^1.2.1", @@ -24978,9 +21682,9 @@ } }, "node_modules/webpack-dev-server": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.0.4.tgz", - "integrity": "sha512-dljXhUgx3HqKP2d8J/fUMvhxGhzjeNVarDLcbO/EWMSgRizDkxHQDZQaLFL5VJY9tRBj2Gz+rvCEYYvhbqPHNA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.1.0.tgz", + "integrity": "sha512-aQpaN81X6tXie1FoOB7xlMfCsN19pSvRAeYUHOdFWOlhpQ/LlbfTqYwwmEDFV0h8GGuqmCmKmT+pxcUV/Nt2gQ==", "dev": true, "license": "MIT", "dependencies": { @@ -24997,8 +21701,7 @@ "colorette": "^2.0.10", "compression": "^1.7.4", "connect-history-api-fallback": "^2.0.0", - "default-gateway": "^6.0.3", - "express": "^4.17.3", + "express": "^4.19.2", "graceful-fs": "^4.2.6", "html-entities": "^2.4.0", "http-proxy-middleware": "^2.0.3", @@ -25006,14 +21709,13 @@ "launch-editor": "^2.6.1", "open": "^10.0.3", "p-retry": "^6.2.0", - "rimraf": "^5.0.5", "schema-utils": "^4.2.0", "selfsigned": "^2.4.1", "serve-index": "^1.9.1", "sockjs": "^0.3.24", "spdy": "^4.0.2", - "webpack-dev-middleware": "^7.1.0", - "ws": "^8.16.0" + "webpack-dev-middleware": "^7.4.2", + "ws": "^8.18.0" }, "bin": { "webpack-dev-server": "bin/webpack-dev-server.js" @@ -25037,31 +21739,48 @@ } } }, - "node_modules/webpack-dev-server/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "node_modules/webpack-dev-server/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" }, - "bin": { - "glob": "dist/esm/bin.mjs" + "engines": { + "node": ">= 8.10.0" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/webpack-dev-server/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" } }, "node_modules/webpack-dev-server/node_modules/http-proxy-middleware": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", - "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz", + "integrity": "sha512-fgVY8AV7qU7z/MmXJ/rxwbrtQH4jBQ9m7kp3llF0liB7glmFeVZFBepQb32T3y8n8k2+AEYuMPCpinYW+/CuRA==", "dev": true, "license": "MIT", "dependencies": { @@ -25083,20 +21802,65 @@ } } }, - "node_modules/webpack-dev-server/node_modules/rimraf": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", - "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "node_modules/webpack-dev-server/node_modules/is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^10.3.7" + "license": "MIT", + "engines": { + "node": ">=10" }, - "bin": { - "rimraf": "dist/esm/bin.mjs" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webpack-dev-server/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/webpack-dev-server/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/webpack-dev-server/node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, "node_modules/webpack-merge": { @@ -25104,7 +21868,6 @@ "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", "dev": true, - "license": "MIT", "dependencies": { "clone-deep": "^4.0.1", "flat": "^5.0.2", @@ -25119,7 +21882,6 @@ "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", "dev": true, - "license": "MIT", "engines": { "node": ">=10.13.0" } @@ -25129,7 +21891,6 @@ "resolved": "https://registry.npmjs.org/webpack-subresource-integrity/-/webpack-subresource-integrity-5.1.0.tgz", "integrity": "sha512-sacXoX+xd8r4WKsy9MvH/q/vBtEHr86cpImXwyg74pFIpERKt6FmB8cXpeuh0ZLgclOlHI4Wcll7+R5L02xk9Q==", "dev": true, - "license": "MIT", "dependencies": { "typed-assert": "^1.0.8" }, @@ -25151,7 +21912,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, - "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -25168,7 +21928,6 @@ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", "dev": true, - "license": "MIT", "peerDependencies": { "ajv": "^6.9.1" } @@ -25178,7 +21937,6 @@ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -25192,7 +21950,6 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true, - "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } @@ -25201,22 +21958,19 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/webpack/node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/webpack/node_modules/schema-utils": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "dev": true, - "license": "MIT", "dependencies": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", @@ -25260,7 +22014,6 @@ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", "dev": true, - "license": "MIT", "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" @@ -25271,7 +22024,6 @@ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, - "license": "ISC", "dependencies": { "isexe": "^2.0.0" }, @@ -25283,17 +22035,64 @@ } }, "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", "dev": true, - "license": "MIT", "dependencies": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -25303,15 +22102,13 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/which-typed-array": { - "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==", + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.16.tgz", + "integrity": "sha512-g+N+GAWiRj66DngFwHvISJd+ITsyphZvD1vChfVg6cEdnzy53GzB3oy0fUNlvhz7H7+MiqhYr26qxQShCpKTTQ==", "dev": true, - "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.7", @@ -25330,15 +22127,13 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/windows-release": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/windows-release/-/windows-release-4.0.0.tgz", "integrity": "sha512-OxmV4wzDKB1x7AZaZgXMVsdJ1qER1ed83ZrTYd5Bwq2HfJVg3DJS8nqlAG4sMoJ7mu8cuRmLEYyU13BKwctRAg==", "dev": true, - "license": "MIT", "dependencies": { "execa": "^4.0.2" }, @@ -25354,7 +22149,6 @@ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -25363,15 +22157,13 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "dev": true, - "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -25387,7 +22179,6 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, - "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -25400,91 +22191,17 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/wrap-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, "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, - "license": "ISC" + "dev": true }, "node_modules/write-file-atomic": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", "dev": true, - "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4", "is-typedarray": "^1.0.0", @@ -25496,15 +22213,13 @@ "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/ws": { "version": "8.17.1", "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=10.0.0" }, @@ -25526,7 +22241,6 @@ "resolved": "https://registry.npmjs.org/xcode/-/xcode-3.0.1.tgz", "integrity": "sha512-kCz5k7J7XbJtjABOvkc5lJmkiDh8VhjVCGNiqdKCscmVpdVUpEAyXv1xmCLkQJ5dsHqx3IPO4XW+NTDhU/fatA==", "dev": true, - "license": "Apache-2.0", "dependencies": { "simple-plist": "^1.1.0", "uuid": "^7.0.3" @@ -25540,7 +22254,6 @@ "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz", "integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==", "dev": true, - "license": "MIT", "bin": { "uuid": "dist/bin/uuid" } @@ -25550,7 +22263,6 @@ "resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz", "integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==", "dev": true, - "license": "MIT", "dependencies": { "sax": "^1.2.4" }, @@ -25562,15 +22274,13 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/xml2js": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", "dev": true, - "license": "MIT", "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" @@ -25584,7 +22294,6 @@ "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", "dev": true, - "license": "MIT", "engines": { "node": ">=4.0" } @@ -25594,7 +22303,6 @@ "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", "dev": true, - "license": "MIT", "engines": { "node": ">=8.0" } @@ -25604,7 +22312,6 @@ "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.32.tgz", "integrity": "sha512-rxMJhSIoiO8vXcWvSifKqhvV96GjiD5wYb8/QHdoRyQvraTpp4IEv944nhGausZZ3u7dhQXteZuZbaqfpB7uYw==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.6.0" } @@ -25614,7 +22321,6 @@ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.4" } @@ -25624,7 +22330,6 @@ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, - "license": "ISC", "engines": { "node": ">=10" } @@ -25633,15 +22338,13 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, - "license": "MIT", "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -25660,7 +22363,6 @@ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", "dev": true, - "license": "ISC", "engines": { "node": ">=10" } @@ -25670,7 +22372,6 @@ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true, - "license": "ISC", "engines": { "node": ">=12" } @@ -25680,7 +22381,6 @@ "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", "dev": true, - "license": "MIT", "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" @@ -25691,7 +22391,6 @@ "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", "dev": true, - "license": "MIT", "engines": { "node": ">=6" } @@ -25701,7 +22400,6 @@ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, - "license": "MIT", "engines": { "node": ">=10" }, @@ -25714,7 +22412,6 @@ "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz", "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==", "dev": true, - "license": "MIT", "engines": { "node": ">=18" }, @@ -25723,10 +22420,9 @@ } }, "node_modules/zone.js": { - "version": "0.14.10", - "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.14.10.tgz", - "integrity": "sha512-YGAhaO7J5ywOXW6InXNlLmfU194F8lVgu7bRntUF3TiG8Y3nBK0x1UJJuHUP/e8IyihkjCYqhCScpSwnlaSRkQ==", - "license": "MIT" + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.15.0.tgz", + "integrity": "sha512-9oxn0IIjbCZkJ67L+LkhYWRyAy7axphb3VgE2MBDlOqnmHMPWGYMxJxBYFueFq/JGY2GMwS0rU+UCLunEmy5UA==" } } } diff --git a/ui/package.json b/ui/package.json index d1e7e5250b9..1de4641ae07 100644 --- a/ui/package.json +++ b/ui/package.json @@ -1,90 +1,93 @@ { "name": "openems-ui", - "version": "2024.11.0-SNAPSHOT", + "version": "2025.2.0-SNAPSHOT", "license": "AGPL-3.0", "private": true, "dependencies": { - "@angular/animations": "18.2.5", - "@angular/common": "18.2.5", - "@angular/core": "18.2.5", - "@angular/forms": "18.2.5", - "@angular/platform-browser": "18.2.5", - "@angular/platform-browser-dynamic": "18.2.5", - "@angular/router": "18.2.5", - "@angular/service-worker": "18.2.5", + "@angular/animations": "19.0.5", + "@angular/common": "19.0.5", + "@angular/core": "19.0.5", + "@angular/forms": "19.0.5", + "@angular/platform-browser": "19.0.5", + "@angular/platform-browser-dynamic": "19.0.5", + "@angular/router": "19.0.5", + "@angular/service-worker": "19.0.5", + "@awesome-cordova-plugins/core": "^6.13.0", + "@awesome-cordova-plugins/file-opener": "^6.13.0", "@capacitor-community/file-opener": "^6.0.1", - "@capacitor/android": "^6.1.2", - "@capacitor/app": "^6.0.1", - "@capacitor/core": "^6.1.2", - "@capacitor/filesystem": "^6.0.1", - "@capacitor/ios": "^6.1.2", - "@capacitor/splash-screen": "^6.0.2", - "@ionic-native/core": "^5.36.0", - "@ionic-native/file-opener": "^5.36.0", - "@ionic/angular": "^6.7.5", - "@ngx-formly/core": "^6.3.7", - "@ngx-formly/ionic": "^6.3.7", - "@ngx-formly/schematics": "^6.3.7", - "@ngx-translate/core": "^15.0.0", + "@capacitor/android": "^6.2.0", + "@capacitor/app": "^6.0.2", + "@capacitor/core": "^6.2.0", + "@capacitor/filesystem": "^6.0.2", + "@capacitor/ios": "^6.2.0", + "@capacitor/splash-screen": "^6.0.3", + "@ionic/angular": "^7.8.6", + "@ngx-formly/core": "^6.3.12", + "@ngx-formly/ionic": "^6.3.12", + "@ngx-formly/schematics": "^6.3.12", + "@ngx-translate/core": "^16.0.4", "@nodro7/angular-mydatepicker": "^0.14.0", - "capacitor-blob-writer": "^1.1.17", - "capacitor-ios-autofill-save-password": "^3.0.0", + "capacitor-blob-writer": "^1.1.19", + "capacitor-ios-autofill-save-password": "^4.0.0", "capacitor-secure-storage-plugin": "^0.10.0", - "chart.js": "^4.4.4", + "chart.js": "^4.4.7", "chartjs-adapter-date-fns": "^3.0.0", - "chartjs-plugin-annotation": "^3.0.1", + "chartjs-plugin-annotation": "^3.1.0", "chartjs-plugin-datalabels": "^2.2.0", - "chartjs-plugin-zoom": "^2.0.1", + "chartjs-plugin-zoom": "^2.2.0", "classlist.js": "^1.1.20150312", "compare-versions": "^6.1.1", "d3": "^7.9.0", - "date-fns": "^2.30.0", + "date-fns": "^4.1.0", "file-saver-es": "^2.0.5", - "ng2-charts": "4.1.1", - "ngx-cookie-service": "18.0.0", - "ngx-device-detector": "^8.0.0", - "ngx-spinner": "^16.0.2", + "ng2-charts": "7.0.0", + "ngx-cookie-service": "19.0.0", + "ngx-device-detector": "^9.0.0", + "ngx-spinner": "^17.0.0", "roboto-fontface": "^0.10.0", - "rxjs": "~6.6.7", - "swiper": "11.1.14", - "tslib": "^2.6.2", - "uuid": "^10.0.0", - "zone.js": "~0.14.7" + "rxjs": "~7.8.1", + "swiper": "11.1.15", + "tslib": "^2.8.1", + "uuid": "^11.0.3", + "zone.js": "~0.15.0" }, "devDependencies": { - "@angular-devkit/build-angular": "^18.2.5", - "@angular-devkit/core": "18.2.5", - "@angular-devkit/schematics": "18.2.5", - "@angular-eslint/builder": "^18.3.1", - "@angular-eslint/eslint-plugin": "^18.3.1", - "@angular-eslint/eslint-plugin-template": "^18.3.1", - "@angular-eslint/template-parser": "^18.3.1", - "@angular/cli": "18.2.5", - "@angular/compiler": "18.2.5", - "@angular/compiler-cli": "18.2.5", - "@angular/language-service": "18.2.5", + "@angular-devkit/build-angular": "^19.0.6", + "@angular-devkit/core": "^19.0.6", + "@angular-devkit/schematics": "^19.0.6", + "@angular-eslint/builder": "^19.0.2", + "@angular-eslint/eslint-plugin": "^19.0.2", + "@angular-eslint/eslint-plugin-template": "^19.0.2", + "@angular-eslint/template-parser": "^19.0.2", + "@angular/cli": "^19.0.6", + "@angular/compiler": "19.0.5", + "@angular/compiler-cli": "19.0.5", + "@angular/language-service": "19.0.5", "@capacitor/assets": "^3.0.5", - "@capacitor/cli": "6.1.2", - "@ionic/angular-toolkit": "^11.0.1", + "@capacitor/cli": "6.2.0", + "@ionic/angular-toolkit": "^12.1.1", "@ionic/cli": "^7.2.0", - "@stylistic/eslint-plugin": "^2.8.0", - "@types/jasmine": "~4.3.6", + "@schematics/angular": "^19.0.6", + "@stylistic/eslint-plugin": "^2.12.1", + "@types/jasmine": "~5.1.5", "@types/jasminewd2": "~2.0.13", "@types/json-schema": "^7.0.15", "@types/node": "^20.12.6", - "@types/qs": "^6.9.16", + "@types/qs": "^6.9.17", "@types/range-parser": "^1.2.7", "@types/send": "^0.17.4", "@types/uuid": "^10.0.0", - "@typescript-eslint/eslint-plugin": "^7.0.0", - "@typescript-eslint/parser": "^7.0.0", - "@typescript-eslint/types": "^8.7.0", - "eslint": "^8.57.0", - "eslint-plugin-import": "2.30.0", - "eslint-plugin-jsdoc": "50.2.4", + "@typescript-eslint/eslint-plugin": "^8.19.0", + "@typescript-eslint/parser": "^8.19.0", + "@typescript-eslint/types": "^8.19.0", + "eslint": "^9.17.0", + "eslint-import-resolver-typescript": "^3.7.0", + "eslint-plugin-check-file": "^2.8.0", + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-jsdoc": "50.6.1", "eslint-plugin-prefer-arrow": "1.2.3", "eslint-plugin-unused-imports": "^4.1.4", - "jasmine-core": "~5.3.0", + "jasmine-core": "~5.5.0", "jasmine-spec-reporter": "~7.0.0", "karma": "~6.4.4", "karma-chrome-launcher": "~3.2.0", @@ -92,9 +95,8 @@ "karma-coverage-istanbul-reporter": "~3.0.3", "karma-jasmine": "~5.1.0", "karma-jasmine-html-reporter": "^2.1.0", - "protractor": "~7.0.0", "ts-node": "^10.9.2", - "typescript": "~5.4.5", + "typescript": "~5.6.3", "typescript-strict-plugin": "^2.4.4" }, "scripts": { diff --git a/ui/src/app/app-routing.module.ts b/ui/src/app/app-routing.module.ts index f84be2e897f..639c4b40cf3 100644 --- a/ui/src/app/app-routing.module.ts +++ b/ui/src/app/app-routing.module.ts @@ -1,7 +1,6 @@ import { NgModule } from "@angular/core"; -import { PreloadAllModules, RouterModule, Routes } from "@angular/router"; +import { NoPreloading, RouterModule, Routes } from "@angular/router"; import { environment } from "src/environments"; -import { ChangelogViewComponent } from "./changelog/view/view"; import { EdgeComponent } from "./edge/edge.component"; import { OverviewComponent as AutarchyChartOverviewComponent } from "./edge/history/common/autarchy/overview/overview"; import { DetailsOverviewComponent as ConsumptionDetailsOverviewComponent } from "./edge/history/common/consumption/details/details.overview"; @@ -16,9 +15,9 @@ import { OverviewComponent as GridOptimizedChargeChartOverviewComponent } from " import { OverviewComponent as TimeOfUseTariffOverviewComponent } from "./edge/history/Controller/Ess/TimeOfUseTariff/overview/overview"; import { DetailsOverviewComponent as DigitalOutputDetailsOverviewComponent } from "./edge/history/Controller/Io/DigitalOutput/details/details.overview"; import { OverviewComponent as DigitalOutputChartOverviewComponent } from "./edge/history/Controller/Io/DigitalOutput/overview/overview"; +import { OverviewComponent as HeatingelementChartOverviewComponent } from "./edge/history/Controller/Io/heatingelement/overview/overview"; import { OverviewComponent as ModbusTcpApiOverviewComponent } from "./edge/history/Controller/ModbusTcpApi/overview/overview"; import { DelayedSellToGridChartOverviewComponent } from "./edge/history/delayedselltogrid/symmetricpeakshavingchartoverview/delayedselltogridchartoverview.component"; -import { HeatingelementChartOverviewComponent } from "./edge/history/heatingelement/heatingelementchartoverview/heatingelementchartoverview.component"; import { HeatPumpChartOverviewComponent } from "./edge/history/heatpump/heatpumpchartoverview/heatpumpchartoverview.component"; import { HistoryComponent as EdgeHistoryComponent } from "./edge/history/history.component"; import { HistoryDataService } from "./edge/history/historydataservice"; @@ -67,7 +66,7 @@ export const routes: Routes = [ { path: "overview", component: OverViewComponent }, { path: "user", component: UserComponent, data: { navbarTitleToBeTranslated: "Menu.user" } }, - { path: "changelog", component: ChangelogViewComponent, data: { navbarTitleToBeTranslated: "Menu.changelog" } }, + { path: "changelog", loadChildren: () => import("./changelog/changelog.module").then(m => m.ChangelogModule), data: { navbarTitleToBeTranslated: "Menu.changelog" } }, // Edge Pages { @@ -91,6 +90,7 @@ export const routes: Routes = [ { path: ":componentId/gridOptimizedChargeChart", component: GridOptimizedChargeChartOverviewComponent }, { path: ":componentId/heatingelementchart", component: HeatingelementChartOverviewComponent }, { path: ":componentId/heatpumpchart", component: HeatPumpChartOverviewComponent }, + { path: ":componentId/modbusTcpApi", component: ModbusTcpApiOverviewComponent }, { path: ":componentId/scheduleChart", component: TimeOfUseTariffOverviewComponent }, { path: ":componentId/symmetricpeakshavingchart", component: SymmetricPeakshavingChartOverviewComponent }, { path: ":componentId/timeslotpeakshavingchart", component: TimeslotPeakshavingChartOverviewComponent }, @@ -101,7 +101,6 @@ export const routes: Routes = [ { path: "gridchart", component: GridChartOverviewComponent }, { path: "gridchart/:componentId", component: GridDetailsOverviewComponent }, { path: "gridchart/:componentId/currentVoltage", component: CurrentAndVoltageOverviewComponent }, - { path: ":componentId/modbusTcpApi", component: ModbusTcpApiOverviewComponent }, { path: "productionchart", component: ProductionChartOverviewComponent }, { path: "productionchart/:componentId", component: DetailsOverviewComponent }, { path: "productionchart/:componentId/currentVoltage", component: CurrentAndVoltageOverviewComponent }, @@ -134,6 +133,7 @@ export const routes: Routes = [ { path: "settings/alerting", component: EdgeSettingsAlerting, canActivate: [hasEdgeRole(Role.OWNER)], data: { navbarTitleToBeTranslated: "Edge.Config.Index.alerting" } }, { path: "settings/jsonrpctest", component: JsonrpcTestComponent, data: { navbarTitle: "Jsonrpc Test" } }, { path: "settings/powerAssistant", component: PowerAssistantComponent, canActivate: [hasEdgeRole(Role.ADMIN)], data: { navbarTitle: "Power-Assistant" } }, + { path: "settings/app", data: { navbarTitle: environment.edgeShortName + "Apps" }, component: EdgeSettingsAppIndex }, ], }, @@ -146,7 +146,7 @@ export const appRoutingProviders: any[] = []; @NgModule({ imports: [ - RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules, paramsInheritanceStrategy: "always" }), + RouterModule.forRoot(routes, { preloadingStrategy: NoPreloading, paramsInheritanceStrategy: "always" }), ], exports: [RouterModule], }) diff --git a/ui/src/app/app.component.html b/ui/src/app/app.component.html index 80c7aa7d2a0..45ada4e553f 100644 --- a/ui/src/app/app.component.html +++ b/ui/src/app/app.component.html @@ -1,4 +1,46 @@ + + + + + + + + Menu.menu + + + + + + + + + +

    {{ user.name }}

    + +

    {{ user.id }}

    +
    + + + + + + + + Menu.overview + + + + + + + + @@ -33,49 +75,6 @@

    Index.connectionFailed

    - - - - - - - - - Menu.menu - - - - - - - - - -

    {{ user.name }}

    - -

    {{ user.id }}

    -
    -
    -
    - - - - - - Menu.overview - - - - - -
    -
    -
    diff --git a/ui/src/app/app.component.ts b/ui/src/app/app.component.ts index 27d05b4410d..d9809a8ede4 100644 --- a/ui/src/app/app.component.ts +++ b/ui/src/app/app.component.ts @@ -16,6 +16,7 @@ import { Language } from "./shared/type/language"; @Component({ selector: "app-root", templateUrl: "app.component.html", + standalone: false, }) export class AppComponent implements OnInit, OnDestroy { diff --git a/ui/src/app/app.module.ts b/ui/src/app/app.module.ts index e8ad40d998a..4a96690019b 100644 --- a/ui/src/app/app.module.ts +++ b/ui/src/app/app.module.ts @@ -9,13 +9,13 @@ import { IonicModule, IonicRouteStrategy } from "@ionic/angular"; import { FORMLY_CONFIG } from "@ngx-formly/core"; import { TranslateLoader, TranslateModule, TranslateService } from "@ngx-translate/core"; import { AngularMyDatePickerModule } from "@nodro7/angular-mydatepicker"; +import { provideCharts, withDefaultRegisterables } from "ng2-charts"; import { CookieService } from "ngx-cookie-service"; import { DeviceDetectorService } from "ngx-device-detector"; import { AppRoutingModule } from "./app-routing.module"; import { AppComponent } from "./app.component"; import { AppService } from "./app.service"; import { CheckForUpdateService } from "./appupdateservice"; -import { ChangelogModule } from "./changelog/changelog.module"; import { EdgeModule } from "./edge/edge.module"; import { SettingsModule as EdgeSettingsModule } from "./edge/settings/settings.module"; import { SystemLogComponent } from "./edge/settings/systemlog/systemlog.component"; @@ -43,7 +43,6 @@ import { UserModule } from "./user/user.module"; AppRoutingModule, BrowserAnimationsModule, BrowserModule, - ChangelogModule, EdgeModule, EdgeSettingsModule, IndexModule, @@ -58,7 +57,7 @@ import { UserModule } from "./user/user.module"; { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }, CookieService, { provide: ErrorHandler, useClass: MyErrorHandler }, - { provide: LOCALE_ID, useValue: Language.DEFAULT.key }, + { provide: LOCALE_ID, useFactory: () => (Language.getByKey(localStorage.LANGUAGE) ?? Language.getByBrowserLang(navigator.language) ?? Language.DEFAULT).key }, // Use factory for formly. This allows us to use translations in validationMessages. { provide: FORMLY_CONFIG, multi: true, useFactory: registerTranslateExtension, deps: [TranslateService] }, DeviceDetectorService, @@ -66,6 +65,7 @@ import { UserModule } from "./user/user.module"; CheckForUpdateService, AppService, AppStateTracker, + provideCharts(withDefaultRegisterables()), ], bootstrap: [AppComponent], }) diff --git a/ui/src/app/app.service.ts b/ui/src/app/app.service.ts index e544f51838a..69172d83672 100644 --- a/ui/src/app/app.service.ts +++ b/ui/src/app/app.service.ts @@ -3,14 +3,14 @@ import { Injectable } from "@angular/core"; import { App } from "@capacitor/app"; import { Capacitor } from "@capacitor/core"; import { Directory, Encoding, Filesystem } from "@capacitor/filesystem"; -import { FileOpener } from "@ionic-native/file-opener"; import { AlertController } from "@ionic/angular"; import { TranslateService } from "@ngx-translate/core"; -import { saveAs } from "file-saver-es"; import { DeviceDetectorService, DeviceInfo } from "ngx-device-detector"; import { BehaviorSubject, Subject } from "rxjs"; import { environment } from "src/environments"; import { JsonrpcRequest } from "./shared/jsonrpc/base"; +import { PreviousRouteService } from "./shared/service/previousRouteService"; +import { ArrayUtils } from "./shared/utils/array/array.utils"; @Injectable() export class AppService { @@ -28,6 +28,7 @@ export class AppService { private alertCtrl: AlertController, private translate: TranslateService, private deviceService: DeviceDetectorService, + private routeService: PreviousRouteService, ) { AppService.deviceInfo = this.deviceService.getDeviceInfo(); AppService.isMobile = this.deviceService.isMobile(); @@ -53,34 +54,6 @@ export class AppService { return null; } - static async writeAndOpenFile(data: Blob, fileName: string) { - if (!AppService.platform) { - saveAs(data, fileName); - } - - const reader = new FileReader(); - reader.readAsDataURL(data); - reader.onloadend = async function () { - try { - const result = await Filesystem.writeFile({ - path: fileName, - data: reader.result.toString(), - directory: Directory.Data, - recursive: true, - encoding: Encoding.UTF8, - }); - - FileOpener.open(result.uri, data.type) - .then(() => console.log("File is opened")) - .catch(e => console.log("Error opening file", e)); - - console.log("Wrote file", result.uri); - } catch (e) { - console.error("Unable to write file", e); - } - }; - } - public listen() { // Don't use in web if (AppService.platform === "web") { @@ -162,11 +135,26 @@ export class AppService { private async updateState() { const { isActive } = await App.getState(); + this.reloadBehavior(isActive); + AppService.isActive.next(isActive); + } - if (isActive === true && AppService.isActive?.getValue() === false) { + /** + * Controls the reload behaviour after app was running in background und got active again + * + * @param isAppCurrentlyActive is app currently active + */ + private reloadBehavior(isAppCurrentlyActive: boolean) { + + const route = this.routeService.getCurrentUrl(); + const isForbiddenToReload: boolean = ArrayUtils.containsStrings(route.split("/"), FORBIDDEN_ROUTES_TO_RELOAD); + + if (isAppCurrentlyActive === true + && AppService.isActive?.getValue() === false + && !isForbiddenToReload) { window.location.reload(); } - - AppService.isActive.next(isActive); } } + +export const FORBIDDEN_ROUTES_TO_RELOAD: string[] = ["login", "installation", "index"]; diff --git a/ui/src/app/changelog/changelog-routing.module.ts b/ui/src/app/changelog/changelog-routing.module.ts new file mode 100644 index 00000000000..0c124f4396a --- /dev/null +++ b/ui/src/app/changelog/changelog-routing.module.ts @@ -0,0 +1,15 @@ +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; +import { ChangelogViewComponent } from "./view/view"; + +const routes: Routes = [ + { + path: "", + component: ChangelogViewComponent, + }, +]; +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class ChangelogRoutingModule { } diff --git a/ui/src/app/changelog/changelog.module.ts b/ui/src/app/changelog/changelog.module.ts index b6a7a5810ae..603c8e70475 100644 --- a/ui/src/app/changelog/changelog.module.ts +++ b/ui/src/app/changelog/changelog.module.ts @@ -1,19 +1,20 @@ +import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; -import { SharedModule } from "./../shared/shared.module"; +import { ChangelogRoutingModule } from "./changelog-routing.module"; import { ChangelogComponent } from "./view/component/changelog.component"; import { ChangelogViewComponent } from "./view/view"; @NgModule({ imports: [ - SharedModule, - ], - declarations: [ + CommonModule, ChangelogComponent, ChangelogViewComponent, + ChangelogRoutingModule, + ], + declarations: [ ], exports: [ ChangelogComponent, - ChangelogViewComponent, ], }) export class ChangelogModule { } diff --git a/ui/src/app/changelog/view/component/changelog.component.ts b/ui/src/app/changelog/view/component/changelog.component.ts index 9bba2dcddae..a97c6f75494 100644 --- a/ui/src/app/changelog/view/component/changelog.component.ts +++ b/ui/src/app/changelog/view/component/changelog.component.ts @@ -1,6 +1,8 @@ +import { CommonModule } from "@angular/common"; import { Component } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; -import { TranslateService } from "@ngx-translate/core"; +import { IonicModule } from "@ionic/angular"; +import { TranslateModule, TranslateService } from "@ngx-translate/core"; import { environment } from "src/environments"; import { Service } from "../../../shared/shared"; import { Role } from "../../../shared/type/role"; @@ -9,6 +11,8 @@ import { Changelog } from "./changelog.constants"; @Component({ selector: "changelog", templateUrl: "./changelog.component.html", + standalone: true, + imports: [IonicModule, CommonModule, TranslateModule], }) export class ChangelogComponent { diff --git a/ui/src/app/changelog/view/component/changelog.constants.ts b/ui/src/app/changelog/view/component/changelog.constants.ts index 054d54208cc..77596a0f31f 100644 --- a/ui/src/app/changelog/view/component/changelog.constants.ts +++ b/ui/src/app/changelog/view/component/changelog.constants.ts @@ -2,7 +2,7 @@ import { Role } from "src/app/shared/type/role"; export class Changelog { - public static readonly UI_VERSION = "2024.11.0-SNAPSHOT"; + public static readonly UI_VERSION = "2025.2.0-SNAPSHOT"; public static product(...products: Product[]) { return products.map(product => Changelog.link(product.name, product.url)).join(", ") + ". "; @@ -27,7 +27,7 @@ export class Changelog { } public static link(title: string, url: string) { - return "" + title + ""; + return "" + title + ""; } } diff --git a/ui/src/app/changelog/view/view.html b/ui/src/app/changelog/view/view.html index 2746d025db1..10f23e85d60 100644 --- a/ui/src/app/changelog/view/view.html +++ b/ui/src/app/changelog/view/view.html @@ -1,4 +1,3 @@ -
    diff --git a/ui/src/app/changelog/view/view.ts b/ui/src/app/changelog/view/view.ts index 7a08765ad46..4d8015e06b4 100644 --- a/ui/src/app/changelog/view/view.ts +++ b/ui/src/app/changelog/view/view.ts @@ -1,7 +1,12 @@ +import { CommonModule } from "@angular/common"; import { Component } from "@angular/core"; +import { IonicModule } from "@ionic/angular"; +import { ChangelogComponent } from "./component/changelog.component"; @Component({ selector: "changelogViewComponent", templateUrl: "./view.html", + standalone: true, + imports: [IonicModule, CommonModule, ChangelogComponent], }) export class ChangelogViewComponent { } diff --git a/ui/src/app/edge/edge.component.ts b/ui/src/app/edge/edge.component.ts index c0acc0aa952..f2f5f9d6728 100644 --- a/ui/src/app/edge/edge.component.ts +++ b/ui/src/app/edge/edge.component.ts @@ -11,6 +11,7 @@ import { ChannelAddress, Edge, Service, Websocket } from "src/app/shared/shared" `, + standalone: false, }) export class EdgeComponent implements OnInit, OnDestroy { diff --git a/ui/src/app/edge/history/Controller/ChannelThreshold/chart/totalchart.component.ts b/ui/src/app/edge/history/Controller/ChannelThreshold/chart/totalchart.component.ts index 29658f87838..a3760f08e2d 100644 --- a/ui/src/app/edge/history/Controller/ChannelThreshold/chart/totalchart.component.ts +++ b/ui/src/app/edge/history/Controller/ChannelThreshold/chart/totalchart.component.ts @@ -3,11 +3,12 @@ import { Component } from "@angular/core"; import { AbstractHistoryChart } from "src/app/shared/components/chart/abstracthistorychart"; import { QueryHistoricTimeseriesEnergyResponse } from "src/app/shared/jsonrpc/response/queryHistoricTimeseriesEnergyResponse"; import { ChartAxis, HistoryUtils, Utils, YAxisType } from "src/app/shared/service/utils"; -import { ChannelAddress, EdgeConfig } from "src/app/shared/shared"; +import { ChannelAddress, ChartConstants, EdgeConfig } from "src/app/shared/shared"; @Component({ selector: "totalChart", templateUrl: "../../../../../shared/components/chart/abstracthistorychart.html", + standalone: false, }) export class TotalChartComponent extends AbstractHistoryChart { @@ -31,8 +32,6 @@ export class TotalChartComponent extends AbstractHistoryChart { const output: HistoryUtils.DisplayValue[] = []; - const colors: string[] = ["rgb(0,0,139)", "rgb(0,191,255)", "rgb(0,0,56)", "rgb(77,77,174)"]; - for (let i = 0; i < controller.length; i++) { const controllerId = controller[i]; output.push({ @@ -46,7 +45,7 @@ export class TotalChartComponent extends AbstractHistoryChart { // TODO add logic to not have to adjust non power data manually .map(val => Utils.multiplySafely(val, 1000)); }, - color: colors[Math.min(i % colors.length, (colors.length - 1))], + color: ChartConstants.Colors.SHADES_OF_YELLOW[i % (ChartConstants.Colors.SHADES_OF_YELLOW.length - 1)], stack: 0, }); } diff --git a/ui/src/app/edge/history/Controller/ChannelThreshold/flat/flat.html b/ui/src/app/edge/history/Controller/ChannelThreshold/flat/flat.html index 17aa513987c..d077d3345fb 100644 --- a/ui/src/app/edge/history/Controller/ChannelThreshold/flat/flat.html +++ b/ui/src/app/edge/history/Controller/ChannelThreshold/flat/flat.html @@ -1,6 +1,6 @@ + [icon]="{name:'radio-button-on-outline', color: 'normal', size: 'large'}"> data["DelayChargeMaximumChargeLimit"], - color: "rgb(253,197,7)", + color: ChartConstants.Colors.YELLOW, borderDash: [3, 3], }, { name: translate.instant("Edge.Index.Widgets.GridOptimizedCharge.minimumCharge"), converter: () => data["SellToGridLimitMinimumChargeLimit"], - color: "rgb(200,0,0)", + color: ChartConstants.Colors.RED, borderDash: [3, 3], }, { - name: translate.instant("General.chargePower"), + name: translate.instant("General.CHARGE"), converter: () => (data["ProductionDcActualPower"] ? @@ -58,7 +60,7 @@ export class GridOptimizedChargeChartComponent extends AbstractHistoryChart { }) : data["EssActivePower"])?.map(val => HistoryUtils.ValueConverter.POSITIVE_AS_ZERO_AND_INVERT_NEGATIVE(val)) ?? null, - color: "rgb(0,223,0)", + color: ChartConstants.Colors.GREEN, }, { name: translate.instant("General.soc"), diff --git a/ui/src/app/edge/history/Controller/Ess/GridoptimizedCharge/chart/sellToGridLimitChart.component.ts b/ui/src/app/edge/history/Controller/Ess/GridoptimizedCharge/chart/sellToGridLimitChart.component.ts index 77072343e95..fdf06a94f49 100644 --- a/ui/src/app/edge/history/Controller/Ess/GridoptimizedCharge/chart/sellToGridLimitChart.component.ts +++ b/ui/src/app/edge/history/Controller/Ess/GridoptimizedCharge/chart/sellToGridLimitChart.component.ts @@ -2,12 +2,14 @@ import { Component } from "@angular/core"; import { TranslateService } from "@ngx-translate/core"; import { AbstractHistoryChart } from "src/app/shared/components/chart/abstracthistorychart"; +import { ChartConstants } from "src/app/shared/components/chart/chart.constants"; import { ChartAxis, HistoryUtils, Utils, YAxisType } from "src/app/shared/service/utils"; import { ChannelAddress } from "src/app/shared/shared"; @Component({ selector: "sellToGridLimitChart", templateUrl: "../../../../../../shared/components/chart/abstracthistorychart.html", + standalone: false, }) export class SellToGridLimitChartComponent extends AbstractHistoryChart { @@ -32,18 +34,20 @@ export class SellToGridLimitChartComponent extends AbstractHistoryChart { { name: translate.instant("General.gridSell"), converter: () => data["ActivePower"], - color: "rgb(0,0,200)", + color: ChartConstants.Colors.PURPLE, }, { name: translate.instant("Edge.Index.Widgets.GridOptimizedCharge.maximumGridFeedIn"), converter: () => data["_PropertyMaximumSellToGridPower"], - color: "rgb(0,0,0)", + color: ChartConstants.Colors.YELLOW, + hideShadow: true, borderDash: [3, 3], }, { name: translate.instant("Edge.Index.Widgets.GridOptimizedCharge.MAXIMUM_GRIDSELL_WITH_CHARGE"), converter: () => data["_PropertyMaximumSellToGridPower"].map(el => Utils.multiplySafely(el, 0.95)), - color: "rgb(200,0,0)", + color: ChartConstants.Colors.RED, + hideShadow: true, borderDash: [3, 3], }, { diff --git a/ui/src/app/edge/history/Controller/Ess/GridoptimizedCharge/flat/flat.html b/ui/src/app/edge/history/Controller/Ess/GridoptimizedCharge/flat/flat.html index a369cd1cdb2..7d3e28ac1ed 100644 --- a/ui/src/app/edge/history/Controller/Ess/GridoptimizedCharge/flat/flat.html +++ b/ui/src/app/edge/history/Controller/Ess/GridoptimizedCharge/flat/flat.html @@ -1,6 +1,6 @@ + [icon]="{name: 'oe-grid-storage', size: 'large', color: 'normal'}"> diff --git a/ui/src/app/edge/history/Controller/Ess/GridoptimizedCharge/flat/flat.ts b/ui/src/app/edge/history/Controller/Ess/GridoptimizedCharge/flat/flat.ts index 67cd22d5d32..d11c3482857 100644 --- a/ui/src/app/edge/history/Controller/Ess/GridoptimizedCharge/flat/flat.ts +++ b/ui/src/app/edge/history/Controller/Ess/GridoptimizedCharge/flat/flat.ts @@ -7,6 +7,7 @@ import { Filter } from "src/app/shared/components/shared/filter"; @Component({ selector: "gridOptimizedChargeWidget", templateUrl: "./flat.html", + standalone: false, }) export class FlatComponent extends AbstractFlatWidget { protected FORMAT_SECONDS_TO_DURATION = Converter.FORMAT_SECONDS_TO_DURATION(this.translate.currentLang); diff --git a/ui/src/app/edge/history/Controller/Ess/GridoptimizedCharge/overview/overview.html b/ui/src/app/edge/history/Controller/Ess/GridoptimizedCharge/overview/overview.html index 0c7ce394dd6..1a8f64c0c8b 100644 --- a/ui/src/app/edge/history/Controller/Ess/GridoptimizedCharge/overview/overview.html +++ b/ui/src/app/edge/history/Controller/Ess/GridoptimizedCharge/overview/overview.html @@ -1,6 +1,6 @@ - + diff --git a/ui/src/app/edge/history/Controller/Ess/GridoptimizedCharge/overview/overview.ts b/ui/src/app/edge/history/Controller/Ess/GridoptimizedCharge/overview/overview.ts index f5e4cab17f3..867174e975c 100644 --- a/ui/src/app/edge/history/Controller/Ess/GridoptimizedCharge/overview/overview.ts +++ b/ui/src/app/edge/history/Controller/Ess/GridoptimizedCharge/overview/overview.ts @@ -5,5 +5,6 @@ import { AbstractHistoryChartOverview } from "src/app/shared/components/chart/ab @Component({ selector: "gridoptimizedcharge-chart-overview", templateUrl: "./overview.html", + standalone: false, }) export class OverviewComponent extends AbstractHistoryChartOverview { } diff --git a/ui/src/app/edge/history/Controller/Ess/TimeOfUseTariff/chart/chart.ts b/ui/src/app/edge/history/Controller/Ess/TimeOfUseTariff/chart/chart.ts index 6a7850fcafb..6b845eb9cf6 100644 --- a/ui/src/app/edge/history/Controller/Ess/TimeOfUseTariff/chart/chart.ts +++ b/ui/src/app/edge/history/Controller/Ess/TimeOfUseTariff/chart/chart.ts @@ -1,15 +1,18 @@ // @ts-strict-ignore import { Component, Input } from "@angular/core"; import * as Chart from "chart.js"; -import { calculateResolution, ChronoUnit, Resolution } from "src/app/edge/history/shared"; +import { ChronoUnit, Resolution, calculateResolution } from "src/app/edge/history/shared"; import { AbstractHistoryChart } from "src/app/shared/components/chart/abstracthistorychart"; +import { ChartConstants } from "src/app/shared/components/chart/chart.constants"; import { ChartAxis, HistoryUtils, TimeOfUseTariffUtils, Utils, YAxisType } from "src/app/shared/service/utils"; import { ChannelAddress, Currency, EdgeConfig } from "src/app/shared/shared"; +import { AssertionUtils } from "src/app/shared/utils/assertions/assertions-utils"; import { ColorUtils } from "src/app/shared/utils/color/color.utils"; @Component({ selector: "scheduleChart", templateUrl: "../../../../../../shared/components/chart/abstracthistorychart.html", + standalone: false, }) export class ChartComponent extends AbstractHistoryChart { @@ -22,7 +25,8 @@ export class ChartComponent extends AbstractHistoryChart { const componentId: string = this.config.getComponentIdsByFactory("Controller.Ess.Time-Of-Use-Tariff")[0]; this.component = this.config.components[componentId]; - const currency = this.config.components["_meta"].properties.currency; + const meta: EdgeConfig.Component = this.config?.getComponent("_meta"); + const currency: string = this.config?.getPropertyFromComponent(meta, "currency"); this.currencyLabel = Currency.getCurrencyLabelByCurrency(currency); this.chartType = "bar"; @@ -52,21 +56,21 @@ export class ChartComponent extends AbstractHistoryChart { converter: () => this.getDataset(data, TimeOfUseTariffUtils.State.Balancing), color: "rgb(51,102,0)", stack: 1, - order: 1, + order: 2, }, { name: this.translate.instant("Edge.Index.Widgets.TIME_OF_USE_TARIFF.STATE.CHARGE_GRID"), converter: () => this.getDataset(data, TimeOfUseTariffUtils.State.ChargeGrid), color: "rgb(0, 204, 204)", stack: 1, - order: 1, + order: 2, }, { name: this.translate.instant("Edge.Index.Widgets.TIME_OF_USE_TARIFF.STATE.DELAY_DISCHARGE"), converter: () => this.getDataset(data, TimeOfUseTariffUtils.State.DelayDischarge), color: "rgb(0,0,0)", stack: 1, - order: 1, + order: 2, }, { name: this.translate.instant("General.soc"), @@ -79,19 +83,19 @@ export class ChartComponent extends AbstractHistoryChart { unit: YAxisType.PERCENTAGE, formatNumber: "1.0-0", }, - order: 0, + order: 1, }, { name: this.translate.instant("General.gridBuy"), converter: () => data["GridBuy"], - color: "rgb(0,0,0)", + color: ChartConstants.Colors.BLUE_GREY, yAxisId: ChartAxis.RIGHT_2, custom: { type: "line", formatNumber: "1.0-0", }, hiddenOnInit: true, - order: 2, + order: 0, }, ]; }, @@ -102,6 +106,10 @@ export class ChartComponent extends AbstractHistoryChart { unit: YAxisType.CURRENCY, position: "left", yAxisId: ChartAxis.LEFT, + customTitle: Currency.getChartCurrencyUnitLabel(currency), + scale: { + dynamicScale: true, + }, }, { unit: YAxisType.PERCENTAGE, @@ -166,6 +174,17 @@ export class ChartComponent extends AbstractHistoryChart { return el; }); + const chartObject = this.chartObject; + this.options.scales[ChartAxis.LEFT].ticks = { + callback: function (value, index, ticks) { + if (index == (ticks.length - 1)) { + const upperMostTick = chartObject.yAxes.find(el => el.unit === YAxisType.CURRENCY).customTitle; + AssertionUtils.assertHasMaxLength(upperMostTick, ChartConstants.MAX_LENGTH_OF_Y_AXIS_TITLE); + return upperMostTick; + } + return value; + }, + }; this.options.scales.x["offset"] = false; this.options["animation"] = false; }); @@ -182,7 +201,18 @@ export class ChartComponent extends AbstractHistoryChart { const prices = data["QuarterlyPrice"] .map(val => TimeOfUseTariffUtils.formatPrice(Utils.multiplySafely(val, 1000))); const states = data["StateMachine"] - .map(val => Utils.multiplySafely(val, 1000)); + .map(val => Utils.multiplySafely(val, 1000)) + .map(val => { + if (val === null) { + return null; + } else if (val < 0.5) { + return 0; // DelayDischarge + } else if (val > 2.5) { + return 3; // ChargeGrid + } else { + return 1; // Balancing + } + }); const length = prices.length; const dataset = Array(length).fill(null); @@ -219,6 +249,11 @@ export class ChartComponent extends AbstractHistoryChart { }) .reduce((acc, curr) => acc.concat(curr), []); - return finalArray.length > 0 ? Math.floor(Math.min(...finalArray)) : 0; + if (finalArray.length === 0) { + return 0; + } + + const min = Math.floor(Math.min(...finalArray)); + return Math.floor(min - (min * 0.05)); } } diff --git a/ui/src/app/edge/history/Controller/Ess/TimeOfUseTariff/flat/flat.html b/ui/src/app/edge/history/Controller/Ess/TimeOfUseTariff/flat/flat.html index 96ad055397c..12e3857a9ae 100644 --- a/ui/src/app/edge/history/Controller/Ess/TimeOfUseTariff/flat/flat.html +++ b/ui/src/app/edge/history/Controller/Ess/TimeOfUseTariff/flat/flat.html @@ -1,6 +1,6 @@ + 'cloud-outline', color:'normal'}"> diff --git a/ui/src/app/edge/history/Controller/Ess/TimeOfUseTariff/flat/flat.ts b/ui/src/app/edge/history/Controller/Ess/TimeOfUseTariff/flat/flat.ts index 126647bbd92..fc64481c9b8 100644 --- a/ui/src/app/edge/history/Controller/Ess/TimeOfUseTariff/flat/flat.ts +++ b/ui/src/app/edge/history/Controller/Ess/TimeOfUseTariff/flat/flat.ts @@ -7,6 +7,7 @@ import { ChannelAddress, CurrentData } from "src/app/shared/shared"; @Component({ selector: "timeOfUseTariffWidget", templateUrl: "./flat.html", + standalone: false, }) export class FlatComponent extends AbstractFlatWidget { diff --git a/ui/src/app/edge/history/Controller/Ess/TimeOfUseTariff/overview/overview.ts b/ui/src/app/edge/history/Controller/Ess/TimeOfUseTariff/overview/overview.ts index 51ed8f95a67..1d850679996 100644 --- a/ui/src/app/edge/history/Controller/Ess/TimeOfUseTariff/overview/overview.ts +++ b/ui/src/app/edge/history/Controller/Ess/TimeOfUseTariff/overview/overview.ts @@ -3,5 +3,6 @@ import { AbstractHistoryChartOverview } from "src/app/shared/components/chart/ab @Component({ templateUrl: "./overview.html", + standalone: false, }) export class OverviewComponent extends AbstractHistoryChartOverview { } diff --git a/ui/src/app/edge/history/Controller/Io/DigitalOutput/chart/chart.ts b/ui/src/app/edge/history/Controller/Io/DigitalOutput/chart/chart.ts index 9daf18f5179..458d8ac56c7 100644 --- a/ui/src/app/edge/history/Controller/Io/DigitalOutput/chart/chart.ts +++ b/ui/src/app/edge/history/Controller/Io/DigitalOutput/chart/chart.ts @@ -4,11 +4,12 @@ import { AbstractHistoryChart } from "src/app/shared/components/chart/abstracthi import { Name } from "src/app/shared/components/shared/name"; import { QueryHistoricTimeseriesEnergyResponse } from "src/app/shared/jsonrpc/response/queryHistoricTimeseriesEnergyResponse"; import { ChartAxis, HistoryUtils, Utils, YAxisType } from "src/app/shared/service/utils"; -import { ChannelAddress, EdgeConfig } from "src/app/shared/shared"; +import { ChannelAddress, ChartConstants, EdgeConfig } from "src/app/shared/shared"; @Component({ selector: "totalChart", templateUrl: "../../../../../../shared/components/chart/abstracthistorychart.html", + standalone: false, }) export class TotalChartComponent extends AbstractHistoryChart { @@ -30,7 +31,6 @@ export class TotalChartComponent extends AbstractHistoryChart { input: input, output: (data: HistoryUtils.ChannelData) => { const output: HistoryUtils.DisplayValue[] = []; - const colors: string[] = ["rgb(0,0,139)", "rgb(0,191,255)", "rgb(0,0,56)", "rgb(77,77,174)"]; for (let i = 0; i < controllers.length; i++) { const controller = controllers[i]; @@ -40,12 +40,11 @@ export class TotalChartComponent extends AbstractHistoryChart { return energyQueryResponse?.result.data[controller.id + "/CumulatedActiveTime"] ?? null; }, converter: () => { - return data[controller.id] // TODO add logic to not have to adjust non power data manually .map(val => Utils.multiplySafely(val, 1000)); }, - color: colors[i % colors.length], + color: ChartConstants.Colors.SHADES_OF_YELLOW[i % (ChartConstants.Colors.SHADES_OF_YELLOW.length - 1)], stack: 0, }); } diff --git a/ui/src/app/edge/history/Controller/Io/DigitalOutput/details/chart/chart.ts b/ui/src/app/edge/history/Controller/Io/DigitalOutput/details/chart/chart.ts index 91c271d820a..46a3e12c8e3 100644 --- a/ui/src/app/edge/history/Controller/Io/DigitalOutput/details/chart/chart.ts +++ b/ui/src/app/edge/history/Controller/Io/DigitalOutput/details/chart/chart.ts @@ -5,11 +5,12 @@ import { AbstractHistoryChart } from "src/app/shared/components/chart/abstracthi import { Name } from "src/app/shared/components/shared/name"; import { QueryHistoricTimeseriesEnergyResponse } from "src/app/shared/jsonrpc/response/queryHistoricTimeseriesEnergyResponse"; import { ChartAxis, HistoryUtils, Utils, YAxisType } from "src/app/shared/service/utils"; -import { ChannelAddress, EdgeConfig } from "src/app/shared/shared"; +import { ChannelAddress, ChartConstants, EdgeConfig } from "src/app/shared/shared"; @Component({ selector: "detailChart", templateUrl: "../../../../../../../shared/components/chart/abstracthistorychart.html", + standalone: false, }) export class ChartComponent extends AbstractHistoryChart { @@ -53,7 +54,7 @@ export class ChartComponent extends AbstractHistoryChart { // TODO add logic to not have to adjust non power data manually ?.map(val => Utils.multiplySafely(val, 1000)); }, - color: "rgb(0,191,255)", + color: ChartConstants.Colors.YELLOW, stack: 0, }); diff --git a/ui/src/app/edge/history/Controller/Io/DigitalOutput/details/details.overview.ts b/ui/src/app/edge/history/Controller/Io/DigitalOutput/details/details.overview.ts index df7cf0ec298..a0a9877aa1c 100644 --- a/ui/src/app/edge/history/Controller/Io/DigitalOutput/details/details.overview.ts +++ b/ui/src/app/edge/history/Controller/Io/DigitalOutput/details/details.overview.ts @@ -3,5 +3,6 @@ import { AbstractHistoryChartOverview } from "src/app/shared/components/chart/ab @Component({ templateUrl: "./details.overview.html", + standalone: false, }) export class DetailsOverviewComponent extends AbstractHistoryChartOverview { } diff --git a/ui/src/app/edge/history/Controller/Io/DigitalOutput/digitalOutput.module.ts b/ui/src/app/edge/history/Controller/Io/DigitalOutput/digitalOutput.module.ts index df92f38b353..cc060d3d5a8 100644 --- a/ui/src/app/edge/history/Controller/Io/DigitalOutput/digitalOutput.module.ts +++ b/ui/src/app/edge/history/Controller/Io/DigitalOutput/digitalOutput.module.ts @@ -1,12 +1,12 @@ import { NgModule } from "@angular/core"; import { BrowserModule } from "@angular/platform-browser"; +import { FooterNavigationModule } from "src/app/shared/components/footer/subnavigation/footerNavigation.module"; import { SharedModule } from "src/app/shared/shared.module"; import { TotalChartComponent } from "./chart/chart"; -import { FlatComponent } from "./flat/flat"; -import { OverviewComponent } from "./overview/overview"; import { ChartComponent } from "./details/chart/chart"; import { DetailsOverviewComponent } from "./details/details.overview"; -import { FooterNavigationModule } from "src/app/shared/components/footer/subnavigation/footerNavigation.module"; +import { FlatComponent } from "./flat/flat"; +import { OverviewComponent } from "./overview/overview"; @NgModule({ imports: [ diff --git a/ui/src/app/edge/history/Controller/Io/DigitalOutput/flat/flat.html b/ui/src/app/edge/history/Controller/Io/DigitalOutput/flat/flat.html index 95886fc8abf..9f443a3fa6d 100644 --- a/ui/src/app/edge/history/Controller/Io/DigitalOutput/flat/flat.html +++ b/ui/src/app/edge/history/Controller/Io/DigitalOutput/flat/flat.html @@ -1,6 +1,6 @@ + 'power-outline',color:'normal'}"> { + + const output: HistoryUtils.DisplayValue[] = []; + + if (chartType === "line") { + output.push({ + name: "Level", + converter: () => data[component.id].map(val => Utils.multiplySafely(val, 1000)), + color: ChartConstants.Colors.RED, + stack: 0, + }); + } + + if (chartType === "bar") { + for (const level of [1, 2, 3]) { + output.push({ + name: "Level " + level, + nameSuffix: (energyQueryResponse: QueryHistoricTimeseriesEnergyResponse) => + energyQueryResponse?.result.data[component.id + "/Level" + level + "CumulatedTime"] ?? null, + converter: () => data[component.id + level] + // TODO add logic to not have to adjust non power data manually + .map(val => Utils.multiplySafely(val, 1000)), + color: phaseColors[level % phaseColors.length], + stack: 0, + }); + } + } + + return output; + }, + tooltip: { + formatNumber: ChartConstants.NumberFormat.NO_DECIMALS, + }, + yAxes: [ + chartType === "line" + ? { + unit: YAxisType.LEVEL, + position: "left", + yAxisId: ChartAxis.LEFT, + } + : { + unit: YAxisType.TIME, + position: "left", + yAxisId: ChartAxis.LEFT, + }, + ], + }; + } + + protected override getChartData(): HistoryUtils.ChartData { + return ChartComponent.getChartData(this.component, AbstractHistoryChart.phaseColors, this.chartType); + } +} diff --git a/ui/src/app/edge/history/Controller/Io/heatingelement/flat/flat.html b/ui/src/app/edge/history/Controller/Io/heatingelement/flat/flat.html new file mode 100644 index 00000000000..fbe00ce3acd --- /dev/null +++ b/ui/src/app/edge/history/Controller/Io/heatingelement/flat/flat.html @@ -0,0 +1,8 @@ + + + + + + diff --git a/ui/src/app/edge/history/Controller/Io/heatingelement/flat/flat.ts b/ui/src/app/edge/history/Controller/Io/heatingelement/flat/flat.ts new file mode 100644 index 00000000000..58a972f42ad --- /dev/null +++ b/ui/src/app/edge/history/Controller/Io/heatingelement/flat/flat.ts @@ -0,0 +1,11 @@ +import { Component } from "@angular/core"; +import { AbstractFlatWidget } from "src/app/shared/components/flat/abstract-flat-widget"; + +@Component({ + selector: "controller-io-heatingelement-widget", + templateUrl: "./flat.html", + standalone: false, +}) +export class FlatComponent extends AbstractFlatWidget { + protected FORMAT_SECONDS_TO_DURATION = this.Converter.FORMAT_SECONDS_TO_DURATION(this.translate.currentLang); +} diff --git a/ui/src/app/edge/history/Controller/Io/heatingelement/heatingelement.module.ts b/ui/src/app/edge/history/Controller/Io/heatingelement/heatingelement.module.ts new file mode 100644 index 00000000000..99310facb87 --- /dev/null +++ b/ui/src/app/edge/history/Controller/Io/heatingelement/heatingelement.module.ts @@ -0,0 +1,24 @@ +import { NgModule } from "@angular/core"; +import { BrowserModule } from "@angular/platform-browser"; +import { SharedModule } from "src/app/shared/shared.module"; +import { ChartComponent } from "./chart/chart"; +import { FlatComponent } from "./flat/flat"; +import { OverviewComponent } from "./overview/overview"; + +@NgModule({ + imports: [ + BrowserModule, + SharedModule, + ], + declarations: [ + ChartComponent, + FlatComponent, + OverviewComponent, + ], + exports: [ + ChartComponent, + FlatComponent, + OverviewComponent, + ], +}) +export class HeatingElement { } diff --git a/ui/src/app/edge/history/Controller/Io/heatingelement/overview/overview.html b/ui/src/app/edge/history/Controller/Io/heatingelement/overview/overview.html new file mode 100644 index 00000000000..d834e111f28 --- /dev/null +++ b/ui/src/app/edge/history/Controller/Io/heatingelement/overview/overview.html @@ -0,0 +1,4 @@ + + + + diff --git a/ui/src/app/edge/history/Controller/Io/heatingelement/overview/overview.ts b/ui/src/app/edge/history/Controller/Io/heatingelement/overview/overview.ts new file mode 100644 index 00000000000..c5a5e8663c2 --- /dev/null +++ b/ui/src/app/edge/history/Controller/Io/heatingelement/overview/overview.ts @@ -0,0 +1,9 @@ +import { Component } from "@angular/core"; +import { AbstractHistoryChartOverview } from "src/app/shared/components/chart/abstractHistoryChartOverview"; + +@Component({ + selector: "controller-io-heatingelement-overview", + templateUrl: "./overview.html", + standalone: false, +}) +export class OverviewComponent extends AbstractHistoryChartOverview { } diff --git a/ui/src/app/edge/history/Controller/ModbusTcpApi/chart/chart.ts b/ui/src/app/edge/history/Controller/ModbusTcpApi/chart/chart.ts index 987109ea767..a87384ceab3 100644 --- a/ui/src/app/edge/history/Controller/ModbusTcpApi/chart/chart.ts +++ b/ui/src/app/edge/history/Controller/ModbusTcpApi/chart/chart.ts @@ -8,6 +8,7 @@ import { ChannelAddress, EdgeConfig } from "src/app/shared/shared"; @Component({ selector: "modbusTcpApiChart", templateUrl: "../../../../../shared/components/chart/abstracthistorychart.html", + standalone: false, }) export class ChartComponent extends AbstractHistoryChart { diff --git a/ui/src/app/edge/history/Controller/ModbusTcpApi/flat/flat.html b/ui/src/app/edge/history/Controller/ModbusTcpApi/flat/flat.html index e7a0c6ad6a6..774733cbe2b 100644 --- a/ui/src/app/edge/history/Controller/ModbusTcpApi/flat/flat.html +++ b/ui/src/app/edge/history/Controller/ModbusTcpApi/flat/flat.html @@ -1,5 +1,10 @@ - - - + + + diff --git a/ui/src/app/edge/history/Controller/ModbusTcpApi/flat/flat.ts b/ui/src/app/edge/history/Controller/ModbusTcpApi/flat/flat.ts index 9a857c71826..1dbc789649a 100644 --- a/ui/src/app/edge/history/Controller/ModbusTcpApi/flat/flat.ts +++ b/ui/src/app/edge/history/Controller/ModbusTcpApi/flat/flat.ts @@ -6,6 +6,7 @@ import { ChannelAddress } from "src/app/shared/shared"; @Component({ selector: "modbusTcpApiWidget", templateUrl: "./flat.html", + standalone: false, }) export class FlatComponent extends AbstractFlatWidget { diff --git a/ui/src/app/edge/history/Controller/ModbusTcpApi/modbusTcpApi.module.ts b/ui/src/app/edge/history/Controller/ModbusTcpApi/modbusTcpApi.module.ts index aef372d5a35..b0ca5fb3021 100644 --- a/ui/src/app/edge/history/Controller/ModbusTcpApi/modbusTcpApi.module.ts +++ b/ui/src/app/edge/history/Controller/ModbusTcpApi/modbusTcpApi.module.ts @@ -1,9 +1,9 @@ import { NgModule } from "@angular/core"; import { BrowserModule } from "@angular/platform-browser"; import { SharedModule } from "src/app/shared/shared.module"; +import { ChartComponent } from "./chart/chart"; import { FlatComponent } from "./flat/flat"; import { OverviewComponent } from "./overview/overview"; -import { ChartComponent } from "./chart/chart"; @NgModule({ imports: [ diff --git a/ui/src/app/edge/history/Controller/ModbusTcpApi/overview/overview.ts b/ui/src/app/edge/history/Controller/ModbusTcpApi/overview/overview.ts index 51ed8f95a67..1d850679996 100644 --- a/ui/src/app/edge/history/Controller/ModbusTcpApi/overview/overview.ts +++ b/ui/src/app/edge/history/Controller/ModbusTcpApi/overview/overview.ts @@ -3,5 +3,6 @@ import { AbstractHistoryChartOverview } from "src/app/shared/components/chart/ab @Component({ templateUrl: "./overview.html", + standalone: false, }) export class OverviewComponent extends AbstractHistoryChartOverview { } diff --git a/ui/src/app/edge/history/Controller/controller.module.ts b/ui/src/app/edge/history/Controller/controller.module.ts index e009e41d838..773e4d92f97 100644 --- a/ui/src/app/edge/history/Controller/controller.module.ts +++ b/ui/src/app/edge/history/Controller/controller.module.ts @@ -1,9 +1,9 @@ import { NgModule } from "@angular/core"; -import { ControllerEss } from "./Ess/ess.module"; -import { ControllerIo } from "./Io/Io.module"; import { ChannelThreshold } from "./ChannelThreshold/channelThreshold.module"; +import { ControllerEss } from "./Ess/ess.module"; import { GridOptimizeCharge } from "./Ess/GridoptimizedCharge/gridOptimizeCharge.module"; import { TimeOfUseTariff } from "./Ess/TimeOfUseTariff/timeOfUseTariff.module"; +import { ControllerIo } from "./Io/Io.module"; import { ModbusTcpApi } from "./ModbusTcpApi/modbusTcpApi.module"; @NgModule({ diff --git a/ui/src/app/edge/history/abstracthistorychart.ts b/ui/src/app/edge/history/abstracthistorychart.ts index 0891b3f453e..a9968fcf779 100644 --- a/ui/src/app/edge/history/abstracthistorychart.ts +++ b/ui/src/app/edge/history/abstracthistorychart.ts @@ -2,7 +2,7 @@ import { TranslateService } from "@ngx-translate/core"; import * as Chart from "chart.js"; import { AbstractHistoryChart as NewAbstractHistoryChart } from "src/app/shared/components/chart/abstracthistorychart"; -import { ChartConstants, XAxisType } from "src/app/shared/components/chart/chart.constants"; +import { XAxisType } from "src/app/shared/components/chart/chart.constants"; import { JsonrpcResponseError } from "src/app/shared/jsonrpc/base"; import { QueryHistoricTimeseriesDataRequest } from "src/app/shared/jsonrpc/request/queryHistoricTimeseriesDataRequest"; import { QueryHistoricTimeseriesEnergyPerPeriodRequest } from "src/app/shared/jsonrpc/request/queryHistoricTimeseriesEnergyPerPeriodRequest"; @@ -10,6 +10,7 @@ import { QueryHistoricTimeseriesDataResponse } from "src/app/shared/jsonrpc/resp import { QueryHistoricTimeseriesEnergyPerPeriodResponse } from "src/app/shared/jsonrpc/response/queryHistoricTimeseriesEnergyPerPeriodResponse"; import { ChartAxis, HistoryUtils, Utils, YAxisType } from "src/app/shared/service/utils"; import { ChannelAddress, Edge, EdgeConfig, Service } from "src/app/shared/shared"; +import { ColorUtils } from "src/app/shared/utils/color/color.utils"; import { DateUtils } from "src/app/shared/utils/date/dateutils"; import { DateTimeUtils } from "src/app/shared/utils/datetime/datetime-utils"; import { ChronoUnit, DEFAULT_TIME_CHART_OPTIONS, EMPTY_DATASET, Resolution, calculateResolution, setLabelVisible } from "./shared"; @@ -57,9 +58,6 @@ export abstract class AbstractHistoryChart { borderColor: "rgba(128,128,0,1)", }; - private activeQueryData: string; - private debounceTimeout: any | null = null; - constructor( public readonly spinnerId: string, protected service: Service, @@ -118,7 +116,6 @@ export abstract class AbstractHistoryChart { public setOptions(options: Chart.ChartOptions): Promise { return new Promise((resolve) => { - const locale = this.service.translate.currentLang; const yAxis: HistoryUtils.yAxes = { position: "left", unit: this.unit, yAxisId: ChartAxis.LEFT }; const chartObject: HistoryUtils.ChartData = { input: [], @@ -155,19 +152,23 @@ export abstract class AbstractHistoryChart { const value = tooltipItem.dataset.data[tooltipItem.dataIndex]; const customUnit = tooltipItem.dataset.unit ?? null; - return label.split(":")[0] + ": " + NewAbstractHistoryChart.getToolTipsSuffix("", value, formatNumber, customUnit ?? unit, "line", locale, translate, conf); + return label.split(":")[0] + ": " + NewAbstractHistoryChart.getToolTipsSuffix("", value, formatNumber, customUnit ?? unit, "line", translate, conf); }; options.plugins.tooltip.callbacks.labelColor = (item: Chart.TooltipItem) => { - const color = colors[item.datasetIndex]; + let backgroundColor = item.dataset.backgroundColor; + + if (Array.isArray(backgroundColor)) { + backgroundColor = backgroundColor[0]; + } - if (!color) { - return; + if (!backgroundColor) { + backgroundColor = item.dataset.borderColor || "rgba(0, 0, 0, 0.5)"; } return { - borderColor: color.borderColor, - backgroundColor: color.backgroundColor, + borderColor: ColorUtils.changeOpacityFromRGBA(backgroundColor, 1), + backgroundColor: ColorUtils.changeOpacityFromRGBA(backgroundColor, 1), }; }; @@ -237,19 +238,19 @@ export abstract class AbstractHistoryChart { break; } - // Only one yAxis defined - options = NewAbstractHistoryChart.getYAxisOptions(options, yAxis, this.translate, "line", locale, ChartConstants.EMPTY_DATASETS, false); - - options.scales.x["stacked"] = true; - options.scales[ChartAxis.LEFT]["stacked"] = false; - options = NewAbstractHistoryChart.applyChartTypeSpecificOptionsChanges("line", options, this.service, chartObject); - /** Overwrite default yAxisId */ this.datasets = this.datasets .map(el => { el["yAxisID"] = ChartAxis.LEFT; return el; }); + + // Only one yAxis defined + options = NewAbstractHistoryChart.getYAxisOptions(options, yAxis, this.translate, "line", this.datasets, true); + options = NewAbstractHistoryChart.applyChartTypeSpecificOptionsChanges("line", options, this.service, chartObject); + options.scales[ChartAxis.LEFT]["stacked"] = false; + options.scales.x["stacked"] = true; + options.scales.x.ticks.color = getComputedStyle(document.documentElement).getPropertyValue("--ion-color-chart-xAxis-ticks"); }).then(() => { this.options = options; resolve(); @@ -271,48 +272,33 @@ export abstract class AbstractHistoryChart { const resolution = res ?? calculateResolution(this.service, fromDate, toDate).resolution; this.errorResponse = null; - - if (this.debounceTimeout) { - clearTimeout(this.debounceTimeout); - } - - this.debounceTimeout = setTimeout(() => { - const result: Promise = new Promise((resolve, reject) => { - this.service.getCurrentEdge().then(edge => { - this.service.getConfig().then(config => { - this.setLabel(config); - this.getChannelAddresses(edge, config).then(channelAddresses => { - const request = new QueryHistoricTimeseriesDataRequest(DateUtils.maxDate(fromDate, this.edge?.firstSetupProtocol), toDate, channelAddresses, resolution); - edge.sendRequest(this.service.websocket, request).then(response => { - resolve(response as QueryHistoricTimeseriesDataResponse); - this.activeQueryData = request.id; - }).catch(error => { - this.errorResponse = error; - resolve(new QueryHistoricTimeseriesDataResponse(error.id, { - timestamps: [null], data: { null: null }, - })); - }); + const result: Promise = new Promise((resolve, reject) => { + this.service.getCurrentEdge().then(edge => { + this.service.getConfig().then(config => { + this.setLabel(config); + this.getChannelAddresses(edge, config).then(channelAddresses => { + const request = new QueryHistoricTimeseriesDataRequest(DateUtils.maxDate(fromDate, this.edge?.firstSetupProtocol), toDate, channelAddresses, resolution); + edge.sendRequest(this.service.websocket, request).then(response => { + resolve(response as QueryHistoricTimeseriesDataResponse); + }).catch(error => { + this.errorResponse = error; + resolve(new QueryHistoricTimeseriesDataResponse(error.id, { + timestamps: [null], data: { null: null }, + })); }); }); }); - }).then((response) => { - if (this.activeQueryData !== response.id) { - return; - } - if (Utils.isDataEmpty(response)) { - this.loading = false; - this.service.stopSpinner(this.spinnerId); - this.initializeChart(); - } - return DateTimeUtils.normalizeTimestamps(resolution.unit, response); }); - - return result; - }, ChartConstants.REQUEST_TIMEOUT); - - return new Promise((resolve) => { - resolve(new QueryHistoricTimeseriesDataResponse("", { timestamps: [], data: {} })); + }).then((response) => { + if (Utils.isDataEmpty(response)) { + this.loading = false; + this.service.stopSpinner(this.spinnerId); + this.initializeChart(); + } + return DateTimeUtils.normalizeTimestamps(resolution.unit, response); }); + + return result; } /** @@ -363,8 +349,7 @@ export abstract class AbstractHistoryChart { * @returns the ChartOptions */ protected createDefaultChartOptions(): Chart.ChartOptions { - const options = Utils.deepCopy(DEFAULT_TIME_CHART_OPTIONS); - return options; + return Utils.deepCopy(DEFAULT_TIME_CHART_OPTIONS); } /** diff --git a/ui/src/app/edge/history/chpsoc/chart.component.ts b/ui/src/app/edge/history/chpsoc/chart.component.ts index 89019ad7b87..f3b99ac069b 100644 --- a/ui/src/app/edge/history/chpsoc/chart.component.ts +++ b/ui/src/app/edge/history/chpsoc/chart.component.ts @@ -10,6 +10,7 @@ import { AbstractHistoryChart } from "../abstracthistorychart"; @Component({ selector: "chpsocchart", templateUrl: "../abstracthistorychart.html", + standalone: false, }) export class ChpSocChartComponent extends AbstractHistoryChart implements OnInit, OnChanges, OnDestroy { diff --git a/ui/src/app/edge/history/chpsoc/widget.component.ts b/ui/src/app/edge/history/chpsoc/widget.component.ts index 9056355309b..76f9a4cf592 100644 --- a/ui/src/app/edge/history/chpsoc/widget.component.ts +++ b/ui/src/app/edge/history/chpsoc/widget.component.ts @@ -10,6 +10,7 @@ import { calculateActiveTimeOverPeriod } from "../shared"; @Component({ selector: ChpSocWidgetComponent.SELECTOR, templateUrl: "./widget.component.html", + standalone: false, }) export class ChpSocWidgetComponent extends AbstractHistoryWidget implements OnInit, OnChanges, OnDestroy { diff --git a/ui/src/app/edge/history/common/autarchy/chart/chart.ts b/ui/src/app/edge/history/common/autarchy/chart/chart.ts index 8a17580e381..29fb0ec2738 100644 --- a/ui/src/app/edge/history/common/autarchy/chart/chart.ts +++ b/ui/src/app/edge/history/common/autarchy/chart/chart.ts @@ -8,6 +8,7 @@ import { ChannelAddress, Utils } from "src/app/shared/shared"; @Component({ selector: "autarchychart", templateUrl: "../../../../../shared/components/chart/abstracthistorychart.html", + standalone: false, }) export class ChartComponent extends AbstractHistoryChart { diff --git a/ui/src/app/edge/history/common/autarchy/flat/flat.html b/ui/src/app/edge/history/common/autarchy/flat/flat.html index 632cc21b2df..7137f648ff3 100644 --- a/ui/src/app/edge/history/common/autarchy/flat/flat.html +++ b/ui/src/app/edge/history/common/autarchy/flat/flat.html @@ -1,4 +1,4 @@ - diff --git a/ui/src/app/edge/history/common/autarchy/flat/flat.ts b/ui/src/app/edge/history/common/autarchy/flat/flat.ts index 429cca30b54..7176f22709f 100644 --- a/ui/src/app/edge/history/common/autarchy/flat/flat.ts +++ b/ui/src/app/edge/history/common/autarchy/flat/flat.ts @@ -6,6 +6,7 @@ import { ChannelAddress, CurrentData, Utils } from "../../../../../shared/shared @Component({ selector: "autarchyWidget", templateUrl: "./flat.html", + standalone: false, }) export class FlatComponent extends AbstractFlatWidget { diff --git a/ui/src/app/edge/history/common/autarchy/overview/overview.ts b/ui/src/app/edge/history/common/autarchy/overview/overview.ts index 51ed8f95a67..1d850679996 100644 --- a/ui/src/app/edge/history/common/autarchy/overview/overview.ts +++ b/ui/src/app/edge/history/common/autarchy/overview/overview.ts @@ -3,5 +3,6 @@ import { AbstractHistoryChartOverview } from "src/app/shared/components/chart/ab @Component({ templateUrl: "./overview.html", + standalone: false, }) export class OverviewComponent extends AbstractHistoryChartOverview { } diff --git a/ui/src/app/edge/history/common/consumption/Consumption.ts b/ui/src/app/edge/history/common/consumption/Consumption.ts index c36c786695d..9b957fb4e03 100644 --- a/ui/src/app/edge/history/common/consumption/Consumption.ts +++ b/ui/src/app/edge/history/common/consumption/Consumption.ts @@ -1,9 +1,9 @@ import { NgModule } from "@angular/core"; import { BrowserModule } from "@angular/platform-browser"; +import { CurrentVoltageModule } from "src/app/shared/components/edge/meter/currentVoltage/currentVoltageModule"; import { FooterNavigationModule } from "src/app/shared/components/footer/subnavigation/footerNavigation.module"; import { SharedModule } from "src/app/shared/shared.module"; -import { CurrentVoltageModule } from "src/app/shared/components/edge/meter/currentVoltage/currentVoltageModule"; import { ChartComponent } from "./chart/chart"; import { ConsumptionMeterChartDetailsComponent } from "./details/chart/consumptionMeter"; import { EvcsChartDetailsComponent } from "./details/chart/evcs"; diff --git a/ui/src/app/edge/history/common/consumption/chart/chart.constants.spec.ts b/ui/src/app/edge/history/common/consumption/chart/chart.constants.spec.ts index c9bc81b39a5..bd73d6ec9cd 100644 --- a/ui/src/app/edge/history/common/consumption/chart/chart.constants.spec.ts +++ b/ui/src/app/edge/history/common/consumption/chart/chart.constants.spec.ts @@ -1,16 +1,16 @@ import { DummyConfig } from "src/app/shared/components/edge/edgeconfig.spec"; import { OeTester } from "src/app/shared/components/shared/testing/common"; import { OeChartTester } from "src/app/shared/components/shared/testing/tester"; -import { removeFunctions, TestContext } from "src/app/shared/components/shared/testing/utils.spec"; +import { TestContext, TestingUtils } from "src/app/shared/components/shared/testing/utils.spec"; import { EdgeConfig } from "src/app/shared/shared"; import { ChartComponent } from "./chart"; export function expectView(config: EdgeConfig, testContext: TestContext, chartType: "line" | "bar", channels: OeTester.Types.Channels, view: OeChartTester.View): void { - expect(removeFunctions(OeChartTester + expect(TestingUtils.removeFunctions(OeChartTester .apply(ChartComponent .getChartData( DummyConfig.convertDummyEdgeConfigToRealEdgeConfig(config), - testContext.translate), chartType, channels, testContext, config))) - .toEqual(removeFunctions(view)); + testContext.translate, DummyConfig.dummyEdge({ version: "2024.1.1" })), chartType, channels, testContext, config))) + .toEqual(TestingUtils.removeFunctions(view)); } diff --git a/ui/src/app/edge/history/common/consumption/chart/chart.spec.ts b/ui/src/app/edge/history/common/consumption/chart/chart.spec.ts index 66697d1c191..17371c27a85 100644 --- a/ui/src/app/edge/history/common/consumption/chart/chart.spec.ts +++ b/ui/src/app/edge/history/common/consumption/chart/chart.spec.ts @@ -1,7 +1,7 @@ // @ts-strict-ignore import { DummyConfig } from "src/app/shared/components/edge/edgeconfig.spec"; import { OeTester } from "src/app/shared/components/shared/testing/common"; -import { sharedSetup, TestContext } from "src/app/shared/components/shared/testing/utils.spec"; +import { TestContext, TestingUtils } from "src/app/shared/components/shared/testing/utils.spec"; import { DATA, LABELS } from "../../energy/chart/chart.constants.spec"; import { History } from "./channels.spec"; @@ -15,7 +15,7 @@ describe("History Consumption", () => { let TEST_CONTEXT: TestContext; beforeEach(async () => - TEST_CONTEXT = await sharedSetup(), + TEST_CONTEXT = await TestingUtils.sharedSetup(), ); it("#getChartData()", () => { diff --git a/ui/src/app/edge/history/common/consumption/chart/chart.ts b/ui/src/app/edge/history/common/consumption/chart/chart.ts index bc5474bde75..c11bd097b8d 100644 --- a/ui/src/app/edge/history/common/consumption/chart/chart.ts +++ b/ui/src/app/edge/history/common/consumption/chart/chart.ts @@ -2,18 +2,20 @@ import { Component } from "@angular/core"; import { TranslateService } from "@ngx-translate/core"; import { AbstractHistoryChart } from "src/app/shared/components/chart/abstracthistorychart"; +import { ChartConstants } from "src/app/shared/components/chart/chart.constants"; +import { EvcsUtils } from "src/app/shared/components/edge/utils/evcs-utils"; import { QueryHistoricTimeseriesEnergyResponse } from "src/app/shared/jsonrpc/response/queryHistoricTimeseriesEnergyResponse"; import { ChartAxis, HistoryUtils, YAxisType } from "src/app/shared/service/utils"; -import { ChannelAddress, EdgeConfig, Utils } from "src/app/shared/shared"; +import { ChannelAddress, Edge, EdgeConfig, Utils } from "src/app/shared/shared"; @Component({ selector: "consumptionchart", templateUrl: "../../../../../shared/components/chart/abstracthistorychart.html", + standalone: false, }) export class ChartComponent extends AbstractHistoryChart { - public static getChartData(config: EdgeConfig, translate: TranslateService): HistoryUtils.ChartData { - + public static getChartData(config: EdgeConfig, translate: TranslateService, edge: Edge): HistoryUtils.ChartData { const inputChannel: HistoryUtils.InputChannel[] = [{ name: "ConsumptionActivePower", powerChannel: ChannelAddress.fromString("_sum/ConsumptionActivePower"), @@ -26,17 +28,19 @@ export class ChartComponent extends AbstractHistoryChart { component.factoryId == "Evcs.Cluster.PeakShaving" || component.factoryId == "Evcs.Cluster.SelfConsumption")); - const consumptionMeters: EdgeConfig.Component[] = config.getComponentsImplementingNature("io.openems.edge.meter.api.ElectricityMeter") - .filter(component => component.isEnabled && config.isTypeConsumptionMetered(component)); - + // TODO Since 2024.11.0 EVCS implements EletricityMeter; use DeprecatedEvcs as filter evcsComponents.forEach(component => { inputChannel.push({ - name: component.id + "/ChargePower", - powerChannel: ChannelAddress.fromString(component.id + "/ChargePower"), + name: component.id + "/" + EvcsUtils.getEvcsPowerChannelId(component, config, edge), + powerChannel: ChannelAddress.fromString(component.id + "/" + EvcsUtils.getEvcsPowerChannelId(component, config, edge)), energyChannel: ChannelAddress.fromString(component.id + "/ActiveConsumptionEnergy"), }); }); + const consumptionMeters: EdgeConfig.Component[] = config.getComponentsImplementingNature("io.openems.edge.meter.api.ElectricityMeter") + .filter(component => component.isEnabled && config.isTypeConsumptionMetered(component) + && !config.getNatureIdsByFactoryId(component.factoryId).includes("io.openems.edge.evcs.api.Evcs")); + consumptionMeters.forEach(meter => { inputChannel.push({ name: meter.id + "/ActivePower", @@ -60,11 +64,11 @@ export class ChartComponent extends AbstractHistoryChart { converter: () => { return data["ConsumptionActivePower"] ?? null; }, - color: "rgb(253,197,7)", + color: ChartConstants.Colors.BLUE, stack: 0, }); - const evcsComponentColors: string[] = ["rgb(0,223,0)", "rgb(0,178,0)", "rgb(0,201,0)", "rgb(0,134,0)", "rgb(0,156,0)"]; + const evcsComponentColors: string[] = ChartConstants.Colors.SHADES_OF_GREEN; evcsComponents.forEach((component, index) => { datasets.push({ name: component.alias, @@ -72,14 +76,14 @@ export class ChartComponent extends AbstractHistoryChart { return energyValues?.result.data[component.id + "/ActiveConsumptionEnergy"]; }, converter: () => { - return data[component.id + "/ChargePower"] ?? null; + return data[component.id + "/" + EvcsUtils.getEvcsPowerChannelId(component, config, edge)] ?? null; }, - color: evcsComponentColors[Math.min(index, (evcsComponentColors.length - 1))], + color: evcsComponentColors[index % (evcsComponentColors.length - 1)], stack: 1, }); }); - const consumptionMeterColors: string[] = ["rgb(220,20,60)", "rgb(202, 158, 6", "rgb(228, 177, 6)", "rgb(177, 138, 5)", "rgb(152, 118, 4)"]; + const consumptionMeterColors: string[] = ChartConstants.Colors.SHADES_OF_YELLOW; consumptionMeters.forEach((meter, index) => { datasets.push({ name: meter.alias, @@ -89,7 +93,7 @@ export class ChartComponent extends AbstractHistoryChart { converter: () => { return data[meter.id + "/ActivePower"] ?? null; }, - color: consumptionMeterColors[Math.min(index, (consumptionMeterColors.length - 1))], + color: consumptionMeterColors[index % (consumptionMeterColors.length - 1)], stack: 1, }); }); @@ -104,7 +108,7 @@ export class ChartComponent extends AbstractHistoryChart { converter: () => { return Utils.calculateOtherConsumption(data, evcsComponents, consumptionMeters); }, - color: "rgb(0,0,0)", + color: ChartConstants.Colors.GREY, stack: 1, }); } @@ -124,7 +128,8 @@ export class ChartComponent extends AbstractHistoryChart { } protected override getChartData() { - return ChartComponent.getChartData(this.config, this.translate); + return ChartComponent.getChartData(this.config, this.translate, this.edge); } + } diff --git a/ui/src/app/edge/history/common/consumption/details/chart/consumptionMeter.spec.ts b/ui/src/app/edge/history/common/consumption/details/chart/consumptionMeter.spec.ts index c73384cbce2..a22a5865910 100644 --- a/ui/src/app/edge/history/common/consumption/details/chart/consumptionMeter.spec.ts +++ b/ui/src/app/edge/history/common/consumption/details/chart/consumptionMeter.spec.ts @@ -3,7 +3,7 @@ import { ActivatedRoute } from "@angular/router"; import { DummyConfig } from "src/app/shared/components/edge/edgeconfig.spec"; import { OeTester } from "src/app/shared/components/shared/testing/common"; import { OeChartTester } from "src/app/shared/components/shared/testing/tester"; -import { removeFunctions, sharedSetupWithComponentIdRoute, TestContext } from "src/app/shared/components/shared/testing/utils.spec"; +import { TestContext, TestingUtils } from "src/app/shared/components/shared/testing/utils.spec"; import { EdgeConfig } from "src/app/shared/shared"; import { DATA, LABELS } from "../../../energy/chart/chart.constants.spec"; import { History } from "./channels.spec"; @@ -16,7 +16,7 @@ describe("History Consumption Details - consumptionMeters", () => { let TEST_CONTEXT: TestContext & { route: ActivatedRoute }; beforeEach(async () => { - TEST_CONTEXT = await sharedSetupWithComponentIdRoute("meter0"); + TEST_CONTEXT = await TestingUtils.setupWithActivatedRoute("meter0"); }); it("#getChartData()", () => { @@ -55,10 +55,10 @@ describe("History Consumption Details - consumptionMeters", () => { }); export function expectView(config: EdgeConfig, testContext: TestContext & { route: ActivatedRoute }, chartType: "line" | "bar", channels: OeTester.Types.Channels, view: OeChartTester.View): void { - expect(removeFunctions(OeChartTester + expect(TestingUtils.removeFunctions(OeChartTester .apply(ConsumptionMeterChartDetailsComponent .getChartData( DummyConfig.convertDummyEdgeConfigToRealEdgeConfig(config), testContext.route, testContext.translate), chartType, channels, testContext, config))) - .toEqual(removeFunctions(view)); + .toEqual(TestingUtils.removeFunctions(view)); } diff --git a/ui/src/app/edge/history/common/consumption/details/chart/consumptionMeter.ts b/ui/src/app/edge/history/common/consumption/details/chart/consumptionMeter.ts index 7fd59547f17..9375b6a7d67 100644 --- a/ui/src/app/edge/history/common/consumption/details/chart/consumptionMeter.ts +++ b/ui/src/app/edge/history/common/consumption/details/chart/consumptionMeter.ts @@ -5,11 +5,12 @@ import { AbstractHistoryChart } from "src/app/shared/components/chart/abstracthi import { Phase } from "src/app/shared/components/shared/phase"; import { QueryHistoricTimeseriesEnergyResponse } from "src/app/shared/jsonrpc/response/queryHistoricTimeseriesEnergyResponse"; import { ChartAxis, HistoryUtils, YAxisType } from "src/app/shared/service/utils"; -import { ChannelAddress, EdgeConfig } from "src/app/shared/shared"; +import { ChannelAddress, ChartConstants, EdgeConfig } from "src/app/shared/shared"; @Component({ selector: "consumptionMeterChart", templateUrl: "../../../../../../shared/components/chart/abstracthistorychart.html", + standalone: false, }) export class ConsumptionMeterChartDetailsComponent extends AbstractHistoryChart { @@ -31,7 +32,7 @@ export class ConsumptionMeterChartDetailsComponent extends AbstractHistoryChart name: component.alias, nameSuffix: (energyQueryResponse: QueryHistoricTimeseriesEnergyResponse) => energyQueryResponse.result.data[component.id + "/ActiveProductionEnergy"], converter: () => data[component.id], - color: "rgb(0,152,204)", + color: ChartConstants.Colors.RED, hiddenOnInit: false, stack: 2, }, diff --git a/ui/src/app/edge/history/common/consumption/details/chart/evcs.spec.ts b/ui/src/app/edge/history/common/consumption/details/chart/evcs.spec.ts index 75a40f7b0ba..0c1aefb365f 100644 --- a/ui/src/app/edge/history/common/consumption/details/chart/evcs.spec.ts +++ b/ui/src/app/edge/history/common/consumption/details/chart/evcs.spec.ts @@ -3,7 +3,7 @@ import { ActivatedRoute } from "@angular/router"; import { DummyConfig } from "src/app/shared/components/edge/edgeconfig.spec"; import { OeTester } from "src/app/shared/components/shared/testing/common"; import { OeChartTester } from "src/app/shared/components/shared/testing/tester"; -import { removeFunctions, sharedSetupWithComponentIdRoute, TestContext } from "src/app/shared/components/shared/testing/utils.spec"; +import { TestContext, TestingUtils } from "src/app/shared/components/shared/testing/utils.spec"; import { EdgeConfig } from "src/app/shared/shared"; import { DATA, LABELS } from "../../../energy/chart/chart.constants.spec"; import { History } from "./channels.spec"; @@ -16,7 +16,7 @@ describe("History Consumption Details - evcs", () => { let TEST_CONTEXT: TestContext & { route: ActivatedRoute }; beforeEach(async () => { - TEST_CONTEXT = await sharedSetupWithComponentIdRoute("evcs0"); + TEST_CONTEXT = await TestingUtils.setupWithActivatedRoute("evcs0"); }); it("#getChartData() - evcs", () => { @@ -49,10 +49,10 @@ describe("History Consumption Details - evcs", () => { }); export function expectView(config: EdgeConfig, testContext: TestContext & { route: ActivatedRoute }, chartType: "line" | "bar", channels: OeTester.Types.Channels, view: OeChartTester.View): void { - expect(removeFunctions(OeChartTester + expect(TestingUtils.removeFunctions(OeChartTester .apply(EvcsChartDetailsComponent .getChartData( DummyConfig.convertDummyEdgeConfigToRealEdgeConfig(config), testContext.route, - testContext.translate), chartType, channels, testContext, config))) - .toEqual(removeFunctions(view)); + testContext.translate, DummyConfig.dummyEdge({ version: "2024.1.1" })), chartType, channels, testContext, config))) + .toEqual(TestingUtils.removeFunctions(view)); } diff --git a/ui/src/app/edge/history/common/consumption/details/chart/evcs.ts b/ui/src/app/edge/history/common/consumption/details/chart/evcs.ts index 62e58482cfe..21c44acc3e1 100644 --- a/ui/src/app/edge/history/common/consumption/details/chart/evcs.ts +++ b/ui/src/app/edge/history/common/consumption/details/chart/evcs.ts @@ -2,30 +2,32 @@ import { Component } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; import { TranslateService } from "@ngx-translate/core"; import { AbstractHistoryChart } from "src/app/shared/components/chart/abstracthistorychart"; +import { EvcsUtils } from "src/app/shared/components/edge/utils/evcs-utils"; import { QueryHistoricTimeseriesEnergyResponse } from "src/app/shared/jsonrpc/response/queryHistoricTimeseriesEnergyResponse"; import { ChartAxis, HistoryUtils, YAxisType } from "src/app/shared/service/utils"; -import { ChannelAddress, EdgeConfig } from "src/app/shared/shared"; +import { ChannelAddress, ChartConstants, Edge, EdgeConfig } from "src/app/shared/shared"; @Component({ selector: "evcsChart", templateUrl: "../../../../../../shared/components/chart/abstracthistorychart.html", + standalone: false, }) export class EvcsChartDetailsComponent extends AbstractHistoryChart { - public static getChartData(config: EdgeConfig, route: ActivatedRoute, translate: TranslateService): HistoryUtils.ChartData { + public static getChartData(config: EdgeConfig, route: ActivatedRoute, translate: TranslateService, edge: Edge | null): HistoryUtils.ChartData { const component = config?.getComponent(route.snapshot.params.componentId); return { input: [{ name: component.id, - powerChannel: ChannelAddress.fromString(component.id + "/ChargePower"), + powerChannel: ChannelAddress.fromString(component.id + "/" + EvcsUtils.getEvcsPowerChannelId(component, config, edge)), energyChannel: ChannelAddress.fromString(component.id + "/ActiveConsumptionEnergy"), }], output: (data: HistoryUtils.ChannelData) => [{ name: component.alias, nameSuffix: (energyQueryResponse: QueryHistoricTimeseriesEnergyResponse) => energyQueryResponse.result.data[component.id + "/ActiveConsumptionEnergy"], converter: () => data[component.id], - color: "rgb(0,152,204)", + color: ChartConstants.Colors.GREEN, hiddenOnInit: false, stack: 2, }], @@ -42,6 +44,6 @@ export class EvcsChartDetailsComponent extends AbstractHistoryChart { } protected override getChartData(): HistoryUtils.ChartData { - return EvcsChartDetailsComponent.getChartData(this.config, this.route, this.translate); + return EvcsChartDetailsComponent.getChartData(this.config, this.route, this.translate, this.edge); } } diff --git a/ui/src/app/edge/history/common/consumption/details/chart/sum.spec.ts b/ui/src/app/edge/history/common/consumption/details/chart/sum.spec.ts index 0192ff39015..7acc6cf4dc3 100644 --- a/ui/src/app/edge/history/common/consumption/details/chart/sum.spec.ts +++ b/ui/src/app/edge/history/common/consumption/details/chart/sum.spec.ts @@ -3,7 +3,7 @@ import { ActivatedRoute } from "@angular/router"; import { DummyConfig } from "src/app/shared/components/edge/edgeconfig.spec"; import { OeTester } from "src/app/shared/components/shared/testing/common"; import { OeChartTester } from "src/app/shared/components/shared/testing/tester"; -import { removeFunctions, sharedSetupWithComponentIdRoute, TestContext } from "src/app/shared/components/shared/testing/utils.spec"; +import { TestContext, TestingUtils } from "src/app/shared/components/shared/testing/utils.spec"; import { EdgeConfig } from "src/app/shared/shared"; import { DATA, LABELS } from "../../../energy/chart/chart.constants.spec"; import { History } from "./channels.spec"; @@ -16,7 +16,7 @@ describe("History Production Details - _sum", () => { let TEST_CONTEXT: TestContext & { route: ActivatedRoute }; beforeEach(async () => { - TEST_CONTEXT = await sharedSetupWithComponentIdRoute("_sum"); + TEST_CONTEXT = await TestingUtils.setupWithActivatedRoute("_sum"); }); it("#getChartData()", () => { @@ -52,10 +52,10 @@ describe("History Production Details - _sum", () => { }); export function expectView(config: EdgeConfig, testContext: TestContext & { route: ActivatedRoute }, chartType: "line" | "bar", channels: OeTester.Types.Channels, view: OeChartTester.View): void { - expect(removeFunctions(OeChartTester + expect(TestingUtils.removeFunctions(OeChartTester .apply(SumChartDetailsComponent .getChartData( DummyConfig.convertDummyEdgeConfigToRealEdgeConfig(config), testContext.route, testContext.translate), chartType, channels, testContext, config))) - .toEqual(removeFunctions(view)); + .toEqual(TestingUtils.removeFunctions(view)); } diff --git a/ui/src/app/edge/history/common/consumption/details/chart/sum.ts b/ui/src/app/edge/history/common/consumption/details/chart/sum.ts index cf25ef1a843..bdd58531cff 100644 --- a/ui/src/app/edge/history/common/consumption/details/chart/sum.ts +++ b/ui/src/app/edge/history/common/consumption/details/chart/sum.ts @@ -10,6 +10,7 @@ import { ChannelAddress, EdgeConfig } from "src/app/shared/shared"; @Component({ selector: "sumChart", templateUrl: "../../../../../../shared/components/chart/abstracthistorychart.html", + standalone: false, }) export class SumChartDetailsComponent extends AbstractHistoryChart { diff --git a/ui/src/app/edge/history/common/consumption/details/details.overview.html b/ui/src/app/edge/history/common/consumption/details/details.overview.html index 31819efc6fb..bf708560a45 100644 --- a/ui/src/app/edge/history/common/consumption/details/details.overview.html +++ b/ui/src/app/edge/history/common/consumption/details/details.overview.html @@ -1,4 +1,4 @@ - + diff --git a/ui/src/app/edge/history/common/consumption/details/details.overview.ts b/ui/src/app/edge/history/common/consumption/details/details.overview.ts index 645fd4194dd..d73e5cc4de1 100644 --- a/ui/src/app/edge/history/common/consumption/details/details.overview.ts +++ b/ui/src/app/edge/history/common/consumption/details/details.overview.ts @@ -9,6 +9,7 @@ import { Role } from "src/app/shared/type/role"; @Component({ templateUrl: "./details.overview.html", + standalone: false, }) export class DetailsOverviewComponent extends AbstractHistoryChartOverview { protected navigationButtons: NavigationOption[] = []; diff --git a/ui/src/app/edge/history/common/consumption/flat/flat.ts b/ui/src/app/edge/history/common/consumption/flat/flat.ts index 149521db911..3e5b5f0b6ad 100644 --- a/ui/src/app/edge/history/common/consumption/flat/flat.ts +++ b/ui/src/app/edge/history/common/consumption/flat/flat.ts @@ -6,6 +6,7 @@ import { ChannelAddress, CurrentData, EdgeConfig } from "../../../../../shared/s @Component({ selector: "consumptionWidget", templateUrl: "./flat.html", + standalone: false, }) export class FlatComponent extends AbstractFlatWidget { @@ -14,27 +15,24 @@ export class FlatComponent extends AbstractFlatWidget { protected totalOtherEnergy: number; protected override getChannelAddresses(): ChannelAddress[] { + const channels: ChannelAddress[] = [new ChannelAddress("_sum", "ConsumptionActiveEnergy")]; this.evcsComponents = this.config?.getComponentsImplementingNature("io.openems.edge.evcs.api.Evcs") .filter(component => !(component.factoryId === "Evcs.Cluster.SelfConsumption") && !(component.factoryId === "Evcs.Cluster.PeakShaving") && !component.isEnabled === false); - - this.consumptionMeterComponents = this.config?.getComponentsImplementingNature("io.openems.edge.meter.api.ElectricityMeter") - .filter(component => component.isEnabled && this.config.isTypeConsumptionMetered(component)); - - const channels: ChannelAddress[] = [new ChannelAddress("_sum", "ConsumptionActiveEnergy")]; - this.evcsComponents.forEach((component) => { channels.push(new ChannelAddress(component.id, "ActiveConsumptionEnergy")); }); + this.consumptionMeterComponents = this.config?.getComponentsImplementingNature("io.openems.edge.meter.api.ElectricityMeter") + .filter(component => component.isEnabled && this.config.isTypeConsumptionMetered(component) + && !this.config.getNatureIdsByFactoryId(component.factoryId).includes("io.openems.edge.evcs.api.Evcs")); this.consumptionMeterComponents.forEach((component) => { channels.push(new ChannelAddress(component.id, "ActiveProductionEnergy")); }); - return channels; } diff --git a/ui/src/app/edge/history/common/consumption/overview/overview.ts b/ui/src/app/edge/history/common/consumption/overview/overview.ts index cf75130de35..8d722971951 100644 --- a/ui/src/app/edge/history/common/consumption/overview/overview.ts +++ b/ui/src/app/edge/history/common/consumption/overview/overview.ts @@ -8,6 +8,7 @@ import { ChannelAddress, EdgeConfig, Service } from "src/app/shared/shared"; @Component({ templateUrl: "./overview.html", + standalone: false, }) export class OverviewComponent extends AbstractHistoryChartOverview { @@ -34,7 +35,8 @@ export class OverviewComponent extends AbstractHistoryChartOverview { !component.isEnabled === false); this.consumptionMeterComponents = this.config?.getComponentsImplementingNature("io.openems.edge.meter.api.ElectricityMeter") - .filter(component => component.isEnabled && this.config.isTypeConsumptionMetered(component)); + .filter(component => component.isEnabled && this.config.isTypeConsumptionMetered(component) + && !this.config.getNatureIdsByFactoryId(component.factoryId).includes("io.openems.edge.evcs.api.Evcs")); const sum: EdgeConfig.Component = this.config.getComponent("_sum"); sum.alias = this.translate.instant("General.TOTAL"); diff --git a/ui/src/app/edge/history/common/energy/chart/channels.spec.ts b/ui/src/app/edge/history/common/energy/chart/channels.spec.ts index 2abfaee9aed..43eac4ef9ed 100644 --- a/ui/src/app/edge/history/common/energy/chart/channels.spec.ts +++ b/ui/src/app/edge/history/common/energy/chart/channels.spec.ts @@ -8,18 +8,21 @@ import { QueryHistoricTimeseriesEnergyResponse } from "src/app/shared/jsonrpc/re export namespace History { - export const LINE_CHART_OPTIONS = (period: string, chartType: "line" | "bar", options: { [key: string]: { scale: { min: number, max: number; } | null, ticks: { stepSize: number }; }; }): OeChartTester.Dataset.Option => ({ + export const LINE_CHART_OPTIONS = (period: string, chartType: "line" | "bar", options: { [key: string]: { scale: { min: number, max: number; } | null, ticks?: { stepSize: number }; }; }): OeChartTester.Dataset.Option => ({ type: "option", options: { - "responsive": true, "maintainAspectRatio": false, "elements": { "point": { "radius": 0, "hitRadius": 0, "hoverRadius": 0 }, "line": { "stepped": false, "fill": true } }, "datasets": { "bar": {}, "line": {} }, + "responsive": true, + "maintainAspectRatio": false, + "elements": { + "point": { "radius": 0, "hitRadius": 0, "hoverRadius": 0 }, + "line": { "stepped": false, "fill": true }, + }, + "datasets": { "bar": {}, "line": {} }, "plugins": { - "colors": { - "enabled": false, - }, - "legend": { - "display": true, "position": "bottom", "labels": { "color": "" }, - }, "tooltip": { - "intersect": false, "mode": "index", "callbacks": {}, + "colors": { "enabled": false }, + "legend": { "display": true, "position": "bottom", "labels": { "color": "" } }, + "tooltip": { + "intersect": false, "mode": "index", "callbacks": {}, "enabled": true, }, "annotation": { annotations: {}, @@ -27,14 +30,33 @@ export namespace History { "datalabels": { display: false, }, - }, "scales": { - "x": { "stacked": true, "offset": false, "type": "time", "ticks": { "source": "auto", "maxTicksLimit": 31 }, "bounds": "ticks", "adapters": { "date": { "locale": { "code": "de", "formatLong": {}, "localize": {}, "match": {}, "options": { "weekStartsOn": 1, "firstWeekContainsDate": 4 } } } }, "time": { "unit": period as TimeUnit, "displayFormats": { "datetime": "yyyy-MM-dd HH:mm:ss", "millisecond": "SSS [ms]", "second": "HH:mm:ss a", "minute": "HH:mm", "hour": "HH:00", "day": "dd", "week": "ll", "month": "MM", "quarter": "[Q]Q - YYYY", "year": "yyyy" } } }, + }, + "scales": { + "x": { + "stacked": true, + "offset": false, + "type": "time", + "ticks": { "source": "auto", "maxTicksLimit": 31 }, + "bounds": "ticks", + "adapters": { "date": { "locale": { "code": "de", "formatLong": {}, "localize": {}, "match": {}, "options": { "weekStartsOn": 1, "firstWeekContainsDate": 4 } } } }, + "time": { "unit": period as TimeUnit, "displayFormats": { "datetime": "yyyy-MM-dd HH:mm:ss", "millisecond": "SSS [ms]", "second": "HH:mm:ss a", "minute": "HH:mm", "hour": "HH:00", "day": "dd", "week": "ll", "month": "MM", "quarter": "[Q]Q - YYYY", "year": "yyyy" } }, + }, "left": { - ...options["left"]?.scale, ...(chartType === "line" ? { stacked: false } : {}), "beginAtZero": true, "title": { "text": "kW", "display": true, "padding": 5, "font": { "size": 11 } }, "position": "left", "grid": { "display": true }, + ...options["left"]?.scale, + ...(chartType === "line" + ? { stacked: false } + : {}), + "beginAtZero": true, + "title": { "text": "kW", "display": false, "padding": 5, "font": { "size": 11 } }, + "position": "left", "grid": { "display": true }, "ticks": { ...options["left"]?.ticks, "color": "", "padding": 5, "maxTicksLimit": ChartConstants.NUMBER_OF_Y_AXIS_TICKS }, }, "right": { - ...options["right"]?.scale, ...(chartType === "line" ? { stacked: false } : {}), "beginAtZero": true, "max": 100, "min": 0, "type": "linear", "title": { "text": "%", "display": true, "font": { "size": 11 }, "padding": 5 }, "position": "right", "grid": { "display": false }, + ...options["right"]?.scale, ...(chartType === "line" ? { stacked: false } : {}), + "beginAtZero": true, + "type": "linear", + "title": { "text": "%", "display": false, "font": { "size": 11 }, "padding": 5 }, + "position": "right", "grid": { "display": false }, "ticks": { ...options["right"]?.ticks, "color": "", @@ -48,15 +70,18 @@ export namespace History { export const BAR_CHART_OPTIONS = (period: string, chartType: "line" | "bar", options: { [key: string]: { scale: { min?: number, max?: number; }, ticks: { stepSize?: number; }; }; }): OeChartTester.Dataset.Option => ({ type: "option", options: { - "responsive": true, "maintainAspectRatio": false, "elements": { "point": { "radius": 0, "hitRadius": 0, "hoverRadius": 0 }, "line": { "stepped": false, "fill": true } }, "datasets": { "bar": { "barPercentage": 1 }, "line": {} }, + "responsive": true, + "maintainAspectRatio": false, + "elements": { + "point": { "radius": 0, "hitRadius": 0, "hoverRadius": 0 }, + "line": { "stepped": false, "fill": true }, + }, + "datasets": { "bar": { "barPercentage": 1 }, "line": {} }, "plugins": { - "colors": { - "enabled": false, - }, - "legend": { - "display": true, "position": "bottom", "labels": { "color": "" }, - }, "tooltip": { - "intersect": false, "mode": "x", "callbacks": {}, + "colors": { "enabled": false }, + "legend": { "display": true, "position": "bottom", "labels": { "color": "" } }, + "tooltip": { + "intersect": false, "mode": "x", "callbacks": {}, "enabled": true, }, "annotation": { annotations: {}, @@ -64,10 +89,25 @@ export namespace History { "datalabels": { display: false, }, - }, "scales": { - "x": { "stacked": true, "offset": true, "type": "time", "ticks": { "source": "auto", "maxTicksLimit": 31 }, "bounds": "ticks", "adapters": { "date": { "locale": { "code": "de", "formatLong": {}, "localize": {}, "match": {}, "options": { "weekStartsOn": 1, "firstWeekContainsDate": 4 } } } }, "time": { "unit": period as TimeUnit, "displayFormats": { "datetime": "yyyy-MM-dd HH:mm:ss", "millisecond": "SSS [ms]", "second": "HH:mm:ss a", "minute": "HH:mm", "hour": "HH:00", "day": "dd", "week": "ll", "month": "MM", "quarter": "[Q]Q - YYYY", "year": "yyyy" } } }, + }, + "scales": { + "x": { + "stacked": true, + "offset": true, + "type": "time", + "ticks": { "source": "auto", "maxTicksLimit": 31 }, + "bounds": "ticks", + "adapters": { "date": { "locale": { "code": "de", "formatLong": {}, "localize": {}, "match": {}, "options": { "weekStartsOn": 1, "firstWeekContainsDate": 4 } } } }, + "time": { "unit": period as TimeUnit, "displayFormats": { "datetime": "yyyy-MM-dd HH:mm:ss", "millisecond": "SSS [ms]", "second": "HH:mm:ss a", "minute": "HH:mm", "hour": "HH:00", "day": "dd", "week": "ll", "month": "MM", "quarter": "[Q]Q - YYYY", "year": "yyyy" } }, + }, + "left": { - ...options["left"]?.scale, ...(chartType === "line" ? { stacked: false } : {}), "beginAtZero": true, "title": { "text": "kWh", "display": true, "padding": 5, "font": { "size": 11 } }, "position": "left", "grid": { "display": true }, + "beginAtZero": true, + ...options["left"]?.scale, ...(chartType === "line" ? { stacked: false } : {}), + "title": { "text": "kWh", "display": false, "padding": 5, "font": { "size": 11 } }, + "position": "left", + "grid": { "display": true }, + "stacked": true, "ticks": { ...options["left"]?.ticks, "color": "", diff --git a/ui/src/app/edge/history/common/energy/chart/chart.constants.spec.ts b/ui/src/app/edge/history/common/energy/chart/chart.constants.spec.ts index 0b7d24e89a5..2eb66fe1a93 100644 --- a/ui/src/app/edge/history/common/energy/chart/chart.constants.spec.ts +++ b/ui/src/app/edge/history/common/energy/chart/chart.constants.spec.ts @@ -1,15 +1,15 @@ import { DummyConfig } from "src/app/shared/components/edge/edgeconfig.spec"; import { OeTester } from "src/app/shared/components/shared/testing/common"; -import { removeFunctions, TestContext } from "src/app/shared/components/shared/testing/utils.spec"; +import { TestContext, TestingUtils } from "src/app/shared/components/shared/testing/utils.spec"; import { EdgeConfig } from "src/app/shared/shared"; import { OeChartTester } from "../../../../../shared/components/shared/testing/tester"; import { ChartComponent } from "./chart"; export function expectView(config: EdgeConfig, testContext: TestContext, chartType: "line" | "bar", channels: OeTester.Types.Channels, view: OeChartTester.View): void { - expect(removeFunctions(OeChartTester + expect(TestingUtils.removeFunctions(OeChartTester .apply(ChartComponent .getChartData(DummyConfig.convertDummyEdgeConfigToRealEdgeConfig(config), chartType, testContext.translate), chartType, channels, testContext, config))) - .toEqual(removeFunctions(view)); + .toEqual(TestingUtils.removeFunctions(view)); } export const DATASET = (data: OeChartTester.Dataset.Data, labels: OeChartTester.Dataset.LegendLabel, options: OeChartTester.Dataset.Option) => ({ diff --git a/ui/src/app/edge/history/common/energy/chart/chart.spec.ts b/ui/src/app/edge/history/common/energy/chart/chart.spec.ts index cd1695a6c81..7528b6fcc26 100644 --- a/ui/src/app/edge/history/common/energy/chart/chart.spec.ts +++ b/ui/src/app/edge/history/common/energy/chart/chart.spec.ts @@ -1,7 +1,7 @@ // @ts-strict-ignore import { History } from "src/app/edge/history/common/energy/chart/channels.spec"; import { DummyConfig } from "src/app/shared/components/edge/edgeconfig.spec"; -import { sharedSetup, TestContext } from "src/app/shared/components/shared/testing/utils.spec"; +import { TestContext, TestingUtils } from "src/app/shared/components/shared/testing/utils.spec"; import { DATA, expectView, LABELS } from "./chart.constants.spec"; @@ -14,7 +14,7 @@ describe("History EnergyMonitor", () => { let TEST_CONTEXT: TestContext; beforeEach(async () => - TEST_CONTEXT = await sharedSetup(), + TEST_CONTEXT = await TestingUtils.sharedSetup(), ); it("getChartData()", () => { @@ -34,7 +34,7 @@ describe("History EnergyMonitor", () => { ], labels: LABELS(History.DAY.dataChannelWithValues.result.timestamps), options: History.LINE_CHART_OPTIONS("hour", "line", { - ["right"]: { ticks: { stepSize: 20 }, scale: null }, + ["right"]: { scale: null }, }), }, }); @@ -55,7 +55,7 @@ describe("History EnergyMonitor", () => { DATA("Ladezustand", History.WEEK.dataChannelWithValues.result.data["_sum/EssSoc"]), ], labels: LABELS(History.WEEK.dataChannelWithValues.result.timestamps), - options: History.LINE_CHART_OPTIONS("day", "line", { ["right"]: { ticks: { stepSize: 20 }, scale: null } }), + options: History.LINE_CHART_OPTIONS("day", "line", { ["right"]: { scale: null } }), }, }); } diff --git a/ui/src/app/edge/history/common/energy/chart/chart.ts b/ui/src/app/edge/history/common/energy/chart/chart.ts index dff22346d79..cee407f80de 100644 --- a/ui/src/app/edge/history/common/energy/chart/chart.ts +++ b/ui/src/app/edge/history/common/energy/chart/chart.ts @@ -2,6 +2,7 @@ import { Component } from "@angular/core"; import { TranslateService } from "@ngx-translate/core"; import { AbstractHistoryChart } from "src/app/shared/components/chart/abstracthistorychart"; +import { ChartConstants } from "src/app/shared/components/chart/chart.constants"; import { QueryHistoricTimeseriesEnergyResponse } from "src/app/shared/jsonrpc/response/queryHistoricTimeseriesEnergyResponse"; import { ChartAxis, HistoryUtils, YAxisType } from "src/app/shared/service/utils"; import { ChannelAddress, EdgeConfig, Utils } from "src/app/shared/shared"; @@ -9,6 +10,7 @@ import { ChannelAddress, EdgeConfig, Utils } from "src/app/shared/shared"; @Component({ selector: "energychart", templateUrl: "../../../../../shared/components/chart/abstracthistorychart.html", + standalone: false, }) export class ChartComponent extends AbstractHistoryChart { @@ -79,7 +81,7 @@ export class ChartComponent extends AbstractHistoryChart { name: translate.instant("General.production"), nameSuffix: (energyValues: QueryHistoricTimeseriesEnergyResponse) => energyValues.result.data["_sum/ProductionActiveEnergy"], converter: () => data["ProductionActivePower"], - color: "rgb(45,143,171)", + color: ChartConstants.Colors.BLUE, stack: 0, hiddenOnInit: chartType == "line" ? false : true, order: 1, @@ -94,27 +96,27 @@ export class ChartComponent extends AbstractHistoryChart { converter: () => data["ProductionActivePower"]?.map((value, index) => Utils.subtractSafely(value, data["GridSell"][index], data["EssCharge"][index])) ?.map(value => HistoryUtils.ValueConverter.NEGATIVE_AS_ZERO(value)), - color: "rgb(244,164,96)", + color: ChartConstants.Colors.ORANGE, stack: [1, 2], order: 2, }], // Charge Power { - name: translate.instant("General.chargePower"), + name: translate.instant("General.CHARGE"), nameSuffix: (energyValues: QueryHistoricTimeseriesEnergyResponse) => energyValues.result.data["_sum/EssDcChargeEnergy"], converter: () => chartType === "line" // ? data["EssCharge"]?.map((value, index) => { return HistoryUtils.ValueConverter.POSITIVE_AS_ZERO_AND_INVERT_NEGATIVE(Utils.subtractSafely(value, data["ProductionDcActual"]?.[index])); }) : data["EssCharge"], - color: "rgb(0,223,0)", + color: ChartConstants.Colors.GREEN, stack: 1, ...(chartType === "line" && { order: 6 }), }, // Discharge Power { - name: translate.instant("General.dischargePower"), + name: translate.instant("General.DISCHARGE"), nameSuffix: (energyValues: QueryHistoricTimeseriesEnergyResponse) => energyValues.result.data["_sum/EssDcDischargeEnergy"], converter: () => { return chartType === "line" ? @@ -122,7 +124,7 @@ export class ChartComponent extends AbstractHistoryChart { return HistoryUtils.ValueConverter.NEGATIVE_AS_ZERO(Utils.subtractSafely(value, data["ProductionDcActual"]?.[index])); }) : data["EssDischarge"]; }, - color: "rgb(200,0,0)", + color: ChartConstants.Colors.RED, stack: 2, ...(chartType === "line" && { order: 5 }), }, @@ -132,7 +134,7 @@ export class ChartComponent extends AbstractHistoryChart { name: translate.instant("General.gridSellAdvanced"), nameSuffix: (energyValues: QueryHistoricTimeseriesEnergyResponse) => energyValues.result.data["_sum/GridSellActiveEnergy"], converter: () => data["GridSell"], - color: "rgb(0,0,200)", + color: ChartConstants.Colors.PURPLE, stack: 1, ...(chartType === "line" && { order: 4 }), }, @@ -142,7 +144,7 @@ export class ChartComponent extends AbstractHistoryChart { name: translate.instant("General.gridBuyAdvanced"), nameSuffix: (energyValues: QueryHistoricTimeseriesEnergyResponse) => energyValues.result.data["_sum/GridBuyActiveEnergy"], converter: () => data["GridBuy"], - color: "rgb(0,0,0)", + color: ChartConstants.Colors.BLUE_GREY, stack: 2, ...(chartType === "line" && { order: 2 }), }, @@ -152,7 +154,7 @@ export class ChartComponent extends AbstractHistoryChart { name: translate.instant("General.consumption"), nameSuffix: (energyValues: QueryHistoricTimeseriesEnergyResponse) => energyValues.result.data["_sum/ConsumptionActiveEnergy"], converter: () => data["Consumption"], - color: "rgb(253,197,7)", + color: ChartConstants.Colors.YELLOW, stack: 3, hiddenOnInit: chartType == "line" ? false : true, ...(chartType === "line" && { order: 0 }), @@ -200,6 +202,7 @@ export class ChartComponent extends AbstractHistoryChart { displayGrid: false, }), ], + normalizeOutputData: true, }; } diff --git a/ui/src/app/edge/history/common/energy/flat/flat.html b/ui/src/app/edge/history/common/energy/flat/flat.html index 9dd2bd9c078..ce40969b412 100644 --- a/ui/src/app/edge/history/common/energy/flat/flat.html +++ b/ui/src/app/edge/history/common/energy/flat/flat.html @@ -14,8 +14,9 @@ - + diff --git a/ui/src/app/edge/history/common/energy/flat/flat.ts b/ui/src/app/edge/history/common/energy/flat/flat.ts index 49db6041eb6..39043d06ea3 100644 --- a/ui/src/app/edge/history/common/energy/flat/flat.ts +++ b/ui/src/app/edge/history/common/energy/flat/flat.ts @@ -11,6 +11,7 @@ import { ChannelAddress, CurrentData, Utils } from "../../../../../shared/shared @Component({ selector: "energy", templateUrl: "./flat.html", + standalone: false, }) export class FlatComponent extends AbstractFlatWidget { diff --git a/ui/src/app/edge/history/common/grid/chart/chart.constants.spec.ts b/ui/src/app/edge/history/common/grid/chart/chart.constants.spec.ts index bba5418804d..146bf945dab 100644 --- a/ui/src/app/edge/history/common/grid/chart/chart.constants.spec.ts +++ b/ui/src/app/edge/history/common/grid/chart/chart.constants.spec.ts @@ -1,14 +1,14 @@ import { DummyConfig } from "src/app/shared/components/edge/edgeconfig.spec"; import { OeTester } from "src/app/shared/components/shared/testing/common"; import { OeChartTester } from "src/app/shared/components/shared/testing/tester"; -import { removeFunctions, TestContext } from "src/app/shared/components/shared/testing/utils.spec"; +import { TestContext, TestingUtils } from "src/app/shared/components/shared/testing/utils.spec"; import { EdgeConfig } from "src/app/shared/shared"; import { ChartComponent } from "./chart"; export function expectView(config: EdgeConfig, testContext: TestContext, chartType: "line" | "bar", channels: OeTester.Types.Channels, view: OeChartTester.View, showPhases: boolean): void { - expect(removeFunctions(OeChartTester + expect(TestingUtils.removeFunctions(OeChartTester .apply(ChartComponent .getChartData(DummyConfig.convertDummyEdgeConfigToRealEdgeConfig(config), chartType, testContext.translate, showPhases), chartType, channels, testContext, config))) - .toEqual(removeFunctions(view)); + .toEqual(TestingUtils.removeFunctions(view)); } diff --git a/ui/src/app/edge/history/common/grid/chart/chart.spec.ts b/ui/src/app/edge/history/common/grid/chart/chart.spec.ts index 64c0455cdf2..b62bf817732 100644 --- a/ui/src/app/edge/history/common/grid/chart/chart.spec.ts +++ b/ui/src/app/edge/history/common/grid/chart/chart.spec.ts @@ -3,7 +3,7 @@ import { History } from "src/app/edge/history/common/energy/chart/channels.spec" import { DummyConfig } from "src/app/shared/components/edge/edgeconfig.spec"; import { OeTester } from "src/app/shared/components/shared/testing/common"; -import { TestContext, sharedSetup } from "src/app/shared/components/shared/testing/utils.spec"; +import { TestContext, TestingUtils } from "src/app/shared/components/shared/testing/utils.spec"; import { ChartAxis } from "src/app/shared/service/utils"; import { DATA, LABELS } from "../../energy/chart/chart.constants.spec"; import { expectView } from "./chart.constants.spec"; @@ -21,7 +21,7 @@ describe("History Grid", () => { let TEST_CONTEXT: TestContext; beforeEach(async () => - TEST_CONTEXT = await sharedSetup(), + TEST_CONTEXT = await TestingUtils.sharedSetup(), ); it("#getChartData()", () => { @@ -51,9 +51,8 @@ describe("History Grid", () => { DATA("Bezug: 0,9 kWh", [null, null, null, 0.031, 0.018, 0, 0.02, 0.016, 0.015, 0.014, 0.009, 0.02, 0.025, 0.025, 0.025, 0.021, 0.012, 0.009, 0.01, 0.011, 0.005, 0.003, 0, 0.015, 0.018, 0.023, 0, 0, 0, 0.002, 0.002, 0.003, 0.015, 0.008, 0.022, 0.027, 0.016, 0.003, 0.002, 0, 0.028, 0.027, 0.017, 0.001, 0, 0, 0, null, null, null, null, 0.011, 0.01, 0.004, 0.006, 0.007, 0.018, 0.008, 0.012, 0.009, 0.004, 0.013, 0.015, 0.012, 0, 0, 0, 0.002, 0, 0.005, 0.001, 0.03, 0.062, 0, 0, 0, 0, 0, 0, 0, 0, 0.015, 0.005, 0.004, 0.007, 0, 0, 0, 0, 0, 0, 0, 0.005, 0, 0, 0, 0, 0, 0, 0.021, 0, 0, 0, 0, 0, 0.003, 0, 0.004, 0, 0, 0.032, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null]), ], labels: LABELS(History.DAY.dataChannelWithValues.result.timestamps), - options: OeTester.ChartOptions.MULTI_LINE_OPTIONS("hour", "line", { - ["right"]: { scale: { max: 1, min: 0 }, ticks: { stepSize: 1 } }, - }), + options: OeTester.ChartOptions.MULTI_LINE_OPTIONS("hour", "line", {}, + ), }, }, false); } @@ -67,9 +66,7 @@ describe("History Grid", () => { DATA("Bezug: 2,4 kWh", [0, 0.011916666666666666, 0.01633333333333333, 0.00609090909090909, 0.015333333333333334, 0.011666666666666665, 0.0024166666666666664, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.02425, 0.004416666666666667, 0.0035833333333333333, 0, 0, 0, 0.04441666666666667, 0, 0.013111111111111112, 0.001, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0011666666666666668, 0, 0, 0, 0.0015833333333333333, 0.013333333333333334, 0.020416666666666666, 0.01125, 0.019727272727272725, 0.012444444444444445, 0.009583333333333334, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.007666666666666667, 0, 0.0023333333333333335, 0.0125, 0.01609090909090909, 0.02016666666666667, 0.014083333333333333, 0.006363636363636363, 0.01955555555555556, 0.04841666666666666, 0.011166666666666667, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.014222222222222221, 0.00225, 0, 0.0036666666666666666, 0.032916666666666664, 0.014666666666666666, 0.0135, 0.017363636363636362, 0.013333333333333334, 0.022083333333333333, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0009166666666666666, 0, 0.0021666666666666666, 0, 0, 0, 0.0005, 0.04841666666666666, 0, 0.005555555555555556, 0.02716666666666667, 0.017333333333333333, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0023333333333333335, 0.008333333333333333, 0.003, 0.015916666666666666, 0.00325, 0, 0.004333333333333333, 0.001, 0, 0, 0.019545454545454546, 0.0017777777777777776, 0.006416666666666667, 0.017666666666666667, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0058, 0.005625, 0, 0]), ], labels: LABELS(History.WEEK.dataChannelWithValues.result.timestamps), - options: OeTester.ChartOptions.MULTI_LINE_OPTIONS("day", "line", { - ["right"]: { scale: { max: 1, min: 0 }, ticks: { stepSize: 1 } }, - }), + options: OeTester.ChartOptions.MULTI_LINE_OPTIONS("day", "line", {}), }, }, false); } @@ -83,9 +80,7 @@ describe("History Grid", () => { DATA("Bezug: 773 kWh", [16, 6, 3, 3, 5, 48, 4, null, 5, 26, 17, 62, 8, 66, 13, 21, 4, 3, 18, 27, 29, null, 118, 85, 2, null, 72, 28, 84, null]), ], labels: LABELS(History.MONTH.energyPerPeriodChannelWithValues.result.timestamps), - options: OeTester.ChartOptions.MULTI_BAR_OPTIONS("day", "bar", { - ["right"]: { scale: {}, ticks: { stepSize: 1 } }, - }), + options: OeTester.ChartOptions.MULTI_BAR_OPTIONS("day", "bar", {}), }, }, false); @@ -100,9 +95,7 @@ describe("History Grid", () => { DATA("Bezug: 23.209 kWh", [9829, 4812, 2915, 2036, 2712, 773, 94, null, null, null, null, null]), ], labels: LABELS(History.YEAR.energyPerPeriodChannelWithValues.result.timestamps), - options: OeTester.ChartOptions.MULTI_BAR_OPTIONS("month", "bar", { - ["right"]: { scale: {}, ticks: { stepSize: 1 } }, - }), + options: OeTester.ChartOptions.MULTI_BAR_OPTIONS("month", "bar", {}), }, }, false); } diff --git a/ui/src/app/edge/history/common/grid/chart/chart.ts b/ui/src/app/edge/history/common/grid/chart/chart.ts index f195179022c..fd47aa2e864 100644 --- a/ui/src/app/edge/history/common/grid/chart/chart.ts +++ b/ui/src/app/edge/history/common/grid/chart/chart.ts @@ -4,6 +4,7 @@ import { TranslateService } from "@ngx-translate/core"; import { BoxAnnotationOptions } from "chartjs-plugin-annotation"; import { GridSectionComponent } from "src/app/edge/live/energymonitor/chart/section/grid.component"; import { AbstractHistoryChart } from "src/app/shared/components/chart/abstracthistorychart"; +import { ChartConstants } from "src/app/shared/components/chart/chart.constants"; import { QueryHistoricTimeseriesEnergyResponse } from "src/app/shared/jsonrpc/response/queryHistoricTimeseriesEnergyResponse"; import { ChartAxis, HistoryUtils, YAxisType } from "src/app/shared/service/utils"; import { ChannelAddress, EdgeConfig } from "src/app/shared/shared"; @@ -12,6 +13,7 @@ import { ChartAnnotationState } from "src/app/shared/type/general"; @Component({ selector: "gridchart", templateUrl: "../../../../../shared/components/chart/abstracthistorychart.html", + standalone: false, }) export class ChartComponent extends AbstractHistoryChart { @@ -99,7 +101,7 @@ export class ChartComponent extends AbstractHistoryChart { name: translate.instant("General.gridSellAdvanced"), nameSuffix: (energyValues: QueryHistoricTimeseriesEnergyResponse) => energyValues?.result.data["_sum/GridSellActiveEnergy"] ?? null, converter: () => data["GridSell"], - color: "rgba(0,0,200)", + color: ChartConstants.Colors.PURPLE, stack: 1, }, { @@ -108,7 +110,7 @@ export class ChartComponent extends AbstractHistoryChart { return energyValues?.result.data["_sum/GridBuyActiveEnergy"] ?? null; }, converter: () => data["GridBuy"], - color: "rgb(0,0,0)", + color: ChartConstants.Colors.BLUE_GREY, stack: 0, }, offGridData ? ({ diff --git a/ui/src/app/edge/history/common/grid/details/chart/chart.spec.ts b/ui/src/app/edge/history/common/grid/details/chart/chart.spec.ts index 286718266cf..272f1f67bbf 100644 --- a/ui/src/app/edge/history/common/grid/details/chart/chart.spec.ts +++ b/ui/src/app/edge/history/common/grid/details/chart/chart.spec.ts @@ -3,7 +3,7 @@ import { ActivatedRoute } from "@angular/router"; import { DummyConfig } from "src/app/shared/components/edge/edgeconfig.spec"; import { OeTester } from "src/app/shared/components/shared/testing/common"; import { OeChartTester } from "src/app/shared/components/shared/testing/tester"; -import { removeFunctions, sharedSetupWithComponentIdRoute, TestContext } from "src/app/shared/components/shared/testing/utils.spec"; +import { TestContext, TestingUtils } from "src/app/shared/components/shared/testing/utils.spec"; import { EdgeConfig } from "src/app/shared/shared"; import { DATA, LABELS } from "../../../energy/chart/chart.constants.spec"; import { History } from "./channels.spec"; @@ -16,7 +16,7 @@ describe("History Grid Details - _sum", () => { let TEST_CONTEXT: TestContext & { route: ActivatedRoute }; beforeEach(async () => { - TEST_CONTEXT = await sharedSetupWithComponentIdRoute("_sum"); + TEST_CONTEXT = await TestingUtils.setupWithActivatedRoute("_sum"); }); it("#getChartData()", () => { @@ -50,9 +50,9 @@ describe("History Grid Details - _sum", () => { }); export function expectView(config: EdgeConfig, testContext: TestContext & { route: ActivatedRoute }, chartType: "line" | "bar", channels: OeTester.Types.Channels, view: OeChartTester.View): void { - expect(removeFunctions(OeChartTester + expect(TestingUtils.removeFunctions(OeChartTester .apply(ChartComponent .getChartData( testContext.translate), chartType, channels, testContext, config))) - .toEqual(removeFunctions(view)); + .toEqual(TestingUtils.removeFunctions(view)); } diff --git a/ui/src/app/edge/history/common/grid/details/chart/chart.ts b/ui/src/app/edge/history/common/grid/details/chart/chart.ts index c941630eb8a..683f967e58f 100644 --- a/ui/src/app/edge/history/common/grid/details/chart/chart.ts +++ b/ui/src/app/edge/history/common/grid/details/chart/chart.ts @@ -2,6 +2,7 @@ import { Component } from "@angular/core"; import { TranslateService } from "@ngx-translate/core"; import { AbstractHistoryChart } from "src/app/shared/components/chart/abstracthistorychart"; +import { ChartConstants } from "src/app/shared/components/chart/chart.constants"; import { Phase } from "src/app/shared/components/shared/phase"; import { DefaultTypes } from "src/app/shared/service/defaulttypes"; import { ChartAxis, HistoryUtils, YAxisType } from "src/app/shared/service/utils"; @@ -10,6 +11,7 @@ import { ChannelAddress } from "src/app/shared/shared"; @Component({ selector: "gridDetailsChart", templateUrl: "../../../../../../shared/components/chart/abstracthistorychart.html", + standalone: false, }) export class ChartComponent extends AbstractHistoryChart { @@ -34,7 +36,7 @@ export class ChartComponent extends AbstractHistoryChart { converter: () => { return data["GridActivePower"]; }, - color: "rgba(0,0,200)", + color: ChartConstants.Colors.BLUE, stack: 1, }, ...Phase.THREE_PHASE.map((phase, index) => ({ diff --git a/ui/src/app/edge/history/common/grid/details/details.overview.html b/ui/src/app/edge/history/common/grid/details/details.overview.html index 1bba13ab767..506e8390f44 100644 --- a/ui/src/app/edge/history/common/grid/details/details.overview.html +++ b/ui/src/app/edge/history/common/grid/details/details.overview.html @@ -1,6 +1,8 @@ - + { - const gridMeter = Object.values(this.config.components) - .find((component) => component.isEnabled && this.config.isTypeGrid(component)) ?? null; + if (!this.component) { + return; + } + const gridMeter = this.config.isTypeGrid(this.component) ?? null; if (!gridMeter) { return; } + const gridMeters = Object.values(this.config.components) + .filter((comp) => comp.isEnabled && this.config.isTypeGrid(comp)) ?? null; + + if (gridMeters?.length == 1) { + this.title = this.translate.instant("General.grid"); + } + this.navigationButtons = [ - { id: "currentVoltage", isEnabled: edge.roleIsAtLeast(Role.INSTALLER), alias: this.translate.instant("Edge.History.CURRENT_AND_VOLTAGE"), callback: () => { this.router.navigate([`../${gridMeter.id}/currentVoltage`], { relativeTo: this.route }); } }]; + { id: "currentVoltage", isEnabled: edge.roleIsAtLeast(Role.INSTALLER), alias: this.translate.instant("Edge.History.CURRENT_AND_VOLTAGE"), callback: () => { this.router.navigate(["./currentVoltage"], { relativeTo: this.route }); } }]; }); } } diff --git a/ui/src/app/edge/history/common/grid/flat/flat.ts b/ui/src/app/edge/history/common/grid/flat/flat.ts index bb7d272b6cf..3c6603321ea 100644 --- a/ui/src/app/edge/history/common/grid/flat/flat.ts +++ b/ui/src/app/edge/history/common/grid/flat/flat.ts @@ -7,6 +7,7 @@ import { TimeUtils } from "src/app/shared/utils/time/timeutils"; @Component({ selector: "gridWidget", templateUrl: "./flat.html", + standalone: false, }) export class FlatComponent extends AbstractFlatWidget { diff --git a/ui/src/app/edge/history/common/grid/overview/overview.html b/ui/src/app/edge/history/common/grid/overview/overview.html index 71e464fef64..51b01128edf 100644 --- a/ui/src/app/edge/history/common/grid/overview/overview.html +++ b/ui/src/app/edge/history/common/grid/overview/overview.html @@ -3,4 +3,4 @@ - + diff --git a/ui/src/app/edge/history/common/grid/overview/overview.ts b/ui/src/app/edge/history/common/grid/overview/overview.ts index b99f248ebf5..bf2695525fd 100644 --- a/ui/src/app/edge/history/common/grid/overview/overview.ts +++ b/ui/src/app/edge/history/common/grid/overview/overview.ts @@ -2,15 +2,19 @@ import { Component } from "@angular/core"; import { ActivatedRoute, Router } from "@angular/router"; import { ModalController } from "@ionic/angular"; import { TranslateService } from "@ngx-translate/core"; +import { filter, takeUntil } from "rxjs/operators"; import { AbstractHistoryChartOverview } from "src/app/shared/components/chart/abstractHistoryChartOverview"; import { NavigationOption } from "src/app/shared/components/footer/subnavigation/footerNavigation"; import { EdgeConfig, Service } from "src/app/shared/shared"; + @Component({ templateUrl: "./overview.html", + standalone: false, }) export class OverviewComponent extends AbstractHistoryChartOverview { protected navigationButtons: NavigationOption[] = []; + protected isAllowed: boolean = false; constructor( public override service: Service, @@ -24,24 +28,23 @@ export class OverviewComponent extends AbstractHistoryChartOverview { protected override afterIsInitialized() { - const sum: EdgeConfig.Component = this.config.getComponent("_sum"); - sum.alias = this.translate.instant("General.TOTAL"); + this.service.historyPeriod.pipe(takeUntil(this.stopOnDestroy), filter(period => !!period)) + .subscribe((period) => { + this.isAllowed = period.isWeekOrDay(); + }); + const navigationButtons: EdgeConfig.Component[] = []; const gridMeters = Object.values(this.config.components) .filter((component) => component.isEnabled && this.config.isTypeGrid(component)); if (!gridMeters) { - navigationButtons.push(sum); + return; } - if (gridMeters?.length <= 1) { - navigationButtons.push(sum); - } else { - navigationButtons.push(...gridMeters); - } + navigationButtons.push(...gridMeters); this.navigationButtons = navigationButtons.map(el => ( - { id: el.id, alias: el.alias, callback: () => { this.router.navigate(["./" + el.id], { relativeTo: this.route }); } } + { id: el.id, alias: navigationButtons.length === 1 ? this.translate.instant("Edge.History.PHASE_ACCURATE") : el.alias, callback: () => { this.router.navigate(["./" + el.id], { relativeTo: this.route }); } } )); } } diff --git a/ui/src/app/edge/history/common/production/chart/totalChart.ts b/ui/src/app/edge/history/common/production/chart/totalChart.ts index 9c54d688961..4d38fc2f5d1 100644 --- a/ui/src/app/edge/history/common/production/chart/totalChart.ts +++ b/ui/src/app/edge/history/common/production/chart/totalChart.ts @@ -1,6 +1,7 @@ // @ts-strict-ignore import { Component } from "@angular/core"; import { AbstractHistoryChart } from "src/app/shared/components/chart/abstracthistorychart"; +import { ChartConstants } from "src/app/shared/components/chart/chart.constants"; import { QueryHistoricTimeseriesEnergyResponse } from "src/app/shared/jsonrpc/response/queryHistoricTimeseriesEnergyResponse"; import { ChartAxis, HistoryUtils, Utils, YAxisType } from "../../../../../shared/service/utils"; @@ -9,6 +10,7 @@ import { ChannelAddress } from "../../../../../shared/shared"; @Component({ selector: "productionTotalChart", templateUrl: "../../../../../shared/components/chart/abstracthistorychart.html", + standalone: false, }) export class TotalChartComponent extends AbstractHistoryChart { @@ -85,7 +87,7 @@ export class TotalChartComponent extends AbstractHistoryChart { converter: () => { return data["ProductionActivePower"]; }, - color: "rgb(0,152,204)", + color: ChartConstants.Colors.BLUE, hiddenOnInit: true, stack: 2, }); @@ -138,7 +140,7 @@ export class TotalChartComponent extends AbstractHistoryChart { }); } - const chargerColors: string[] = ["rgb(0,223,0)", "rgb(0,178,0)", "rgb(0,201,0)", "rgb(0,134,0)", "rgb(0,156,0)"]; + const chargerColors: string[] = ["rgb(0,223,0)", "rgb(0,134,0)", "rgb(0,201,0)", "rgb(0,134,0)", "rgb(0,156,0)"]; // ChargerComponents for (let i = 0; i < chargerComponents.length; i++) { const component = chargerComponents[i]; diff --git a/ui/src/app/edge/history/common/production/details/chart/charger.spec.ts b/ui/src/app/edge/history/common/production/details/chart/charger.spec.ts index 08cfeb6e0b9..bad59b215fe 100644 --- a/ui/src/app/edge/history/common/production/details/chart/charger.spec.ts +++ b/ui/src/app/edge/history/common/production/details/chart/charger.spec.ts @@ -3,7 +3,7 @@ import { ActivatedRoute } from "@angular/router"; import { DummyConfig } from "src/app/shared/components/edge/edgeconfig.spec"; import { OeTester } from "src/app/shared/components/shared/testing/common"; import { OeChartTester } from "src/app/shared/components/shared/testing/tester"; -import { removeFunctions, sharedSetupWithComponentIdRoute, TestContext } from "src/app/shared/components/shared/testing/utils.spec"; +import { TestContext, TestingUtils } from "src/app/shared/components/shared/testing/utils.spec"; import { EdgeConfig } from "src/app/shared/shared"; import { DATA, LABELS } from "../../../energy/chart/chart.constants.spec"; import { History } from "./channels.spec"; @@ -16,7 +16,7 @@ describe("History Production Details - chargers", () => { let TEST_CONTEXT: TestContext & { route: ActivatedRoute }; beforeEach(async () => { - TEST_CONTEXT = await sharedSetupWithComponentIdRoute("charger0"); + TEST_CONTEXT = await TestingUtils.setupWithActivatedRoute("charger0"); }); it("#getChartData()", () => { @@ -49,10 +49,10 @@ describe("History Production Details - chargers", () => { }); export function expectView(config: EdgeConfig, testContext: TestContext & { route: ActivatedRoute }, chartType: "line" | "bar", channels: OeTester.Types.Channels, view: OeChartTester.View): void { - expect(removeFunctions(OeChartTester + expect(TestingUtils.removeFunctions(OeChartTester .apply(ChargerChartDetailsComponent .getChartData( DummyConfig.convertDummyEdgeConfigToRealEdgeConfig(config), testContext.route, testContext.translate), chartType, channels, testContext, config))) - .toEqual(removeFunctions(view)); + .toEqual(TestingUtils.removeFunctions(view)); } diff --git a/ui/src/app/edge/history/common/production/details/chart/charger.ts b/ui/src/app/edge/history/common/production/details/chart/charger.ts index 7673bac3ffd..8cd557f5f4c 100644 --- a/ui/src/app/edge/history/common/production/details/chart/charger.ts +++ b/ui/src/app/edge/history/common/production/details/chart/charger.ts @@ -9,6 +9,7 @@ import { ChannelAddress, EdgeConfig } from "src/app/shared/shared"; @Component({ selector: "chargerChart", templateUrl: "../../../../../../shared/components/chart/abstracthistorychart.html", + standalone: false, }) export class ChargerChartDetailsComponent extends AbstractHistoryChart { diff --git a/ui/src/app/edge/history/common/production/details/chart/productionMeter.spec.ts b/ui/src/app/edge/history/common/production/details/chart/productionMeter.spec.ts index d3cc8490cc1..5e700fc5f51 100644 --- a/ui/src/app/edge/history/common/production/details/chart/productionMeter.spec.ts +++ b/ui/src/app/edge/history/common/production/details/chart/productionMeter.spec.ts @@ -3,7 +3,7 @@ import { ActivatedRoute } from "@angular/router"; import { DummyConfig } from "src/app/shared/components/edge/edgeconfig.spec"; import { OeTester } from "src/app/shared/components/shared/testing/common"; import { OeChartTester } from "src/app/shared/components/shared/testing/tester"; -import { removeFunctions, sharedSetupWithComponentIdRoute, TestContext } from "src/app/shared/components/shared/testing/utils.spec"; +import { TestContext, TestingUtils } from "src/app/shared/components/shared/testing/utils.spec"; import { EdgeConfig } from "src/app/shared/shared"; import { DATA, LABELS } from "../../../energy/chart/chart.constants.spec"; import { History } from "./channels.spec"; @@ -14,9 +14,10 @@ describe("History Production Details - productionMeters", () => { DummyConfig.Component.SOLAR_EDGE_PV_INVERTER("meter0", "Whirlpool"), ); + let TEST_CONTEXT: TestContext & { route: ActivatedRoute }; beforeEach(async () => { - TEST_CONTEXT = await sharedSetupWithComponentIdRoute("meter0"); + TEST_CONTEXT = await TestingUtils.setupWithActivatedRoute("meter0"); }); it("#getChartData()", () => { @@ -52,17 +53,10 @@ describe("History Production Details - productionMeters", () => { }); export function expectView(config: EdgeConfig, testContext: TestContext & { route: ActivatedRoute }, chartType: "line" | "bar", channels: OeTester.Types.Channels, view: OeChartTester.View): void { - sessionStorage.setItem("mapping to int", JSON.stringify(History.MONTH.energyPerPeriodChannelWithValues.result.data["meter0/ActiveProductionEnergy"].map(el => el != null ? Math.round(el) : null))); - sessionStorage.setItem("phase", JSON.stringify(OeChartTester - .apply(ProductionMeterChartDetailsComponent - .getChartData( - DummyConfig.convertDummyEdgeConfigToRealEdgeConfig(config), testContext.route, - testContext.translate), chartType, channels, testContext, config))); - - expect(removeFunctions(OeChartTester + expect(TestingUtils.removeFunctions(OeChartTester .apply(ProductionMeterChartDetailsComponent .getChartData( DummyConfig.convertDummyEdgeConfigToRealEdgeConfig(config), testContext.route, testContext.translate), chartType, channels, testContext, config))) - .toEqual(removeFunctions(view)); + .toEqual(TestingUtils.removeFunctions(view)); } diff --git a/ui/src/app/edge/history/common/production/details/chart/productionMeter.ts b/ui/src/app/edge/history/common/production/details/chart/productionMeter.ts index 634398525b7..448b235211b 100644 --- a/ui/src/app/edge/history/common/production/details/chart/productionMeter.ts +++ b/ui/src/app/edge/history/common/production/details/chart/productionMeter.ts @@ -10,6 +10,7 @@ import { ChannelAddress, EdgeConfig } from "src/app/shared/shared"; @Component({ selector: "productionMeterChart", templateUrl: "../../../../../../shared/components/chart/abstracthistorychart.html", + standalone: false, }) export class ProductionMeterChartDetailsComponent extends AbstractHistoryChart { diff --git a/ui/src/app/edge/history/common/production/details/chart/sum.spec.ts b/ui/src/app/edge/history/common/production/details/chart/sum.spec.ts index 286910ff833..cecf79ca3e4 100644 --- a/ui/src/app/edge/history/common/production/details/chart/sum.spec.ts +++ b/ui/src/app/edge/history/common/production/details/chart/sum.spec.ts @@ -3,7 +3,7 @@ import { ActivatedRoute } from "@angular/router"; import { DummyConfig } from "src/app/shared/components/edge/edgeconfig.spec"; import { OeTester } from "src/app/shared/components/shared/testing/common"; import { OeChartTester } from "src/app/shared/components/shared/testing/tester"; -import { removeFunctions, sharedSetupWithComponentIdRoute, TestContext } from "src/app/shared/components/shared/testing/utils.spec"; +import { TestContext, TestingUtils } from "src/app/shared/components/shared/testing/utils.spec"; import { EdgeConfig } from "src/app/shared/shared"; import { DATA, LABELS } from "../../../energy/chart/chart.constants.spec"; import { History } from "./channels.spec"; @@ -17,7 +17,8 @@ describe("History Production Details - _sum", () => { let TEST_CONTEXT: TestContext & { route: ActivatedRoute }; beforeEach(async () => { - TEST_CONTEXT = await sharedSetupWithComponentIdRoute("_sum"); + TEST_CONTEXT = await TestingUtils.setupWithActivatedRoute("_sum"); + }); it("#getChartData() - asymmetricMeter && no essDcCharger configured", () => { @@ -116,10 +117,10 @@ describe("History Production Details - _sum", () => { }); export function expectView(config: EdgeConfig, testContext: TestContext & { route: ActivatedRoute }, chartType: "line" | "bar", channels: OeTester.Types.Channels, view: OeChartTester.View): void { - expect(removeFunctions(OeChartTester + expect(TestingUtils.removeFunctions(OeChartTester .apply(SumChartDetailsComponent .getChartData( DummyConfig.convertDummyEdgeConfigToRealEdgeConfig(config), testContext.route, testContext.translate), chartType, channels, testContext, config))) - .toEqual(removeFunctions(view)); + .toEqual(TestingUtils.removeFunctions(view)); } diff --git a/ui/src/app/edge/history/common/production/details/chart/sum.ts b/ui/src/app/edge/history/common/production/details/chart/sum.ts index 6c630946ae0..460e4c85d22 100644 --- a/ui/src/app/edge/history/common/production/details/chart/sum.ts +++ b/ui/src/app/edge/history/common/production/details/chart/sum.ts @@ -3,6 +3,7 @@ import { Component } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; import { TranslateService } from "@ngx-translate/core"; import { AbstractHistoryChart } from "src/app/shared/components/chart/abstracthistorychart"; +import { ChartConstants } from "src/app/shared/components/chart/chart.constants"; import { Phase } from "src/app/shared/components/shared/phase"; import { QueryHistoricTimeseriesEnergyResponse } from "src/app/shared/jsonrpc/response/queryHistoricTimeseriesEnergyResponse"; import { ChartAxis, HistoryUtils, Utils, YAxisType } from "src/app/shared/service/utils"; @@ -11,6 +12,7 @@ import { ChannelAddress, EdgeConfig } from "src/app/shared/shared"; @Component({ selector: "sumChart", templateUrl: "../../../../../../shared/components/chart/abstracthistorychart.html", + standalone: false, }) export class SumChartDetailsComponent extends AbstractHistoryChart { @@ -67,7 +69,7 @@ export class SumChartDetailsComponent extends AbstractHistoryChart { name: translate.instant("General.TOTAL"), nameSuffix: (energyQueryResponse: QueryHistoricTimeseriesEnergyResponse) => energyQueryResponse.result.data["_sum/ProductionActiveEnergy"], converter: () => data[component.id], - color: "rgb(0,152,204)", + color: ChartConstants.Colors.BLUE, hiddenOnInit: false, stack: 2, }, diff --git a/ui/src/app/edge/history/common/production/details/details.overview.html b/ui/src/app/edge/history/common/production/details/details.overview.html index 1884e99ef81..0e69bd49bec 100644 --- a/ui/src/app/edge/history/common/production/details/details.overview.html +++ b/ui/src/app/edge/history/common/production/details/details.overview.html @@ -1,4 +1,4 @@ - diff --git a/ui/src/app/edge/history/common/production/details/details.overview.ts b/ui/src/app/edge/history/common/production/details/details.overview.ts index 985d1698b2b..f4c1d893442 100644 --- a/ui/src/app/edge/history/common/production/details/details.overview.ts +++ b/ui/src/app/edge/history/common/production/details/details.overview.ts @@ -9,6 +9,7 @@ import { Role } from "src/app/shared/type/role"; @Component({ templateUrl: "./details.overview.html", + standalone: false, }) export class DetailsOverviewComponent extends AbstractHistoryChartOverview { protected navigationButtons: NavigationOption[] = []; diff --git a/ui/src/app/edge/history/common/production/flat/flat.ts b/ui/src/app/edge/history/common/production/flat/flat.ts index 9b61575701d..a1a7a027273 100644 --- a/ui/src/app/edge/history/common/production/flat/flat.ts +++ b/ui/src/app/edge/history/common/production/flat/flat.ts @@ -6,6 +6,7 @@ import { ChannelAddress, EdgeConfig, Utils } from "../../../../../shared/shared" @Component({ selector: "productionWidget", templateUrl: "./flat.html", + standalone: false, }) export class FlatComponent extends AbstractFlatWidget { diff --git a/ui/src/app/edge/history/common/production/overview/overview.ts b/ui/src/app/edge/history/common/production/overview/overview.ts index e75c1e02ea0..6fe174aa285 100644 --- a/ui/src/app/edge/history/common/production/overview/overview.ts +++ b/ui/src/app/edge/history/common/production/overview/overview.ts @@ -8,6 +8,7 @@ import { ChannelAddress, EdgeConfig, Service } from "../../../../../shared/share @Component({ templateUrl: "./overview.html", + standalone: false, }) export class OverviewComponent extends AbstractHistoryChartOverview { protected chargerComponents: EdgeConfig.Component[] = []; @@ -35,10 +36,8 @@ export class OverviewComponent extends AbstractHistoryChartOverview { this.config.getComponentsImplementingNature("io.openems.edge.meter.api.ElectricityMeter") .filter(component => component.isEnabled && this.config.isProducer(component)); - const sum: EdgeConfig.Component = this.config.getComponent("_sum"); - sum.alias = this.translate.instant("General.TOTAL"); - this.navigationButtons = [sum, ...this.chargerComponents, ...this.productionMeterComponents].map(el => ( + this.navigationButtons = [...this.productionMeterComponents, ...this.chargerComponents].map(el => ( { id: el.id, alias: el.alias, callback: () => { this.router.navigate(["./" + el.id], { relativeTo: this.route }); } } )); return []; diff --git a/ui/src/app/edge/history/common/production/production.ts b/ui/src/app/edge/history/common/production/production.ts index d4b7b9ce39c..b78789fc8de 100644 --- a/ui/src/app/edge/history/common/production/production.ts +++ b/ui/src/app/edge/history/common/production/production.ts @@ -1,9 +1,9 @@ import { NgModule } from "@angular/core"; import { BrowserModule } from "@angular/platform-browser"; +import { CurrentVoltageModule } from "src/app/shared/components/edge/meter/currentVoltage/currentVoltageModule"; import { FooterNavigationModule } from "src/app/shared/components/footer/subnavigation/footerNavigation.module"; import { SharedModule } from "src/app/shared/shared.module"; -import { CurrentVoltageModule } from "src/app/shared/components/edge/meter/currentVoltage/currentVoltageModule"; import { TotalChartComponent } from "./chart/totalChart"; import { ChargerChartDetailsComponent } from "./details/chart/charger"; import { ProductionMeterChartDetailsComponent } from "./details/chart/productionMeter"; diff --git a/ui/src/app/edge/history/common/selfconsumption/chart/chart.component.ts b/ui/src/app/edge/history/common/selfconsumption/chart/chart.component.ts index 02c9e4f827f..bcbad38dc1f 100644 --- a/ui/src/app/edge/history/common/selfconsumption/chart/chart.component.ts +++ b/ui/src/app/edge/history/common/selfconsumption/chart/chart.component.ts @@ -1,6 +1,7 @@ // @ts-strict-ignore import { Component } from "@angular/core"; import { AbstractHistoryChart } from "src/app/shared/components/chart/abstracthistorychart"; +import { ChartConstants } from "src/app/shared/components/chart/chart.constants"; import { QueryHistoricTimeseriesEnergyResponse } from "src/app/shared/jsonrpc/response/queryHistoricTimeseriesEnergyResponse"; import { ChartAxis, HistoryUtils, Utils, YAxisType } from "src/app/shared/service/utils"; import { ChannelAddress } from "src/app/shared/shared"; @@ -8,6 +9,7 @@ import { ChannelAddress } from "src/app/shared/shared"; @Component({ selector: "selfconsumptionChart", templateUrl: "../../../../../shared/components/chart/abstracthistorychart.html", + standalone: false, }) export class ChartComponent extends AbstractHistoryChart { @@ -38,7 +40,7 @@ export class ChartComponent extends AbstractHistoryChart { Utils.calculateSelfConsumption(value, data["ProductionActivePower"][index]), ); }, - color: "rgb(253,197,7)", + color: ChartConstants.Colors.YELLOW, }]; }, tooltip: { diff --git a/ui/src/app/edge/history/common/selfconsumption/flat/flat.html b/ui/src/app/edge/history/common/selfconsumption/flat/flat.html index a3def18d075..f84bc5eb115 100644 --- a/ui/src/app/edge/history/common/selfconsumption/flat/flat.html +++ b/ui/src/app/edge/history/common/selfconsumption/flat/flat.html @@ -1,5 +1,6 @@ - + + diff --git a/ui/src/app/edge/history/common/selfconsumption/flat/flat.ts b/ui/src/app/edge/history/common/selfconsumption/flat/flat.ts index 4cc5d5fe127..f91f1a90ddb 100644 --- a/ui/src/app/edge/history/common/selfconsumption/flat/flat.ts +++ b/ui/src/app/edge/history/common/selfconsumption/flat/flat.ts @@ -6,6 +6,7 @@ import { ChannelAddress, CurrentData, Utils } from "src/app/shared/shared"; @Component({ selector: "selfconsumptionWidget", templateUrl: "./flat.html", + standalone: false, }) export class FlatComponent extends AbstractFlatWidget { diff --git a/ui/src/app/edge/history/common/selfconsumption/overview/overview.ts b/ui/src/app/edge/history/common/selfconsumption/overview/overview.ts index 51ed8f95a67..1d850679996 100644 --- a/ui/src/app/edge/history/common/selfconsumption/overview/overview.ts +++ b/ui/src/app/edge/history/common/selfconsumption/overview/overview.ts @@ -3,5 +3,6 @@ import { AbstractHistoryChartOverview } from "src/app/shared/components/chart/ab @Component({ templateUrl: "./overview.html", + standalone: false, }) export class OverviewComponent extends AbstractHistoryChartOverview { } diff --git a/ui/src/app/edge/history/delayedselltogrid/chart.component.ts b/ui/src/app/edge/history/delayedselltogrid/chart.component.ts index deb3efeec3c..ce1d4454d4a 100644 --- a/ui/src/app/edge/history/delayedselltogrid/chart.component.ts +++ b/ui/src/app/edge/history/delayedselltogrid/chart.component.ts @@ -11,6 +11,7 @@ import { AbstractHistoryChart } from "../abstracthistorychart"; @Component({ selector: "delayedselltogridgchart", templateUrl: "../abstracthistorychart.html", + standalone: false, }) export class DelayedSellToGridChartComponent extends AbstractHistoryChart implements OnInit, OnChanges, OnDestroy { @@ -147,7 +148,7 @@ export class DelayedSellToGridChartComponent extends AbstractHistoryChart implem } }); datasets.push({ - label: this.translate.instant("General.chargePower"), + label: this.translate.instant("General.CHARGE"), data: chargeData, borderDash: [10, 10], }); @@ -168,7 +169,7 @@ export class DelayedSellToGridChartComponent extends AbstractHistoryChart implem } }); datasets.push({ - label: this.translate.instant("General.dischargePower"), + label: this.translate.instant("General.DISCHARGE"), data: dischargeData, borderDash: [10, 10], }); diff --git a/ui/src/app/edge/history/delayedselltogrid/symmetricpeakshavingchartoverview/delayedselltogridchartoverview.component.html b/ui/src/app/edge/history/delayedselltogrid/symmetricpeakshavingchartoverview/delayedselltogridchartoverview.component.html index 63997a1e1c5..11f9a6243d4 100644 --- a/ui/src/app/edge/history/delayedselltogrid/symmetricpeakshavingchartoverview/delayedselltogridchartoverview.component.html +++ b/ui/src/app/edge/history/delayedselltogrid/symmetricpeakshavingchartoverview/delayedselltogridchartoverview.component.html @@ -1,14 +1,14 @@ - - {{ component.alias }} - + + {{ component.alias }} + - + - + diff --git a/ui/src/app/edge/history/delayedselltogrid/symmetricpeakshavingchartoverview/delayedselltogridchartoverview.component.ts b/ui/src/app/edge/history/delayedselltogrid/symmetricpeakshavingchartoverview/delayedselltogridchartoverview.component.ts index b7b4f44eed3..3729f5d1476 100644 --- a/ui/src/app/edge/history/delayedselltogrid/symmetricpeakshavingchartoverview/delayedselltogridchartoverview.component.ts +++ b/ui/src/app/edge/history/delayedselltogrid/symmetricpeakshavingchartoverview/delayedselltogridchartoverview.component.ts @@ -5,6 +5,7 @@ import { Edge, EdgeConfig, Service } from "../../../../shared/shared"; @Component({ selector: DelayedSellToGridChartOverviewComponent.SELECTOR, templateUrl: "./delayedselltogridchartoverview.component.html", + standalone: false, }) export class DelayedSellToGridChartOverviewComponent implements OnInit { diff --git a/ui/src/app/edge/history/delayedselltogrid/widget.component.html b/ui/src/app/edge/history/delayedselltogrid/widget.component.html index 9850fcb7ddd..f68fc28424b 100644 --- a/ui/src/app/edge/history/delayedselltogrid/widget.component.html +++ b/ui/src/app/edge/history/delayedselltogrid/widget.component.html @@ -1,10 +1,10 @@ - + {{ component.alias }} - +
    diff --git a/ui/src/app/edge/history/delayedselltogrid/widget.component.ts b/ui/src/app/edge/history/delayedselltogrid/widget.component.ts index 7d618d15e6e..7a35ef46991 100644 --- a/ui/src/app/edge/history/delayedselltogrid/widget.component.ts +++ b/ui/src/app/edge/history/delayedselltogrid/widget.component.ts @@ -6,6 +6,7 @@ import { Edge, EdgeConfig, Service } from "src/app/shared/shared"; @Component({ selector: DelayedSellToGridWidgetComponent.SELECTOR, templateUrl: "./widget.component.html", + standalone: false, }) export class DelayedSellToGridWidgetComponent implements OnInit { diff --git a/ui/src/app/edge/history/heatingelement/chart.component.ts b/ui/src/app/edge/history/heatingelement/chart.component.ts deleted file mode 100644 index 49dd46bf02c..00000000000 --- a/ui/src/app/edge/history/heatingelement/chart.component.ts +++ /dev/null @@ -1,126 +0,0 @@ -// @ts-strict-ignore -import { Component, Input, OnChanges, OnDestroy, OnInit } from "@angular/core"; -import { ActivatedRoute } from "@angular/router"; -import { TranslateService } from "@ngx-translate/core"; - -import type { ChartOptions } from "chart.js"; -import { DefaultTypes } from "src/app/shared/service/defaulttypes"; -import { ChartAxis, YAxisType } from "src/app/shared/service/utils"; - -import { QueryHistoricTimeseriesDataResponse } from "../../../shared/jsonrpc/response/queryHistoricTimeseriesDataResponse"; -import { ChannelAddress, Edge, EdgeConfig, Service } from "../../../shared/shared"; -import { AbstractHistoryChart } from "../abstracthistorychart"; - -@Component({ - selector: "heatingelementChart", - templateUrl: "../abstracthistorychart.html", -}) -export class HeatingelementChartComponent extends AbstractHistoryChart implements OnInit, OnChanges, OnDestroy { - - @Input({ required: true }) public period!: DefaultTypes.HistoryPeriod; - @Input({ required: true }) public component!: EdgeConfig.Component; - - constructor( - protected override service: Service, - protected override translate: TranslateService, - private route: ActivatedRoute, - ) { - super("heatingelement-chart", service, translate); - } - - ngOnChanges() { - this.updateChart(); - } - - ngOnInit() { - this.startSpinner(); - this.setLabel(); - } - - ngOnDestroy() { - this.unsubscribeChartRefresh(); - } - - public getChartHeight(): number { - return window.innerHeight / 1.3; - } - - protected updateChart() { - this.autoSubscribeChartRefresh(); - this.startSpinner(); - this.colors = []; - this.loading = true; - this.queryHistoricTimeseriesData(this.period.from, this.period.to).then(response => { - this.service.getCurrentEdge().then(() => { - const result = (response as QueryHistoricTimeseriesDataResponse).result; - // convert labels - const labels: Date[] = []; - for (const timestamp of result.timestamps) { - labels.push(new Date(timestamp)); - } - this.labels = labels; - - // convert datasets - const datasets = []; - const level = this.component.id + "/Level"; - - if (level in result.data) { - const levelData = result.data[level].map(value => { - if (value == null) { - return null; - } else { - return value; - } - }); - datasets.push({ - label: "Level", - data: levelData, - }); - this.colors.push({ - backgroundColor: "rgba(200,0,0,0.05)", - borderColor: "rgba(200,0,0,1)", - }); - } - this.datasets = datasets; - this.loading = false; - this.stopSpinner(); - - }).catch(reason => { - console.error(reason); // TODO error message - this.initializeChart(); - return; - }); - - }).catch(reason => { - console.error(reason); // TODO error message - this.initializeChart(); - return; - }).finally(async () => { - this.formatNumber = "1.0-1"; - this.unit = YAxisType.NONE; - await this.setOptions(this.options); - this.applyControllerSpecificOptions(this.options); - }); - } - - protected getChannelAddresses(edge: Edge, config: EdgeConfig): Promise { - return new Promise((resolve) => { - const levels = new ChannelAddress(this.component.id, "Level"); - const channeladdresses = [levels]; - resolve(channeladdresses); - }); - } - - protected applyControllerSpecificOptions(options: ChartOptions) { - options.scales[ChartAxis.LEFT]["title"].text = "Level"; - options.scales[ChartAxis.LEFT]["beginAtZero"] = true; - options.scales[ChartAxis.LEFT].max = 3; - options.scales[ChartAxis.LEFT].ticks["stepSize"] = 1; - this.options = options; - } - - protected setLabel() { - this.options = this.createDefaultChartOptions(); - } - -} diff --git a/ui/src/app/edge/history/heatingelement/heatingelementchartoverview/heatingelementchartoverview.component.html b/ui/src/app/edge/history/heatingelement/heatingelementchartoverview/heatingelementchartoverview.component.html deleted file mode 100644 index 9b116e552fb..00000000000 --- a/ui/src/app/edge/history/heatingelement/heatingelementchartoverview/heatingelementchartoverview.component.html +++ /dev/null @@ -1,23 +0,0 @@ - - - - {{ component.alias }} - - - - - - - - - - - - - - - - - - - diff --git a/ui/src/app/edge/history/heatingelement/heatingelementchartoverview/heatingelementchartoverview.component.ts b/ui/src/app/edge/history/heatingelement/heatingelementchartoverview/heatingelementchartoverview.component.ts deleted file mode 100644 index f615818d7c3..00000000000 --- a/ui/src/app/edge/history/heatingelement/heatingelementchartoverview/heatingelementchartoverview.component.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Component, OnInit } from "@angular/core"; -import { ActivatedRoute } from "@angular/router"; -import { Edge, EdgeConfig, Service } from "../../../../shared/shared"; - -@Component({ - selector: HeatingelementChartOverviewComponent.SELECTOR, - templateUrl: "./heatingelementchartoverview.component.html", -}) -export class HeatingelementChartOverviewComponent implements OnInit { - - private static readonly SELECTOR = "heatingelement-chart-overview"; - public edge: Edge | null = null; - public component: EdgeConfig.Component | null = null; - - constructor( - public service: Service, - private route: ActivatedRoute, - ) { } - - ngOnInit() { - this.service.getCurrentEdge().then(edge => { - this.service.getConfig().then(config => { - this.component = config.getComponent(this.route.snapshot.params.componentId); - this.service.getConfig().then(config => { - this.edge = edge; - this.component = config.getComponent(this.route.snapshot.params.componentId); - }); - }); - }); - } -} diff --git a/ui/src/app/edge/history/heatingelement/widget.component.html b/ui/src/app/edge/history/heatingelement/widget.component.html deleted file mode 100644 index 49b50bff319..00000000000 --- a/ui/src/app/edge/history/heatingelement/widget.component.html +++ /dev/null @@ -1,37 +0,0 @@ - - - - {{ component.alias }} - - - - - - - - - - - - - - - - - -
    - Edge.History.activeDuration Level 1 - - {{ activeTimeOverPeriodLevel1 | formatSecondsToDuration }} -
    - Edge.History.activeDuration Level 2 - - {{ activeTimeOverPeriodLevel2 | formatSecondsToDuration }} -
    - Edge.History.activeDuration Level 3 - {{ activeTimeOverPeriodLevel3 | formatSecondsToDuration }} -
    -
    -
    -
    -
    diff --git a/ui/src/app/edge/history/heatingelement/widget.component.ts b/ui/src/app/edge/history/heatingelement/widget.component.ts deleted file mode 100644 index 262c025d23c..00000000000 --- a/ui/src/app/edge/history/heatingelement/widget.component.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { Component, Input, OnChanges, OnDestroy, OnInit } from "@angular/core"; -import { ActivatedRoute } from "@angular/router"; -import { QueryHistoricTimeseriesDataResponse } from "src/app/shared/jsonrpc/response/queryHistoricTimeseriesDataResponse"; -import { DefaultTypes } from "src/app/shared/service/defaulttypes"; - -import { ChannelAddress, Edge, EdgeConfig, Service } from "../../../shared/shared"; -import { AbstractHistoryWidget } from "../abstracthistorywidget"; - -@Component({ - selector: HeatingelementWidgetComponent.SELECTOR, - templateUrl: "./widget.component.html", -}) -export class HeatingelementWidgetComponent extends AbstractHistoryWidget implements OnInit, OnChanges, OnDestroy { - - private static readonly SELECTOR = "heatingelementWidget"; - @Input({ required: true }) public period!: DefaultTypes.HistoryPeriod; - @Input({ required: true }) public componentId!: string; - - - public component: EdgeConfig.Component | null = null; - - public activeTimeOverPeriodLevel1: number | null = null; - public activeTimeOverPeriodLevel2: number | null = null; - public activeTimeOverPeriodLevel3: number | null = null; - - public edge: Edge | null = null; - - constructor( - public override service: Service, - private route: ActivatedRoute, - ) { - super(service); - } - - ngOnInit() { - this.service.getCurrentEdge().then(edge => { - this.edge = edge; - this.service.getConfig().then(config => { - this.component = config.getComponent(this.componentId); - }); - }); - } - - ngOnDestroy() { - this.unsubscribeWidgetRefresh(); - } - - ngOnChanges() { - this.updateValues(); - } - - public getCumulativeValue(channeladdress: string, response: QueryHistoricTimeseriesDataResponse) { - const array = response.result.data[channeladdress]; - const firstValue = array.find(el => el != null) ?? 0; - const lastValue = array.slice().reverse().find(el => el != null) ?? 0; - return lastValue - firstValue; - } - - protected updateValues() { - this.queryHistoricTimeseriesData(this.service.historyPeriod.value.from, this.service.historyPeriod.value.to).then(response => { - this.activeTimeOverPeriodLevel1 = this.getCumulativeValue(this.componentId + "/Level1CumulatedTime", response); - this.activeTimeOverPeriodLevel2 = this.getCumulativeValue(this.componentId + "/Level2CumulatedTime", response); - this.activeTimeOverPeriodLevel3 = this.getCumulativeValue(this.componentId + "/Level3CumulatedTime", response); - }); - } - - protected getChannelAddresses(edge: Edge, config: EdgeConfig): Promise { - return new Promise((resolve) => { - const channeladdresses = [ - new ChannelAddress(this.componentId, "Level1CumulatedTime"), - new ChannelAddress(this.componentId, "Level2CumulatedTime"), - new ChannelAddress(this.componentId, "Level3CumulatedTime"), - ]; - resolve(channeladdresses); - }); - } -} diff --git a/ui/src/app/edge/history/heatpump/chart.component.ts b/ui/src/app/edge/history/heatpump/chart.component.ts index 4e39df05a3c..ae96fd0faf5 100644 --- a/ui/src/app/edge/history/heatpump/chart.component.ts +++ b/ui/src/app/edge/history/heatpump/chart.component.ts @@ -3,15 +3,18 @@ import { Component, Input, OnChanges, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; import { TranslateService } from "@ngx-translate/core"; import * as Chart from "chart.js"; +import { ChartConstants } from "src/app/shared/components/chart/chart.constants"; import { DefaultTypes } from "src/app/shared/service/defaulttypes"; import { ChartAxis } from "src/app/shared/service/utils"; +import { ColorUtils } from "src/app/shared/utils/color/color.utils"; import { ChannelAddress, EdgeConfig, Service } from "../../../shared/shared"; import { AbstractHistoryChart } from "../abstracthistorychart"; @Component({ selector: "heatpumpchart", templateUrl: "../abstracthistorychart.html", + standalone: false, }) export class HeatPumpChartComponent extends AbstractHistoryChart implements OnInit, OnChanges, OnDestroy { @@ -76,8 +79,8 @@ export class HeatPumpChartComponent extends AbstractHistoryChart implements OnIn hidden: false, }); this.colors.push({ - backgroundColor: "rgba(200,0,0,0.05)", - borderColor: "rgba(200,0,0,1)", + backgroundColor: ColorUtils.rgbStringToRGBA(ChartConstants.Colors.RED, 0.05), + borderColor: ChartConstants.Colors.RED, }); } this.datasets = datasets; diff --git a/ui/src/app/edge/history/heatpump/heatpumpchartoverview/heatpumpchartoverview.component.html b/ui/src/app/edge/history/heatpump/heatpumpchartoverview/heatpumpchartoverview.component.html index f8d4c9d4459..3cc2640675e 100644 --- a/ui/src/app/edge/history/heatpump/heatpumpchartoverview/heatpumpchartoverview.component.html +++ b/ui/src/app/edge/history/heatpump/heatpumpchartoverview/heatpumpchartoverview.component.html @@ -1,14 +1,14 @@ - - {{ component.alias }} - + + {{ component.alias }} + - + - + diff --git a/ui/src/app/edge/history/heatpump/heatpumpchartoverview/heatpumpchartoverview.component.ts b/ui/src/app/edge/history/heatpump/heatpumpchartoverview/heatpumpchartoverview.component.ts index e17887c4e55..ad8a47f7c6e 100644 --- a/ui/src/app/edge/history/heatpump/heatpumpchartoverview/heatpumpchartoverview.component.ts +++ b/ui/src/app/edge/history/heatpump/heatpumpchartoverview/heatpumpchartoverview.component.ts @@ -6,6 +6,7 @@ import { Edge, EdgeConfig, Service } from "../../../../shared/shared"; @Component({ selector: HeatPumpChartOverviewComponent.SELECTOR, templateUrl: "./heatpumpchartoverview.component.html", + standalone: false, }) export class HeatPumpChartOverviewComponent implements OnInit { diff --git a/ui/src/app/edge/history/heatpump/widget.component.html b/ui/src/app/edge/history/heatpump/widget.component.html index c664db8d882..390179e6900 100644 --- a/ui/src/app/edge/history/heatpump/widget.component.html +++ b/ui/src/app/edge/history/heatpump/widget.component.html @@ -1,11 +1,12 @@ - - - + + + {{ component.alias }} - + diff --git a/ui/src/app/edge/history/heatpump/widget.component.ts b/ui/src/app/edge/history/heatpump/widget.component.ts index 90fc211cca5..4df65667abf 100644 --- a/ui/src/app/edge/history/heatpump/widget.component.ts +++ b/ui/src/app/edge/history/heatpump/widget.component.ts @@ -10,6 +10,7 @@ import { AbstractHistoryWidget } from "../abstracthistorywidget"; @Component({ selector: HeatpumpWidgetComponent.SELECTOR, templateUrl: "./widget.component.html", + standalone: false, }) export class HeatpumpWidgetComponent extends AbstractHistoryWidget implements OnInit, OnChanges, OnDestroy { diff --git a/ui/src/app/edge/history/history.component.html b/ui/src/app/edge/history/history.component.html index bd86a09bf04..45655fbc05e 100644 --- a/ui/src/app/edge/history/history.component.html +++ b/ui/src/app/edge/history/history.component.html @@ -1,4 +1,3 @@ -
    @@ -46,10 +45,6 @@ - - - - @@ -89,8 +84,8 @@ - - + + diff --git a/ui/src/app/edge/history/history.component.ts b/ui/src/app/edge/history/history.component.ts index e2349530d0d..6d61f56adbf 100644 --- a/ui/src/app/edge/history/history.component.ts +++ b/ui/src/app/edge/history/history.component.ts @@ -1,9 +1,8 @@ // @ts-strict-ignore -import { Component, OnInit, ViewChild } from "@angular/core"; +import { Component, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; import { TranslateService } from "@ngx-translate/core"; import { AppService } from "src/app/app.service"; -import { HeaderComponent } from "src/app/shared/components/header/header.component"; import { JsonrpcResponseError } from "src/app/shared/jsonrpc/base"; import { Edge, EdgeConfig, EdgePermission, Service, Widgets } from "src/app/shared/shared"; import { environment } from "src/environments"; @@ -11,11 +10,10 @@ import { environment } from "src/environments"; @Component({ selector: "history", templateUrl: "./history.component.html", + standalone: false, }) export class HistoryComponent implements OnInit { - @ViewChild(HeaderComponent, { static: false }) public HeaderComponent: HeaderComponent; - // is a Timedata service available, i.e. can historic data be queried. public isTimedataAvailable: boolean = true; @@ -65,12 +63,6 @@ export class HistoryComponent implements OnInit { }); } - // checks arrows when ChartPage is closed - // double viewchild is used to prevent undefined state of PickDateComponent - ionViewDidEnter() { - this.HeaderComponent.PickDateComponent.checkArrowAutomaticForwarding(); - } - updateOnWindowResize() { const ref = /* fix proportions */ Math.min(window.innerHeight - 150, /* handle grid breakpoints */(window.innerWidth < 768 ? window.innerWidth - 150 : window.innerWidth - 400)); diff --git a/ui/src/app/edge/history/history.module.ts b/ui/src/app/edge/history/history.module.ts index 9d45b5ba2df..0c0fd1d6c06 100644 --- a/ui/src/app/edge/history/history.module.ts +++ b/ui/src/app/edge/history/history.module.ts @@ -1,6 +1,5 @@ import { NgModule } from "@angular/core"; import { HistoryDataErrorModule } from "src/app/shared/components/history-data-error/history-data-error.module"; - import { SharedModule } from "../../shared/shared.module"; import { ChpSocChartComponent } from "./chpsoc/chart.component"; import { ChpSocWidgetComponent } from "./chpsoc/widget.component"; @@ -9,9 +8,6 @@ import { Controller } from "./Controller/controller.module"; import { DelayedSellToGridChartComponent } from "./delayedselltogrid/chart.component"; import { DelayedSellToGridChartOverviewComponent } from "./delayedselltogrid/symmetricpeakshavingchartoverview/delayedselltogridchartoverview.component"; import { DelayedSellToGridWidgetComponent } from "./delayedselltogrid/widget.component"; -import { HeatingelementChartComponent } from "./heatingelement/chart.component"; -import { HeatingelementChartOverviewComponent } from "./heatingelement/heatingelementchartoverview/heatingelementchartoverview.component"; -import { HeatingelementWidgetComponent } from "./heatingelement/widget.component"; import { HeatPumpChartComponent } from "./heatpump/chart.component"; import { HeatPumpChartOverviewComponent } from "./heatpump/heatpumpchartoverview/heatpumpchartoverview.component"; import { HeatpumpWidgetComponent } from "./heatpump/widget.component"; @@ -36,10 +32,10 @@ import { StorageComponent } from "./storage/widget.component"; @NgModule({ imports: [ - SharedModule, Common, Controller, HistoryDataErrorModule, + SharedModule, ], declarations: [ AsymmetricPeakshavingChartComponent, @@ -50,13 +46,11 @@ import { StorageComponent } from "./storage/widget.component"; DelayedSellToGridChartComponent, DelayedSellToGridChartOverviewComponent, DelayedSellToGridWidgetComponent, - HeatingelementChartComponent, - HeatingelementChartOverviewComponent, - HeatingelementWidgetComponent, HeatPumpChartComponent, HeatPumpChartOverviewComponent, HeatpumpWidgetComponent, HistoryComponent, + HistoryParentComponent, SocStorageChartComponent, StorageChargerChartComponent, StorageChartOverviewComponent, @@ -70,7 +64,6 @@ import { StorageComponent } from "./storage/widget.component"; TimeslotPeakshavingChartComponent, TimeslotPeakshavingChartOverviewComponent, TimeslotPeakshavingWidgetComponent, - HistoryParentComponent, ], }) export class HistoryModule { } diff --git a/ui/src/app/edge/history/historyparent.component.ts b/ui/src/app/edge/history/historyparent.component.ts index 4a8c28f551a..2042b719c39 100644 --- a/ui/src/app/edge/history/historyparent.component.ts +++ b/ui/src/app/edge/history/historyparent.component.ts @@ -7,5 +7,6 @@ import { Component } from "@angular/core"; `, + standalone: false, }) export class HistoryParentComponent { } diff --git a/ui/src/app/edge/history/peakshaving/asymmetric/asymmetricpeakshavingchartoverview/asymmetricpeakshavingchartoverview.component.html b/ui/src/app/edge/history/peakshaving/asymmetric/asymmetricpeakshavingchartoverview/asymmetricpeakshavingchartoverview.component.html index 4c6ce917658..03689ee9986 100644 --- a/ui/src/app/edge/history/peakshaving/asymmetric/asymmetricpeakshavingchartoverview/asymmetricpeakshavingchartoverview.component.html +++ b/ui/src/app/edge/history/peakshaving/asymmetric/asymmetricpeakshavingchartoverview/asymmetricpeakshavingchartoverview.component.html @@ -1,14 +1,14 @@ - - {{ component.alias }} - + + {{ component.alias }} + - + - + diff --git a/ui/src/app/edge/history/peakshaving/asymmetric/asymmetricpeakshavingchartoverview/asymmetricpeakshavingchartoverview.component.ts b/ui/src/app/edge/history/peakshaving/asymmetric/asymmetricpeakshavingchartoverview/asymmetricpeakshavingchartoverview.component.ts index 0cac1b4d2f5..780e8a1fed1 100644 --- a/ui/src/app/edge/history/peakshaving/asymmetric/asymmetricpeakshavingchartoverview/asymmetricpeakshavingchartoverview.component.ts +++ b/ui/src/app/edge/history/peakshaving/asymmetric/asymmetricpeakshavingchartoverview/asymmetricpeakshavingchartoverview.component.ts @@ -5,6 +5,7 @@ import { Edge, EdgeConfig, Service } from "../../../../../shared/shared"; @Component({ selector: AsymmetricPeakshavingChartOverviewComponent.SELECTOR, templateUrl: "./asymmetricpeakshavingchartoverview.component.html", + standalone: false, }) export class AsymmetricPeakshavingChartOverviewComponent implements OnInit { diff --git a/ui/src/app/edge/history/peakshaving/asymmetric/chart.component.ts b/ui/src/app/edge/history/peakshaving/asymmetric/chart.component.ts index d9b4b4ae008..7eb7ea282c2 100644 --- a/ui/src/app/edge/history/peakshaving/asymmetric/chart.component.ts +++ b/ui/src/app/edge/history/peakshaving/asymmetric/chart.component.ts @@ -11,6 +11,7 @@ import { AbstractHistoryChart } from "../../abstracthistorychart"; @Component({ selector: "asymmetricpeakshavingchart", templateUrl: "../../abstracthistorychart.html", + standalone: false, }) export class AsymmetricPeakshavingChartComponent extends AbstractHistoryChart implements OnInit, OnChanges, OnDestroy { @@ -178,7 +179,7 @@ export class AsymmetricPeakshavingChartComponent extends AbstractHistoryChart im } }); datasets.push({ - label: this.translate.instant("General.chargePower"), + label: this.translate.instant("General.CHARGE"), data: chargeData, }); this.colors.push({ @@ -198,7 +199,7 @@ export class AsymmetricPeakshavingChartComponent extends AbstractHistoryChart im } }); datasets.push({ - label: this.translate.instant("General.dischargePower"), + label: this.translate.instant("General.DISCHARGE"), data: dischargeData, }); this.colors.push({ diff --git a/ui/src/app/edge/history/peakshaving/asymmetric/widget.component.html b/ui/src/app/edge/history/peakshaving/asymmetric/widget.component.html index 89e8082f29d..fd65d43b28a 100644 --- a/ui/src/app/edge/history/peakshaving/asymmetric/widget.component.html +++ b/ui/src/app/edge/history/peakshaving/asymmetric/widget.component.html @@ -1,10 +1,11 @@ - + + {{ component.alias }} - +
    Edge.Index.Widgets.HeatPump.normalOperation
    diff --git a/ui/src/app/edge/history/peakshaving/asymmetric/widget.component.ts b/ui/src/app/edge/history/peakshaving/asymmetric/widget.component.ts index 1d58538f990..797a284111d 100644 --- a/ui/src/app/edge/history/peakshaving/asymmetric/widget.component.ts +++ b/ui/src/app/edge/history/peakshaving/asymmetric/widget.component.ts @@ -6,6 +6,7 @@ import { Edge, EdgeConfig, Service } from "src/app/shared/shared"; @Component({ selector: AsymmetricPeakshavingWidgetComponent.SELECTOR, templateUrl: "./widget.component.html", + standalone: false, }) export class AsymmetricPeakshavingWidgetComponent implements OnInit { diff --git a/ui/src/app/edge/history/peakshaving/symmetric/chart.component.ts b/ui/src/app/edge/history/peakshaving/symmetric/chart.component.ts index 4c9d97e725f..6371eb8a81d 100644 --- a/ui/src/app/edge/history/peakshaving/symmetric/chart.component.ts +++ b/ui/src/app/edge/history/peakshaving/symmetric/chart.component.ts @@ -10,6 +10,7 @@ import { AbstractHistoryChart } from "../../abstracthistorychart"; @Component({ selector: "symmetricpeakshavingchart", templateUrl: "../../abstracthistorychart.html", + standalone: false, }) export class SymmetricPeakshavingChartComponent extends AbstractHistoryChart implements OnInit, OnChanges, OnDestroy { @@ -145,7 +146,7 @@ export class SymmetricPeakshavingChartComponent extends AbstractHistoryChart imp } }); datasets.push({ - label: this.translate.instant("General.chargePower"), + label: this.translate.instant("General.CHARGE"), data: chargeData, borderDash: [10, 10], }); @@ -166,7 +167,7 @@ export class SymmetricPeakshavingChartComponent extends AbstractHistoryChart imp } }); datasets.push({ - label: this.translate.instant("General.dischargePower"), + label: this.translate.instant("General.DISCHARGE"), data: dischargeData, borderDash: [10, 10], }); diff --git a/ui/src/app/edge/history/peakshaving/symmetric/symmetricpeakshavingchartoverview/symmetricpeakshavingchartoverview.component.html b/ui/src/app/edge/history/peakshaving/symmetric/symmetricpeakshavingchartoverview/symmetricpeakshavingchartoverview.component.html index 278617c5783..3a74296ee27 100644 --- a/ui/src/app/edge/history/peakshaving/symmetric/symmetricpeakshavingchartoverview/symmetricpeakshavingchartoverview.component.html +++ b/ui/src/app/edge/history/peakshaving/symmetric/symmetricpeakshavingchartoverview/symmetricpeakshavingchartoverview.component.html @@ -1,14 +1,14 @@ - - {{ component.alias }} - + + {{ component.alias }} + - + - + diff --git a/ui/src/app/edge/history/peakshaving/symmetric/symmetricpeakshavingchartoverview/symmetricpeakshavingchartoverview.component.ts b/ui/src/app/edge/history/peakshaving/symmetric/symmetricpeakshavingchartoverview/symmetricpeakshavingchartoverview.component.ts index 5734f7485cc..1d0a6100573 100644 --- a/ui/src/app/edge/history/peakshaving/symmetric/symmetricpeakshavingchartoverview/symmetricpeakshavingchartoverview.component.ts +++ b/ui/src/app/edge/history/peakshaving/symmetric/symmetricpeakshavingchartoverview/symmetricpeakshavingchartoverview.component.ts @@ -5,6 +5,7 @@ import { Edge, EdgeConfig, Service } from "../../../../../shared/shared"; @Component({ selector: SymmetricPeakshavingChartOverviewComponent.SELECTOR, templateUrl: "./symmetricpeakshavingchartoverview.component.html", + standalone: false, }) export class SymmetricPeakshavingChartOverviewComponent implements OnInit { diff --git a/ui/src/app/edge/history/peakshaving/symmetric/widget.component.html b/ui/src/app/edge/history/peakshaving/symmetric/widget.component.html index dbf3842991d..16e02041e08 100644 --- a/ui/src/app/edge/history/peakshaving/symmetric/widget.component.html +++ b/ui/src/app/edge/history/peakshaving/symmetric/widget.component.html @@ -1,10 +1,10 @@ - + {{ component.alias }} - +
    diff --git a/ui/src/app/edge/history/peakshaving/symmetric/widget.component.ts b/ui/src/app/edge/history/peakshaving/symmetric/widget.component.ts index 62f86d76e86..46d6603894a 100644 --- a/ui/src/app/edge/history/peakshaving/symmetric/widget.component.ts +++ b/ui/src/app/edge/history/peakshaving/symmetric/widget.component.ts @@ -6,6 +6,7 @@ import { Edge, EdgeConfig, Service } from "src/app/shared/shared"; @Component({ selector: SymmetricPeakshavingWidgetComponent.SELECTOR, templateUrl: "./widget.component.html", + standalone: false, }) export class SymmetricPeakshavingWidgetComponent implements OnInit { diff --git a/ui/src/app/edge/history/peakshaving/timeslot/chart.component.ts b/ui/src/app/edge/history/peakshaving/timeslot/chart.component.ts index 4a62f084032..de1ad3e60b3 100644 --- a/ui/src/app/edge/history/peakshaving/timeslot/chart.component.ts +++ b/ui/src/app/edge/history/peakshaving/timeslot/chart.component.ts @@ -11,6 +11,7 @@ import { AbstractHistoryChart } from "../../abstracthistorychart"; @Component({ selector: "timeslotpeakshavingchart", templateUrl: "../../abstracthistorychart.html", + standalone: false, }) export class TimeslotPeakshavingChartComponent extends AbstractHistoryChart implements OnInit, OnChanges, OnDestroy { @@ -159,7 +160,7 @@ export class TimeslotPeakshavingChartComponent extends AbstractHistoryChart impl } }); datasets.push({ - label: this.translate.instant("General.chargePower"), + label: this.translate.instant("General.CHARGE"), data: chargeData, borderDash: [10, 10], }); @@ -180,7 +181,7 @@ export class TimeslotPeakshavingChartComponent extends AbstractHistoryChart impl } }); datasets.push({ - label: this.translate.instant("General.dischargePower"), + label: this.translate.instant("General.DISCHARGE"), data: dischargeData, borderDash: [10, 10], }); diff --git a/ui/src/app/edge/history/peakshaving/timeslot/timeslotpeakshavingchartoverview/timeslotpeakshavingchartoverview.component.html b/ui/src/app/edge/history/peakshaving/timeslot/timeslotpeakshavingchartoverview/timeslotpeakshavingchartoverview.component.html index 61e67f32806..ea37d1c4692 100644 --- a/ui/src/app/edge/history/peakshaving/timeslot/timeslotpeakshavingchartoverview/timeslotpeakshavingchartoverview.component.html +++ b/ui/src/app/edge/history/peakshaving/timeslot/timeslotpeakshavingchartoverview/timeslotpeakshavingchartoverview.component.html @@ -1,14 +1,14 @@ - - {{ component.alias }} - + + {{ component.alias }} + - + - + diff --git a/ui/src/app/edge/history/peakshaving/timeslot/timeslotpeakshavingchartoverview/timeslotpeakshavingchartoverview.component.ts b/ui/src/app/edge/history/peakshaving/timeslot/timeslotpeakshavingchartoverview/timeslotpeakshavingchartoverview.component.ts index c8c2ed8e7f3..828420c1362 100644 --- a/ui/src/app/edge/history/peakshaving/timeslot/timeslotpeakshavingchartoverview/timeslotpeakshavingchartoverview.component.ts +++ b/ui/src/app/edge/history/peakshaving/timeslot/timeslotpeakshavingchartoverview/timeslotpeakshavingchartoverview.component.ts @@ -5,6 +5,7 @@ import { Edge, EdgeConfig, Service } from "../../../../../shared/shared"; @Component({ selector: TimeslotPeakshavingChartOverviewComponent.SELECTOR, templateUrl: "./timeslotpeakshavingchartoverview.component.html", + standalone: false, }) export class TimeslotPeakshavingChartOverviewComponent implements OnInit { diff --git a/ui/src/app/edge/history/peakshaving/timeslot/widget.component.html b/ui/src/app/edge/history/peakshaving/timeslot/widget.component.html index 912f7c07fc5..582403623a3 100644 --- a/ui/src/app/edge/history/peakshaving/timeslot/widget.component.html +++ b/ui/src/app/edge/history/peakshaving/timeslot/widget.component.html @@ -1,10 +1,10 @@ - + {{ component.alias }} - + @@ -189,15 +191,11 @@ diff --git a/ui/src/app/edge/live/Controller/Io/ChannelSingleThreshold/modal/modal.component.ts b/ui/src/app/edge/live/Controller/Io/ChannelSingleThreshold/modal/modal.component.ts index 738b4c396bc..52f67095c23 100644 --- a/ui/src/app/edge/live/Controller/Io/ChannelSingleThreshold/modal/modal.component.ts +++ b/ui/src/app/edge/live/Controller/Io/ChannelSingleThreshold/modal/modal.component.ts @@ -11,6 +11,7 @@ type inputMode = "SOC" | "GRIDSELL" | "GRIDBUY" | "PRODUCTION" | "OTHER"; @Component({ selector: "Io_ChannelSingleThresholdModalComponent", templateUrl: "./modal.component.html", + standalone: false, }) export class Controller_Io_ChannelSingleThresholdModalComponent implements OnInit { diff --git a/ui/src/app/edge/live/Controller/Io/FixDigitalOutput/Io_FixDigitalOutput.html b/ui/src/app/edge/live/Controller/Io/FixDigitalOutput/Io_FixDigitalOutput.html index 6a40da4eee7..11278a80c59 100644 --- a/ui/src/app/edge/live/Controller/Io/FixDigitalOutput/Io_FixDigitalOutput.html +++ b/ui/src/app/edge/live/Controller/Io/FixDigitalOutput/Io_FixDigitalOutput.html @@ -1,5 +1,7 @@ - + + diff --git a/ui/src/app/edge/live/Controller/Io/FixDigitalOutput/Io_FixDigitalOutput.ts b/ui/src/app/edge/live/Controller/Io/FixDigitalOutput/Io_FixDigitalOutput.ts index b45630af970..89f686ed24f 100644 --- a/ui/src/app/edge/live/Controller/Io/FixDigitalOutput/Io_FixDigitalOutput.ts +++ b/ui/src/app/edge/live/Controller/Io/FixDigitalOutput/Io_FixDigitalOutput.ts @@ -8,6 +8,7 @@ import { Controller_Io_FixDigitalOutputModalComponent } from "./modal/modal.comp @Component({ selector: "Controller_Io_FixDigitalOutput", templateUrl: "./Io_FixDigitalOutput.html", + standalone: false, }) export class Controller_Io_FixDigitalOutputComponent extends AbstractFlatWidget { diff --git a/ui/src/app/edge/live/Controller/Io/FixDigitalOutput/modal/modal.component.html b/ui/src/app/edge/live/Controller/Io/FixDigitalOutput/modal/modal.component.html index 2fd0e5ba02f..b95fc16e6cf 100644 --- a/ui/src/app/edge/live/Controller/Io/FixDigitalOutput/modal/modal.component.html +++ b/ui/src/app/edge/live/Controller/Io/FixDigitalOutput/modal/modal.component.html @@ -1,5 +1,6 @@ - + {{ component.alias }} @@ -23,16 +24,18 @@ - + General.on - + - + General.off - + diff --git a/ui/src/app/edge/live/Controller/Io/FixDigitalOutput/modal/modal.component.ts b/ui/src/app/edge/live/Controller/Io/FixDigitalOutput/modal/modal.component.ts index ccc9dafd2b2..0fcb5416eca 100644 --- a/ui/src/app/edge/live/Controller/Io/FixDigitalOutput/modal/modal.component.ts +++ b/ui/src/app/edge/live/Controller/Io/FixDigitalOutput/modal/modal.component.ts @@ -7,6 +7,7 @@ import { Edge, EdgeConfig, Service, Websocket } from "src/app/shared/shared"; @Component({ selector: "fixdigitaloutput-modal", templateUrl: "./modal.component.html", + standalone: false, }) export class Controller_Io_FixDigitalOutputModalComponent { diff --git a/ui/src/app/edge/live/Controller/Io/HeatingElement/flat/flat.html b/ui/src/app/edge/live/Controller/Io/HeatingElement/flat/flat.html index f2440c4a5d5..0b68e20aa55 100644 --- a/ui/src/app/edge/live/Controller/Io/HeatingElement/flat/flat.html +++ b/ui/src/app/edge/live/Controller/Io/HeatingElement/flat/flat.html @@ -1,5 +1,6 @@ - + + diff --git a/ui/src/app/edge/live/Controller/Io/HeatingElement/flat/flat.ts b/ui/src/app/edge/live/Controller/Io/HeatingElement/flat/flat.ts index 732c697cb1a..b54cec5b791 100644 --- a/ui/src/app/edge/live/Controller/Io/HeatingElement/flat/flat.ts +++ b/ui/src/app/edge/live/Controller/Io/HeatingElement/flat/flat.ts @@ -10,6 +10,7 @@ import { ModalComponent } from "../modal/modal"; @Component({ selector: "Controller_Io_HeatingElement", templateUrl: "./flat.html", + standalone: false, }) export class FlatComponent extends AbstractFlatWidget { diff --git a/ui/src/app/edge/live/Controller/Io/HeatingElement/modal/modal.html b/ui/src/app/edge/live/Controller/Io/HeatingElement/modal/modal.html index 3a8e729979e..2cdd9adc10a 100644 --- a/ui/src/app/edge/live/Controller/Io/HeatingElement/modal/modal.html +++ b/ui/src/app/edge/live/Controller/Io/HeatingElement/modal/modal.html @@ -13,7 +13,7 @@ + { name: ('General.off' | translate), value: 'MANUAL_OFF', icon: {color:'danger', name: 'power'}}]"> diff --git a/ui/src/app/edge/live/Controller/Io/HeatingElement/modal/modal.ts b/ui/src/app/edge/live/Controller/Io/HeatingElement/modal/modal.ts index 8a9bd6ad535..4bf0cb81771 100644 --- a/ui/src/app/edge/live/Controller/Io/HeatingElement/modal/modal.ts +++ b/ui/src/app/edge/live/Controller/Io/HeatingElement/modal/modal.ts @@ -9,6 +9,7 @@ import { Mode, WorkMode } from "src/app/shared/type/general"; @Component({ selector: "heatingelement-modal", templateUrl: "./modal.html", + standalone: false, }) export class ModalComponent extends AbstractModal implements OnInit { diff --git a/ui/src/app/edge/live/Controller/Io/Heatpump/Io_Heatpump.html b/ui/src/app/edge/live/Controller/Io/Heatpump/Io_Heatpump.html index c0ade929d82..b9f00c4917c 100644 --- a/ui/src/app/edge/live/Controller/Io/Heatpump/Io_Heatpump.html +++ b/ui/src/app/edge/live/Controller/Io/Heatpump/Io_Heatpump.html @@ -1,5 +1,6 @@ + [icon]="{color: 'normal', name: 'flame', size: 'medium'}" [title]="component.alias"> + diff --git a/ui/src/app/edge/live/Controller/Io/Heatpump/Io_Heatpump.ts b/ui/src/app/edge/live/Controller/Io/Heatpump/Io_Heatpump.ts index 3962850c043..eb0c6a769b2 100644 --- a/ui/src/app/edge/live/Controller/Io/Heatpump/Io_Heatpump.ts +++ b/ui/src/app/edge/live/Controller/Io/Heatpump/Io_Heatpump.ts @@ -9,6 +9,7 @@ import { Controller_Io_HeatpumpModalComponent } from "./modal/modal.component"; @Component({ selector: "Controller_Io_Heatpump", templateUrl: "./Io_Heatpump.html", + standalone: false, }) export class Controller_Io_HeatpumpComponent extends AbstractFlatWidget { diff --git a/ui/src/app/edge/live/Controller/Io/Heatpump/modal/modal.component.html b/ui/src/app/edge/live/Controller/Io/Heatpump/modal/modal.component.html index 59063fd023e..7d3e87bb60f 100644 --- a/ui/src/app/edge/live/Controller/Io/Heatpump/modal/modal.component.html +++ b/ui/src/app/edge/live/Controller/Io/Heatpump/modal/modal.component.html @@ -1,5 +1,6 @@ - + {{ component.alias }} @@ -62,13 +63,15 @@ General.manually - + General.automatic - + @@ -124,9 +127,8 @@ + style="text-align: end;" label=" W"> -  W @@ -161,9 +163,8 @@ @@ -217,9 +218,8 @@ @@ -256,9 +256,8 @@ diff --git a/ui/src/app/edge/live/Controller/Io/Heatpump/modal/modal.component.ts b/ui/src/app/edge/live/Controller/Io/Heatpump/modal/modal.component.ts index d12d02ab21b..d39875d0339 100644 --- a/ui/src/app/edge/live/Controller/Io/Heatpump/modal/modal.component.ts +++ b/ui/src/app/edge/live/Controller/Io/Heatpump/modal/modal.component.ts @@ -11,6 +11,7 @@ type AutomaticEnableMode = "automaticRecommendationCtrlEnabled" | "automaticForc @Component({ selector: "heatpump-modal", templateUrl: "./modal.component.html", + standalone: false, }) export class Controller_Io_HeatpumpModalComponent implements OnInit { diff --git a/ui/src/app/edge/live/Controller/ModbusTcpApi/flat/flat.html b/ui/src/app/edge/live/Controller/ModbusTcpApi/flat/flat.html index 9df8ac9fb01..661499d50d3 100644 --- a/ui/src/app/edge/live/Controller/ModbusTcpApi/flat/flat.html +++ b/ui/src/app/edge/live/Controller/ModbusTcpApi/flat/flat.html @@ -1,3 +1,5 @@ - - + + diff --git a/ui/src/app/edge/live/Controller/ModbusTcpApi/flat/flat.ts b/ui/src/app/edge/live/Controller/ModbusTcpApi/flat/flat.ts index 89b23b86751..deae63a8046 100644 --- a/ui/src/app/edge/live/Controller/ModbusTcpApi/flat/flat.ts +++ b/ui/src/app/edge/live/Controller/ModbusTcpApi/flat/flat.ts @@ -7,6 +7,7 @@ import { ModalComponent } from "../modal/modal"; @Component({ selector: "Controller_Api_ModbusTcp", templateUrl: "./flat.html", + standalone: false, }) export class FlatComponent extends AbstractFlatWidget { diff --git a/ui/src/app/edge/live/Controller/ModbusTcpApi/modal/modal.ts b/ui/src/app/edge/live/Controller/ModbusTcpApi/modal/modal.ts index ae11a7a121a..c4a8ef62502 100644 --- a/ui/src/app/edge/live/Controller/ModbusTcpApi/modal/modal.ts +++ b/ui/src/app/edge/live/Controller/ModbusTcpApi/modal/modal.ts @@ -8,6 +8,7 @@ import { OverrideStatus } from "src/app/shared/type/general"; @Component({ templateUrl: "./modal.html", + standalone: false, }) export class ModalComponent extends AbstractModal { diff --git a/ui/src/app/edge/live/Controller/PeakShaving/Asymmetric/Asymmetric.html b/ui/src/app/edge/live/Controller/PeakShaving/Asymmetric/Asymmetric.html index 9f457005bed..9e79dd07da8 100644 --- a/ui/src/app/edge/live/Controller/PeakShaving/Asymmetric/Asymmetric.html +++ b/ui/src/app/edge/live/Controller/PeakShaving/Asymmetric/Asymmetric.html @@ -1,5 +1,6 @@ + [icon]="{name: 'trending-down-outline', color: 'normal'}"> + diff --git a/ui/src/app/edge/live/Controller/PeakShaving/Asymmetric/Asymmetric.ts b/ui/src/app/edge/live/Controller/PeakShaving/Asymmetric/Asymmetric.ts index 569e45069ed..fb73b321cbb 100644 --- a/ui/src/app/edge/live/Controller/PeakShaving/Asymmetric/Asymmetric.ts +++ b/ui/src/app/edge/live/Controller/PeakShaving/Asymmetric/Asymmetric.ts @@ -9,6 +9,7 @@ import { Controller_Asymmetric_PeakShavingModalComponent } from "./modal/modal.c @Component({ selector: "Controller_Asymmetric_PeakShaving", templateUrl: "./Asymmetric.html", + standalone: false, }) export class Controller_Asymmetric_PeakShavingComponent extends AbstractFlatWidget { diff --git a/ui/src/app/edge/live/Controller/PeakShaving/Asymmetric/modal/modal.component.html b/ui/src/app/edge/live/Controller/PeakShaving/Asymmetric/modal/modal.component.html index ebcb86c320f..0f99848add1 100644 --- a/ui/src/app/edge/live/Controller/PeakShaving/Asymmetric/modal/modal.component.html +++ b/ui/src/app/edge/live/Controller/PeakShaving/Asymmetric/modal/modal.component.html @@ -1,5 +1,6 @@ - + {{ component.alias }} @@ -75,9 +76,8 @@ @@ -90,9 +90,8 @@ diff --git a/ui/src/app/edge/live/Controller/PeakShaving/Asymmetric/modal/modal.component.ts b/ui/src/app/edge/live/Controller/PeakShaving/Asymmetric/modal/modal.component.ts index d00c47f25d5..8af40cc8ad5 100644 --- a/ui/src/app/edge/live/Controller/PeakShaving/Asymmetric/modal/modal.component.ts +++ b/ui/src/app/edge/live/Controller/PeakShaving/Asymmetric/modal/modal.component.ts @@ -9,6 +9,7 @@ import { Edge, EdgeConfig, Service, Websocket } from "../../../../../../shared/s @Component({ selector: "asymmetricpeakshaving-modal", templateUrl: "./modal.component.html", + standalone: false, }) export class Controller_Asymmetric_PeakShavingModalComponent implements OnInit { diff --git a/ui/src/app/edge/live/Controller/PeakShaving/Symmetric/Symmetric.html b/ui/src/app/edge/live/Controller/PeakShaving/Symmetric/Symmetric.html index 1ae94833bb1..48c58179b2e 100644 --- a/ui/src/app/edge/live/Controller/PeakShaving/Symmetric/Symmetric.html +++ b/ui/src/app/edge/live/Controller/PeakShaving/Symmetric/Symmetric.html @@ -1,5 +1,6 @@ + [icon]="{name: 'trending-down-outline', color: 'normal'}"> + - + {{ component.alias }} @@ -37,9 +38,8 @@ @@ -52,9 +52,8 @@ diff --git a/ui/src/app/edge/live/Controller/PeakShaving/Symmetric/modal/modal.component.ts b/ui/src/app/edge/live/Controller/PeakShaving/Symmetric/modal/modal.component.ts index aa2f498dc60..64540de15a8 100644 --- a/ui/src/app/edge/live/Controller/PeakShaving/Symmetric/modal/modal.component.ts +++ b/ui/src/app/edge/live/Controller/PeakShaving/Symmetric/modal/modal.component.ts @@ -8,6 +8,7 @@ import { Edge, EdgeConfig, Service, Websocket } from "../../../../../../shared/s @Component({ selector: "symmetricpeakshaving-modal", templateUrl: "./modal.component.html", + standalone: false, }) export class Controller_Symmetric_PeakShavingModalComponent implements OnInit { diff --git a/ui/src/app/edge/live/Controller/PeakShaving/Symmetric_TimeSlot/Symmetric_TimeSlot.html b/ui/src/app/edge/live/Controller/PeakShaving/Symmetric_TimeSlot/Symmetric_TimeSlot.html index 20ba62c9fe4..7c672cb2029 100644 --- a/ui/src/app/edge/live/Controller/PeakShaving/Symmetric_TimeSlot/Symmetric_TimeSlot.html +++ b/ui/src/app/edge/live/Controller/PeakShaving/Symmetric_TimeSlot/Symmetric_TimeSlot.html @@ -1,4 +1,6 @@ - + + - + {{ component.alias }} @@ -37,9 +38,8 @@ @@ -52,9 +52,8 @@ @@ -123,9 +122,8 @@ diff --git a/ui/src/app/edge/live/Controller/PeakShaving/Symmetric_TimeSlot/modal/modal.component.ts b/ui/src/app/edge/live/Controller/PeakShaving/Symmetric_TimeSlot/modal/modal.component.ts index 21f16820d4b..7b430fea13e 100644 --- a/ui/src/app/edge/live/Controller/PeakShaving/Symmetric_TimeSlot/modal/modal.component.ts +++ b/ui/src/app/edge/live/Controller/PeakShaving/Symmetric_TimeSlot/modal/modal.component.ts @@ -8,6 +8,7 @@ import { Edge, EdgeConfig, Service, Websocket } from "../../../../../../shared/s @Component({ selector: "timeslotpeakshaving-modal", templateUrl: "./modal.component.html", + standalone: false, }) export class Controller_Symmetric_TimeSlot_PeakShavingModalComponent implements OnInit { diff --git a/ui/src/app/edge/live/Io/Api_DigitalInput/Io_Api_DigitalInput.ts b/ui/src/app/edge/live/Io/Api_DigitalInput/Io_Api_DigitalInput.ts index 091f47eff6c..27f9de5e63f 100644 --- a/ui/src/app/edge/live/Io/Api_DigitalInput/Io_Api_DigitalInput.ts +++ b/ui/src/app/edge/live/Io/Api_DigitalInput/Io_Api_DigitalInput.ts @@ -7,6 +7,7 @@ import { Io_Api_DigitalInput_ModalComponent } from "./modal/modal.component"; @Component({ selector: "Io_Api_DigitalInput", templateUrl: "./Io_Api_DigitalInput.html", + standalone: false, }) export class Io_Api_DigitalInputComponent extends AbstractFlatWidget { diff --git a/ui/src/app/edge/live/Io/Api_DigitalInput/modal/modal.component.html b/ui/src/app/edge/live/Io/Api_DigitalInput/modal/modal.component.html index 3d98e20dabb..b38e74c1204 100644 --- a/ui/src/app/edge/live/Io/Api_DigitalInput/modal/modal.component.html +++ b/ui/src/app/edge/live/Io/Api_DigitalInput/modal/modal.component.html @@ -1,5 +1,6 @@ - + General.digitalInputs diff --git a/ui/src/app/edge/live/Io/Api_DigitalInput/modal/modal.component.ts b/ui/src/app/edge/live/Io/Api_DigitalInput/modal/modal.component.ts index 199010f1217..fab03412088 100644 --- a/ui/src/app/edge/live/Io/Api_DigitalInput/modal/modal.component.ts +++ b/ui/src/app/edge/live/Io/Api_DigitalInput/modal/modal.component.ts @@ -9,6 +9,7 @@ import { ChannelAddress, Edge, EdgeConfig, EdgePermission, Service, Websocket } @Component({ selector: "Io_Api_DigitalInputModal", templateUrl: "./modal.component.html", + standalone: false, }) export class Io_Api_DigitalInput_ModalComponent implements OnInit, OnDestroy { private static readonly SELECTOR = "Io_Api_DigitalInput_ModalComponent"; diff --git a/ui/src/app/edge/live/Multiple/Evcs_Api_Cluster/Evcs_Api_Cluster.html b/ui/src/app/edge/live/Multiple/Evcs_Api_Cluster/Evcs_Api_Cluster.html index 7743c265a65..821e39c81cf 100644 --- a/ui/src/app/edge/live/Multiple/Evcs_Api_Cluster/Evcs_Api_Cluster.html +++ b/ui/src/app/edge/live/Multiple/Evcs_Api_Cluster/Evcs_Api_Cluster.html @@ -1,5 +1,6 @@ + [icon]="{name: 'oe-evcs', color: 'normal'}" [title]="alias | translate"> + - + {{ alias }} diff --git a/ui/src/app/edge/live/Multiple/Evcs_Api_Cluster/modal/evcsCluster-modal.page.ts b/ui/src/app/edge/live/Multiple/Evcs_Api_Cluster/modal/evcsCluster-modal.page.ts index 3fbac0a50c9..85c81d5e17e 100644 --- a/ui/src/app/edge/live/Multiple/Evcs_Api_Cluster/modal/evcsCluster-modal.page.ts +++ b/ui/src/app/edge/live/Multiple/Evcs_Api_Cluster/modal/evcsCluster-modal.page.ts @@ -11,6 +11,7 @@ type Priority = "CAR" | "STORAGE"; @Component({ selector: "Evcs_Api_Cluster-modal", templateUrl: "./evcsCluster-modal.page.html", + standalone: false, }) export class Evcs_Api_ClusterModalComponent implements OnInit { diff --git a/ui/src/app/edge/live/common/autarchy/flat/flat.html b/ui/src/app/edge/live/common/autarchy/flat/flat.html index 1b425accf5e..e7f5b5aee9a 100644 --- a/ui/src/app/edge/live/common/autarchy/flat/flat.html +++ b/ui/src/app/edge/live/common/autarchy/flat/flat.html @@ -1,5 +1,5 @@ - diff --git a/ui/src/app/edge/live/common/autarchy/flat/flat.ts b/ui/src/app/edge/live/common/autarchy/flat/flat.ts index e9620384f0e..742fbccf7b4 100644 --- a/ui/src/app/edge/live/common/autarchy/flat/flat.ts +++ b/ui/src/app/edge/live/common/autarchy/flat/flat.ts @@ -7,6 +7,7 @@ import { ModalComponent } from "../modal/modal"; @Component({ selector: "Common_Autarchy", templateUrl: "./flat.html", + standalone: false, }) export class FlatComponent extends AbstractFlatWidget { diff --git a/ui/src/app/edge/live/common/autarchy/modal/modal.spec.ts b/ui/src/app/edge/live/common/autarchy/modal/modal.spec.ts index d35e4c7ea81..57b1aa0d532 100644 --- a/ui/src/app/edge/live/common/autarchy/modal/modal.spec.ts +++ b/ui/src/app/edge/live/common/autarchy/modal/modal.spec.ts @@ -1,6 +1,6 @@ import { LINE_INFO } from "src/app/shared/components/edge/edgeconfig.spec"; import { OeFormlyViewTester } from "src/app/shared/components/shared/testing/tester"; -import { sharedSetup, TestContext } from "src/app/shared/components/shared/testing/utils.spec"; +import { TestContext, TestingUtils } from "src/app/shared/components/shared/testing/utils.spec"; import { ModalComponent } from "./modal"; @@ -14,7 +14,7 @@ export function expectView(testContext: TestContext, viewContext: OeFormlyViewTe describe("Autarchy - Modal", () => { let TEST_CONTEXT: TestContext; - beforeEach(async () => TEST_CONTEXT = await sharedSetup()); + beforeEach(async () => TEST_CONTEXT = await TestingUtils.sharedSetup()); it("generateView()", () => { { diff --git a/ui/src/app/edge/live/common/autarchy/modal/modal.ts b/ui/src/app/edge/live/common/autarchy/modal/modal.ts index 15ed4d35841..246a6cc3682 100644 --- a/ui/src/app/edge/live/common/autarchy/modal/modal.ts +++ b/ui/src/app/edge/live/common/autarchy/modal/modal.ts @@ -6,6 +6,7 @@ import { Role } from "src/app/shared/type/role"; @Component({ templateUrl: "../../../../../shared/components/formly/formly-field-modal/template.html", + standalone: false, }) export class ModalComponent extends AbstractFormlyComponent { diff --git a/ui/src/app/edge/live/common/consumption/flat/flat.html b/ui/src/app/edge/live/common/consumption/flat/flat.html index 761302becfe..458deefc1bd 100644 --- a/ui/src/app/edge/live/common/consumption/flat/flat.html +++ b/ui/src/app/edge/live/common/consumption/flat/flat.html @@ -1,4 +1,4 @@ - + diff --git a/ui/src/app/edge/live/common/consumption/flat/flat.ts b/ui/src/app/edge/live/common/consumption/flat/flat.ts index 85b0069083f..259ca5bb040 100644 --- a/ui/src/app/edge/live/common/consumption/flat/flat.ts +++ b/ui/src/app/edge/live/common/consumption/flat/flat.ts @@ -7,6 +7,7 @@ import { ModalComponent } from "../modal/modal"; @Component({ selector: "consumption", templateUrl: "./flat.html", + standalone: false, }) export class FlatComponent extends AbstractFlatWidget { @@ -50,8 +51,11 @@ export class FlatComponent extends AbstractFlatWidget { // Get EVCSs this.evcss = this.config.getComponentsImplementingNature("io.openems.edge.evcs.api.Evcs") - .filter(component => !(component.factoryId == "Evcs.Cluster.SelfConsumption") && - !(component.factoryId == "Evcs.Cluster.PeakShaving") && !component.isEnabled == false); + .filter(component => + !(component.factoryId == "Evcs.Cluster.SelfConsumption") && + !(component.factoryId == "Evcs.Cluster.PeakShaving") && + !(this.config.factories[component.factoryId].natureIds.includes("io.openems.edge.meter.api.ElectricityMeter")) && + !component.isEnabled == false); for (const component of this.evcss) { channelAddresses.push( diff --git a/ui/src/app/edge/live/common/consumption/modal/modal.spec.ts b/ui/src/app/edge/live/common/consumption/modal/modal.spec.ts index d81a191bf67..f8452fc361f 100644 --- a/ui/src/app/edge/live/common/consumption/modal/modal.spec.ts +++ b/ui/src/app/edge/live/common/consumption/modal/modal.spec.ts @@ -2,13 +2,13 @@ import { CHANNEL_LINE, DummyConfig, LINE_HORIZONTAL, LINE_INFO_PHASES_DE, VALUE_FROM_CHANNELS_LINE } from "src/app/shared/components/edge/edgeconfig.spec"; import { TextIndentation } from "src/app/shared/components/modal/modal-line/modal-line"; import { OeFormlyViewTester } from "src/app/shared/components/shared/testing/tester"; -import { sharedSetup } from "src/app/shared/components/shared/testing/utils.spec"; +import { TestingUtils } from "src/app/shared/components/shared/testing/utils.spec"; import { expectView } from "./modal.constants.spec"; describe("Consumption - Modal", () => { let TEST_CONTEXT; - beforeEach(async () => TEST_CONTEXT = await sharedSetup()); + beforeEach(async () => TEST_CONTEXT = await TestingUtils.sharedSetup()); it("generateView()", () => { diff --git a/ui/src/app/edge/live/common/consumption/modal/modal.ts b/ui/src/app/edge/live/common/consumption/modal/modal.ts index ccdcd9af819..5dfda3b57ce 100644 --- a/ui/src/app/edge/live/common/consumption/modal/modal.ts +++ b/ui/src/app/edge/live/common/consumption/modal/modal.ts @@ -10,14 +10,18 @@ import { ChannelAddress, CurrentData, EdgeConfig } from "../../../../../shared/s @Component({ templateUrl: "../../../../../shared/components/formly/formly-field-modal/template.html", + standalone: false, }) export class ModalComponent extends AbstractFormlyComponent { public static generateView(config: EdgeConfig, translate: TranslateService): OeFormlyView { const evcss: EdgeConfig.Component[] | null = config.getComponentsImplementingNature("io.openems.edge.evcs.api.Evcs") - .filter(component => !(component.factoryId == "Evcs.Cluster.SelfConsumption") && - !(component.factoryId == "Evcs.Cluster.PeakShaving") && !component.isEnabled == false); + .filter(component => + !(component.factoryId == "Evcs.Cluster.SelfConsumption") && + !(component.factoryId == "Evcs.Cluster.PeakShaving") && + !(config.factories[component.factoryId].natureIds.includes("io.openems.edge.meter.api.ElectricityMeter")) && + !component.isEnabled == false); const consumptionMeters: EdgeConfig.Component[] | null = config.getComponentsImplementingNature("io.openems.edge.meter.api.ElectricityMeter") .filter(component => component.isEnabled && config.isTypeConsumptionMetered(component)); diff --git a/ui/src/app/edge/live/common/grid/flat/flat.ts b/ui/src/app/edge/live/common/grid/flat/flat.ts index 64bc50f0ff6..64f2981273c 100644 --- a/ui/src/app/edge/live/common/grid/flat/flat.ts +++ b/ui/src/app/edge/live/common/grid/flat/flat.ts @@ -10,6 +10,7 @@ import { ModalComponent } from "../modal/modal"; @Component({ selector: "grid", templateUrl: "./flat.html", + standalone: false, }) export class FlatComponent extends AbstractFlatWidget { diff --git a/ui/src/app/edge/live/common/grid/modal/modal.spec.ts b/ui/src/app/edge/live/common/grid/modal/modal.spec.ts index f886559ec80..b93203c4249 100644 --- a/ui/src/app/edge/live/common/grid/modal/modal.spec.ts +++ b/ui/src/app/edge/live/common/grid/modal/modal.spec.ts @@ -1,6 +1,6 @@ import { CHANNEL_LINE, DummyConfig, LINE_HORIZONTAL, LINE_INFO_PHASES_DE, PHASE_ADMIN, PHASE_GUEST, VALUE_FROM_CHANNELS_LINE } from "src/app/shared/components/edge/edgeconfig.spec"; import { OeFormlyViewTester } from "src/app/shared/components/shared/testing/tester"; -import { sharedSetup, TestContext } from "src/app/shared/components/shared/testing/utils.spec"; +import { TestContext, TestingUtils } from "src/app/shared/components/shared/testing/utils.spec"; import { GridMode } from "src/app/shared/shared"; import { Role } from "src/app/shared/type/role"; @@ -19,7 +19,7 @@ const VIEW_CONTEXT = (properties?: {}): OeFormlyViewTester.Context => ({ describe("Grid - Modal", () => { let TEST_CONTEXT: TestContext; - beforeEach(async () => TEST_CONTEXT = await sharedSetup()); + beforeEach(async () => TEST_CONTEXT = await TestingUtils.sharedSetup()); it("generateView()", () => { { diff --git a/ui/src/app/edge/live/common/grid/modal/modal.ts b/ui/src/app/edge/live/common/grid/modal/modal.ts index 899d9f6642a..3fdca6d5a3d 100644 --- a/ui/src/app/edge/live/common/grid/modal/modal.ts +++ b/ui/src/app/edge/live/common/grid/modal/modal.ts @@ -11,6 +11,7 @@ import { GridSectionComponent } from "../../../energymonitor/chart/section/grid. @Component({ templateUrl: "../../../../../shared/components/formly/formly-field-modal/template.html", + standalone: false, }) export class ModalComponent extends AbstractFormlyComponent { @@ -135,6 +136,7 @@ export class ModalComponent extends AbstractFormlyComponent { channel: ChannelAddress.fromString(component.id + "/ActivePower" + phase), converter: Name.SUFFIX_FOR_GRID_SELL_OR_GRID_BUY(translate, translate.instant("General.phase") + " " + phase), }, + indentation: TextIndentation.SINGLE, children: ModalComponent.generatePhasesLineItems(role, phase, component), }); @@ -150,7 +152,7 @@ export class ModalComponent extends AbstractFormlyComponent { }, { type: "item", channel: component.id + "/Current" + phase, - converter: Converter.CURRENT_IN_MILLIAMPERE_TO_AMPERE, + converter: Converter.CURRENT_IN_MILLIAMPERE_TO_ABSOLUTE_AMPERE, }); } diff --git a/ui/src/app/edge/live/common/production/flat/flat.ts b/ui/src/app/edge/live/common/production/flat/flat.ts index 678ef3f8c87..6e0fe1c96de 100644 --- a/ui/src/app/edge/live/common/production/flat/flat.ts +++ b/ui/src/app/edge/live/common/production/flat/flat.ts @@ -6,6 +6,7 @@ import { ModalComponent } from "../modal/modal"; @Component({ selector: "Common_Production", templateUrl: "./flat.html", + standalone: false, }) export class FlatComponent extends AbstractFlatWidget { diff --git a/ui/src/app/edge/live/common/production/modal/modal.ts b/ui/src/app/edge/live/common/production/modal/modal.ts index 013ab5b5307..49192686bb4 100644 --- a/ui/src/app/edge/live/common/production/modal/modal.ts +++ b/ui/src/app/edge/live/common/production/modal/modal.ts @@ -4,6 +4,7 @@ import { ChannelAddress, EdgeConfig, Utils } from "src/app/shared/shared"; @Component({ templateUrl: "./modal.html", + standalone: false, }) export class ModalComponent extends AbstractModal { diff --git a/ui/src/app/edge/live/common/selfconsumption/flat/flat.html b/ui/src/app/edge/live/common/selfconsumption/flat/flat.html index 87d7e770b3e..b31023bc43e 100644 --- a/ui/src/app/edge/live/common/selfconsumption/flat/flat.html +++ b/ui/src/app/edge/live/common/selfconsumption/flat/flat.html @@ -1,4 +1,4 @@ - + diff --git a/ui/src/app/edge/live/common/selfconsumption/flat/flat.ts b/ui/src/app/edge/live/common/selfconsumption/flat/flat.ts index f1bf518f2b2..188d9e11aaa 100644 --- a/ui/src/app/edge/live/common/selfconsumption/flat/flat.ts +++ b/ui/src/app/edge/live/common/selfconsumption/flat/flat.ts @@ -8,6 +8,7 @@ import { ModalComponent } from "../modal/modal"; @Component({ selector: "Common_Selfconsumption", templateUrl: "./flat.html", + standalone: false, }) export class FlatComponent extends AbstractFlatWidget { diff --git a/ui/src/app/edge/live/common/selfconsumption/modal/modal.spec.ts b/ui/src/app/edge/live/common/selfconsumption/modal/modal.spec.ts index 9571d751120..c3b6660f3ec 100644 --- a/ui/src/app/edge/live/common/selfconsumption/modal/modal.spec.ts +++ b/ui/src/app/edge/live/common/selfconsumption/modal/modal.spec.ts @@ -1,6 +1,6 @@ import { LINE_INFO } from "src/app/shared/components/edge/edgeconfig.spec"; import { OeFormlyViewTester } from "src/app/shared/components/shared/testing/tester"; -import { sharedSetup, TestContext } from "src/app/shared/components/shared/testing/utils.spec"; +import { TestContext, TestingUtils } from "src/app/shared/components/shared/testing/utils.spec"; import { ModalComponent } from "./modal"; @@ -15,7 +15,7 @@ export function expectView(testContext: TestContext, viewContext: OeFormlyViewTe describe("SelfConsumption - Modal", () => { let TEST_CONTEXT: TestContext; - beforeEach(async () => TEST_CONTEXT = await sharedSetup()); + beforeEach(async () => TEST_CONTEXT = await TestingUtils.sharedSetup()); it("generateView()", () => { { diff --git a/ui/src/app/edge/live/common/selfconsumption/modal/modal.ts b/ui/src/app/edge/live/common/selfconsumption/modal/modal.ts index 81894e904dd..acf5ac8ca75 100644 --- a/ui/src/app/edge/live/common/selfconsumption/modal/modal.ts +++ b/ui/src/app/edge/live/common/selfconsumption/modal/modal.ts @@ -6,6 +6,7 @@ import { Role } from "src/app/shared/type/role"; @Component({ templateUrl: "../../../../../shared/components/formly/formly-field-modal/template.html", + standalone: false, }) export class ModalComponent extends AbstractFormlyComponent { diff --git a/ui/src/app/edge/live/common/storage/modal/modal.component.html b/ui/src/app/edge/live/common/storage/modal/modal.component.html index 4e83cd402e7..391b36d33cc 100644 --- a/ui/src/app/edge/live/common/storage/modal/modal.component.html +++ b/ui/src/app/edge/live/common/storage/modal/modal.component.html @@ -1,5 +1,5 @@ - + General.storageSystem @@ -30,12 +30,12 @@ - + - + @@ -52,7 +52,7 @@ - + - + @@ -340,7 +356,7 @@ - + - + - + - + @@ -746,7 +762,7 @@ @@ -38,9 +38,8 @@ diff --git a/ui/src/app/edge/live/delayedselltogrid/modal/modal.component.ts b/ui/src/app/edge/live/delayedselltogrid/modal/modal.component.ts index b688531b982..f9207df30f4 100644 --- a/ui/src/app/edge/live/delayedselltogrid/modal/modal.component.ts +++ b/ui/src/app/edge/live/delayedselltogrid/modal/modal.component.ts @@ -8,6 +8,7 @@ import { Edge, EdgeConfig, Service, Websocket } from "../../../../shared/shared" @Component({ selector: DelayedSellToGridModalComponent.SELECTOR, templateUrl: "./modal.component.html", + standalone: false, }) export class DelayedSellToGridModalComponent implements OnInit { diff --git a/ui/src/app/edge/live/energymonitor/chart/chart.component.html b/ui/src/app/edge/live/energymonitor/chart/chart.component.html index c2eddea61c8..d27b5bac185 100644 --- a/ui/src/app/edge/live/energymonitor/chart/chart.component.html +++ b/ui/src/app/edge/live/energymonitor/chart/chart.component.html @@ -1,7 +1,7 @@
    - + diff --git a/ui/src/app/edge/live/energymonitor/chart/chart.component.ts b/ui/src/app/edge/live/energymonitor/chart/chart.component.ts index f0852e5d2f4..7d03dd28c23 100644 --- a/ui/src/app/edge/live/energymonitor/chart/chart.component.ts +++ b/ui/src/app/edge/live/energymonitor/chart/chart.component.ts @@ -12,6 +12,7 @@ import { StorageSectionComponent } from "./section/storage.component"; @Component({ selector: "energymonitor-chart", templateUrl: "./chart.component.html", + standalone: false, }) export class EnergymonitorChartComponent implements OnInit, OnDestroy { diff --git a/ui/src/app/edge/live/energymonitor/chart/section/consumption.component.html b/ui/src/app/edge/live/energymonitor/chart/section/consumption.component.html index fdef929c624..6064b6c3571 100644 --- a/ui/src/app/edge/live/energymonitor/chart/section/consumption.component.html +++ b/ui/src/app/edge/live/energymonitor/chart/section/consumption.component.html @@ -1,21 +1,23 @@ - - + + + - + + style="fill: var(--ion-item-background)" /> + font-family="sans-serif" font-size="square.valueText.fontsize" fill="var(--ion-text-color)"> + {{valueText}} - diff --git a/ui/src/app/edge/live/energymonitor/chart/section/consumption.component.ts b/ui/src/app/edge/live/energymonitor/chart/section/consumption.component.ts index 440054a0768..e5f09134931 100644 --- a/ui/src/app/edge/live/energymonitor/chart/section/consumption.component.ts +++ b/ui/src/app/edge/live/energymonitor/chart/section/consumption.component.ts @@ -24,6 +24,7 @@ import { AbstractSection, EnergyFlow, Ratio, SvgEnergyFlow, SvgSquare, SvgSquare transition("hide => show", animate("0ms ease-in")), ]), ], + standalone: false, }) export class ConsumptionSectionComponent extends AbstractSection implements OnInit, OnDestroy { diff --git a/ui/src/app/edge/live/energymonitor/chart/section/grid.component.html b/ui/src/app/edge/live/energymonitor/chart/section/grid.component.html index d35a089e698..3ca808fdf38 100644 --- a/ui/src/app/edge/live/energymonitor/chart/section/grid.component.html +++ b/ui/src/app/edge/live/energymonitor/chart/section/grid.component.html @@ -1,27 +1,27 @@ - - + + - + + style="fill: var(--ion-item-background)" /> + style="fill: var(--ion-item-background)" /> + font-family="sans-serif" font-size="square.valueText.fontsize" fill="var(--ion-text-color)"> {{valueText}} - diff --git a/ui/src/app/edge/live/energymonitor/chart/section/grid.component.ts b/ui/src/app/edge/live/energymonitor/chart/section/grid.component.ts index c0c61013161..05d70bb9f76 100644 --- a/ui/src/app/edge/live/energymonitor/chart/section/grid.component.ts +++ b/ui/src/app/edge/live/energymonitor/chart/section/grid.component.ts @@ -37,6 +37,7 @@ import { AbstractSection, EnergyFlow, Ratio, SvgEnergyFlow, SvgSquare, SvgSquare transition("hide => show", animate("0ms ease-in")), ]), ], + standalone: false, }) export class GridSectionComponent extends AbstractSection implements OnInit, OnDestroy { @@ -54,7 +55,7 @@ export class GridSectionComponent extends AbstractSection implements OnInit, OnD service: Service, unitpipe: UnitvaluePipe, ) { - super("General.grid", "left", "#1d1d1d", translate, service, "Grid"); + super("General.grid", "left", "var(--ion-color-dark)", translate, service, "Grid"); this.unitpipe = unitpipe; } diff --git a/ui/src/app/edge/live/energymonitor/chart/section/production.component.html b/ui/src/app/edge/live/energymonitor/chart/section/production.component.html index 520a36472ff..7d63121b108 100644 --- a/ui/src/app/edge/live/energymonitor/chart/section/production.component.html +++ b/ui/src/app/edge/live/energymonitor/chart/section/production.component.html @@ -1,21 +1,22 @@ - - + + - + + style="fill: var(--ion-item-background)" /> + font-family="sans-serif" font-size="square.valueText.fontsize" fill="var(--ion-text-color)"> {{valueText}} - diff --git a/ui/src/app/edge/live/energymonitor/chart/section/production.component.ts b/ui/src/app/edge/live/energymonitor/chart/section/production.component.ts index a5efc3de02e..3bdc163621c 100644 --- a/ui/src/app/edge/live/energymonitor/chart/section/production.component.ts +++ b/ui/src/app/edge/live/energymonitor/chart/section/production.component.ts @@ -24,6 +24,7 @@ import { AbstractSection, EnergyFlow, Ratio, SvgEnergyFlow, SvgSquare, SvgSquare transition("hide => show", animate("0ms ease-in")), ]), ], + standalone: false, }) export class ProductionSectionComponent extends AbstractSection implements OnInit, OnDestroy { @@ -38,7 +39,7 @@ export class ProductionSectionComponent extends AbstractSection implements OnIni service: Service, unitpipe: UnitvaluePipe, ) { - super("General.production", "up", "#36aed1", translate, service, "Common_Production"); + super("General.production", "up", "var(--ion-color-primary)", translate, service, "Common_Production"); this.unitpipe = unitpipe; } diff --git a/ui/src/app/edge/live/energymonitor/chart/section/storage.component.html b/ui/src/app/edge/live/energymonitor/chart/section/storage.component.html index 266ac898342..50ad664b8d2 100644 --- a/ui/src/app/edge/live/energymonitor/chart/section/storage.component.html +++ b/ui/src/app/edge/live/energymonitor/chart/section/storage.component.html @@ -1,27 +1,28 @@ - - + + + - + + style="fill: var(--ion-item-background)" /> + style="fill: var(--ion-item-background)" /> + font-family="sans-serif" font-size="square.valueText.fontsize" fill="var(--ion-text-color)"> {{valueText}} - diff --git a/ui/src/app/edge/live/energymonitor/chart/section/storage.component.ts b/ui/src/app/edge/live/energymonitor/chart/section/storage.component.ts index e98c6d563cf..8ea180fc516 100644 --- a/ui/src/app/edge/live/energymonitor/chart/section/storage.component.ts +++ b/ui/src/app/edge/live/energymonitor/chart/section/storage.component.ts @@ -37,6 +37,7 @@ import { AbstractSection, EnergyFlow, Ratio, SvgEnergyFlow, SvgSquare, SvgSquare transition("hide => show", animate("0ms ease-out")), ]), ], + standalone: false, }) export class StorageSectionComponent extends AbstractSection implements OnInit, OnDestroy { diff --git a/ui/src/app/edge/live/energymonitor/energymonitor.component.html b/ui/src/app/edge/live/energymonitor/energymonitor.component.html index 779a8c37cc4..5772890908b 100644 --- a/ui/src/app/edge/live/energymonitor/energymonitor.component.html +++ b/ui/src/app/edge/live/energymonitor/energymonitor.component.html @@ -1,6 +1,6 @@ - - + + Edge.Index.Energymonitor.title diff --git a/ui/src/app/edge/live/energymonitor/energymonitor.component.ts b/ui/src/app/edge/live/energymonitor/energymonitor.component.ts index 8e07bbd2b83..00d82018d69 100644 --- a/ui/src/app/edge/live/energymonitor/energymonitor.component.ts +++ b/ui/src/app/edge/live/energymonitor/energymonitor.component.ts @@ -1,10 +1,12 @@ import { Component, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; +import { DataService } from "src/app/shared/components/shared/dataservice"; import { ChannelAddress, Edge, Service, Websocket } from "../../../shared/shared"; @Component({ selector: EnergymonitorComponent.SELECTOR, templateUrl: "./energymonitor.component.html", + standalone: false, }) export class EnergymonitorComponent implements OnInit, OnDestroy { @@ -15,6 +17,7 @@ export class EnergymonitorComponent implements OnInit, OnDestroy { private service: Service, private websocket: Websocket, private route: ActivatedRoute, + private dataService: DataService, ) { } ngOnInit() { @@ -25,7 +28,7 @@ export class EnergymonitorComponent implements OnInit, OnDestroy { ? [new ChannelAddress("_sum", "EssMinDischargePower"), new ChannelAddress("_sum", "EssMaxDischargePower")] : [new ChannelAddress("_sum", "EssMaxApparentPower")]; - edge.subscribeChannels(this.websocket, EnergymonitorComponent.SELECTOR, [ + this.dataService.getValues([ // Ess new ChannelAddress("_sum", "EssSoc"), new ChannelAddress("_sum", "EssActivePower"), ...essMinMaxChannels, @@ -35,7 +38,7 @@ export class EnergymonitorComponent implements OnInit, OnDestroy { new ChannelAddress("_sum", "ProductionActivePower"), new ChannelAddress("_sum", "ProductionDcActualPower"), new ChannelAddress("_sum", "ProductionAcActivePower"), new ChannelAddress("_sum", "ProductionMaxActivePower"), // Consumption new ChannelAddress("_sum", "ConsumptionActivePower"), new ChannelAddress("_sum", "ConsumptionMaxActivePower"), - ]); + ], edge); }); } diff --git a/ui/src/app/edge/live/info/info.component.ts b/ui/src/app/edge/live/info/info.component.ts index 92da7235422..c092e02ddc8 100644 --- a/ui/src/app/edge/live/info/info.component.ts +++ b/ui/src/app/edge/live/info/info.component.ts @@ -3,5 +3,6 @@ import { Component } from "@angular/core"; @Component({ selector: "info", templateUrl: "./info.component.html", + standalone: false, }) export class InfoComponent { } diff --git a/ui/src/app/edge/live/live.component.html b/ui/src/app/edge/live/live.component.html index 88a68dd2777..9749758d16d 100644 --- a/ui/src/app/edge/live/live.component.html +++ b/ui/src/app/edge/live/live.component.html @@ -1,10 +1,8 @@ -
    - + - - + @@ -103,7 +101,8 @@ - + + @@ -160,8 +159,9 @@ - + - + + \ No newline at end of file diff --git a/ui/src/app/edge/live/live.component.ts b/ui/src/app/edge/live/live.component.ts index 335b7d1fa62..5d8ca28f8f5 100644 --- a/ui/src/app/edge/live/live.component.ts +++ b/ui/src/app/edge/live/live.component.ts @@ -4,18 +4,23 @@ import { RefresherCustomEvent } from "@ionic/angular"; import { Subject } from "rxjs"; import { DataService } from "src/app/shared/components/shared/dataservice"; import { Edge, EdgeConfig, EdgePermission, Service, Utils, Websocket, Widgets } from "src/app/shared/shared"; +import { DateTimeUtils } from "src/app/shared/utils/datetime/datetime-utils"; @Component({ selector: "live", templateUrl: "./live.component.html", + standalone: false, }) export class LiveComponent implements OnInit, OnDestroy { - public edge: Edge | null = null; - public config: EdgeConfig | null = null; - public widgets: Widgets | null = null; + protected edge: Edge | null = null; + protected config: EdgeConfig | null = null; + protected widgets: Widgets | null = null; protected isModbusTcpWidgetAllowed: boolean = false; + protected showRefreshDragDown: boolean = false; + private stopOnDestroy: Subject = new Subject(); + private interval: ReturnType | undefined; constructor( private route: ActivatedRoute, @@ -34,12 +39,31 @@ export class LiveComponent implements OnInit, OnDestroy { this.config = config; this.widgets = config.widgets; }); + this.checkIfRefreshNeeded(); } public ngOnDestroy() { + clearInterval(this.interval); this.stopOnDestroy.next(); this.stopOnDestroy.complete(); } protected handleRefresh: (ev: RefresherCustomEvent) => void = (ev: RefresherCustomEvent) => this.dataService.refresh(ev); + + protected checkIfRefreshNeeded() { + this.interval = setInterval(async () => { + + if (this.edge?.isOnline === false) { + this.showRefreshDragDown = false; + return; + } + + const lastUpdate: Date | null = this.dataService.lastUpdated(); + if (lastUpdate == null) { + this.showRefreshDragDown = true; + return; + } + this.showRefreshDragDown = DateTimeUtils.isDifferenceInSecondsGreaterThan(20, new Date(), lastUpdate); + }, 5000); + } } diff --git a/ui/src/app/edge/live/live.module.ts b/ui/src/app/edge/live/live.module.ts index 944e2240b03..e8da5f6ff81 100644 --- a/ui/src/app/edge/live/live.module.ts +++ b/ui/src/app/edge/live/live.module.ts @@ -1,15 +1,24 @@ import { NgModule } from "@angular/core"; import { BrowserModule } from "@angular/platform-browser"; import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; +import { EdgeOfflineModule } from "src/app/shared/components/edge/offline/offline.module"; +import { PullToRefreshComponent } from "src/app/shared/components/pull-to-refresh/pull-to-refresh"; import { SharedModule } from "./../../shared/shared.module"; +import { Common_Autarchy } from "./common/autarchy/Common_Autarchy"; +import { Common_Consumption } from "./common/consumption/Common_Consumption"; +import { Common_Grid } from "./common/grid/Common_Grid"; +import { Common_Production } from "./common/production/Common_Production"; +import { Common_Selfconsumption } from "./common/selfconsumption/Common_Selfconsumption"; +import { StorageModalComponent } from "./common/storage/modal/modal.component"; +import { StorageComponent } from "./common/storage/storage.component"; import { Controller_ChannelthresholdComponent } from "./Controller/Channelthreshold/Channelthreshold"; import { Controller_ChpSocComponent } from "./Controller/ChpSoc/ChpSoc"; import { Controller_ChpSocModalComponent } from "./Controller/ChpSoc/modal/modal.component"; import { Controller_Ess_FixActivePower } from "./Controller/Ess/FixActivePower/Ess_FixActivePower"; import { Controller_Ess_GridOptimizedCharge } from "./Controller/Ess/GridOptimizedCharge/Ess_GridOptimizedCharge"; import { Controller_Ess_TimeOfUseTariff } from "./Controller/Ess/TimeOfUseTariff/Ess_TimeOfUseTariff"; -import { Controller_Evcs } from "./Controller/Evcs/Evcs"; import { AdministrationComponent } from "./Controller/Evcs/administration/administration.component"; +import { Controller_Evcs } from "./Controller/Evcs/Evcs"; import { Controller_Io_ChannelSingleThresholdComponent } from "./Controller/Io/ChannelSingleThreshold/Io_ChannelSingleThreshold"; import { Controller_Io_ChannelSingleThresholdModalComponent } from "./Controller/Io/ChannelSingleThreshold/modal/modal.component"; import { Controller_Io_FixDigitalOutputComponent } from "./Controller/Io/FixDigitalOutput/Io_FixDigitalOutput"; @@ -20,48 +29,40 @@ import { Controller_Io_HeatpumpModalComponent } from "./Controller/Io/Heatpump/m import { Controller_Api_ModbusTcp } from "./Controller/ModbusTcpApi/modbusTcpApi.module"; import { Controller_Asymmetric_PeakShavingComponent } from "./Controller/PeakShaving/Asymmetric/Asymmetric"; import { Controller_Asymmetric_PeakShavingModalComponent } from "./Controller/PeakShaving/Asymmetric/modal/modal.component"; -import { Controller_Symmetric_PeakShavingComponent } from "./Controller/PeakShaving/Symmetric/Symmetric"; import { Controller_Symmetric_PeakShavingModalComponent } from "./Controller/PeakShaving/Symmetric/modal/modal.component"; -import { Controller_Symmetric_TimeSlot_PeakShavingComponent } from "./Controller/PeakShaving/Symmetric_TimeSlot/Symmetric_TimeSlot"; +import { Controller_Symmetric_PeakShavingComponent } from "./Controller/PeakShaving/Symmetric/Symmetric"; import { Controller_Symmetric_TimeSlot_PeakShavingModalComponent } from "./Controller/PeakShaving/Symmetric_TimeSlot/modal/modal.component"; -import { Io_Api_DigitalInputComponent } from "./Io/Api_DigitalInput/Io_Api_DigitalInput"; -import { Io_Api_DigitalInput_ModalComponent } from "./Io/Api_DigitalInput/modal/modal.component"; -import { Evcs_Api_ClusterComponent } from "./Multiple/Evcs_Api_Cluster/Evcs_Api_Cluster"; -import { EvcsChartComponent } from "./Multiple/Evcs_Api_Cluster/modal/evcs-chart/evcs.chart"; -import { Evcs_Api_ClusterModalComponent } from "./Multiple/Evcs_Api_Cluster/modal/evcsCluster-modal.page"; -import { Common_Autarchy } from "./common/autarchy/Common_Autarchy"; -import { Common_Consumption } from "./common/consumption/Common_Consumption"; -import { Common_Grid } from "./common/grid/Common_Grid"; -import { Common_Production } from "./common/production/Common_Production"; -import { Common_Selfconsumption } from "./common/selfconsumption/Common_Selfconsumption"; -import { StorageModalComponent } from "./common/storage/modal/modal.component"; -import { StorageComponent } from "./common/storage/storage.component"; +import { Controller_Symmetric_TimeSlot_PeakShavingComponent } from "./Controller/PeakShaving/Symmetric_TimeSlot/Symmetric_TimeSlot"; import { DelayedSellToGridComponent } from "./delayedselltogrid/delayedselltogrid.component"; import { DelayedSellToGridModalComponent } from "./delayedselltogrid/modal/modal.component"; import { EnergymonitorModule } from "./energymonitor/energymonitor.module"; import { InfoComponent } from "./info/info.component"; +import { Io_Api_DigitalInputComponent } from "./Io/Api_DigitalInput/Io_Api_DigitalInput"; +import { Io_Api_DigitalInput_ModalComponent } from "./Io/Api_DigitalInput/modal/modal.component"; import { LiveComponent } from "./live.component"; -import { OfflineComponent } from "./offline/offline.component"; +import { Evcs_Api_ClusterComponent } from "./Multiple/Evcs_Api_Cluster/Evcs_Api_Cluster"; +import { EvcsChartComponent } from "./Multiple/Evcs_Api_Cluster/modal/evcs-chart/evcs.chart"; +import { Evcs_Api_ClusterModalComponent } from "./Multiple/Evcs_Api_Cluster/modal/evcsCluster-modal.page"; @NgModule({ imports: [ BrowserAnimationsModule, BrowserModule, - // Common Common_Autarchy, - Common_Production, - Common_Selfconsumption, Common_Consumption, Common_Grid, - // Controller + Common_Production, + Common_Selfconsumption, + Controller_Api_ModbusTcp, Controller_Ess_FixActivePower, Controller_Ess_GridOptimizedCharge, + Controller_Ess_TimeOfUseTariff, + Controller_Evcs, Controller_Io_HeatingElement, - Controller_Api_ModbusTcp, + EdgeOfflineModule, EnergymonitorModule, SharedModule, - Controller_Evcs, - Controller_Ess_TimeOfUseTariff, + PullToRefreshComponent, ], declarations: [ AdministrationComponent, @@ -90,7 +91,6 @@ import { OfflineComponent } from "./offline/offline.component"; Io_Api_DigitalInput_ModalComponent, Io_Api_DigitalInputComponent, LiveComponent, - OfflineComponent, StorageComponent, StorageModalComponent, ], diff --git a/ui/src/app/edge/live/livedataservice.ts b/ui/src/app/edge/live/livedataservice.ts index e4eda4ab642..05ce9562a68 100644 --- a/ui/src/app/edge/live/livedataservice.ts +++ b/ui/src/app/edge/live/livedataservice.ts @@ -3,6 +3,7 @@ import { Directive, Inject, OnDestroy } from "@angular/core"; import { RefresherCustomEvent } from "@ionic/angular"; import { takeUntil } from "rxjs/operators"; import { v4 as uuidv4 } from "uuid"; +import { AppService } from "src/app/app.service"; import { DataService } from "../../shared/components/shared/dataservice"; import { ChannelAddress, Edge, Service, Websocket } from "../../shared/shared"; @@ -17,6 +18,11 @@ export class LiveDataService extends DataService implements OnDestroy { @Inject(Service) protected service: Service, ) { super(); + + this.service.getCurrentEdge().then((edge) => { + edge.currentData.pipe(takeUntil(this.stopOnDestroy)) + .subscribe(() => this.lastUpdated.set(new Date())); + }); } public getValues(channelAddresses: ChannelAddress[], edge: Edge, componentId: string) { @@ -40,6 +46,7 @@ export class LiveDataService extends DataService implements OnDestroy { } this.currentValue.next({ allComponents: allComponents }); + this.lastUpdated.set(new Date()); }); } @@ -50,15 +57,11 @@ export class LiveDataService extends DataService implements OnDestroy { } public unsubscribeFromChannels(channels: ChannelAddress[]) { + this.lastUpdated.set(null); this.edge.unsubscribeFromChannels(this.websocket, channels); } public override refresh(ev: RefresherCustomEvent) { - this.currentValue.next({ allComponents: {} }); - this.edge.unsubscribeFromChannels(this.websocket, this.subscribedChannelAddresses); - setTimeout(() => { - this.edge.subscribeChannels(this.websocket, "", this.subscribedChannelAddresses); - ev.target.complete(); - }, 2000); + AppService.handleRefresh(); } } diff --git a/ui/src/app/edge/live/offline/offline.component.ts b/ui/src/app/edge/live/offline/offline.component.ts deleted file mode 100644 index 5af0358d185..00000000000 --- a/ui/src/app/edge/live/offline/offline.component.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Component, OnInit } from "@angular/core"; -import { Edge, Service, Utils } from "src/app/shared/shared"; -import { DateUtils } from "src/app/shared/utils/date/dateutils"; - -// TODO add translations when refactoring offline.component.html -@Component({ - selector: "offline", - templateUrl: "./offline.component.html", -}) -export class OfflineComponent implements OnInit { - - protected edge: Edge | null = null; - protected timeSinceOffline: string | null = null; - - constructor( - public service: Service, - ) { } - - private static formatSecondsToFullMinutes(date: string): null | string { - - const _date: Date | null = DateUtils.stringToDate(date); - if (!_date) { - return null; - } - - const milliSeconds: number = _date.getTime(); - const _diff: number | null = Utils.subtractSafely(new Date().getTime(), milliSeconds); - - if (_diff === null) { - return null; - } - - if (_diff > 2 * 24 * 60 * 60 * 1000) { - return Utils.floorSafely(Utils.divideSafely(_diff, 24 * 60 * 60 * 1000)) + " Tagen"; - } - - if (_diff > 2 * 60 * 60 * 1000) { - return Utils.floorSafely(Utils.divideSafely(_diff, 60 * 60 * 1000)) + " Stunden"; - } - - const minutes: number | null = Utils.floorSafely(Utils.divideSafely(_diff, 60 * 1000)); - return Utils.floorSafely(Utils.divideSafely(_diff, 60 * 1000)) + " " + (minutes === 1 ? "Minute" : "Minuten"); - } - - ngOnInit() { - this.service.getCurrentEdge().then(edge => { - this.edge = edge; - this.timeSinceOffline = OfflineComponent.formatSecondsToFullMinutes(edge.lastmessage.toString()); - }); - } -} diff --git a/ui/src/app/edge/settings/alerting/alerting.component.html b/ui/src/app/edge/settings/alerting/alerting.component.html index 0177269976c..434cce275e7 100644 --- a/ui/src/app/edge/settings/alerting/alerting.component.html +++ b/ui/src/app/edge/settings/alerting/alerting.component.html @@ -6,7 +6,7 @@ vertical-align: middle; } -
    + @@ -20,7 +20,7 @@ -
    {{user.name}} + {{user.userLogin}} @@ -57,7 +57,7 @@ + formControlName="offlineEdgeDelay" [label]="'Edge.Config.ALERTING.OFFLINE' | translate"> {{option.label}} diff --git a/ui/src/app/edge/settings/alerting/alerting.component.ts b/ui/src/app/edge/settings/alerting/alerting.component.ts index 865420ec5bc..a4e6f4c2c64 100644 --- a/ui/src/app/edge/settings/alerting/alerting.component.ts +++ b/ui/src/app/edge/settings/alerting/alerting.component.ts @@ -22,6 +22,7 @@ type DetailedAlertingSetting = AlertingSetting & { isOfflineActive: boolean, isF @Component({ selector: AlertingComponent.SELECTOR, templateUrl: "./alerting.component.html", + standalone: false, }) export class AlertingComponent implements OnInit { diff --git a/ui/src/app/edge/settings/app/formly/formly-text.ts b/ui/src/app/edge/settings/app/formly/formly-text.ts index d8fe8a1c681..f1f2532e0dc 100644 --- a/ui/src/app/edge/settings/app/formly/formly-text.ts +++ b/ui/src/app/edge/settings/app/formly/formly-text.ts @@ -10,6 +10,7 @@ import { FieldType, FieldTypeConfig } from "@ngx-formly/core"; `, encapsulation: ViewEncapsulation.None, + standalone: false, }) export class FormlyTextComponent extends FieldType { diff --git a/ui/src/app/edge/settings/app/formly/input-with-unit.ts b/ui/src/app/edge/settings/app/formly/input-with-unit.ts index 12852b9c35a..8e50c740538 100644 --- a/ui/src/app/edge/settings/app/formly/input-with-unit.ts +++ b/ui/src/app/edge/settings/app/formly/input-with-unit.ts @@ -15,5 +15,6 @@ import { FieldWrapper } from "@ngx-formly/core"; `, + standalone: false, }) export class FormlyInputWithUnitComponent extends FieldWrapper { } diff --git a/ui/src/app/edge/settings/app/formly/option-group-picker/formly-option-group-picker.component.ts b/ui/src/app/edge/settings/app/formly/option-group-picker/formly-option-group-picker.component.ts index 0e0ec8e1f60..dee315e6bcf 100644 --- a/ui/src/app/edge/settings/app/formly/option-group-picker/formly-option-group-picker.component.ts +++ b/ui/src/app/edge/settings/app/formly/option-group-picker/formly-option-group-picker.component.ts @@ -6,6 +6,7 @@ import { Option, OptionGroup, OptionGroupConfig, getTitleFromOptionConfig } from @Component({ selector: "formly-option-group-picker", templateUrl: "./formly-option-group-picker.component.html", + standalone: false, }) export class FormlyOptionGroupPickerComponent extends FieldType implements OnInit { diff --git a/ui/src/app/edge/settings/app/formly/reorder-select/formly-reorder-array.component.ts b/ui/src/app/edge/settings/app/formly/reorder-select/formly-reorder-array.component.ts index 808171892bb..69210c13941 100644 --- a/ui/src/app/edge/settings/app/formly/reorder-select/formly-reorder-array.component.ts +++ b/ui/src/app/edge/settings/app/formly/reorder-select/formly-reorder-array.component.ts @@ -6,6 +6,7 @@ import { FieldType, FieldTypeConfig, FormlyFieldConfig, FormlyFieldProps } from @Component({ selector: "reorder-array", templateUrl: "./formly-reorder-array.component.html", + standalone: false, }) export class FormlyReorderArrayComponent extends FieldType - {{ app.shortName ?? app.name }} @@ -61,7 +60,7 @@

    -
    + diff --git a/ui/src/app/edge/settings/app/index.component.ts b/ui/src/app/edge/settings/app/index.component.ts index e0b1966f563..dad25214d8b 100644 --- a/ui/src/app/edge/settings/app/index.component.ts +++ b/ui/src/app/edge/settings/app/index.component.ts @@ -24,6 +24,7 @@ import { canEnterKey } from "./permissions"; @Component({ selector: IndexComponent.SELECTOR, templateUrl: "./index.component.html", + standalone: false, }) export class IndexComponent implements OnInit, OnDestroy { @@ -104,6 +105,7 @@ export class IndexComponent implements OnInit, OnDestroy { } this.installedApps.appCategories = []; this.availableApps.appCategories = []; + this.incompatibleApps.appCategories = []; const sortedApps = []; this.apps.forEach(app => { diff --git a/ui/src/app/edge/settings/app/install.component.html b/ui/src/app/edge/settings/app/install.component.html index 2418d652e3a..9b805f1fd96 100644 --- a/ui/src/app/edge/settings/app/install.component.html +++ b/ui/src/app/edge/settings/app/install.component.html @@ -1,4 +1,3 @@ -
    @@ -8,7 +7,6 @@
    - {{ appName }} diff --git a/ui/src/app/edge/settings/app/install.component.ts b/ui/src/app/edge/settings/app/install.component.ts index d41cb72f035..ae8e69625d4 100644 --- a/ui/src/app/edge/settings/app/install.component.ts +++ b/ui/src/app/edge/settings/app/install.component.ts @@ -21,6 +21,7 @@ import { hasPredefinedKey } from "./permissions"; @Component({ selector: InstallAppComponent.SELECTOR, templateUrl: "./install.component.html", + standalone: false, }) export class InstallAppComponent implements OnInit, OnDestroy { diff --git a/ui/src/app/edge/settings/app/keypopup/modal.component.ts b/ui/src/app/edge/settings/app/keypopup/modal.component.ts index be5889a230d..d9f51e8f9f3 100644 --- a/ui/src/app/edge/settings/app/keypopup/modal.component.ts +++ b/ui/src/app/edge/settings/app/keypopup/modal.component.ts @@ -19,6 +19,7 @@ import { Key } from "./key"; @Component({ selector: KeyModalComponent.SELECTOR, templateUrl: "./modal.component.html", + standalone: false, }) export class KeyModalComponent implements OnInit { diff --git a/ui/src/app/edge/settings/app/single.component.html b/ui/src/app/edge/settings/app/single.component.html index 465413bd056..3896e3aa39c 100644 --- a/ui/src/app/edge/settings/app/single.component.html +++ b/ui/src/app/edge/settings/app/single.component.html @@ -1,4 +1,3 @@ -
    diff --git a/ui/src/app/edge/settings/app/single.component.ts b/ui/src/app/edge/settings/app/single.component.ts index 0051a69d016..6b937248e98 100644 --- a/ui/src/app/edge/settings/app/single.component.ts +++ b/ui/src/app/edge/settings/app/single.component.ts @@ -23,6 +23,7 @@ import { canEnterKey, hasKeyModel, hasPredefinedKey } from "./permissions"; @Component({ selector: SingleAppComponent.SELECTOR, templateUrl: "./single.component.html", + standalone: false, }) export class SingleAppComponent implements OnInit, OnDestroy { diff --git a/ui/src/app/edge/settings/app/update.component.html b/ui/src/app/edge/settings/app/update.component.html index 039d79b1826..e114a7f8ab4 100644 --- a/ui/src/app/edge/settings/app/update.component.html +++ b/ui/src/app/edge/settings/app/update.component.html @@ -1,4 +1,3 @@ -
    @@ -6,7 +5,6 @@ - {{ appName }} @@ -17,7 +15,7 @@ [disabled]="(!((!varinstance.form.pristine) && (varinstance.form.valid))) || varinstance.isDeleting || varinstance.isUpdating" translate> Edge.Config.App.updateApp - Edge.Config.App.deleteApp diff --git a/ui/src/app/edge/settings/app/update.component.ts b/ui/src/app/edge/settings/app/update.component.ts index 94c049bbcfb..aab5ea1eecd 100644 --- a/ui/src/app/edge/settings/app/update.component.ts +++ b/ui/src/app/edge/settings/app/update.component.ts @@ -2,6 +2,7 @@ import { Component, OnInit } from "@angular/core"; import { FormGroup } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; +import { AlertController } from "@ionic/angular"; import { FormlyFieldConfig } from "@ngx-formly/core"; import { TranslateService } from "@ngx-translate/core"; import { ComponentJsonApiRequest } from "src/app/shared/jsonrpc/request/componentJsonApiRequest"; @@ -24,6 +25,7 @@ interface MyInstance { @Component({ selector: UpdateAppComponent.SELECTOR, templateUrl: "./update.component.html", + standalone: false, }) export class UpdateAppComponent implements OnInit { @@ -42,6 +44,7 @@ export class UpdateAppComponent implements OnInit { private service: Service, private router: Router, private translate: TranslateService, + private alertCtrl: AlertController, ) { } @@ -126,6 +129,25 @@ export class UpdateAppComponent implements OnInit { }); } + protected async submitDelete(instance: MyInstance) { + const translate = this.translate; + + const alert = this.alertCtrl.create({ + subHeader: translate.instant("Edge.Config.App.DELETE_CONFIRM_HEADLINE"), + message: translate.instant("Edge.Config.App.DELETE_CONFIRM_DESCRIPTION"), + buttons: [{ + text: translate.instant("General.cancel"), + role: "cancel", + }, + { + text: translate.instant("Edge.Config.App.DELETE_CONFIRM"), + handler: () => this.delete(instance), + }], + cssClass: "alertController", + }); + (await alert).present(); + } + protected delete(instance: MyInstance) { this.service.startSpinnerTransparentBackground(instance.instanceId); instance.isDeleting = true; diff --git a/ui/src/app/edge/settings/channels/channels.component.html b/ui/src/app/edge/settings/channels/channels.component.html index ecef95c38f1..65acae2546f 100644 --- a/ui/src/app/edge/settings/channels/channels.component.html +++ b/ui/src/app/edge/settings/channels/channels.component.html @@ -1,10 +1,9 @@ -
    - + @@ -14,17 +13,20 @@ - - + {{ entry.value.id }} ({{ entry.value.alias }}) - + + [disabled]="!selectedComponentId?.value" [label]="'CHANNELS.CHANNEL'| translate" + justify="space-between">
    - CHANNELS.MORE_CHANNELS - {{ entry.key }} diff --git a/ui/src/app/edge/settings/channels/channels.component.ts b/ui/src/app/edge/settings/channels/channels.component.ts index 36ea1f7911c..49447062d92 100644 --- a/ui/src/app/edge/settings/channels/channels.component.ts +++ b/ui/src/app/edge/settings/channels/channels.component.ts @@ -3,17 +3,18 @@ import { Component } from "@angular/core"; import { ActivatedRoute, Router } from "@angular/router"; import { TranslateService } from "@ngx-translate/core"; import { PersistencePriority } from "src/app/shared/components/edge/edgeconfig"; -import { SetChannelValueRequest } from "src/app/shared/jsonrpc/request/setChannelValueRequest"; -import { environment } from "src/environments"; import { ComponentJsonApiRequest } from "src/app/shared/jsonrpc/request/componentJsonApiRequest"; import { GetChannelsOfComponentRequest } from "src/app/shared/jsonrpc/request/getChannelsOfComponentRequest"; +import { SetChannelValueRequest } from "src/app/shared/jsonrpc/request/setChannelValueRequest"; import { Channel, GetChannelsOfComponentResponse } from "src/app/shared/jsonrpc/response/getChannelsOfComponentResponse"; +import { environment } from "src/environments"; import { ChannelAddress, Edge, EdgeConfig, EdgePermission, Service, Websocket } from "../../../shared/shared"; @Component({ selector: ChannelsComponent.SELECTOR, templateUrl: "./channels.component.html", + standalone: false, }) export class ChannelsComponent { diff --git a/ui/src/app/edge/settings/component/install/index.component.html b/ui/src/app/edge/settings/component/install/index.component.html index c918b508485..7724f09a979 100644 --- a/ui/src/app/edge/settings/component/install/index.component.html +++ b/ui/src/app/edge/settings/component/install/index.component.html @@ -1,6 +1,6 @@ -
    - + @@ -18,7 +18,7 @@

    {{ item.name }}

    - {{ item.description }} + {{ item.description }}
    diff --git a/ui/src/app/edge/settings/component/install/index.component.ts b/ui/src/app/edge/settings/component/install/index.component.ts index a31df0b6bf0..f77ee049ec9 100644 --- a/ui/src/app/edge/settings/component/install/index.component.ts +++ b/ui/src/app/edge/settings/component/install/index.component.ts @@ -13,6 +13,7 @@ interface MyCategorizedFactories extends CategorizedFactories { @Component({ selector: IndexComponent.SELECTOR, templateUrl: "./index.component.html", + standalone: false, }) export class IndexComponent implements OnInit { diff --git a/ui/src/app/edge/settings/component/install/install.component.html b/ui/src/app/edge/settings/component/install/install.component.html index ae3916b1def..1c7bd5689a3 100644 --- a/ui/src/app/edge/settings/component/install/install.component.html +++ b/ui/src/app/edge/settings/component/install/install.component.html @@ -1,4 +1,3 @@ -
    @@ -7,8 +6,10 @@ - {{ factory.name }} - {{ factory.description }} + {{ factory.name }} + + + {{ factory.description }} diff --git a/ui/src/app/edge/settings/component/install/install.component.ts b/ui/src/app/edge/settings/component/install/install.component.ts index 965ac7d874e..7c658a688b8 100644 --- a/ui/src/app/edge/settings/component/install/install.component.ts +++ b/ui/src/app/edge/settings/component/install/install.component.ts @@ -8,6 +8,7 @@ import { Edge, EdgeConfig, Service, Utils, Websocket } from "../../../../shared/ @Component({ selector: ComponentInstallComponent.SELECTOR, templateUrl: "./install.component.html", + standalone: false, }) export class ComponentInstallComponent implements OnInit { diff --git a/ui/src/app/edge/settings/component/update/index.component.html b/ui/src/app/edge/settings/component/update/index.component.html index 30373db23e2..76f55ccd6e6 100644 --- a/ui/src/app/edge/settings/component/update/index.component.html +++ b/ui/src/app/edge/settings/component/update/index.component.html @@ -1,6 +1,6 @@ -
    - + @@ -21,7 +21,7 @@

    {{ item.alias }}

    -

    {{ factory.name }} ({{ factory.description }})

    +

    {{ factory.name }} ({{ factory.description }})

    diff --git a/ui/src/app/edge/settings/component/update/index.component.ts b/ui/src/app/edge/settings/component/update/index.component.ts index d35810ed057..757ce52469a 100644 --- a/ui/src/app/edge/settings/component/update/index.component.ts +++ b/ui/src/app/edge/settings/component/update/index.component.ts @@ -11,6 +11,7 @@ interface MyCategorizedComponents extends CategorizedComponents { @Component({ selector: IndexComponent.SELECTOR, templateUrl: "./index.component.html", + standalone: false, }) export class IndexComponent implements OnInit { diff --git a/ui/src/app/edge/settings/component/update/update.component.html b/ui/src/app/edge/settings/component/update/update.component.html index aeaee6f0114..acae7bcefff 100644 --- a/ui/src/app/edge/settings/component/update/update.component.html +++ b/ui/src/app/edge/settings/component/update/update.component.html @@ -1,15 +1,16 @@ -
    - - {{ factory.name }} - {{ factory.description }} + {{ factory.name }} + + {{ + factory.description + }} diff --git a/ui/src/app/edge/settings/component/update/update.component.ts b/ui/src/app/edge/settings/component/update/update.component.ts index 3c882e05473..71c4b29e0ab 100644 --- a/ui/src/app/edge/settings/component/update/update.component.ts +++ b/ui/src/app/edge/settings/component/update/update.component.ts @@ -8,6 +8,7 @@ import { Edge, EdgeConfig, Service, Utils, Websocket } from "../../../../shared/ @Component({ selector: ComponentUpdateComponent.SELECTOR, templateUrl: "./update.component.html", + standalone: false, }) export class ComponentUpdateComponent implements OnInit { diff --git a/ui/src/app/edge/settings/energy-journey/shared/components/constant-line.scss b/ui/src/app/edge/settings/energy-journey/shared/components/constant-line.scss new file mode 100644 index 00000000000..7abe6d79a1e --- /dev/null +++ b/ui/src/app/edge/settings/energy-journey/shared/components/constant-line.scss @@ -0,0 +1,18 @@ +@import "variables"; +@import "src/global.scss"; + + +:host { + + div { + color: var(--ion-color-text); + } + + div.constant { + background-color: var(--ion-color-medium-tint); + } + + div.editable { + background-color: var(--ion-color-primary-tint); + } +} diff --git a/ui/src/app/edge/settings/jsonrpctest/jsonrpctest.html b/ui/src/app/edge/settings/jsonrpctest/jsonrpctest.html index 0468ec0c4d5..9359068039c 100644 --- a/ui/src/app/edge/settings/jsonrpctest/jsonrpctest.html +++ b/ui/src/app/edge/settings/jsonrpctest/jsonrpctest.html @@ -31,7 +31,6 @@ -
    diff --git a/ui/src/app/edge/settings/jsonrpctest/jsonrpctest.ts b/ui/src/app/edge/settings/jsonrpctest/jsonrpctest.ts index 8bab188a03f..b11f7b497fb 100644 --- a/ui/src/app/edge/settings/jsonrpctest/jsonrpctest.ts +++ b/ui/src/app/edge/settings/jsonrpctest/jsonrpctest.ts @@ -9,6 +9,7 @@ import { environment } from "src/environments"; @Component({ selector: JsonrpcTestComponent.SELECTOR, templateUrl: "./jsonrpctest.html", + standalone: false, }) export class JsonrpcTestComponent implements OnInit { diff --git a/ui/src/app/edge/settings/network/network.component.html b/ui/src/app/edge/settings/network/network.component.html index aaae39ad4ae..1526c04e99c 100644 --- a/ui/src/app/edge/settings/network/network.component.html +++ b/ui/src/app/edge/settings/network/network.component.html @@ -1,4 +1,3 @@ -
    diff --git a/ui/src/app/edge/settings/network/network.component.ts b/ui/src/app/edge/settings/network/network.component.ts index 99b9af92671..fdba7efb1be 100644 --- a/ui/src/app/edge/settings/network/network.component.ts +++ b/ui/src/app/edge/settings/network/network.component.ts @@ -13,6 +13,7 @@ import { InterfaceForm, InterfaceModel, IpAddress, NetworkConfig, NetworkInterfa @Component({ selector: NetworkComponent.SELECTOR, templateUrl: "./network.component.html", + standalone: false, }) export class NetworkComponent implements OnInit { @@ -75,7 +76,7 @@ export class NetworkComponent implements OnInit { this.handleNetworkConfigResponse(response); } } catch (reason: any) { - this.service.toast(this.translate.instant("Edge.Network.errorReading") + reason?.error?.message ?? "Unknown error", "danger"); + this.service.toast(this.translate.instant("Edge.Network.errorReading") + reason?.error?.message, "danger"); } } @@ -187,7 +188,7 @@ export class NetworkComponent implements OnInit { })); this.service.toast(this.translate.instant("Edge.Network.successUpdate") + `[${interfaceName}].`, "success"); } catch (reason: any) { - this.service.toast(this.translate.instant("Edge.Network.errorUpdating") + `[${interfaceName}].` + reason?.error?.message ?? "Unknown error", "danger"); + this.service.toast(this.translate.instant("Edge.Network.errorUpdating") + `[${interfaceName}].` + reason?.error?.message, "danger"); } } diff --git a/ui/src/app/edge/settings/powerassistant/powerassistant.html b/ui/src/app/edge/settings/powerassistant/powerassistant.html index 8fdb3666116..e428d16d831 100644 --- a/ui/src/app/edge/settings/powerassistant/powerassistant.html +++ b/ui/src/app/edge/settings/powerassistant/powerassistant.html @@ -7,7 +7,7 @@
    -
    + diff --git a/ui/src/app/edge/settings/powerassistant/powerassistant.ts b/ui/src/app/edge/settings/powerassistant/powerassistant.ts index 4dcc5708141..cbbc6bec221 100644 --- a/ui/src/app/edge/settings/powerassistant/powerassistant.ts +++ b/ui/src/app/edge/settings/powerassistant/powerassistant.ts @@ -3,6 +3,7 @@ import { formatNumber } from "@angular/common"; import { Component } from "@angular/core"; import { AbstractFlatWidget } from "src/app/shared/components/flat/abstract-flat-widget"; import { DataService } from "src/app/shared/components/shared/dataservice"; +import { Language } from "src/app/shared/type/language"; import { ChannelAddress, CurrentData, EdgeConfig, Utils } from "../../../shared/shared"; import { LiveDataService } from "../../live/livedataservice"; @@ -34,6 +35,7 @@ type Entry = { useClass: LiveDataService, provide: DataService, }], + standalone: false, }) export class PowerAssistantComponent extends AbstractFlatWidget { @@ -221,10 +223,11 @@ export class PowerAssistantComponent extends AbstractFlatWidget { export namespace Converter { export function unit(unit: string): (value: any) => string { return function (value: any): string { + const locale: string = (Language.getByKey(localStorage.LANGUAGE) ?? Language.DEFAULT).i18nLocaleKey; if (value == null) { return "-"; } else if (value >= 0) { - return formatNumber(value, "de", "1.0-0") + " " + unit; + return formatNumber(value, locale, "1.0-0") + " " + unit; } }; } diff --git a/ui/src/app/edge/settings/profile/aliasupdate.component.html b/ui/src/app/edge/settings/profile/aliasupdate.component.html index 1e63df550f6..4ccd98240d3 100644 --- a/ui/src/app/edge/settings/profile/aliasupdate.component.html +++ b/ui/src/app/edge/settings/profile/aliasupdate.component.html @@ -1,4 +1,3 @@ -
    @@ -7,15 +6,15 @@ - {{ factory.name }} + {{ factory.name }} {{ factory.description }} - General.currentName - + diff --git a/ui/src/app/edge/settings/profile/aliasupdate.component.ts b/ui/src/app/edge/settings/profile/aliasupdate.component.ts index d0b91e080ca..ccfa09afb50 100644 --- a/ui/src/app/edge/settings/profile/aliasupdate.component.ts +++ b/ui/src/app/edge/settings/profile/aliasupdate.component.ts @@ -8,6 +8,7 @@ import { Edge, EdgeConfig, Service, Websocket } from "src/app/shared/shared"; @Component({ selector: "aliasupdate", templateUrl: "./aliasupdate.component.html", + standalone: false, }) export class AliasUpdateComponent implements OnInit { diff --git a/ui/src/app/edge/settings/profile/profile.component.html b/ui/src/app/edge/settings/profile/profile.component.html index a5adddc50c3..9cde0096885 100644 --- a/ui/src/app/edge/settings/profile/profile.component.html +++ b/ui/src/app/edge/settings/profile/profile.component.html @@ -1,4 +1,3 @@ -
    @@ -25,8 +24,9 @@ {{ edge.producttype }} - {{ environment.edgeShortName }} Version - {{ environment.edgeShortName }} + Version + {{ edge.version | version:edge.role }} @@ -59,7 +59,7 @@ - + {{ category.category.title }} diff --git a/ui/src/app/edge/settings/profile/profile.component.ts b/ui/src/app/edge/settings/profile/profile.component.ts index f33d018f26a..cbc086d4715 100644 --- a/ui/src/app/edge/settings/profile/profile.component.ts +++ b/ui/src/app/edge/settings/profile/profile.component.ts @@ -14,6 +14,7 @@ import { GetModbusProtocolExportXlsxRequest } from "./modbusapi/getModbusProtoco @Component({ selector: ProfileComponent.SELECTOR, templateUrl: "./profile.component.html", + standalone: false, }) export class ProfileComponent implements OnInit { diff --git a/ui/src/app/edge/settings/settings.component.html b/ui/src/app/edge/settings/settings.component.html index abfdf9a2a04..92147239be4 100644 --- a/ui/src/app/edge/settings/settings.component.html +++ b/ui/src/app/edge/settings/settings.component.html @@ -1,4 +1,3 @@ -
    @@ -99,7 +98,7 @@ - + Edge.Config.Index.adjustComponents @@ -114,7 +113,7 @@ - + Edge.Config.Index.addComponents @@ -129,7 +128,7 @@ - + Channels @@ -144,46 +143,45 @@ - - Power Assistant + + Edge.Config.Index.systemExecute - + - + - - Jsonrpc test + + Power Assistant - + - + - - Edge.Config.Index.systemExecute + + Jsonrpc test - + - diff --git a/ui/src/app/edge/settings/settings.component.ts b/ui/src/app/edge/settings/settings.component.ts index 8c667833490..6c9cacbd4b2 100644 --- a/ui/src/app/edge/settings/settings.component.ts +++ b/ui/src/app/edge/settings/settings.component.ts @@ -8,6 +8,7 @@ import { JsonrpcTestPermission } from "./jsonrpctest/jsonrpctest.permission"; @Component({ selector: "settings", templateUrl: "./settings.component.html", + standalone: false, }) export class SettingsComponent implements OnInit { diff --git a/ui/src/app/edge/settings/settings.module.ts b/ui/src/app/edge/settings/settings.module.ts index 3e12f05e109..f47ec2103b6 100644 --- a/ui/src/app/edge/settings/settings.module.ts +++ b/ui/src/app/edge/settings/settings.module.ts @@ -1,6 +1,6 @@ import { NgModule } from "@angular/core"; -import { ChangelogModule } from "src/app/changelog/changelog.module"; - +import { ChangelogComponent } from "src/app/changelog/view/component/changelog.component"; +import { ComponentsModule } from "src/app/shared/components/components.module"; import { SharedModule } from "./../../shared/shared.module"; import { AlertingComponent } from "./alerting/alerting.component"; import { AppModule } from "./app/app.module"; @@ -24,8 +24,9 @@ import { SystemExecuteComponent } from "./systemexecute/systemexecute.component" imports: [ AppModule, SharedModule, - ChangelogModule, + ChangelogComponent, PowerAssistantModule, + ComponentsModule, ], declarations: [ AlertingComponent, diff --git a/ui/src/app/edge/settings/system/executesystemupdate.component.html b/ui/src/app/edge/settings/system/executesystemupdate.component.html deleted file mode 100644 index 53281fa214c..00000000000 --- a/ui/src/app/edge/settings/system/executesystemupdate.component.html +++ /dev/null @@ -1,121 +0,0 @@ - - - - - - - - Das {{environment.edgeShortName}} wird neugestartet...
    - (Dies kann bis zu 10 min dauern) -
    - - - - -
    - - - Das {{environment.edgeShortName}} ist offline. - - -
    -
    - - - - - - - - - - Installierte Version: - {{ state.version }} - - - - - Das System ist auf dem - aktuellsten - Softwarestand - - - - - - - - Installierte Version: - {{ state.currentVersion }} - - - Neueste Version: - {{ state.latestVersion }} - - - - Neueste Version - installieren - - - - - - - - - - - - - - - - - Suche nach Updates... - - - System kann nicht geupdated werden! - - - - - - - Update wird - ausgeführt...
    - (Dies kann bis zu 10 min dauern)
    - - Update abgeschlossen - -
    -
    - - - - - - -
    -
    -
    -
    -
    - - - -
    -
    -
    diff --git a/ui/src/app/edge/settings/system/executesystemupdate.component.ts b/ui/src/app/edge/settings/system/executesystemupdate.component.ts deleted file mode 100644 index badb135f909..00000000000 --- a/ui/src/app/edge/settings/system/executesystemupdate.component.ts +++ /dev/null @@ -1,64 +0,0 @@ -// @ts-strict-ignore -import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core"; -import { Edge, Service, Websocket } from "src/app/shared/shared"; -import { environment } from "src/environments"; -import { ExecuteSystemUpdate } from "./executeSystemUpdate"; -import { SystemUpdateState } from "./getSystemUpdateStateResponse"; - -@Component({ - selector: ExecuteSystemUpdateComponent.SELECTOR, - templateUrl: "./executesystemupdate.component.html", -}) -export class ExecuteSystemUpdateComponent implements OnInit, OnDestroy { - - private static readonly SELECTOR = "executesystemupdate"; - @Input() public executeUpdateInstantly: boolean = false; - @Input() public edge: Edge; - @Output() public stateChanged: EventEmitter = new EventEmitter(); - public readonly spinnerId: string = ExecuteSystemUpdateComponent.SELECTOR; - - public readonly environment = environment; - protected executeUpdate: ExecuteSystemUpdate | null = null; - - protected isWaiting: boolean; - - constructor( - private websocket: Websocket, - private service: Service) { } - - ngOnInit() { - this.executeUpdate = new ExecuteSystemUpdate(this.edge, this.websocket); - - this.executeUpdate.systemUpdateStateChange = (systemUpdateState) => { - this.stateChanged.emit(systemUpdateState); - if (systemUpdateState.updated) { - this.service.stopSpinner(this.spinnerId); - this.isWaiting = false; - } - }; - - this.service.startSpinnerTransparentBackground(this.spinnerId); - this.isWaiting = true; - this.executeUpdate.start() - .finally(() => { - if (!this.executeUpdate.systemUpdateState.running) { - this.service.stopSpinner(this.spinnerId); - this.isWaiting = false; - } - if (this.executeUpdate.systemUpdateState.available && this.executeUpdateInstantly) { - this.executeSystemUpdate(); - } - }); - } - - public ngOnDestroy() { - this.executeUpdate.stop(); - } - - public executeSystemUpdate() { - this.service.startSpinnerTransparentBackground(this.spinnerId); - this.isWaiting = true; - this.executeUpdate.executeSystemUpdate(); - } - -} diff --git a/ui/src/app/edge/settings/system/maintenance/maintenance.ts b/ui/src/app/edge/settings/system/maintenance/maintenance.ts index cda8f1e81d6..c5854fce684 100644 --- a/ui/src/app/edge/settings/system/maintenance/maintenance.ts +++ b/ui/src/app/edge/settings/system/maintenance/maintenance.ts @@ -28,6 +28,7 @@ enum SystemRestartState { } } `], + standalone: false, }) export class MaintenanceComponent implements OnInit { diff --git a/ui/src/app/edge/settings/system/oe-system-update.component.html b/ui/src/app/edge/settings/system/oe-system-update.component.html index a4d54cc1894..2fb34fa0c22 100644 --- a/ui/src/app/edge/settings/system/oe-system-update.component.html +++ b/ui/src/app/edge/settings/system/oe-system-update.component.html @@ -4,19 +4,19 @@ - + {{ 'SETTINGS.SYSTEM_UPDATE.EDGE_RESTARTING' | translate: { edge: environment.edgeShortName } }}
    {{ 'SETTINGS.SYSTEM_UPDATE.UPDATE_TIME'| translate}}
    - +
    - + {{ 'SETTINGS.SYSTEM_UPDATE.OFFLINE' | translate: { edgeShortName: environment.edgeShortName } }} @@ -55,7 +55,8 @@ - +
    diff --git a/ui/src/app/edge/settings/system/oe-system-update.component.ts b/ui/src/app/edge/settings/system/oe-system-update.component.ts index b721f60aa44..e8c7383def0 100644 --- a/ui/src/app/edge/settings/system/oe-system-update.component.ts +++ b/ui/src/app/edge/settings/system/oe-system-update.component.ts @@ -2,14 +2,21 @@ import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core"; import { AlertController } from "@ionic/angular"; import { TranslateService } from "@ngx-translate/core"; +import { DataService } from "src/app/shared/components/shared/dataservice"; import { Edge, presentAlert, Service, Websocket } from "src/app/shared/shared"; import { environment } from "src/environments"; +import { LiveDataService } from "../../live/livedataservice"; import { ExecuteSystemUpdate } from "./executeSystemUpdate"; import { SystemUpdateState } from "./getSystemUpdateStateResponse"; @Component({ selector: OeSystemUpdateComponent.SELECTOR, templateUrl: "./oe-system-update.component.html", + standalone: false, + providers: [{ + useClass: LiveDataService, + provide: DataService, + }], }) export class OeSystemUpdateComponent implements OnInit, OnDestroy { diff --git a/ui/src/app/edge/settings/system/system.component.html b/ui/src/app/edge/settings/system/system.component.html index fb00b72fee6..154e66467b4 100644 --- a/ui/src/app/edge/settings/system/system.component.html +++ b/ui/src/app/edge/settings/system/system.component.html @@ -1,4 +1,3 @@ -
    diff --git a/ui/src/app/edge/settings/system/system.component.ts b/ui/src/app/edge/settings/system/system.component.ts index e6398f41b90..02a6b07617e 100644 --- a/ui/src/app/edge/settings/system/system.component.ts +++ b/ui/src/app/edge/settings/system/system.component.ts @@ -1,37 +1,33 @@ // @ts-strict-ignore -import { Component, OnInit } from "@angular/core"; -import { ActivatedRoute } from "@angular/router"; +import { Component, effect } from "@angular/core"; import { environment } from "src/environments"; import { Edge, Service, UserPermission, Utils } from "../../../shared/shared"; @Component({ selector: SystemComponent.SELECTOR, templateUrl: "./system.component.html", + standalone: false, }) -export class SystemComponent implements OnInit { +export class SystemComponent { private static readonly SELECTOR = "system"; - public readonly environment = environment; - public readonly spinnerId: string = SystemComponent.SELECTOR; - public showLog: boolean = false; - public readonly ESTIMATED_REBOOT_TIME = 600; // Seconds till the openems service is restarted after update - - public edge: Edge; - public restartTime: number = this.ESTIMATED_REBOOT_TIME; - + protected readonly environment = environment; + protected readonly spinnerId: string = SystemComponent.SELECTOR; + protected showLog: boolean = false; + protected readonly ESTIMATED_REBOOT_TIME = 600; // Seconds till the openems service is restarted after update + protected edge: Edge; + protected restartTime: number = this.ESTIMATED_REBOOT_TIME; protected canSeeSystemRestart: boolean = false; constructor( - private route: ActivatedRoute, protected utils: Utils, private service: Service, - ) { } - - ngOnInit() { - this.service.getCurrentEdge().then(edge => { - this.edge = edge; - this.canSeeSystemRestart = UserPermission.isAllowedToSeeSystemRestart(this.service.currentUser, edge); + ) { + effect(async () => { + const user = this.service.currentUser(); + this.edge = await this.service.getCurrentEdge(); + this.canSeeSystemRestart = UserPermission.isAllowedToSeeSystemRestart(user, this.edge); }); } } diff --git a/ui/src/app/edge/settings/systemexecute/systemexecute.component.html b/ui/src/app/edge/settings/systemexecute/systemexecute.component.html index c22504d89c5..5855a9ed9bd 100644 --- a/ui/src/app/edge/settings/systemexecute/systemexecute.component.html +++ b/ui/src/app/edge/settings/systemexecute/systemexecute.component.html @@ -1,4 +1,3 @@ -
    @@ -27,26 +26,22 @@
    - Username - + - Password - + - Timeout - + - Run in background - + Run in + background - Command - + Send diff --git a/ui/src/app/edge/settings/systemexecute/systemexecute.component.ts b/ui/src/app/edge/settings/systemexecute/systemexecute.component.ts index 174de06b640..dba922a4201 100644 --- a/ui/src/app/edge/settings/systemexecute/systemexecute.component.ts +++ b/ui/src/app/edge/settings/systemexecute/systemexecute.component.ts @@ -19,6 +19,7 @@ const COMMANDS: { [key: string]: CommandFunction; } = { @Component({ selector: SystemExecuteComponent.SELECTOR, templateUrl: "./systemexecute.component.html", + standalone: false, }) export class SystemExecuteComponent implements OnInit { diff --git a/ui/src/app/edge/settings/systemlog/systemlog.component.html b/ui/src/app/edge/settings/systemlog/systemlog.component.html index 37919bc7547..37e145db9ae 100644 --- a/ui/src/app/edge/settings/systemlog/systemlog.component.html +++ b/ui/src/app/edge/settings/systemlog/systemlog.component.html @@ -1,4 +1,3 @@ -
    @@ -29,10 +28,8 @@ - {{filters.placeholder}} - {{filter.name}} @@ -46,7 +43,9 @@ - + diff --git a/ui/src/app/edge/settings/systemlog/systemlog.component.ts b/ui/src/app/edge/settings/systemlog/systemlog.component.ts index 8b2776a41a9..a4e01e1a5bb 100644 --- a/ui/src/app/edge/settings/systemlog/systemlog.component.ts +++ b/ui/src/app/edge/settings/systemlog/systemlog.component.ts @@ -34,6 +34,7 @@ export const LOG_LEVEL_FILTER = (translate: TranslateService): Filter => ({ @Component({ selector: SystemLogComponent.SELECTOR, templateUrl: "./systemlog.component.html", + standalone: false, }) export class SystemLogComponent implements OnInit, OnDestroy { diff --git a/ui/src/app/index/filter/filter.component.html b/ui/src/app/index/filter/filter.component.html index aa8c62bc3e6..bc1cb2eb93d 100644 --- a/ui/src/app/index/filter/filter.component.html +++ b/ui/src/app/index/filter/filter.component.html @@ -1,18 +1,16 @@ - - {{filter.placeholder}} - + + {{option.name}} - +
    diff --git a/ui/src/app/index/filter/filter.component.ts b/ui/src/app/index/filter/filter.component.ts index 336008c0ca3..ede482b3bd6 100644 --- a/ui/src/app/index/filter/filter.component.ts +++ b/ui/src/app/index/filter/filter.component.ts @@ -9,6 +9,7 @@ import { SUM_STATES } from "../shared/sumState"; @Component({ selector: "oe-filter", templateUrl: "./filter.component.html", + standalone: false, }) export class FilterComponent { diff --git a/ui/src/app/index/login.component.html b/ui/src/app/index/login.component.html index 4c4cf15f044..87af5bc4c35 100644 --- a/ui/src/app/index/login.component.html +++ b/ui/src/app/index/login.component.html @@ -47,14 +47,14 @@
    - E-Mail / Login.user + [placeholder]="'E-Mail / ' + ('Login.passwordLabel' | translate)" + [label]="'E-Mail / ' + ('Login.user' | translate)" label-placement="floating"> - Login.passwordLabel - + diff --git a/ui/src/app/index/login.component.ts b/ui/src/app/index/login.component.ts index 4fa20f9aeff..8c738c710ba 100644 --- a/ui/src/app/index/login.component.ts +++ b/ui/src/app/index/login.component.ts @@ -1,21 +1,25 @@ // @ts-strict-ignore -import { AfterContentChecked, ChangeDetectorRef, Component, OnDestroy, OnInit } from "@angular/core"; +import { AfterContentChecked, ChangeDetectorRef, Component, OnDestroy, OnInit, effect } from "@angular/core"; import { FormGroup } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; +import { Capacitor } from "@capacitor/core"; +import { ModalController, PopoverController, ViewWillEnter } from "@ionic/angular"; import { Subject } from "rxjs"; import { environment } from "src/environments"; -import { Capacitor } from "@capacitor/core"; import { AppService } from "../app.service"; import { AuthenticateWithPasswordRequest } from "../shared/jsonrpc/request/authenticateWithPasswordRequest"; import { States } from "../shared/ngrx-store/states"; import { Edge, Service, Utils, Websocket } from "../shared/shared"; +import { UserComponent } from "../user/user.component"; @Component({ selector: "login", templateUrl: "./login.component.html", + standalone: false, }) -export class LoginComponent implements OnInit, AfterContentChecked, OnDestroy { +export class LoginComponent implements ViewWillEnter, AfterContentChecked, OnDestroy, OnInit { + public currentThemeMode: string; public environment = environment; public form: FormGroup; protected formIsDisabled: boolean = false; @@ -32,19 +36,27 @@ export class LoginComponent implements OnInit, AfterContentChecked, OnDestroy { private router: Router, private route: ActivatedRoute, private cdref: ChangeDetectorRef, - ) { } + protected popoverctrl: PopoverController, + protected modalctrl: ModalController, + ) { + effect(() => { + const user = this.service.currentUser(); + this.currentThemeMode = UserComponent.getPreferedColorSchemeFromTheme(UserComponent.getCurrentTheme(user)); + UserComponent.applyUserSettings(user); + }); + } /** - * Trims credentials - * - * @param password the password - * @param username the username - * @returns trimmed credentials - */ - public static trimCredentials(password: string, username?: string): { password: string, username?: string } { + * Preprocesses the credentials + * + * @param password the password + * @param username the username + * @returns trimmed credentials + */ + public static preprocessCredentials(password: string, username?: string): { password: string, username?: string } { return { password: password?.trim(), - ...(username && { username: username?.trim() }), + ...(username && { username: username?.trim().toLowerCase() }), }; } @@ -53,18 +65,16 @@ export class LoginComponent implements OnInit, AfterContentChecked, OnDestroy { } ngOnInit() { - - // TODO add websocket status observable const interval = setInterval(() => { - if (this.websocket.status === "online") { + if (this.websocket.status === "online" && !this.router.url.split("/").includes("live")) { this.router.navigate(["/overview"]); clearInterval(interval); } }, 1000); } - async ionViewWillEnter() { + async ionViewWillEnter() { // Execute Login-Request if url path matches 'demo' if (this.route.snapshot.routeConfig.path == "demo") { @@ -95,7 +105,7 @@ export class LoginComponent implements OnInit, AfterContentChecked, OnDestroy { public doLogin(param: { username?: string, password: string }) { this.websocket.state.set(States.AUTHENTICATION_WITH_CREDENTIALS); - param = LoginComponent.trimCredentials(param.password, param.username); + param = LoginComponent.preprocessCredentials(param.password, param.username); // Prevent that user submits via keyevent 'enter' multiple times if (this.formIsDisabled) { @@ -107,7 +117,7 @@ export class LoginComponent implements OnInit, AfterContentChecked, OnDestroy { .finally(() => { // Unclean - this.ngOnInit(); + this.ionViewWillEnter(); this.formIsDisabled = false; }); } diff --git a/ui/src/app/index/login.spec.ts b/ui/src/app/index/login.spec.ts index dad874f51d4..9feea8aa9ce 100644 --- a/ui/src/app/index/login.spec.ts +++ b/ui/src/app/index/login.spec.ts @@ -12,26 +12,30 @@ describe("Login", () => { }).compileComponents(); }); - it("#trimCredentials should trim password and username", () => { + it("#preprocessCredentials should trim password and username and should lowerCase username", () => { { // Username and password - OpenEMS Backend - expect(LoginComponent.trimCredentials(password, username)).toEqual({ password: "password", username: "username" }); + expect(LoginComponent.preprocessCredentials(password, username)).toEqual({ password: "password", username: "username" }); } { // Only Password - OpenEMS Edge - expect(LoginComponent.trimCredentials(password)).toEqual({ password: "password" }); + expect(LoginComponent.preprocessCredentials(password)).toEqual({ password: "password" }); } { // Password is null - expect(LoginComponent.trimCredentials(null)).toEqual({ password: undefined }); + expect(LoginComponent.preprocessCredentials(null)).toEqual({ password: undefined }); } { // Username is null - expect(LoginComponent.trimCredentials(password, null)).toEqual({ password: "password" }); + expect(LoginComponent.preprocessCredentials(password, null)).toEqual({ password: "password" }); } { // Username and password are null - expect(LoginComponent.trimCredentials(null, null)).toEqual({ password: undefined }); + expect(LoginComponent.preprocessCredentials(null, null)).toEqual({ password: undefined }); + } + { + // Username in Upper case + expect(LoginComponent.preprocessCredentials(password, username.toUpperCase())).toEqual({ password: "password", username: "username" }); } }); }); diff --git a/ui/src/app/index/overview/overview.component.html b/ui/src/app/index/overview/overview.component.html index e1a0fa4e05d..6b6988a838b 100644 --- a/ui/src/app/index/overview/overview.component.html +++ b/ui/src/app/index/overview/overview.component.html @@ -1,4 +1,3 @@ -
    @@ -41,7 +40,9 @@ - + @@ -73,13 +74,13 @@

    {{ edge.comment }}

    -

    ID: {{ edge.id }}

    -

    +

    ID: {{ edge.id }}

    +

    Index.TYPE: {{ edge.producttype }}

    -

    +

    Index.LOGGED_IN_AS: {{ edge.getRoleString() }}

    @@ -89,7 +90,8 @@

    {{ edge.comment }}

    - + diff --git a/ui/src/app/index/overview/overview.component.ts b/ui/src/app/index/overview/overview.component.ts index f8a129eaa3a..cbfd985dea2 100644 --- a/ui/src/app/index/overview/overview.component.ts +++ b/ui/src/app/index/overview/overview.component.ts @@ -1,8 +1,8 @@ // @ts-strict-ignore -import { Component, OnDestroy, OnInit } from "@angular/core"; +import { Component, OnDestroy } from "@angular/core"; import { FormGroup } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; -import { InfiniteScrollCustomEvent } from "@ionic/angular"; +import { InfiniteScrollCustomEvent, ViewWillEnter } from "@ionic/angular"; import { TranslateService } from "@ngx-translate/core"; import { Subject } from "rxjs"; import { filter, take } from "rxjs/operators"; @@ -10,14 +10,14 @@ import { Pagination } from "src/app/shared/service/pagination"; import { Edge, Service, Utils, Websocket } from "src/app/shared/shared"; import { Role } from "src/app/shared/type/role"; import { environment } from "src/environments"; - import { ChosenFilter } from "../filter/filter.component"; @Component({ selector: "overview", templateUrl: "./overview.component.html", + standalone: false, }) -export class OverViewComponent implements OnInit, OnDestroy { +export class OverViewComponent implements ViewWillEnter, OnDestroy { public environment = environment; /** True, if there is no access to any Edge. */ public noEdges: boolean = false; @@ -50,7 +50,7 @@ export class OverViewComponent implements OnInit, OnDestroy { public pagination: Pagination, ) { } - ngOnInit() { + ionViewWillEnter() { this.page = 0; this.filteredEdges = []; this.limitReached = false; @@ -139,7 +139,6 @@ export class OverViewComponent implements OnInit, OnDestroy { take(1), ) .subscribe(metadata => { - const edgeIds = Object.keys(metadata.edges); this.noEdges = edgeIds.length === 0; this.loggedInUserCanInstall = Role.isAtLeast(metadata.user.globalRole, "installer"); diff --git a/ui/src/app/index/shared/loading-screen.html b/ui/src/app/index/shared/loading-screen.html index 481d1773885..61216a8b4f7 100644 --- a/ui/src/app/index/shared/loading-screen.html +++ b/ui/src/app/index/shared/loading-screen.html @@ -17,7 +17,11 @@ - + @if (environment.backend === 'OpenEMS Backend') { + + }@else { + + } @@ -30,7 +34,7 @@
    - + @@ -61,3 +65,36 @@ + + + + + + + OFFLINE.EDGE.LOCAL_ACCESS_CURRENTLY_NOT_POSSIBLE + + + + + + + + + OFFLINE.EDGE.DESCRIPTION + + + + + + + + + + LOADING_SCREEN.SERVER_NOT_ACCESSIBLE_TRY_RELOADING + + + + + diff --git a/ui/src/app/index/shared/loading-screen.ts b/ui/src/app/index/shared/loading-screen.ts index 05322949c51..4d62398f147 100644 --- a/ui/src/app/index/shared/loading-screen.ts +++ b/ui/src/app/index/shared/loading-screen.ts @@ -9,6 +9,7 @@ import { Service, Websocket } from "../../shared/shared"; @Component({ selector: "index", templateUrl: "./loading-screen.html", + standalone: false, }) export class LoadingScreenComponent { diff --git a/ui/src/app/index/shared/sumState.ts b/ui/src/app/index/shared/sumState.ts index 38a892087d6..56df768fbee 100644 --- a/ui/src/app/index/shared/sumState.ts +++ b/ui/src/app/index/shared/sumState.ts @@ -41,6 +41,7 @@ export enum SumState { font-size: 20pt !important; } `], + standalone: false, }) export class SumStateComponent implements OnInit { diff --git a/ui/src/app/registration/modal/modal.component.ts b/ui/src/app/registration/modal/modal.component.ts index 430f0d24d1f..181c28afe46 100644 --- a/ui/src/app/registration/modal/modal.component.ts +++ b/ui/src/app/registration/modal/modal.component.ts @@ -11,6 +11,7 @@ import { environment } from "src/environments"; @Component({ selector: "registration-modal", templateUrl: "./modal.component.html", + standalone: false, }) export class RegistrationModalComponent implements OnInit { diff --git a/ui/src/app/registration/registration.component.ts b/ui/src/app/registration/registration.component.ts index 6ac8752cff7..927a4159fb1 100644 --- a/ui/src/app/registration/registration.component.ts +++ b/ui/src/app/registration/registration.component.ts @@ -5,6 +5,7 @@ import { RegistrationModalComponent } from "./modal/modal.component"; @Component({ selector: "registration", templateUrl: "./registration.component.html", + standalone: false, }) export class RegistrationComponent { diff --git a/ui/src/app/shared/components/abstracthistorywidget.ts b/ui/src/app/shared/components/abstracthistorywidget.ts index dfc48dffa2a..76204d7e133 100644 --- a/ui/src/app/shared/components/abstracthistorywidget.ts +++ b/ui/src/app/shared/components/abstracthistorywidget.ts @@ -4,9 +4,9 @@ import { ActivatedRoute } from "@angular/router"; import { ModalController } from "@ionic/angular"; import { TranslateService } from "@ngx-translate/core"; import { Subject } from "rxjs"; +import { v4 as uuidv4 } from "uuid"; import { DefaultTypes } from "src/app/shared/service/defaulttypes"; import { ChannelAddress, CurrentData, Edge, EdgeConfig, Service, Websocket } from "src/app/shared/shared"; -import { v4 as uuidv4 } from "uuid"; // NOTE: Auto-refresh of widgets is currently disabled to reduce server load @Directive() diff --git a/ui/src/app/shared/components/chart/abstracthistorychart.ts b/ui/src/app/shared/components/chart/abstracthistorychart.ts index b30e69af4b8..4fc3deb9545 100644 --- a/ui/src/app/shared/components/chart/abstracthistorychart.ts +++ b/ui/src/app/shared/components/chart/abstracthistorychart.ts @@ -4,13 +4,15 @@ import { ChangeDetectorRef, Directive, Input, OnDestroy, OnInit } from "@angular import { ActivatedRoute } from "@angular/router"; import { TranslateService } from "@ngx-translate/core"; import * as Chart from "chart.js"; +import "chartjs-adapter-date-fns"; import annotationPlugin from "chartjs-plugin-annotation"; +import { v4 as uuidv4 } from "uuid"; + import { ChronoUnit, DEFAULT_NUMBER_CHART_OPTIONS, DEFAULT_TIME_CHART_OPTIONS, Resolution, calculateResolution, isLabelVisible, setLabelVisible } from "src/app/edge/history/shared"; import { QueryHistoricTimeseriesEnergyPerPeriodResponse } from "src/app/shared/jsonrpc/response/queryHistoricTimeseriesEnergyPerPeriodResponse"; import { DefaultTypes } from "src/app/shared/service/defaulttypes"; -import { v4 as uuidv4 } from "uuid"; - import { JsonrpcResponseError } from "../../jsonrpc/base"; +import { JsonRpcUtils } from "../../jsonrpc/jsonrpcutils"; import { QueryHistoricTimeseriesDataRequest } from "../../jsonrpc/request/queryHistoricTimeseriesDataRequest"; import { QueryHistoricTimeseriesEnergyPerPeriodRequest } from "../../jsonrpc/request/queryHistoricTimeseriesEnergyPerPeriodRequest"; import { QueryHistoricTimeseriesEnergyRequest } from "../../jsonrpc/request/queryHistoricTimeseriesEnergyRequest"; @@ -27,8 +29,6 @@ import { TimeUtils } from "../../utils/time/timeutils"; import { Converter } from "../shared/converter"; import { ChartConstants, XAxisType } from "./chart.constants"; -import "chartjs-adapter-date-fns"; - Chart.Chart.register(annotationPlugin); // NOTE: Auto-refresh of widgets is currently disabled to reduce server load @@ -36,7 +36,7 @@ Chart.Chart.register(annotationPlugin); @Directive() export abstract class AbstractHistoryChart implements OnInit, OnDestroy { - protected static readonly phaseColors: string[] = ["rgb(255,127,80)", "rgb(0,0,255)", "rgb(128,128,0)"]; + protected static readonly phaseColors: string[] = ["rgb(255,127,80)", "rgb(91, 92, 214)", "rgb(128,128,0)"]; /** Title for Chart, diplayed above the Chart */ @Input() public chartTitle: string = ""; @@ -166,15 +166,20 @@ export abstract class AbstractHistoryChart implements OnInit, OnDestroy { public static fillData(element: HistoryUtils.DisplayValue, label: string, chartObject: HistoryUtils.ChartData, chartType: "line" | "bar", data: number[] | null): { datasets: Chart.ChartDataset[], legendOptions: { label: string, strokeThroughHidingStyle: boolean, hideLabelInLegend: boolean; }[]; } { const legendOptions: { label: string, strokeThroughHidingStyle: boolean, hideLabelInLegend: boolean; }[] = []; const datasets: Chart.ChartDataset[] = []; + let normalizedData: (number | null)[] = data; + + if (chartObject.normalizeOutputData == true) { + normalizedData = JsonRpcUtils.normalizeQueryData(data); + } // Enable one dataset to be displayed in multiple stacks if (Array.isArray(element.stack)) { for (const stack of element.stack) { - datasets.push(AbstractHistoryChart.getDataSet(element, label, data, stack, chartObject, element.custom?.type ?? chartType)); + datasets.push(AbstractHistoryChart.getDataSet(element, label, normalizedData, stack, chartObject, element.custom?.type ?? chartType)); legendOptions.push(AbstractHistoryChart.getLegendOptions(label, element)); } } else { - datasets.push(AbstractHistoryChart.getDataSet(element, label, data, element.stack, chartObject, element.custom?.type ?? chartType)); + datasets.push(AbstractHistoryChart.getDataSet(element, label, normalizedData, element.stack, chartObject, element.custom?.type ?? chartType)); legendOptions.push(AbstractHistoryChart.getLegendOptions(label, element)); } @@ -207,7 +212,7 @@ export abstract class AbstractHistoryChart implements OnInit, OnDestroy { */ public static getColors(color: string, chartType: "line" | "bar"): { backgroundColor: string, borderColor: string } { return { - backgroundColor: "rgba(" + (chartType == "bar" ? color.split("(").pop().split(")")[0] + ",0.4)" : color.split("(").pop().split(")")[0] + ",0.05)"), + backgroundColor: "rgba(" + (chartType == "bar" ? color.split("(").pop().split(")")[0] + ",0.7)" : color.split("(").pop().split(")")[0] + ",0.05)"), borderColor: "rgba(" + color.split("(").pop().split(")")[0] + ",1)", }; } @@ -310,7 +315,7 @@ export abstract class AbstractHistoryChart implements OnInit, OnDestroy { case YAxisType.TIME: return translate.instant("Edge.Index.Widgets.Channeltreshold.ACTIVE_TIME_OVER_PERIOD"); case YAxisType.PERCENTAGE: - return translate.instant("General.percentage"); + return "%"; case YAxisType.REACTIVE: return "var"; case YAxisType.ENERGY: @@ -320,9 +325,9 @@ export abstract class AbstractHistoryChart implements OnInit, OnDestroy { return "kW"; } case YAxisType.VOLTAGE: - return translate.instant("Edge.History.VOLTAGE"); + return "V"; case YAxisType.CURRENT: - return translate.instant("Edge.History.CURRENT"); + return "A"; case YAxisType.NONE: return ""; default: @@ -347,20 +352,17 @@ export abstract class AbstractHistoryChart implements OnInit, OnDestroy { translate: TranslateService, legendOptions: { label: string, strokeThroughHidingStyle: boolean; }[], channelData: { data: { [name: string]: number[]; }; }, - locale: string, config: EdgeConfig, datasets: Chart.ChartDataset[], chartOptionsType: XAxisType, labels: (Date | string)[], ): Chart.ChartOptions { - let tooltipsLabel: string | null = null; let options: Chart.ChartOptions = Utils.deepCopy(Utils.deepCopy(AbstractHistoryChart.getDefaultOptions(chartOptionsType, service, labels))); - const displayValues: HistoryUtils.DisplayValue[] = chartObject.output(channelData.data); + const displayValues: HistoryUtils.DisplayValue[] = chartObject.output(channelData.data, labels); - const showYAxisType: boolean = chartObject.yAxes.length > 1; chartObject.yAxes.forEach((element) => { - options = AbstractHistoryChart.getYAxisOptions(options, element, translate, chartType, locale, datasets, showYAxisType); + options = AbstractHistoryChart.getYAxisOptions(options, element, translate, chartType, datasets, true); }); options.plugins.tooltip.callbacks.title = (tooltipItems: Chart.TooltipItem[]): string => { @@ -391,13 +393,23 @@ export abstract class AbstractHistoryChart implements OnInit, OnDestroy { tooltipsLabel = AbstractHistoryChart.getToolTipsAfterTitleLabel(unit, chartType, value, translate); } - return label.split(":")[0] + ": " + AbstractHistoryChart.getToolTipsSuffix(tooltipsLabel, value, displayValue.custom?.formatNumber ?? chartObject.tooltip.formatNumber, unit, chartType, locale, translate, config); + return label.split(":")[0] + ": " + AbstractHistoryChart.getToolTipsSuffix(tooltipsLabel, value, displayValue.custom?.formatNumber ?? chartObject.tooltip.formatNumber, unit, chartType, translate, config); }; options.plugins.tooltip.callbacks.labelColor = (item: Chart.TooltipItem) => { + let backgroundColor = item.dataset.backgroundColor; + + if (Array.isArray(backgroundColor)) { + backgroundColor = backgroundColor[0]; + } + + if (!backgroundColor) { + backgroundColor = item.dataset.borderColor || "rgba(0, 0, 0, 0.5)"; + } + return { - borderColor: ColorUtils.changeOpacityFromRGBA(item.dataset.borderColor, 1), - backgroundColor: item.dataset.backgroundColor, + borderColor: ColorUtils.changeOpacityFromRGBA(backgroundColor, 1), + backgroundColor: ColorUtils.changeOpacityFromRGBA(backgroundColor, 1), }; }; @@ -431,6 +443,7 @@ export abstract class AbstractHistoryChart implements OnInit, OnDestroy { }; options.plugins.tooltip.callbacks.afterTitle = function (items: Chart.TooltipItem[]) { + const locale: string = (Language.getByKey(localStorage.LANGUAGE) ?? Language.DEFAULT).i18nLocaleKey; if (items?.length === 0) { return null; @@ -454,16 +467,25 @@ export abstract class AbstractHistoryChart implements OnInit, OnDestroy { const totalValue = datasets.filter(el => el.stack == stack).reduce((_total, dataset) => Utils.addSafely(_total, Math.abs(dataset.data[datasetIndex])), 0); if (afterTitle) { - return afterTitle + ": " + formatNumber(totalValue, "de", chartObject.tooltip.formatNumber) + " " + tooltipsLabel; + return afterTitle + ": " + formatNumber(totalValue, locale, chartObject.tooltip.formatNumber) + " " + tooltipsLabel; } return null; }; + options.plugins.tooltip.enabled = chartObject.tooltip.enabled ?? true; + // Remove duplicates from legend, if legendItem with two or more occurrences in legend, use one legendItem to trigger them both - options.plugins.legend.onClick = function (event: Chart.ChartEvent, legendItem: Chart.LegendItem, legend) { + options.plugins.legend.onClick = function (event: Chart.ChartEvent, legendItem: Chart.LegendItem, legend: Chart.LegendElement) { const chart: Chart.Chart = this.chart; + function rebuildScales(chart: Chart.Chart) { + let options = chart.options; + chartObject.yAxes.forEach((element) => { + options = AbstractHistoryChart.getYAxisOptions(options, element, translate, chartType, _dataSets, true); + }); + } + const legendItems = chart.data.datasets.reduce((arr, ds, i) => { if (ds.label == legendItem.text) { arr.push({ label: ds.label, index: i }); @@ -479,14 +501,23 @@ export abstract class AbstractHistoryChart implements OnInit, OnDestroy { meta.hidden = meta.hidden === null ? !chart.data.datasets[item.index].hidden : null; }); - // We hid a dataset ... rerender the chart + /** needs to be set, cause property async set */ + const _dataSets: Chart.ChartDataset[] = datasets.map((v, k) => { + if (k === legendItem.datasetIndex) { + v.hidden = !v.hidden; + } + return v; + }); + + rebuildScales(chart); chart.update(); }; + options.scales.x.ticks["source"] = "auto"; options.scales.x.ticks.maxTicksLimit = 31; options.scales.x["bounds"] = "ticks"; - options; + options.scales.x.ticks.color = getComputedStyle(document.documentElement).getPropertyValue("--ion-color-chart-xAxis-ticks"); options = AbstractHistoryChart.getExternalPluginFeatures(displayValues, options, chartType); return options; @@ -502,12 +533,10 @@ export abstract class AbstractHistoryChart implements OnInit, OnDestroy { * @param locale the current locale * @returns the chart options {@link Chart.ChartOptions} */ - public static getYAxisOptions(options: Chart.ChartOptions, element: HistoryUtils.yAxes, translate: TranslateService, chartType: "line" | "bar", locale: string, datasets: Chart.ChartDataset[], showYAxisType?: boolean): Chart.ChartOptions { - + public static getYAxisOptions(options: Chart.ChartOptions, element: HistoryUtils.yAxes, translate: TranslateService, chartType: "line" | "bar", datasets: Chart.ChartDataset[], showYAxisType?: boolean): Chart.ChartOptions { + const locale: string = (Language.getByKey(localStorage.LANGUAGE) ?? Language.DEFAULT).i18nLocaleKey; const baseConfig = ChartConstants.DEFAULT_Y_SCALE_OPTIONS(element, translate, chartType, datasets, showYAxisType); - switch (element.unit) { - case YAxisType.RELAY: options.scales[element.yAxisId] = { ...baseConfig, @@ -518,7 +547,7 @@ export abstract class AbstractHistoryChart implements OnInit, OnDestroy { ...baseConfig.ticks, stepSize: 1, // Two states are possible - callback: function (value, index, ticks) { + callback: function (value, index, ticks: Chart.Tick[]) { return Converter.ON_OFF(translate)(value); }, padding: 5, @@ -556,23 +585,40 @@ export abstract class AbstractHistoryChart implements OnInit, OnDestroy { }, }; break; - case YAxisType.POWER: - case YAxisType.ENERGY: - case YAxisType.REACTIVE: + case YAxisType.LEVEL: + options.scales[element.yAxisId] = { + ...baseConfig, + min: 0, + max: 3, + beginAtZero: true, + ticks: { + stepSize: 1, + }, + }; + break; case YAxisType.VOLTAGE: case YAxisType.CURRENT: - case YAxisType.NONE: - options.scales[element.yAxisId] = baseConfig; + options.scales[element.yAxisId] = { + ...baseConfig, + beginAtZero: false, + }; break; case YAxisType.CURRENCY: options.scales[element.yAxisId] = { ...baseConfig, beginAtZero: false, ticks: { + ...baseConfig.ticks, source: "auto", }, }; break; + case YAxisType.POWER: + case YAxisType.ENERGY: + case YAxisType.REACTIVE: + case YAxisType.NONE: + options.scales[element.yAxisId] = baseConfig; + break; } return options; } @@ -586,15 +632,16 @@ export abstract class AbstractHistoryChart implements OnInit, OnDestroy { * @returns a string, that is either the baseName, if no suffix is provided, or a baseName with a formatted number */ public static getTooltipsLabelName(baseName: string, unit: YAxisType, suffix?: number | string): string { + const locale: string = (Language.getByKey(localStorage.LANGUAGE) ?? Language.DEFAULT).i18nLocaleKey; if (suffix != null) { if (typeof suffix === "string") { return baseName + " " + suffix; } else { switch (unit) { case YAxisType.ENERGY: - return baseName + ": " + formatNumber(suffix / 1000, "de", "1.0-1") + " kWh"; + return baseName + ": " + formatNumber(suffix / 1000, locale, "1.0-1") + " kWh"; case YAxisType.PERCENTAGE: - return baseName + ": " + formatNumber(suffix, "de", "1.0-1") + " %"; + return baseName + ": " + formatNumber(suffix, locale, "1.0-1") + " %"; case YAxisType.RELAY: case YAxisType.TIME: { const pipe = new FormatSecondsToDurationPipe(new DecimalPipe(Language.DE.key)); @@ -614,7 +661,9 @@ export abstract class AbstractHistoryChart implements OnInit, OnDestroy { * @param title the YAxisType * @returns the tooltips suffix */ - public static getToolTipsSuffix(label: any, value: number, format: string, title: YAxisType, chartType: "bar" | "line", language: string, translate: TranslateService, config: EdgeConfig): string { + public static getToolTipsSuffix(label: any, value: number, format: string, title: YAxisType, chartType: "bar" | "line", translate: TranslateService, config: EdgeConfig): string { + const language: string = (Language.getByKey(localStorage.LANGUAGE) ?? Language.DEFAULT).key; + const locale: string = (Language.getByKey(localStorage.LANGUAGE) ?? Language.DEFAULT).i18nLocaleKey; let tooltipsLabel: string | null = null; switch (title) { case YAxisType.RELAY: { @@ -625,7 +674,8 @@ export abstract class AbstractHistoryChart implements OnInit, OnDestroy { return pipe.transform(value); } case YAxisType.CURRENCY: { - const currency = config.components["_meta"].properties.currency; + const meta: EdgeConfig.Component = config?.getComponent("_meta"); + const currency: string = config?.getPropertyFromComponent(meta, "currency"); tooltipsLabel = Currency.getCurrencyLabelByCurrency(currency); break; } @@ -656,7 +706,7 @@ export abstract class AbstractHistoryChart implements OnInit, OnDestroy { break; } - return formatNumber(value, "de", format) + " " + tooltipsLabel; + return formatNumber(value, locale, format) + " " + tooltipsLabel; } public static getDefaultOptions(xAxisType: XAxisType, service: Service, labels: (Date | string)[]): Chart.ChartOptions { @@ -869,7 +919,6 @@ export abstract class AbstractHistoryChart implements OnInit, OnDestroy { this.queryHistoricTimeseriesEnergy(this.service.historyPeriod.value.from, this.service.historyPeriod.value.to), ]) .then(([dataResponse, energyResponse]) => { - dataResponse = DateTimeUtils.normalizeTimestamps(unit, dataResponse); this.chartType = "line"; this.chartObject = this.getChartData(); @@ -1037,8 +1086,7 @@ export abstract class AbstractHistoryChart implements OnInit, OnDestroy { * Sets the Labels of the Chart */ protected setChartLabel() { - const locale = this.service.translate.currentLang; - this.options = AbstractHistoryChart.getOptions(this.chartObject, this.chartType, this.service, this.translate, this.legendOptions, this.channelData, locale, this.config, this.datasets, this.xAxisScalingType, this.labels); + this.options = AbstractHistoryChart.getOptions(this.chartObject, this.chartType, this.service, this.translate, this.legendOptions, this.channelData, this.config, this.datasets, this.xAxisScalingType, this.labels); this.loading = false; this.stopSpinner(); } diff --git a/ui/src/app/shared/components/chart/chart.constants.spec.ts b/ui/src/app/shared/components/chart/chart.constants.spec.ts index 2e38cb4cbe7..3687e1d71fb 100644 --- a/ui/src/app/shared/components/chart/chart.constants.spec.ts +++ b/ui/src/app/shared/components/chart/chart.constants.spec.ts @@ -7,9 +7,9 @@ import { ChartConstants } from "./chart.constants"; describe("Chart constants", () => { it("#calculateStepSize", () => { - expect(ChartConstants.calculateStepSize(0, 10)).toEqual(2.5); + expect(ChartConstants.calculateStepSize(0, 10)).toEqual(2); expect(ChartConstants.calculateStepSize(0, null)).toEqual(null); - expect(ChartConstants.calculateStepSize(-10, 0)).toEqual(2.5); + expect(ChartConstants.calculateStepSize(-10, 0)).toEqual(2); expect(ChartConstants.calculateStepSize(undefined, 0)).toEqual(null); // min higher than max @@ -26,9 +26,9 @@ describe("Chart constants", () => { }, ]; - expect(ChartConstants.getScaleOptions([], yAxis)).toEqual({ min: null, max: null, stepSize: null }); - expect(ChartConstants.getScaleOptions(datasets, yAxis)).toEqual({ min: 0, max: 1892, stepSize: 473 }); - expect(ChartConstants.getScaleOptions(null, yAxis)).toEqual(null); - expect(ChartConstants.getScaleOptions(null, null)).toEqual(null); + expect(ChartConstants.getScaleOptions([], yAxis, "line")).toEqual({ min: null, max: null, stepSize: null }); + expect(ChartConstants.getScaleOptions(datasets, yAxis, "line")).toEqual({ min: 0, max: 1892, stepSize: 378.4 }); + expect(ChartConstants.getScaleOptions(null, yAxis, "line")).toEqual({ min: null, max: null, stepSize: null }); + expect(ChartConstants.getScaleOptions(null, null, "line")).toEqual({ min: null, max: null, stepSize: null }); }); }); diff --git a/ui/src/app/shared/components/chart/chart.constants.ts b/ui/src/app/shared/components/chart/chart.constants.ts index 93bf4dc58e7..7c3d97ae2a3 100644 --- a/ui/src/app/shared/components/chart/chart.constants.ts +++ b/ui/src/app/shared/components/chart/chart.constants.ts @@ -1,19 +1,22 @@ // @ts-strict-ignore -import { ChartComponentLike, ChartDataset } from "chart.js"; - import { formatNumber } from "@angular/common"; import { TranslateService } from "@ngx-translate/core"; +import { ChartComponentLike, ChartDataset } from "chart.js"; import ChartDataLabels from "chartjs-plugin-datalabels"; +import { RGBColor } from "../../service/defaulttypes"; import { HistoryUtils, Utils } from "../../service/utils"; +import { Language } from "../../type/language"; import { ArrayUtils } from "../../utils/array/array.utils"; +import { AssertionUtils } from "../../utils/assertions/assertions-utils"; import { AbstractHistoryChart } from "./abstracthistorychart"; -export class ChartConstants { - public static readonly NUMBER_OF_Y_AXIS_TICKS: number = 6; - public static readonly EMPTY_DATASETS: ChartDataset[] = []; - public static readonly REQUEST_TIMEOUT = 500; +export namespace ChartConstants { + export const NUMBER_OF_Y_AXIS_TICKS: number = 7; + export const MAX_LENGTH_OF_Y_AXIS_TITLE: number = 6; + export const EMPTY_DATASETS: ChartDataset[] = []; + export const REQUEST_TIMEOUT = 500; - public static Plugins = class { + export class Plugins { public static readonly DEFAULT_EMPTY_SCREEN: (text: string) => ChartComponentLike = (text) => ({ id: "empty_chart", @@ -40,8 +43,10 @@ export class ChartConstants { */ public static readonly BAR_CHART_DATALABELS = (unit: string, disable: boolean): any => ({ ...ChartDataLabels, + color: getComputedStyle(document.documentElement).getPropertyValue("--ion-color-text"), formatter: (value, ctx) => { - return formatNumber(value, "de", "1.0-0") + "\xa0" + unit ?? null; + const locale: string = (Language.getByKey(localStorage.LANGUAGE) ?? Language.DEFAULT).i18nLocaleKey; + return formatNumber(value, locale, "1.0-0") + "\xa0" + unit; }, ...{ anchor: "end", offset: -18, align: "start", clip: false, clamp: true, @@ -49,7 +54,26 @@ export class ChartConstants { plugin: ChartDataLabels, display: disable, }); - }; + } + + export namespace Colors { + export const BLUE: string = new RGBColor(54, 174, 209).toString(); + export const RED: string = new RGBColor(255, 98, 63).toString(); + export const GREEN: string = new RGBColor(14, 190, 84).toString(); + export const ORANGE: string = new RGBColor(234, 147, 45).toString(); + export const PURPLE: string = new RGBColor(91, 92, 214).toString(); + export const YELLOW: string = new RGBColor(255, 206, 0).toString(); + export const BLUE_GREY: string = new RGBColor(77, 106, 130).toString(); + export const GREY: string = new RGBColor(189, 189, 189).toString(); + + export const SHADES_OF_RED: string[] = [RED, "rgb(204,78,50)", "rgb(153,59,38)", "rgb(102,39,25)", "rgb(51,20,13)"]; + export const SHADES_OF_GREEN: string[] = [GREEN, "rgb(11,152,67)", "rgb(8,114,50)", "rgb(6,76,34)", "rgb(3,38,17)"]; + export const SHADES_OF_YELLOW: string[] = [YELLOW, "rgb(204,165,0)", "rgb(153,124,0)", "rgb(102,82,0)", "rgb(255,221,77)"]; + } + + export class NumberFormat { + public static NO_DECIMALS: string = "1.0-0"; + } /** * Default yScale CartesianScaleTypeRegistry.linear @@ -60,27 +84,41 @@ export class ChartConstants { * @param datasets the chart datasets * @returns scale options */ - public static DEFAULT_Y_SCALE_OPTIONS = (element: HistoryUtils.yAxes, translate: TranslateService, chartType: "line" | "bar", datasets: ChartDataset[], showYAxisTitle?: boolean) => { + export const DEFAULT_Y_SCALE_OPTIONS = (element: HistoryUtils.yAxes, translate: TranslateService, chartType: "line" | "bar", datasets: ChartDataset[], showYAxisTitle?: boolean) => { const beginAtZero: boolean = ChartConstants.isDataSeriesPositive(datasets); + const scaleOptions: ReturnType = getScaleOptions(datasets, element, chartType); return { title: { - text: element.customTitle ?? AbstractHistoryChart.getYAxisType(element.unit, translate, chartType), - display: showYAxisTitle, + color: getComputedStyle(document.documentElement).getPropertyValue("--ion-color-chart-primary"), + text: element.customTitle ?? AbstractHistoryChart.getYAxisType(element.unit, translate, chartType, element.customTitle), + display: false, padding: 5, font: { size: 11, }, }, + stacked: chartType === "line" ? false : true, beginAtZero: beginAtZero, position: element.position, grid: { display: element.displayGrid ?? true, }, + ...(scaleOptions?.min !== null && { min: scaleOptions.min }), + ...(scaleOptions?.max !== null && { max: scaleOptions.max }), ticks: { color: getComputedStyle(document.documentElement).getPropertyValue("--ion-color-text"), padding: 5, maxTicksLimit: ChartConstants.NUMBER_OF_Y_AXIS_TICKS, + ...(scaleOptions?.stepSize && { stepSize: scaleOptions.stepSize }), + callback: function (value, index, ticks) { + if (index == (ticks.length - 1) && showYAxisTitle) { + const upperMostTick = element.customTitle ?? AbstractHistoryChart.getYAxisType(element.unit, translate, chartType); + AssertionUtils.assertHasMaxLength(upperMostTick, ChartConstants.MAX_LENGTH_OF_Y_AXIS_TITLE); + return upperMostTick; + } + return value; + }, }, }; }; @@ -92,11 +130,50 @@ export class ChartConstants { * @param yAxis the yAxis * @returns min, max and stepsize for datasets belonging to this yAxis */ - public static getScaleOptions(datasets: ChartDataset[], yAxis: HistoryUtils.yAxes): { min: number; max: number; stepSize: number; } | null { + export function getScaleOptions(datasets: ChartDataset[], yAxis: HistoryUtils.yAxes, chartType: "line" | "bar"): { min: number; max: number; stepSize: number; } | null { + + const stackMap: { [index: string]: ChartDataset } = {}; + datasets?.filter(el => el["yAxisID"] === yAxis.yAxisId).forEach((dataset, index) => { + const stackId = dataset.stack || "default"; // If no stack is defined, use "default" + + if (dataset.hidden) { + return; + } + + if (chartType === "line") { + stackMap[index] = dataset; + return; + } + + if (!(stackId in stackMap)) { + // If the stack doesn"t exist yet, create an entry + stackMap[stackId] = { ...dataset, data: [...dataset.data] }; + } else { + // If the stack already exists, merge the data arrays + stackMap[stackId].data = stackMap[stackId].data.map((value, index) => { + return Utils.addSafely(value as number, (dataset.data[index] as number)); // Sum data points or handle missing values + }); + } + }); + + return Object.values(stackMap) + .reduce((arr: { min: number, max: number, stepSize: number }, dataset: ChartDataset) => { + let currMin: number | null; + if (yAxis.scale?.dynamicScale) { + currMin = ArrayUtils.findSmallestNumber(dataset.data as number[]); + + if (chartType === "bar") { + // to start the y-axis a few percent below the lowest value + // Applies only bar charts with dynamic scale set to true (schedule charts) + currMin = Math.floor(currMin - (currMin * 0.05)); + } + } else { + + // Starts yAxis at least at 0 + currMin = ArrayUtils.findSmallestNumber([...dataset.data as number[], 0]); + } - return datasets?.filter(el => el["yAxisID"] === yAxis.yAxisId) - .reduce((arr, dataset) => { - const min = Math.floor(Math.min(arr.min, ArrayUtils.findSmallestNumber(dataset.data as number[]))) ?? null; + const min = Math.floor(Math.min(...[arr.min, currMin].filter(el => el != null))) ?? null; const max = Math.ceil(Math.max(arr.max, ArrayUtils.findBiggestNumber(dataset.data as number[]))) ?? null; if (max === null || min === null) { @@ -106,7 +183,7 @@ export class ChartConstants { arr = { min: min, max: max, - stepSize: Math.max(arr.stepSize, ChartConstants.calculateStepSize(min, max)), + stepSize: Math.max(arr?.stepSize ?? 0, ChartConstants.calculateStepSize(min, max)), }; return arr; @@ -121,7 +198,7 @@ export class ChartConstants { * @param max the maximum * @returns the stepSize if max and min are not null and min is smaller than max */ - public static calculateStepSize(min: number, max: number): number | null { + export function calculateStepSize(min: number, max: number): number | null { if (min == null || max == null || min > max) { return null; @@ -140,7 +217,7 @@ export class ChartConstants { * @param datasets the chart datasets * @returns true, if only positive data exists */ - private static isDataSeriesPositive(datasets: ChartDataset[]): boolean { + export function isDataSeriesPositive(datasets: ChartDataset[]): boolean { return datasets.filter(el => el != null).map(el => el.data).every(el => el.every(e => (e as number) >= 0)); } } diff --git a/ui/src/app/shared/components/chart/chart.html b/ui/src/app/shared/components/chart/chart.html index 075411661cb..2f02fd3dfe6 100644 --- a/ui/src/app/shared/components/chart/chart.html +++ b/ui/src/app/shared/components/chart/chart.html @@ -1,21 +1,22 @@ - - {{title}} - + + {{title}} + - + - + - + - + - +
    +
    diff --git a/ui/src/app/shared/components/chart/chart.module.ts b/ui/src/app/shared/components/chart/chart.module.ts index f86efd0b7df..b1993f939a4 100644 --- a/ui/src/app/shared/components/chart/chart.module.ts +++ b/ui/src/app/shared/components/chart/chart.module.ts @@ -4,7 +4,7 @@ import { BrowserModule } from "@angular/platform-browser"; import { RouterModule } from "@angular/router"; import { IonicModule } from "@ionic/angular"; import { TranslateModule } from "@ngx-translate/core"; -import { NgChartsModule } from "ng2-charts"; +import { BaseChartDirective } from "ng2-charts"; import { NgxSpinnerModule } from "ngx-spinner"; import { PipeModule } from "../../pipe/pipe"; @@ -18,7 +18,7 @@ import { ChartComponent } from "./chart"; IonicModule, PipeModule, TranslateModule, - NgChartsModule, + BaseChartDirective, CommonModule, NgxSpinnerModule.forRoot({ type: "ball-clip-rotate-multiple", diff --git a/ui/src/app/shared/components/chart/chart.ts b/ui/src/app/shared/components/chart/chart.ts index d2633c16f14..c55de430a8f 100644 --- a/ui/src/app/shared/components/chart/chart.ts +++ b/ui/src/app/shared/components/chart/chart.ts @@ -11,6 +11,7 @@ import { Edge, Service } from "../../shared"; @Component({ selector: "oe-chart", templateUrl: "./chart.html", + standalone: false, }) export class ChartComponent implements OnInit, OnChanges { diff --git a/ui/src/app/shared/components/components.module.ts b/ui/src/app/shared/components/components.module.ts index 8e39c2dc74e..cb3b444a6fb 100644 --- a/ui/src/app/shared/components/components.module.ts +++ b/ui/src/app/shared/components/components.module.ts @@ -9,9 +9,9 @@ import { PipeModule } from "../pipe/pipe"; import { ChartModule } from "./chart/chart.module"; import { FlatWidgetComponent } from "./flat/flat"; import { FlatWidgetHorizontalLineComponent } from "./flat/flat-widget-horizontal-line/flat-widget-horizontal-line"; -import { FlatWidgetLineDividerComponent } from "./flat/flat-widget-line-divider/flat-widget-line-divider"; import { FlatWidgetLineComponent } from "./flat/flat-widget-line/flat-widget-line"; import { FlatWidgetLineItemComponent } from "./flat/flat-widget-line/flat-widget-line-item/flat-widget-line-item"; +import { FlatWidgetLineDividerComponent } from "./flat/flat-widget-line-divider/flat-widget-line-divider"; import { FlatWidgetPercentagebarComponent } from "./flat/flat-widget-percentagebar/flat-widget-percentagebar"; import { FooterComponent } from "./footer/footer"; import { FooterNavigationModule } from "./footer/subnavigation/footerNavigation.module"; diff --git a/ui/src/app/shared/components/edge/edge.ts b/ui/src/app/shared/components/edge/edge.ts index babbb91da9e..64cb0fe962a 100644 --- a/ui/src/app/shared/components/edge/edge.ts +++ b/ui/src/app/shared/components/edge/edge.ts @@ -22,9 +22,9 @@ import { GetChannelResponse } from "../../jsonrpc/response/getChannelResponse"; import { Channel, GetChannelsOfComponentResponse } from "../../jsonrpc/response/getChannelsOfComponentResponse"; import { GetEdgeConfigResponse } from "../../jsonrpc/response/getEdgeConfigResponse"; import { GetPropertiesOfFactoryResponse } from "../../jsonrpc/response/getPropertiesOfFactoryResponse"; -import { ArrayUtils } from "../../service/arrayutils"; import { ChannelAddress, EdgePermission, SystemLog, Websocket } from "../../shared"; import { Role } from "../../type/role"; +import { ArrayUtils } from "../../utils/array/array.utils"; import { CurrentData } from "./currentdata"; import { EdgeConfig } from "./edgeconfig"; diff --git a/ui/src/app/shared/components/edge/edgeconfig.spec.ts b/ui/src/app/shared/components/edge/edgeconfig.spec.ts index ec7d23d4a2c..e4d7ecfc6ec 100644 --- a/ui/src/app/shared/components/edge/edgeconfig.spec.ts +++ b/ui/src/app/shared/components/edge/edgeconfig.spec.ts @@ -170,6 +170,19 @@ export namespace DummyConfig { "io.openems.edge.evcs.hardybarth.EvcsHardyBarth", "io.openems.edge.evcs.api.ManagedEvcs", "io.openems.edge.evcs.api.Evcs", + "io.openems.edge.evcs.api.DeprecatedEvcs", + "io.openems.edge.meter.api.ElectricityMeter", + ], + }; + + export const EVCS_MENNEKES = { + id: "Evcs.Mennekes", + natureIds: [ + "io.openems.edge.common.component.OpenemsComponent", + "io.openems.edge.evcs.hardybarth.EvcsHardyBarth", + "io.openems.edge.evcs.api.ManagedEvcs", + "io.openems.edge.evcs.api.Evcs", + "io.openems.edge.meter.api.ElectricityMeter", ], }; @@ -209,6 +222,17 @@ export namespace DummyConfig { channels: {}, }); + export const EVCS_MENNEKES = (id: string, alias?: string): Component => ({ + id: id, + alias: alias ?? id, + factoryId: "Evcs.Mennekes", + factory: Factory.EVCS_MENNEKES, + properties: { + enabled: "true", + }, + channels: {}, + }); + export const SOCOMEC_GRID_METER = (id: string, alias?: string): Component => ({ id: id, alias: alias ?? id, diff --git a/ui/src/app/shared/components/edge/edgeconfig.ts b/ui/src/app/shared/components/edge/edgeconfig.ts index 4b12b73aed2..0ddf42d24ca 100644 --- a/ui/src/app/shared/components/edge/edgeconfig.ts +++ b/ui/src/app/shared/components/edge/edgeconfig.ts @@ -115,7 +115,7 @@ export class EdgeConfig { category: { title: "Zähler", icon: "speedometer-outline" }, factories: [ EdgeConfig.getFactoriesByNature(factories, "io.openems.edge.meter.api.SymmetricMeter"), // TODO replaced by ElectricityMeter - EdgeConfig.getFactoriesByNature(factories, "io.openems.edge.meter.api.ElectricityMeter"), + EdgeConfig.getFactoriesByNature(factories, "io.openems.edge.meter.api.ElectricityMeter", "io.openems.edge.evcs.api.Evcs"), EdgeConfig.getFactoriesByNature(factories, "io.openems.edge.ess.dccharger.api.EssDcCharger"), ].flat(2), }, @@ -258,13 +258,20 @@ export class EdgeConfig { /** * Get Factories of Nature. * - * @param natureId the given Nature. + * @param factories the given EdgeConfig.Factory + * @param includeNature the name of the Nature to be included + * @param excludeNature an optional name of a Nature to be excluded */ - public static getFactoriesByNature(factories: { [id: string]: EdgeConfig.Factory }, natureId: string): EdgeConfig.Factory[] { + public static getFactoriesByNature(factories: { [id: string]: EdgeConfig.Factory }, includeNature: string, excludeNature?: string): EdgeConfig.Factory[] { const result = []; - const nature = EdgeConfig.getNaturesOfFactories(factories)[natureId]; - if (nature) { - for (const factoryId of nature.factoryIds) { + const natures = EdgeConfig.getNaturesOfFactories(factories); + const include = natures[includeNature]; + const excludes = excludeNature != null && excludeNature in natures ? natures[excludeNature].factoryIds : []; + if (include) { + for (const factoryId of include.factoryIds) { + if (excludes.includes(factoryId)) { + continue; + } if (factoryId in factories) { result.push(factories[factoryId]); } @@ -509,25 +516,19 @@ export class EdgeConfig { if (component.properties["type"] == "PRODUCTION") { return true; } + const natureIds = this.getNatureIdsByFactoryId(component.factoryId); + if (natureIds.includes("io.openems.edge.pvinverter.api.ManagedSymmetricPvInverter") + || natureIds.includes("io.openems.edge.ess.dccharger.api.EssDcCharger")) { + return true; + } // TODO properties in OSGi Component annotations are not transmitted correctly with Apache Felix SCR switch (component.factoryId) { case "Fenecon.Dess.PvMeter": case "Fenecon.Mini.PvMeter": case "Fenecon.Pro.PvMeter": - case "Kaco.BlueplanetHybrid10.PvInverter": - case "Kostal.Piko.Charger": - case "PV-Inverter.Fronius": - case "PV-Inverter.KACO.blueplanet": - case "PV-Inverter.Kostal": - case "PV-Inverter.SMA.SunnyTripower": - case "PV-Inverter.Solarlog": - case "PV-Inverter.SunSpec": case "Simulator.ProductionMeter.Acting": - case "Simulator.PvInverter": - case "SolarEdge.PV-Inverter": return true; } - return false; } @@ -540,11 +541,14 @@ export class EdgeConfig { public isTypeConsumptionMetered(component: EdgeConfig.Component) { if (component.properties["type"] == "CONSUMPTION_METERED") { return true; - } else { - switch (component.factoryId) { - case "GoodWe.EmergencyPowerMeter": - return true; - } + } + switch (component.factoryId) { + case "GoodWe.EmergencyPowerMeter": + return true; + } + const natures = this.getNatureIdsByFactoryId(component.factoryId); + if (natures.includes("io.openems.edge.evcs.api.Evcs") && !natures.includes("io.openems.edge.evcs.api.MetaEvcs")) { + return true; } return false; } @@ -680,6 +684,17 @@ export class EdgeConfig { return null; } } + + /** + * Safely gets a property from a component if it exists, else returns null. + * + * @param component The component from which to retrieve the property. + * @param property The property name to retrieve. + * @returns The property value if it exists, otherwise null. + */ + public getPropertyFromComponent(component: EdgeConfig.Component | null, property: string): T | null { + return component?.properties[property] ?? null; + } } export enum PersistencePriority { diff --git a/ui/src/app/shared/components/edge/meter/currentVoltage/chart/asymmetricMeter.ts b/ui/src/app/shared/components/edge/meter/currentVoltage/chart/asymmetricMeter.ts index 54d5f05ee5c..3856ddb0682 100644 --- a/ui/src/app/shared/components/edge/meter/currentVoltage/chart/asymmetricMeter.ts +++ b/ui/src/app/shared/components/edge/meter/currentVoltage/chart/asymmetricMeter.ts @@ -7,6 +7,7 @@ import { ChannelAddress } from "src/app/shared/shared"; @Component({ selector: "currentVoltageAsymmetricChart", templateUrl: "../../../../../components/chart/abstracthistorychart.html", + standalone: false, }) export class CurrentVoltageAsymmetricChartComponent extends AbstractHistoryChart { @@ -34,7 +35,7 @@ export class CurrentVoltageAsymmetricChartComponent extends AbstractHistoryChart }, hideShadow: true, color: currentPhasesColors[index], - yAxisId: ChartAxis.RIGHT, + yAxisId: ChartAxis.LEFT, })), ...Phase.THREE_PHASE.map((phase, index) => ({ name: this.translate.instant("Edge.History.VOLTAGE") + " " + phase, @@ -43,6 +44,7 @@ export class CurrentVoltageAsymmetricChartComponent extends AbstractHistoryChart }, hideShadow: true, color: voltagePhasesColors[index], + yAxisId: ChartAxis.RIGHT, })), ], tooltip: { @@ -51,13 +53,14 @@ export class CurrentVoltageAsymmetricChartComponent extends AbstractHistoryChart }, yAxes: [{ unit: YAxisType.VOLTAGE, - position: "left", - yAxisId: ChartAxis.LEFT, + position: "right", + yAxisId: ChartAxis.RIGHT, + displayGrid: false, }, { unit: YAxisType.CURRENT, - position: "right", - yAxisId: ChartAxis.RIGHT, + position: "left", + yAxisId: ChartAxis.LEFT, }, ], }; diff --git a/ui/src/app/shared/components/edge/meter/currentVoltage/chart/symmetricMeter.ts b/ui/src/app/shared/components/edge/meter/currentVoltage/chart/symmetricMeter.ts index e7998dda9f6..8a3c9c14776 100644 --- a/ui/src/app/shared/components/edge/meter/currentVoltage/chart/symmetricMeter.ts +++ b/ui/src/app/shared/components/edge/meter/currentVoltage/chart/symmetricMeter.ts @@ -6,6 +6,7 @@ import { ChannelAddress } from "src/app/shared/shared"; @Component({ selector: "currentVoltageChart", templateUrl: "../../../../../components/chart/abstracthistorychart.html", + standalone: false, }) export class CurrentVoltageSymmetricChartComponent extends AbstractHistoryChart { @@ -35,7 +36,7 @@ export class CurrentVoltageSymmetricChartComponent extends AbstractHistoryChart hiddenOnInit: false, stack: 1, - yAxisId: ChartAxis.RIGHT, + yAxisId: ChartAxis.LEFT, }, { name: this.translate.instant("Edge.History.VOLTAGE"), @@ -45,7 +46,7 @@ export class CurrentVoltageSymmetricChartComponent extends AbstractHistoryChart color: "rgb(255,0,0)", hiddenOnInit: false, stack: 1, - yAxisId: ChartAxis.LEFT, + yAxisId: ChartAxis.RIGHT, }, ], tooltip: { @@ -54,13 +55,14 @@ export class CurrentVoltageSymmetricChartComponent extends AbstractHistoryChart }, yAxes: [{ unit: YAxisType.VOLTAGE, - position: "left", - yAxisId: ChartAxis.LEFT, + position: "right", + yAxisId: ChartAxis.RIGHT, + displayGrid: false, }, { unit: YAxisType.CURRENT, - position: "right", - yAxisId: ChartAxis.RIGHT, + position: "left", + yAxisId: ChartAxis.LEFT, }, ], }; diff --git a/ui/src/app/shared/components/edge/meter/currentVoltage/currentVoltage.overview.html b/ui/src/app/shared/components/edge/meter/currentVoltage/currentVoltage.overview.html index 71727e415e6..0f1ff84692e 100644 --- a/ui/src/app/shared/components/edge/meter/currentVoltage/currentVoltage.overview.html +++ b/ui/src/app/shared/components/edge/meter/currentVoltage/currentVoltage.overview.html @@ -1,7 +1,8 @@ - + diff --git a/ui/src/app/shared/components/edge/meter/currentVoltage/currentVoltage.overview.ts b/ui/src/app/shared/components/edge/meter/currentVoltage/currentVoltage.overview.ts index 698d2bc6966..2578fd34c3c 100644 --- a/ui/src/app/shared/components/edge/meter/currentVoltage/currentVoltage.overview.ts +++ b/ui/src/app/shared/components/edge/meter/currentVoltage/currentVoltage.overview.ts @@ -3,6 +3,7 @@ import { AbstractHistoryChartOverview } from "src/app/shared/components/chart/ab @Component({ templateUrl: "./currentVoltage.overview.html", + standalone: false, }) export class CurrentAndVoltageOverviewComponent extends AbstractHistoryChartOverview { diff --git a/ui/src/app/shared/components/edge/meter/currentVoltage/currentVoltageModule.ts b/ui/src/app/shared/components/edge/meter/currentVoltage/currentVoltageModule.ts index 8455ccf34b3..ce96740e46f 100644 --- a/ui/src/app/shared/components/edge/meter/currentVoltage/currentVoltageModule.ts +++ b/ui/src/app/shared/components/edge/meter/currentVoltage/currentVoltageModule.ts @@ -2,7 +2,7 @@ import { NgModule } from "@angular/core"; import { BrowserModule } from "@angular/platform-browser"; import { IonicModule } from "@ionic/angular"; import { TranslateModule } from "@ngx-translate/core"; -import { NgChartsModule } from "ng2-charts"; +import { BaseChartDirective } from "ng2-charts"; import { NgxSpinnerModule } from "ngx-spinner"; import { FooterNavigationModule } from "src/app/shared/components/footer/subnavigation/footerNavigation.module"; import { ChartModule } from "../../../chart/chart.module"; @@ -18,8 +18,8 @@ import { CurrentAndVoltageOverviewComponent } from "./currentVoltage.overview"; IonicModule, FooterNavigationModule, TranslateModule, - NgChartsModule, - NgChartsModule, + BaseChartDirective, + BaseChartDirective, HistoryDataErrorModule, NgxSpinnerModule.forRoot({ type: "ball-clip-rotate-multiple", diff --git a/ui/src/app/shared/components/edge/meter/electricity/modal.component.ts b/ui/src/app/shared/components/edge/meter/electricity/modal.component.ts index 6e6d6e981a2..621eea9f092 100644 --- a/ui/src/app/shared/components/edge/meter/electricity/modal.component.ts +++ b/ui/src/app/shared/components/edge/meter/electricity/modal.component.ts @@ -7,6 +7,7 @@ import { Role } from "src/app/shared/type/role"; @Component({ selector: "oe-electricity-meter", templateUrl: "./modal.component.html", + standalone: false, }) export class ElectricityMeterComponent extends AbstractModalLine implements OnInit { diff --git a/ui/src/app/shared/components/edge/meter/esscharger/modal.component.ts b/ui/src/app/shared/components/edge/meter/esscharger/modal.component.ts index f469c1420f2..bf94c3fdd86 100644 --- a/ui/src/app/shared/components/edge/meter/esscharger/modal.component.ts +++ b/ui/src/app/shared/components/edge/meter/esscharger/modal.component.ts @@ -7,6 +7,7 @@ import { EdgeConfig } from "../../edgeconfig"; @Component({ selector: "oe-ess-charger", templateUrl: "./modal.component.html", + standalone: false, }) export class EssChargerComponent { @Input({ required: true }) public component!: EdgeConfig.Component; diff --git a/ui/src/app/shared/components/edge/meter/meter.module.ts b/ui/src/app/shared/components/edge/meter/meter.module.ts index c3ffddae822..9269892f3b1 100644 --- a/ui/src/app/shared/components/edge/meter/meter.module.ts +++ b/ui/src/app/shared/components/edge/meter/meter.module.ts @@ -3,7 +3,7 @@ import { NgModule } from "@angular/core"; import { BrowserModule } from "@angular/platform-browser"; import { IonicModule } from "@ionic/angular"; import { TranslateModule } from "@ngx-translate/core"; -import { NgChartsModule } from "ng2-charts"; +import { BaseChartDirective } from "ng2-charts"; import { NgxSpinnerModule } from "ngx-spinner"; import { PipeModule } from "src/app/shared/pipe/pipe"; @@ -18,7 +18,7 @@ import { EssChargerComponent } from "./esscharger/modal.component"; IonicModule, PipeModule, TranslateModule, - NgChartsModule, + BaseChartDirective, CommonModule, NgxSpinnerModule.forRoot({ type: "ball-clip-rotate-multiple", diff --git a/ui/src/app/shared/components/edge/offline/offline.component.html b/ui/src/app/shared/components/edge/offline/offline.component.html new file mode 100644 index 00000000000..4517b57f3c8 --- /dev/null +++ b/ui/src/app/shared/components/edge/offline/offline.component.html @@ -0,0 +1,6 @@ + + + + {{environment.edgeShortName}} ist offline + + diff --git a/ui/src/app/shared/components/edge/offline/offline.component.ts b/ui/src/app/shared/components/edge/offline/offline.component.ts new file mode 100644 index 00000000000..9c399a3bd1b --- /dev/null +++ b/ui/src/app/shared/components/edge/offline/offline.component.ts @@ -0,0 +1,86 @@ +import { Component, OnInit } from "@angular/core"; +import { TranslateService } from "@ngx-translate/core"; +import { Edge, Producttype, Service, Utils } from "src/app/shared/shared"; +import { Role } from "src/app/shared/type/role"; +import { DateUtils } from "src/app/shared/utils/date/dateutils"; +import { TimeUtils } from "src/app/shared/utils/time/timeutils"; +import { environment } from "src/environments"; + +@Component({ + selector: "oe-offline", + templateUrl: "./offline.component.html", + styles: [` + ion-item > ion-label > h3 { + font-weight: bolder; + } + `], + standalone: false, +}) +export class OfflineComponent implements OnInit { + + protected edge: Edge | null = null; + protected timeSinceOffline: string | null = null; + protected isAtLeastInstaller: boolean = false; + protected readonly environment = environment; + protected readonly Producttype = Producttype; + + constructor( + public service: Service, + private translate: TranslateService, + ) { } + + /** + * Formats a valid + * + * @param ms the milli seconds + * @param translate the translate service + * @returns a string if passed milli seconds are not null, else null + */ + public static formatMilliSecondsToValidRange(ms: number, translate: TranslateService): string { + const TWO_DAYS = 2 * 24 * 60 * 60 * 1000; + const TWO_HOURS = 2 * 60 * 60 * 1000; + let translationKey: { singular: string, plural: string } = { singular: "General.TIME.MINUTE", plural: "General.TIME.MINUTES" }; + let convertedSeconds: number = TimeUtils.getMinutesFromMilliSeconds(ms) ?? 0; + + if (ms > TWO_DAYS) { + convertedSeconds = TimeUtils.getDaysFromMilliSeconds(ms) ?? 0; + translationKey = { singular: "General.TIME.DAY", plural: "General.TIME.DAYS" }; + } else if (ms > TWO_HOURS) { + convertedSeconds = TimeUtils.getHoursFromMilliSeconds(ms) ?? 0; + translationKey = { singular: "General.TIME.HOUR", plural: "General.TIME.HOURS" }; + } + + return TimeUtils.getDurationText(convertedSeconds, translate, translationKey.singular, translationKey.plural); + } + + /** + * Gets a formatted text representing the time since the edge has been offline + * + * @param date the date string + * @param translate the translate service + * @returns a string if date is convertable to a Date, else null + */ + private static getTimeSinceEdgeIsOffline(date: string, translate: TranslateService): string | null { + + const _date: Date | null = DateUtils.stringToDate(date); + if (!_date) { + return null; + } + + const milliSeconds: number = _date.getTime(); + const _diff: number | null = Utils.subtractSafely(new Date().getTime(), milliSeconds); + + if (_diff === null) { + return null; + } + return OfflineComponent.formatMilliSecondsToValidRange(_diff, translate); + } + + ngOnInit() { + this.service.getCurrentEdge().then(edge => { + this.edge = edge; + this.isAtLeastInstaller = this.edge.roleIsAtLeast(Role.INSTALLER); + this.timeSinceOffline = OfflineComponent.getTimeSinceEdgeIsOffline(edge.lastmessage?.toString(), this.translate); + }); + } +} diff --git a/ui/src/app/shared/components/edge/offline/offline.module.ts b/ui/src/app/shared/components/edge/offline/offline.module.ts new file mode 100644 index 00000000000..31303623647 --- /dev/null +++ b/ui/src/app/shared/components/edge/offline/offline.module.ts @@ -0,0 +1,20 @@ +import { NgModule } from "@angular/core"; +import { BrowserModule } from "@angular/platform-browser"; +import { IonicModule } from "@ionic/angular"; +import { TranslateModule } from "@ngx-translate/core"; +import { OfflineComponent } from "./offline.component"; + +@NgModule({ + imports: [ + BrowserModule, + IonicModule, + TranslateModule, + ], + declarations: [ + OfflineComponent, + ], + exports: [ + OfflineComponent, + ], +}) +export class EdgeOfflineModule { } diff --git a/ui/src/app/shared/components/edge/offline/offline.spec.ts b/ui/src/app/shared/components/edge/offline/offline.spec.ts new file mode 100644 index 00000000000..f1f1fd128f2 --- /dev/null +++ b/ui/src/app/shared/components/edge/offline/offline.spec.ts @@ -0,0 +1,23 @@ +import { TestContext, TestingUtils } from "../../shared/testing/utils.spec"; +import { OfflineComponent } from "./offline.component"; + +describe("OfflineComponent", () => { + + let TEST_CONTEXT: TestContext; + beforeAll(async () => { + TEST_CONTEXT = await TestingUtils.sharedSetup(); + }); + + it("-formatMilliSecondsToValidRange - 1 minute", () => { + const ms: number = 60000; + expect(OfflineComponent.formatMilliSecondsToValidRange(ms, TEST_CONTEXT.translate)).toBe("1 Minute"); + }); + it("-formatMilliSecondsToValidRange - 2 hours", () => { + const ms: number = 60000 * 121; + expect(OfflineComponent.formatMilliSecondsToValidRange(ms, TEST_CONTEXT.translate)).toBe("2 Stunden"); + }); + it("-formatMilliSecondsToValidRange - 1 day", () => { + const ms: number = 3 * 24 * 60 * 60 * 1000; + expect(OfflineComponent.formatMilliSecondsToValidRange(ms, TEST_CONTEXT.translate)).toBe("3 Tage"); + }); +}); diff --git a/ui/src/app/shared/components/edge/utils/evcs-utils.spec.ts b/ui/src/app/shared/components/edge/utils/evcs-utils.spec.ts new file mode 100644 index 00000000000..766c267638a --- /dev/null +++ b/ui/src/app/shared/components/edge/utils/evcs-utils.spec.ts @@ -0,0 +1,51 @@ +// @ts-strict-ignore +import { DummyConfig } from "../edgeconfig.spec"; +import { EvcsUtils } from "./evcs-utils"; + +describe("EvcsUtils", () => { + const config = DummyConfig.from( + DummyConfig.Component.EVCS_HARDY_BARTH("evcs0", "Charging Station"), + DummyConfig.Component.EVCS_MENNEKES("evcs1", "Wallbox"), + ); + + it("#getEvcsPowerChannelId should return 'ChargePower' when edge is null", () => { + const result = EvcsUtils.getEvcsPowerChannelId( + config.getComponent("evcs0"), + config, + null, + ); + expect(result).toBe("ChargePower"); + }); + + it("#getEvcsPowerChannelId should return 'ChargePower' for old edges", () => { + const result = EvcsUtils.getEvcsPowerChannelId( + config.getComponent("evcs0"), + config, + DummyConfig.dummyEdge({ version: "2024.10.1" }), + ); + expect(result).toBe("ChargePower"); + }); + + it("#getEvcsPowerChannelId should return 'ChargePower' for deprecated components", () => { + const result = EvcsUtils.getEvcsPowerChannelId( + config.getComponent("evcs0"), + config, + DummyConfig.dummyEdge({ version: "2024.10.2" }), + ); + expect(result).toBe("ChargePower"); + }); + + it("#getEvcsPowerChannelId should return 'ActivePower' for non deprecated components", () => { + const result = EvcsUtils.getEvcsPowerChannelId( + config.getComponent("evcs1"), + config, + DummyConfig.dummyEdge({ version: "2024.10.2" }), + ); + expect(result).toBe("ActivePower"); + }); + + it("#getEvcsPowerChannelId should return 'ChargePower' for null inputs", () => { + const result = EvcsUtils.getEvcsPowerChannelId(null, null, DummyConfig.dummyEdge({ version: "2024.10.2" })); + expect(result).toBe("ChargePower"); + }); +}); diff --git a/ui/src/app/shared/components/edge/utils/evcs-utils.ts b/ui/src/app/shared/components/edge/utils/evcs-utils.ts new file mode 100644 index 00000000000..f26ae710c3b --- /dev/null +++ b/ui/src/app/shared/components/edge/utils/evcs-utils.ts @@ -0,0 +1,24 @@ +import { Edge } from "../edge"; +import { EdgeConfig } from "../edgeconfig"; + +export class EvcsUtils { + + /** + * Retrieves the appropriate power channel ID for an Electric Vehicle Charging Station (EVCS) component. + * + * The method returns "ActivePower", unless the given `edge` object does not meet the minimum + * required version or the component implements DeprecatedEvcs, in which case it returns "ChargePower". + * + * @param component - The component for which to determine the power channel ID. + * @param config - The EdgeConfig. + * @param edge - The edge instance + * @returns - Returns "ActivePower" if the edge version is at least "2024.10.2" and + * the component is not deprecated. Otherwise, returns "ChargePower". + */ + public static getEvcsPowerChannelId(component: EdgeConfig.Component, config: EdgeConfig, edge: Edge | null): "ActivePower" | "ChargePower" { + if (edge && component && config && (!config.hasComponentNature("io.openems.edge.evcs.api.DeprecatedEvcs", component.id) && edge.isVersionAtLeast("2024.10.2"))) { + return "ActivePower"; + } + return "ChargePower"; + } +} diff --git a/ui/src/app/shared/components/flat/abstract-flat-widget-line.ts b/ui/src/app/shared/components/flat/abstract-flat-widget-line.ts index 48738330c12..04821ded125 100644 --- a/ui/src/app/shared/components/flat/abstract-flat-widget-line.ts +++ b/ui/src/app/shared/components/flat/abstract-flat-widget-line.ts @@ -4,8 +4,8 @@ import { ActivatedRoute } from "@angular/router"; import { ModalController } from "@ionic/angular"; import { Subject } from "rxjs"; import { takeUntil } from "rxjs/operators"; -import { ChannelAddress, Edge, Service, Websocket } from "src/app/shared/shared"; import { v4 as uuidv4 } from "uuid"; +import { ChannelAddress, Edge, Service, Websocket } from "src/app/shared/shared"; import { DataService } from "../shared/dataservice"; import { Filter } from "../shared/filter"; @@ -30,7 +30,10 @@ export abstract class AbstractFlatWidgetLine implements OnChanges, OnDestroy { */ public displayValue: string | null = null; + protected displayName: string = null; protected show: boolean = true; + + private _name: string | ((value: any) => string); private _channelAddress: ChannelAddress | null = null; /** @@ -48,6 +51,15 @@ export abstract class AbstractFlatWidgetLine implements OnChanges, OnDestroy { @Inject(DataService) private dataService: DataService, ) { } + @Input() set name(value: string | { channel: ChannelAddress, converter: (value: any) => string }) { + if (typeof value === "object") { + this.subscribe(value.channel); + this._name = value.converter; + } else { + this._name = value; + } + } + /** Channel defines the channel, you need for this line */ @Input() set channelAddress(channelAddress: string) { @@ -79,7 +91,14 @@ export abstract class AbstractFlatWidgetLine implements OnChanges, OnDestroy { } protected setValue(value: any) { + if (typeof this._name == "function") { + this.displayName = this._name(value); + + } else { + this.displayName = this._name; + } this.displayValue = this.converter(value); + if (this.filter) { this.show = this.filter(value); } diff --git a/ui/src/app/shared/components/flat/abstract-flat-widget.ts b/ui/src/app/shared/components/flat/abstract-flat-widget.ts index bddb7f5283e..4a4e6131ada 100644 --- a/ui/src/app/shared/components/flat/abstract-flat-widget.ts +++ b/ui/src/app/shared/components/flat/abstract-flat-widget.ts @@ -1,14 +1,14 @@ // @ts-strict-ignore -import { Directive, Inject, Input, OnDestroy, OnInit } from "@angular/core"; +import { Directive, Input, OnDestroy, OnInit, Inject } from "@angular/core"; +import { FormBuilder, FormGroup } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; import { ModalController } from "@ionic/angular"; import { TranslateService } from "@ngx-translate/core"; import { Subject } from "rxjs"; import { takeUntil } from "rxjs/operators"; -import { ChannelAddress, CurrentData, Edge, EdgeConfig, Utils } from "src/app/shared/shared"; import { v4 as uuidv4 } from "uuid"; +import { ChannelAddress, CurrentData, Edge, EdgeConfig, Utils } from "src/app/shared/shared"; -import { FormBuilder, FormGroup } from "@angular/forms"; import { Service } from "../../service/service"; import { Websocket } from "../../service/websocket"; import { Converter } from "../shared/converter"; diff --git a/ui/src/app/shared/components/flat/flat-widget-horizontal-line/flat-widget-horizontal-line.ts b/ui/src/app/shared/components/flat/flat-widget-horizontal-line/flat-widget-horizontal-line.ts index 514fbd0c68b..1c07146282f 100644 --- a/ui/src/app/shared/components/flat/flat-widget-horizontal-line/flat-widget-horizontal-line.ts +++ b/ui/src/app/shared/components/flat/flat-widget-horizontal-line/flat-widget-horizontal-line.ts @@ -6,6 +6,7 @@ import { Component, Input } from "@angular/core"; @Component({ selector: "oe-flat-widget-horizontal-line", templateUrl: "./flat-widget-horizontal-line.html", + standalone: false, }) export class FlatWidgetHorizontalLineComponent { /** Components-Array to iterate over */ diff --git a/ui/src/app/shared/components/flat/flat-widget-line-divider/flat-widget-line-divider.ts b/ui/src/app/shared/components/flat/flat-widget-line-divider/flat-widget-line-divider.ts index 059b5e487fe..a56d602898d 100644 --- a/ui/src/app/shared/components/flat/flat-widget-line-divider/flat-widget-line-divider.ts +++ b/ui/src/app/shared/components/flat/flat-widget-line-divider/flat-widget-line-divider.ts @@ -8,6 +8,7 @@ import { Icon } from "src/app/shared/type/widget"; @Component({ selector: "oe-flat-widget-line-divider", templateUrl: "./flat-widget-line-divider.html", + standalone: false, }) export class FlatWidgetLineDividerComponent { diff --git a/ui/src/app/shared/components/flat/flat-widget-line/flat-widget-line-item/flat-widget-line-item.html b/ui/src/app/shared/components/flat/flat-widget-line/flat-widget-line-item/flat-widget-line-item.html index 7ac84753d40..a33df0902d6 100644 --- a/ui/src/app/shared/components/flat/flat-widget-line/flat-widget-line-item/flat-widget-line-item.html +++ b/ui/src/app/shared/components/flat/flat-widget-line/flat-widget-line-item/flat-widget-line-item.html @@ -1,4 +1,5 @@ -

    diff --git a/ui/src/app/shared/components/flat/flat-widget-line/flat-widget-line-item/flat-widget-line-item.ts b/ui/src/app/shared/components/flat/flat-widget-line/flat-widget-line-item/flat-widget-line-item.ts index 5b5885ced53..a441ccac75b 100644 --- a/ui/src/app/shared/components/flat/flat-widget-line/flat-widget-line-item/flat-widget-line-item.ts +++ b/ui/src/app/shared/components/flat/flat-widget-line/flat-widget-line-item/flat-widget-line-item.ts @@ -5,5 +5,6 @@ import { AbstractFlatWidgetLine } from "../../abstract-flat-widget-line"; /** If multiple items in line use this */ selector: "oe-flat-widget-line-item", templateUrl: "./flat-widget-line-item.html", + standalone: false, }) export class FlatWidgetLineItemComponent extends AbstractFlatWidgetLine { } diff --git a/ui/src/app/shared/components/flat/flat-widget-line/flat-widget-line.html b/ui/src/app/shared/components/flat/flat-widget-line/flat-widget-line.html index 46424574ca4..34a66cd6ce6 100644 --- a/ui/src/app/shared/components/flat/flat-widget-line/flat-widget-line.html +++ b/ui/src/app/shared/components/flat/flat-widget-line/flat-widget-line.html @@ -1,11 +1,11 @@ -
    diff --git a/ui/src/app/edge/history/peakshaving/timeslot/widget.component.ts b/ui/src/app/edge/history/peakshaving/timeslot/widget.component.ts index 7b23394df53..73869c295c8 100644 --- a/ui/src/app/edge/history/peakshaving/timeslot/widget.component.ts +++ b/ui/src/app/edge/history/peakshaving/timeslot/widget.component.ts @@ -6,6 +6,7 @@ import { Edge, EdgeConfig, Service } from "src/app/shared/shared"; @Component({ selector: TimeslotPeakshavingWidgetComponent.SELECTOR, templateUrl: "./widget.component.html", + standalone: false, }) export class TimeslotPeakshavingWidgetComponent implements OnInit { diff --git a/ui/src/app/edge/history/shared.ts b/ui/src/app/edge/history/shared.ts index ac05ac3b66f..c946f9b6b3c 100644 --- a/ui/src/app/edge/history/shared.ts +++ b/ui/src/app/edge/history/shared.ts @@ -1,7 +1,10 @@ // @ts-strict-ignore import * as Chart from "chart.js"; +/* eslint-disable import/no-duplicates */ +// cf. https://github.com/import-js/eslint-plugin-import/issues/1479 import { differenceInDays, differenceInMinutes, startOfDay } from "date-fns"; import { de } from "date-fns/locale"; +/* eslint-enable import/no-duplicates */ import { QueryHistoricTimeseriesDataResponse } from "src/app/shared/jsonrpc/response/queryHistoricTimeseriesDataResponse"; import { ChannelAddress, Service } from "src/app/shared/shared"; import { DateUtils } from "src/app/shared/utils/date/dateutils"; @@ -12,6 +15,12 @@ export interface Dataset { hidden: boolean; } +export enum Theme { + LIGHT = "light", + DARK = "dark", + SYSTEM = "system", +} + export const EMPTY_DATASET = [{ label: "no Data available", data: [], @@ -128,7 +137,7 @@ export type ChartOptions = { ticks: { source?: string, maxTicksLimit?: number - } + }, }] }, tooltips: { @@ -204,8 +213,7 @@ export const DEFAULT_TIME_CHART_OPTIONS = (): Chart.ChartOptions => ({ stacked: true, offset: false, type: "time", - ticks: { - }, + ticks: {}, bounds: "data", adapters: { date: { diff --git a/ui/src/app/edge/history/storage/chargerchart.component.ts b/ui/src/app/edge/history/storage/chargerchart.component.ts index 44395de3842..c0cda83dfcb 100644 --- a/ui/src/app/edge/history/storage/chargerchart.component.ts +++ b/ui/src/app/edge/history/storage/chargerchart.component.ts @@ -10,6 +10,7 @@ import { AbstractHistoryChart } from "../abstracthistorychart"; @Component({ selector: "storageChargerChart", templateUrl: "../abstracthistorychart.html", + standalone: false, }) export class StorageChargerChartComponent extends AbstractHistoryChart implements OnInit, OnChanges, OnDestroy { @@ -70,7 +71,7 @@ export class StorageChargerChartComponent extends AbstractHistoryChart implement }); if (address.channelId == "ActualPower") { datasets.push({ - label: this.translate.instant("General.chargePower"), + label: this.translate.instant("General.CHARGE"), data: chargerData, hidden: false, }); diff --git a/ui/src/app/edge/history/storage/esschart.component.ts b/ui/src/app/edge/history/storage/esschart.component.ts index cd83e95a11f..0f41a18dea5 100644 --- a/ui/src/app/edge/history/storage/esschart.component.ts +++ b/ui/src/app/edge/history/storage/esschart.component.ts @@ -10,6 +10,7 @@ import { AbstractHistoryChart } from "../abstracthistorychart"; @Component({ selector: "storageESSChart", templateUrl: "../abstracthistorychart.html", + standalone: false, }) export class StorageESSChartComponent extends AbstractHistoryChart implements OnInit, OnChanges, OnDestroy { diff --git a/ui/src/app/edge/history/storage/singlechart.component.ts b/ui/src/app/edge/history/storage/singlechart.component.ts index 4d987a4b2ee..573f7145b0e 100644 --- a/ui/src/app/edge/history/storage/singlechart.component.ts +++ b/ui/src/app/edge/history/storage/singlechart.component.ts @@ -6,13 +6,16 @@ import { TranslateService } from "@ngx-translate/core"; import * as Chart from "chart.js"; import { DefaultTypes } from "src/app/shared/service/defaulttypes"; import { ChartAxis, YAxisType } from "src/app/shared/service/utils"; +import { Language } from "src/app/shared/type/language"; +import { ObjectUtils } from "src/app/shared/utils/object/object.utils"; import { ChannelAddress, Edge, EdgeConfig, Service, Utils } from "../../../shared/shared"; import { AbstractHistoryChart } from "../abstracthistorychart"; @Component({ selector: "storageSingleChart", templateUrl: "../abstracthistorychart.html", + standalone: false, }) export class StorageSingleChartComponent extends AbstractHistoryChart implements OnInit, OnChanges, OnDestroy { @@ -155,7 +158,7 @@ export class StorageSingleChartComponent extends AbstractHistoryChart implements borderColor: "rgba(0,223,0,1)", }); } - if ("_sum/EssActivePowerL1" && "_sum/EssActivePowerL2" && "_sum/EssActivePowerL3" in result.data && this.showPhases == true) { + if (ObjectUtils.hasKeys(result.data, ["_sum/EssActivePowerL1", "_sum/EssActivePowerL2", "_sum/EssActivePowerL3"]) && this.showPhases == true) { if (channelAddress.channelId == "EssActivePowerL1") { datasets.push({ label: this.translate.instant("General.phase") + " " + "L1", @@ -208,6 +211,7 @@ export class StorageSingleChartComponent extends AbstractHistoryChart implements private applyControllerSpecificChartOptions(options: Chart.ChartOptions) { const translate = this.translate; + const locale: string = (Language.getByKey(localStorage.LANGUAGE) ?? Language.DEFAULT).i18nLocaleKey; options.scales[ChartAxis.LEFT].min = null; options.plugins.tooltip.callbacks.label = function (tooltipItem: Chart.TooltipItem) { @@ -216,18 +220,18 @@ export class StorageSingleChartComponent extends AbstractHistoryChart implements // 0.005 to prevent showing Charge or Discharge if value is e.g. 0.00232138 if (value < -0.005) { if (label.includes(translate.instant("General.phase"))) { - label += " " + translate.instant("General.chargePower"); + label += " " + translate.instant("General.CHARGE"); } else { - label = translate.instant("General.chargePower"); + label = translate.instant("General.CHARGE"); } } else if (value > 0.005) { if (label.includes(translate.instant("General.phase"))) { - label += " " + translate.instant("General.dischargePower"); + label += " " + translate.instant("General.DISCHARGE"); } else { - label = translate.instant("General.dischargePower"); + label = translate.instant("General.DISCHARGE"); } } - return label + ": " + formatNumber(value, "de", "1.0-2") + " kW"; + return label + ": " + formatNumber(value, locale, "1.0-2") + " kW"; }; // Data doesnt have all datapoints for period diff --git a/ui/src/app/edge/history/storage/socchart.component.ts b/ui/src/app/edge/history/storage/socchart.component.ts index bf127df1811..7d79d1fce72 100644 --- a/ui/src/app/edge/history/storage/socchart.component.ts +++ b/ui/src/app/edge/history/storage/socchart.component.ts @@ -11,6 +11,7 @@ import { AbstractHistoryChart } from "../abstracthistorychart"; @Component({ selector: "socStorageChart", templateUrl: "../abstracthistorychart.html", + standalone: false, }) export class SocStorageChartComponent extends AbstractHistoryChart implements OnInit, OnChanges, OnDestroy { @@ -82,8 +83,8 @@ export class SocStorageChartComponent extends AbstractHistoryChart implements On data: data, }); this.colors.push({ - backgroundColor: "rgba(0,223,0,0.05)", - borderColor: "rgba(0,223,0,1)", + backgroundColor: getComputedStyle(document.documentElement).getPropertyValue("--ion-color-charge-rgba"), + borderColor: getComputedStyle(document.documentElement).getPropertyValue("--ion-color-charge-primary"), }); } if (channelAddress.channelId === "Soc" && moreThanOneESS) { @@ -92,8 +93,8 @@ export class SocStorageChartComponent extends AbstractHistoryChart implements On data: data, }); this.colors.push({ - backgroundColor: "rgba(128,128,128,0.05)", - borderColor: "rgba(128,128,128,1)", + backgroundColor: getComputedStyle(document.documentElement).getPropertyValue("--ion-color-grey-rgba"), + borderColor: getComputedStyle(document.documentElement).getPropertyValue("--ion-color-grey-primary"), }); } } @@ -106,8 +107,8 @@ export class SocStorageChartComponent extends AbstractHistoryChart implements On }); this.colors.push({ - backgroundColor: "rgba(1, 1, 1,0)", - borderColor: "rgba(1, 1, 1,1)", + backgroundColor: getComputedStyle(document.documentElement).getPropertyValue("--ion-color-emergencyreserve-rgba"), + borderColor: getComputedStyle(document.documentElement).getPropertyValue("--ion-color-emergencyreserve-primary"), }); } }); diff --git a/ui/src/app/edge/history/storage/storagechartoverview/storagechartoverview.component.html b/ui/src/app/edge/history/storage/storagechartoverview/storagechartoverview.component.html index 749335aba84..e145a52ca1e 100644 --- a/ui/src/app/edge/history/storage/storagechartoverview/storagechartoverview.component.html +++ b/ui/src/app/edge/history/storage/storagechartoverview/storagechartoverview.component.html @@ -1,19 +1,20 @@ - - General.storageSystem + + General.storageSystem - + - + - + - + diff --git a/ui/src/app/edge/history/storage/storagechartoverview/storagechartoverview.component.ts b/ui/src/app/edge/history/storage/storagechartoverview/storagechartoverview.component.ts index 9562ad8825a..d774dad9866 100644 --- a/ui/src/app/edge/history/storage/storagechartoverview/storagechartoverview.component.ts +++ b/ui/src/app/edge/history/storage/storagechartoverview/storagechartoverview.component.ts @@ -6,6 +6,7 @@ import { Edge, EdgeConfig, Service, Utils } from "../../../../shared/shared"; @Component({ selector: StorageChartOverviewComponent.SELECTOR, templateUrl: "./storagechartoverview.component.html", + standalone: false, }) export class StorageChartOverviewComponent implements OnInit { diff --git a/ui/src/app/edge/history/storage/totalchart.component.ts b/ui/src/app/edge/history/storage/totalchart.component.ts index 13375ae97db..4be181bc8fa 100644 --- a/ui/src/app/edge/history/storage/totalchart.component.ts +++ b/ui/src/app/edge/history/storage/totalchart.component.ts @@ -1,4 +1,5 @@ // @ts-strict-ignore +import { formatNumber } from "@angular/common"; import { Component, Input, OnChanges, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; import { TranslateService } from "@ngx-translate/core"; @@ -6,13 +7,15 @@ import * as Chart from "chart.js"; import { DefaultTypes } from "src/app/shared/service/defaulttypes"; import { ChartAxis, Utils, YAxisType } from "src/app/shared/service/utils"; import { ChannelAddress, Edge, EdgeConfig, Service } from "src/app/shared/shared"; +import { Language } from "src/app/shared/type/language"; -import { formatNumber } from "@angular/common"; +import { ObjectUtils } from "src/app/shared/utils/object/object.utils"; import { AbstractHistoryChart } from "../abstracthistorychart"; @Component({ selector: "storageTotalChart", templateUrl: "../abstracthistorychart.html", + standalone: false, }) export class StorageTotalChartComponent extends AbstractHistoryChart implements OnInit, OnChanges, OnDestroy { @@ -110,7 +113,8 @@ export class StorageTotalChartComponent extends AbstractHistoryChart implements backgroundColor: "rgba(0,223,0,0.05)", borderColor: "rgba(0,223,0,1)", }); - } if ("_sum/EssActivePowerL1" && "_sum/EssActivePowerL2" && "_sum/EssActivePowerL3" in result.data && this.showPhases == true) { + + } if (ObjectUtils.hasKeys(result.data, ["_sum/EssActivePowerL1", "_sum/EssActivePowerL2", "_sum/EssActivePowerL3"]) && this.showPhases == true) { if (channelAddress.channelId == "EssActivePowerL1") { datasets.push({ label: this.translate.instant("General.phase") + " " + "L1", @@ -245,6 +249,7 @@ export class StorageTotalChartComponent extends AbstractHistoryChart implements private applyControllerSpecificChartOptions(options: Chart.ChartOptions) { const translate = this.translate; + const locale: string = (Language.getByKey(localStorage.LANGUAGE) ?? Language.DEFAULT).i18nLocaleKey; options.scales[ChartAxis.LEFT].min = null; options.plugins.tooltip.callbacks.label = function (tooltipItem: Chart.TooltipItem) { @@ -252,11 +257,11 @@ export class StorageTotalChartComponent extends AbstractHistoryChart implements const value = tooltipItem.dataset.data[tooltipItem.dataIndex]; // 0.005 to prevent showing Charge or Discharge if value is e.g. 0.00232138 if (value < -0.005) { - label += " " + translate.instant("General.chargePower"); + label += " " + translate.instant("General.CHARGE"); } else if (value > 0.005) { - label += " " + translate.instant("General.dischargePower"); + label += " " + translate.instant("General.DISCHARGE"); } - return label + ": " + formatNumber(value, "de", "1.0-2") + " kW"; + return label + ": " + formatNumber(value, locale, "1.0-2") + " kW"; }; } } diff --git a/ui/src/app/edge/history/storage/widget.component.html b/ui/src/app/edge/history/storage/widget.component.html index 79f3aaac4ce..3581e9efb66 100644 --- a/ui/src/app/edge/history/storage/widget.component.html +++ b/ui/src/app/edge/history/storage/widget.component.html @@ -1,19 +1,19 @@ - - + + General.storageSystem - + - + - + diff --git a/ui/src/app/edge/history/storage/widget.component.ts b/ui/src/app/edge/history/storage/widget.component.ts index 9e4b95790f8..0545e1b99ad 100644 --- a/ui/src/app/edge/history/storage/widget.component.ts +++ b/ui/src/app/edge/history/storage/widget.component.ts @@ -9,6 +9,7 @@ import { AbstractHistoryWidget } from "../abstracthistorywidget"; @Component({ selector: StorageComponent.SELECTOR, templateUrl: "./widget.component.html", + standalone: false, }) export class StorageComponent extends AbstractHistoryWidget implements OnInit, OnChanges, OnDestroy { diff --git a/ui/src/app/edge/live/Controller/Channelthreshold/Channelthreshold.html b/ui/src/app/edge/live/Controller/Channelthreshold/Channelthreshold.html index 8cb74e66409..21cf476ec98 100644 --- a/ui/src/app/edge/live/Controller/Channelthreshold/Channelthreshold.html +++ b/ui/src/app/edge/live/Controller/Channelthreshold/Channelthreshold.html @@ -1,6 +1,6 @@ - + diff --git a/ui/src/app/edge/live/Controller/Channelthreshold/Channelthreshold.ts b/ui/src/app/edge/live/Controller/Channelthreshold/Channelthreshold.ts index e33b705a5a7..1998f808a15 100644 --- a/ui/src/app/edge/live/Controller/Channelthreshold/Channelthreshold.ts +++ b/ui/src/app/edge/live/Controller/Channelthreshold/Channelthreshold.ts @@ -8,6 +8,7 @@ import { ChannelAddress, CurrentData } from "../../../../shared/shared"; @Component({ selector: "Controller_Channelthreshold", templateUrl: "./Channelthreshold.html", + standalone: false, }) export class Controller_ChannelthresholdComponent extends AbstractFlatWidget { @@ -15,7 +16,7 @@ export class Controller_ChannelthresholdComponent extends AbstractFlatWidget { public icon: Icon = { name: "", size: "large", - color: "dark", + color: "normal", }; public state: string = "?"; diff --git a/ui/src/app/edge/live/Controller/ChpSoc/ChpSoc.html b/ui/src/app/edge/live/Controller/ChpSoc/ChpSoc.html index 8dc374e330e..7377618d8e8 100644 --- a/ui/src/app/edge/live/Controller/ChpSoc/ChpSoc.html +++ b/ui/src/app/edge/live/Controller/ChpSoc/ChpSoc.html @@ -1,30 +1,28 @@ + 'flame-outline', color: 'normal'}"> - + - - {{ - lowThresholdValue + - " %" }} - {{ - highThresholdValue + - " %" }} - + + + {{lowThresholdValue + " %" }} + + + {{highThresholdValue + " %" }} + + diff --git a/ui/src/app/edge/live/Controller/ChpSoc/ChpSoc.ts b/ui/src/app/edge/live/Controller/ChpSoc/ChpSoc.ts index 48adf7600d5..0c0d5ef3256 100644 --- a/ui/src/app/edge/live/Controller/ChpSoc/ChpSoc.ts +++ b/ui/src/app/edge/live/Controller/ChpSoc/ChpSoc.ts @@ -9,6 +9,7 @@ import { Controller_ChpSocModalComponent } from "./modal/modal.component"; @Component({ selector: "Controller_ChpSocComponent", templateUrl: "./ChpSoc.html", + standalone: false, }) export class Controller_ChpSocComponent extends AbstractFlatWidget { @@ -28,6 +29,11 @@ export class Controller_ChpSocComponent extends AbstractFlatWidget { color: "primary", }; + protected get thresholdDelta() { + const delta = this.highThresholdValue - this.lowThresholdValue; + return delta < 0 ? 0 : delta; + } + async presentModal() { const modal = await this.modalController.create({ component: Controller_ChpSocModalComponent, diff --git a/ui/src/app/edge/live/Controller/ChpSoc/modal/modal.component.html b/ui/src/app/edge/live/Controller/ChpSoc/modal/modal.component.html index 531dec7234d..5d70991a5f3 100644 --- a/ui/src/app/edge/live/Controller/ChpSoc/modal/modal.component.html +++ b/ui/src/app/edge/live/Controller/ChpSoc/modal/modal.component.html @@ -1,5 +1,5 @@ - + {{ component.alias }} @@ -27,20 +27,23 @@ General.on - + General.automatic - + General.off - + diff --git a/ui/src/app/edge/live/Controller/ChpSoc/modal/modal.component.ts b/ui/src/app/edge/live/Controller/ChpSoc/modal/modal.component.ts index 0c557361d5a..be0198c29c6 100644 --- a/ui/src/app/edge/live/Controller/ChpSoc/modal/modal.component.ts +++ b/ui/src/app/edge/live/Controller/ChpSoc/modal/modal.component.ts @@ -12,6 +12,7 @@ type mode = "MANUAL_ON" | "MANUAL_OFF" | "AUTOMATIC"; @Component({ selector: Controller_ChpSocModalComponent.SELECTOR, templateUrl: "./modal.component.html", + standalone: false, }) export class Controller_ChpSocModalComponent implements OnInit { diff --git a/ui/src/app/edge/live/Controller/Ess/FixActivePower/flat/flat.html b/ui/src/app/edge/live/Controller/Ess/FixActivePower/flat/flat.html index 61ce74a5bd8..9a4f516f640 100644 --- a/ui/src/app/edge/live/Controller/Ess/FixActivePower/flat/flat.html +++ b/ui/src/app/edge/live/Controller/Ess/FixActivePower/flat/flat.html @@ -1,5 +1,6 @@ - + diff --git a/ui/src/app/edge/live/Controller/Ess/FixActivePower/flat/flat.ts b/ui/src/app/edge/live/Controller/Ess/FixActivePower/flat/flat.ts index 73429cc8694..54d587f1acf 100644 --- a/ui/src/app/edge/live/Controller/Ess/FixActivePower/flat/flat.ts +++ b/ui/src/app/edge/live/Controller/Ess/FixActivePower/flat/flat.ts @@ -9,6 +9,7 @@ import { ModalComponent } from "../modal/modal"; @Component({ selector: "Controller_Ess_FixActivePower", templateUrl: "./flat.html", + standalone: false, }) export class FlatComponent extends AbstractFlatWidget { diff --git a/ui/src/app/edge/live/Controller/Ess/FixActivePower/modal/modal.html b/ui/src/app/edge/live/Controller/Ess/FixActivePower/modal/modal.html index 78d026f8af5..8a2477ec730 100644 --- a/ui/src/app/edge/live/Controller/Ess/FixActivePower/modal/modal.html +++ b/ui/src/app/edge/live/Controller/Ess/FixActivePower/modal/modal.html @@ -14,7 +14,7 @@ + { name: ('General.off' | translate), value: 'MANUAL_OFF', icon: {color:'danger', name: 'power-outline'}}]"> diff --git a/ui/src/app/edge/live/Controller/Ess/FixActivePower/modal/modal.ts b/ui/src/app/edge/live/Controller/Ess/FixActivePower/modal/modal.ts index 8e1c941963a..aa3b6d7ece7 100644 --- a/ui/src/app/edge/live/Controller/Ess/FixActivePower/modal/modal.ts +++ b/ui/src/app/edge/live/Controller/Ess/FixActivePower/modal/modal.ts @@ -6,6 +6,7 @@ import { ChannelAddress, CurrentData, Utils } from "src/app/shared/shared"; @Component({ templateUrl: "./modal.html", + standalone: false, }) export class ModalComponent extends AbstractModal { diff --git a/ui/src/app/edge/live/Controller/Ess/GridOptimizedCharge/flat/flat.ts b/ui/src/app/edge/live/Controller/Ess/GridOptimizedCharge/flat/flat.ts index 6ba344dd692..8e1f7725825 100644 --- a/ui/src/app/edge/live/Controller/Ess/GridOptimizedCharge/flat/flat.ts +++ b/ui/src/app/edge/live/Controller/Ess/GridOptimizedCharge/flat/flat.ts @@ -8,6 +8,7 @@ import { ModalComponent } from "../modal/modal"; @Component({ selector: "Controller_Ess_GridOptimizedCharge", templateUrl: "./flat.html", + standalone: false, }) export class FlatComponent extends AbstractFlatWidget { diff --git a/ui/src/app/edge/live/Controller/Ess/GridOptimizedCharge/modal/modal.html b/ui/src/app/edge/live/Controller/Ess/GridOptimizedCharge/modal/modal.html index 299cad33151..e17c3ab3952 100644 --- a/ui/src/app/edge/live/Controller/Ess/GridOptimizedCharge/modal/modal.html +++ b/ui/src/app/edge/live/Controller/Ess/GridOptimizedCharge/modal/modal.html @@ -36,7 +36,7 @@ + { name: ('General.off' | translate), value: 'OFF', icon: {color:'danger', name: 'power'}}]"> diff --git a/ui/src/app/edge/live/Controller/Ess/GridOptimizedCharge/modal/modal.ts b/ui/src/app/edge/live/Controller/Ess/GridOptimizedCharge/modal/modal.ts index c13073bbc2a..82b9a472a39 100644 --- a/ui/src/app/edge/live/Controller/Ess/GridOptimizedCharge/modal/modal.ts +++ b/ui/src/app/edge/live/Controller/Ess/GridOptimizedCharge/modal/modal.ts @@ -8,6 +8,7 @@ import { Role } from "src/app/shared/type/role"; @Component({ templateUrl: "./modal.html", changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false, }) export class ModalComponent extends AbstractModal { diff --git a/ui/src/app/edge/live/Controller/Ess/GridOptimizedCharge/modal/predictionChart.ts b/ui/src/app/edge/live/Controller/Ess/GridOptimizedCharge/modal/predictionChart.ts index 15ff02bd65f..83123d70ca9 100644 --- a/ui/src/app/edge/live/Controller/Ess/GridOptimizedCharge/modal/predictionChart.ts +++ b/ui/src/app/edge/live/Controller/Ess/GridOptimizedCharge/modal/predictionChart.ts @@ -12,6 +12,7 @@ import { ChannelAddress, Edge, EdgeConfig, Service, Utils } from "src/app/shared @Component({ selector: "predictionChart", templateUrl: "../../../../../history/abstracthistorychart.html", + standalone: false, }) export class PredictionChartComponent extends AbstractHistoryChart implements OnInit, OnChanges, OnDestroy { diff --git a/ui/src/app/edge/live/Controller/Ess/TimeOfUseTariff/Ess_TimeOfUseTariff.ts b/ui/src/app/edge/live/Controller/Ess/TimeOfUseTariff/Ess_TimeOfUseTariff.ts index 6757a7d7489..9ea009a4f84 100644 --- a/ui/src/app/edge/live/Controller/Ess/TimeOfUseTariff/Ess_TimeOfUseTariff.ts +++ b/ui/src/app/edge/live/Controller/Ess/TimeOfUseTariff/Ess_TimeOfUseTariff.ts @@ -4,8 +4,9 @@ import { BrowserModule } from "@angular/platform-browser"; import { TranslateService } from "@ngx-translate/core"; import { ChartDataset } from "chart.js"; import { ChartAxis, TimeOfUseTariffUtils } from "src/app/shared/service/utils"; -import { Utils } from "src/app/shared/shared"; +import { ChartConstants, Utils } from "src/app/shared/shared"; import { SharedModule } from "src/app/shared/shared.module"; +import { ColorUtils } from "src/app/shared/utils/color/color.utils"; import { FlatComponent } from "./flat/flat"; import { ModalComponent } from "./modal/modal"; import { SchedulePowerAndSocChartComponent } from "./modal/powerSocChart"; @@ -152,8 +153,8 @@ export namespace Controller_Ess_TimeOfUseTariff { order: 2, }); colors.push({ - backgroundColor: "rgba(0,0,0, 0.2)", - borderColor: "rgba(0,0,0, 1)", + backgroundColor: ChartConstants.Colors.BLUE_GREY, + borderColor: ColorUtils.rgbStringToRGBA(ChartConstants.Colors.BLUE_GREY, 1), }); const scheduleChartData: Controller_Ess_TimeOfUseTariff.ScheduleChartData = { diff --git a/ui/src/app/edge/live/Controller/Ess/TimeOfUseTariff/flat/flat.html b/ui/src/app/edge/live/Controller/Ess/TimeOfUseTariff/flat/flat.html index 25962cb6b4a..74acd3a699d 100644 --- a/ui/src/app/edge/live/Controller/Ess/TimeOfUseTariff/flat/flat.html +++ b/ui/src/app/edge/live/Controller/Ess/TimeOfUseTariff/flat/flat.html @@ -1,5 +1,6 @@ + 'cloud-outline', color: 'normal'}" (click)="presentModal()"> + (meta, "currency"); + const currencyLabel: Currency.Label = Currency.getCurrencyLabelByCurrency(currency); this.priceWithCurrency = Utils.CONVERT_PRICE_TO_CENT_PER_KWH(2, currencyLabel)(quarterlyPrice); } } diff --git a/ui/src/app/edge/live/Controller/Ess/TimeOfUseTariff/modal/modal.html b/ui/src/app/edge/live/Controller/Ess/TimeOfUseTariff/modal/modal.html index 7fffeb3ac01..bd30c4b3b05 100644 --- a/ui/src/app/edge/live/Controller/Ess/TimeOfUseTariff/modal/modal.html +++ b/ui/src/app/edge/live/Controller/Ess/TimeOfUseTariff/modal/modal.html @@ -15,7 +15,7 @@ + { name: ('General.off' | translate), value: 'OFF', icon: {color:'danger', name: 'power'}}]"> diff --git a/ui/src/app/edge/live/Controller/Ess/TimeOfUseTariff/modal/modal.ts b/ui/src/app/edge/live/Controller/Ess/TimeOfUseTariff/modal/modal.ts index 54ccfba47cb..cf2594246f6 100644 --- a/ui/src/app/edge/live/Controller/Ess/TimeOfUseTariff/modal/modal.ts +++ b/ui/src/app/edge/live/Controller/Ess/TimeOfUseTariff/modal/modal.ts @@ -2,11 +2,12 @@ import { Component } from "@angular/core"; import { FormControl, FormGroup } from "@angular/forms"; import { AbstractModal } from "src/app/shared/components/modal/abstractModal"; -import { ChannelAddress, Currency, CurrentData } from "src/app/shared/shared"; +import { ChannelAddress, Currency, CurrentData, EdgeConfig } from "src/app/shared/shared"; import { Controller_Ess_TimeOfUseTariff } from "../Ess_TimeOfUseTariff"; @Component({ templateUrl: "./modal.html", + standalone: false, }) export class ModalComponent extends AbstractModal { @@ -42,7 +43,9 @@ export class ModalComponent extends AbstractModal { protected override onCurrentData(currentData: CurrentData): void { const quarterlyPrice = currentData.allComponents[this.component.id + "/QuarterlyPrices"]; - const currencyLabel: string = Currency.getCurrencyLabelByEdgeId(this.edge?.id); + const meta: EdgeConfig.Component = this.config?.getComponent("_meta"); + const currency: string = this.config?.getPropertyFromComponent(meta, "currency"); + const currencyLabel: Currency.Label = Currency.getCurrencyLabelByCurrency(currency); this.priceWithCurrency = this.Utils.CONVERT_PRICE_TO_CENT_PER_KWH(2, currencyLabel)(quarterlyPrice); } } diff --git a/ui/src/app/edge/live/Controller/Ess/TimeOfUseTariff/modal/powerSocChart.ts b/ui/src/app/edge/live/Controller/Ess/TimeOfUseTariff/modal/powerSocChart.ts index 61e457c6d86..0d19eab02ba 100644 --- a/ui/src/app/edge/live/Controller/Ess/TimeOfUseTariff/modal/powerSocChart.ts +++ b/ui/src/app/edge/live/Controller/Ess/TimeOfUseTariff/modal/powerSocChart.ts @@ -9,12 +9,14 @@ import { ChartConstants } from "src/app/shared/components/chart/chart.constants" import { ComponentJsonApiRequest } from "src/app/shared/jsonrpc/request/componentJsonApiRequest"; import { ChartAxis, HistoryUtils, TimeOfUseTariffUtils, Utils, YAxisType } from "src/app/shared/service/utils"; import { ChannelAddress, Edge, EdgeConfig, Service, Websocket } from "src/app/shared/shared"; +import { ColorUtils } from "src/app/shared/utils/color/color.utils"; import { GetScheduleRequest } from "../../../../../../shared/jsonrpc/request/getScheduleRequest"; import { GetScheduleResponse } from "../../../../../../shared/jsonrpc/response/getScheduleResponse"; @Component({ selector: "powerSocChart", templateUrl: "../../../../../history/abstracthistorychart.html", + standalone: false, }) export class SchedulePowerAndSocChartComponent extends AbstractHistoryChart implements OnInit, OnChanges, OnDestroy { @@ -104,8 +106,8 @@ export class SchedulePowerAndSocChartComponent extends AbstractHistoryChart impl order: 1, }); this.colors.push({ - backgroundColor: "rgba(0,0,0, 0.2)", - borderColor: "rgba(0,0,0, 1)", + backgroundColor: ColorUtils.rgbStringToRGBA(ChartConstants.Colors.BLUE_GREY, 0.2), + borderColor: ChartConstants.Colors.BLUE_GREY, }); datasets.push({ @@ -116,8 +118,8 @@ export class SchedulePowerAndSocChartComponent extends AbstractHistoryChart impl order: 1, }); this.colors.push({ - backgroundColor: "rgba(0,0,200, 0.2)", - borderColor: "rgba(0,0,200, 1)", + backgroundColor: ColorUtils.rgbStringToRGBA(ChartConstants.Colors.PURPLE, 0.2), + borderColor: ChartConstants.Colors.PURPLE, }); datasets.push({ @@ -128,8 +130,8 @@ export class SchedulePowerAndSocChartComponent extends AbstractHistoryChart impl order: 1, }); this.colors.push({ - backgroundColor: "rgba(45,143,171, 0.2)", - borderColor: "rgba(45,143,171, 1)", + backgroundColor: ColorUtils.rgbStringToRGBA(ChartConstants.Colors.BLUE, 0.2), + borderColor: ChartConstants.Colors.BLUE, }); datasets.push({ @@ -140,34 +142,34 @@ export class SchedulePowerAndSocChartComponent extends AbstractHistoryChart impl order: 1, }); this.colors.push({ - backgroundColor: "rgba(253,197,7,0.2)", - borderColor: "rgba(253,197,7,1)", + backgroundColor: ColorUtils.rgbStringToRGBA(ChartConstants.Colors.YELLOW, 0.2), + borderColor: ChartConstants.Colors.YELLOW, }); datasets.push({ type: "line", - label: this.translate.instant("General.chargePower"), + label: this.translate.instant("General.CHARGE"), data: essChargeArray.map(v => Utils.divideSafely(v, 1000)), // [W] to [kW] hidden: true, order: 1, unit: YAxisType.POWER, }); this.colors.push({ - backgroundColor: "rgba(0,223,0, 0.2)", - borderColor: "rgba(0,223,0, 1)", + backgroundColor: ColorUtils.rgbStringToRGBA(ChartConstants.Colors.GREEN, 0.2), + borderColor: ChartConstants.Colors.GREEN, }); datasets.push({ type: "line", - label: this.translate.instant("General.dischargePower"), + label: this.translate.instant("General.DISCHARGE"), data: essDischargeArray.map(v => Utils.divideSafely(v, 1000)), // [W] to [kW] hidden: true, order: 1, unit: YAxisType.POWER, }); this.colors.push({ - backgroundColor: "rgba(200,0,0, 0.2)", - borderColor: "rgba(200,0,0, 1)", + backgroundColor: ColorUtils.rgbStringToRGBA(ChartConstants.Colors.RED, 0.2), + borderColor: ChartConstants.Colors.RED, }); // State of charge data @@ -204,10 +206,9 @@ export class SchedulePowerAndSocChartComponent extends AbstractHistoryChart impl private applyControllerSpecificOptions() { const rightYAxis: HistoryUtils.yAxes = { position: "right", unit: YAxisType.PERCENTAGE, yAxisId: ChartAxis.RIGHT }; const leftYAxis: HistoryUtils.yAxes = { position: "left", unit: YAxisType.POWER, yAxisId: ChartAxis.LEFT }; - const locale = this.service.translate.currentLang; - this.options = NewAbstractHistoryChart.getYAxisOptions(this.options, rightYAxis, this.translate, "line", locale, ChartConstants.EMPTY_DATASETS, true); - this.options = NewAbstractHistoryChart.getYAxisOptions(this.options, leftYAxis, this.translate, "line", locale, ChartConstants.EMPTY_DATASETS, true); + this.options = NewAbstractHistoryChart.getYAxisOptions(this.options, rightYAxis, this.translate, "line", ChartConstants.EMPTY_DATASETS, true); + this.options = NewAbstractHistoryChart.getYAxisOptions(this.options, leftYAxis, this.translate, "line", ChartConstants.EMPTY_DATASETS, true); this.datasets = this.datasets.map((el: Chart.ChartDataset) => { @@ -219,6 +220,7 @@ export class SchedulePowerAndSocChartComponent extends AbstractHistoryChart impl }); this.options.scales.x["ticks"] = { source: "auto", autoSkip: false }; + this.options.scales.x.ticks.color = getComputedStyle(document.documentElement).getPropertyValue("--ion-color-chart-xAxis-ticks"); this.options.scales.x.ticks.callback = function (value, index, values) { const date = new Date(value); diff --git a/ui/src/app/edge/live/Controller/Ess/TimeOfUseTariff/modal/statePriceChart.ts b/ui/src/app/edge/live/Controller/Ess/TimeOfUseTariff/modal/statePriceChart.ts index 3e690c5828f..6e348cd5ea8 100644 --- a/ui/src/app/edge/live/Controller/Ess/TimeOfUseTariff/modal/statePriceChart.ts +++ b/ui/src/app/edge/live/Controller/Ess/TimeOfUseTariff/modal/statePriceChart.ts @@ -3,14 +3,14 @@ import { Component, Input, OnChanges, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; import { TranslateService } from "@ngx-translate/core"; import * as Chart from "chart.js"; +import { filter, take } from "rxjs/operators"; import { AbstractHistoryChart } from "src/app/edge/history/abstracthistorychart"; +import { calculateResolution } from "src/app/edge/history/shared"; import { AbstractHistoryChart as NewAbstractHistoryChart } from "src/app/shared/components/chart/abstracthistorychart"; +import { ChartConstants } from "src/app/shared/components/chart/chart.constants"; import { ComponentJsonApiRequest } from "src/app/shared/jsonrpc/request/componentJsonApiRequest"; import { ChartAxis, HistoryUtils, TimeOfUseTariffUtils, YAxisType } from "src/app/shared/service/utils"; import { ChannelAddress, Currency, Edge, EdgeConfig, Service, Websocket } from "src/app/shared/shared"; - -import { calculateResolution } from "src/app/edge/history/shared"; -import { ChartConstants } from "src/app/shared/components/chart/chart.constants"; import { ColorUtils } from "src/app/shared/utils/color/color.utils"; import { GetScheduleRequest } from "../../../../../../shared/jsonrpc/request/getScheduleRequest"; import { GetScheduleResponse } from "../../../../../../shared/jsonrpc/response/getScheduleResponse"; @@ -19,6 +19,7 @@ import { Controller_Ess_TimeOfUseTariff } from "../Ess_TimeOfUseTariff"; @Component({ selector: "statePriceChart", templateUrl: "../../../../../history/abstracthistorychart.html", + standalone: false, }) export class ScheduleStateAndPriceChartComponent extends AbstractHistoryChart implements OnInit, OnChanges, OnDestroy { @@ -27,6 +28,7 @@ export class ScheduleStateAndPriceChartComponent extends AbstractHistoryChart im @Input({ required: true }) public component!: EdgeConfig.Component; private currencyLabel: Currency.Label; // Default + private currencyUnit: Currency.Unit; // Default constructor( protected override service: Service, @@ -41,8 +43,13 @@ export class ScheduleStateAndPriceChartComponent extends AbstractHistoryChart im return TimeOfUseTariffUtils.getChartHeight(this.service.isSmartphoneResolution); } - public ngOnChanges() { - this.currencyLabel = Currency.getCurrencyLabelByEdgeId(this.edge.id); + public async ngOnChanges() { + this.edge.getConfig(this.websocket).pipe(filter(config => !!config), take(1)).subscribe(config => { + const meta: EdgeConfig.Component = config?.getComponent("_meta"); + const currency: string = config?.getPropertyFromComponent(meta, "currency"); + this.currencyLabel = Currency.getCurrencyLabelByCurrency(currency); + this.currencyUnit = Currency.getChartCurrencyUnitLabel(currency); + }); this.updateChart(); } @@ -107,15 +114,15 @@ export class ScheduleStateAndPriceChartComponent extends AbstractHistoryChart im } private applyControllerSpecificOptions() { - const locale = this.service.translate.currentLang; - const rightYaxisSoc: HistoryUtils.yAxes = { position: "right", unit: YAxisType.PERCENTAGE, yAxisId: ChartAxis.RIGHT }; - this.options = NewAbstractHistoryChart.getYAxisOptions(this.options, rightYaxisSoc, this.translate, "line", locale, ChartConstants.EMPTY_DATASETS); + const rightYaxisSoc: HistoryUtils.yAxes = { position: "right", unit: YAxisType.PERCENTAGE, yAxisId: ChartAxis.RIGHT, displayGrid: true }; + this.options = NewAbstractHistoryChart.getYAxisOptions(this.options, rightYaxisSoc, this.translate, "line", this.datasets, true); const rightYAxisPower: HistoryUtils.yAxes = { position: "right", unit: YAxisType.POWER, yAxisId: ChartAxis.RIGHT_2 }; - this.options = NewAbstractHistoryChart.getYAxisOptions(this.options, rightYAxisPower, this.translate, "line", locale, ChartConstants.EMPTY_DATASETS); + this.options = NewAbstractHistoryChart.getYAxisOptions(this.options, rightYAxisPower, this.translate, "line", this.datasets, true); this.options.scales.x["time"].unit = calculateResolution(this.service, this.service.historyPeriod.value.from, this.service.historyPeriod.value.to).timeFormat; this.options.scales.x["ticks"] = { source: "auto", autoSkip: false }; + this.options.scales.x.ticks.color = getComputedStyle(document.documentElement).getPropertyValue("--ion-color-chart-xAxis-ticks"); this.options.scales.x.ticks.maxTicksLimit = 30; this.options.scales.x["offset"] = false; this.options.scales.x.ticks.callback = function (value) { @@ -166,13 +173,19 @@ export class ScheduleStateAndPriceChartComponent extends AbstractHistoryChart im return el; }); + const leftYAxis: HistoryUtils.yAxes = { position: "left", unit: this.unit, yAxisId: ChartAxis.LEFT, customTitle: this.currencyUnit, scale: { dynamicScale: true } }; + [rightYaxisSoc, rightYAxisPower].forEach((element) => { + this.options = NewAbstractHistoryChart.getYAxisOptions(this.options, element, this.translate, "line", this.datasets, true); + }); - this.options.scales[ChartAxis.LEFT]["title"].text = this.currencyLabel; + this.options.scales[ChartAxis.LEFT] = { + ...this.options.scales[ChartAxis.LEFT], + ...ChartConstants.DEFAULT_Y_SCALE_OPTIONS(leftYAxis, this.translate, "bar", this.datasets.filter(el => el["yAxisID"] === ChartAxis.LEFT), true), + }; this.options.scales[ChartAxis.RIGHT].grid.display = false; this.options.scales[ChartAxis.RIGHT_2].suggestedMin = 0; this.options.scales[ChartAxis.RIGHT_2].suggestedMax = 1; this.options.scales[ChartAxis.RIGHT_2].grid.display = false; this.options["animation"] = false; } - } diff --git a/ui/src/app/edge/live/Controller/Evcs/Evcs.ts b/ui/src/app/edge/live/Controller/Evcs/Evcs.ts index 89fd473682e..a35c74389c0 100644 --- a/ui/src/app/edge/live/Controller/Evcs/Evcs.ts +++ b/ui/src/app/edge/live/Controller/Evcs/Evcs.ts @@ -1,8 +1,12 @@ import { NgModule } from "@angular/core"; import { BrowserModule } from "@angular/platform-browser"; +import { TranslateService } from "@ngx-translate/core"; +import { ChartDataset } from "chart.js"; +import { TimeOfUseTariffUtils } from "src/app/shared/service/utils"; import { SharedModule } from "src/app/shared/shared.module"; import { FlatComponent } from "./flat/flat"; import { ModalComponent } from "./modal/modal"; +import { ScheduleChartComponent } from "./modal/scheduleChart"; import { PopoverComponent } from "./popover/popover"; @NgModule({ @@ -14,6 +18,7 @@ import { PopoverComponent } from "./popover/popover"; FlatComponent, ModalComponent, PopoverComponent, + ScheduleChartComponent, ], exports: [ FlatComponent, @@ -21,4 +26,106 @@ import { PopoverComponent } from "./popover/popover"; }) export class Controller_Evcs { } +export namespace Controller_Evcs { + export type ScheduleChartData = { + datasets: ChartDataset[], + colors: any[], + labels: Date[] + }; + + export enum SmartMode { + ZERO = 0, + SURPLUS_PV = 1, + FORCE = 3, + } + + /** + * Gets the schedule chart data containing datasets, colors and labels. + * + * @param size The length of the dataset + * @param prices The Time-of-Use-Tariff quarterly price array + * @param states The Time-of-Use-Tariff state array + * @param timestamps The Time-of-Use-Tariff timestamps array + * @param translate The Translate service + * @returns The ScheduleChartData. + */ + export function getScheduleChartData(size: number, prices: number[], states: number[], timestamps: string[], translate: TranslateService): Controller_Evcs.ScheduleChartData { + + const datasets: ChartDataset[] = []; + const colors: any[] = []; + const labels: Date[] = []; + + // Initializing States. + const barZero = Array(size).fill(null); + const barSurplusPv = Array(size).fill(null); + const barForce = Array(size).fill(null); + + for (let index = 0; index < size; index++) { + const quarterlyPrice = TimeOfUseTariffUtils.formatPrice(prices[index]); + const state = states[index]; + labels.push(new Date(timestamps[index])); + + if (state !== null) { + switch (state) { + case Controller_Evcs.SmartMode.ZERO: + barZero[index] = quarterlyPrice; + break; + case Controller_Evcs.SmartMode.SURPLUS_PV: + barSurplusPv[index] = quarterlyPrice; + break; + case Controller_Evcs.SmartMode.FORCE: + barForce[index] = quarterlyPrice; + break; + } + } + } + + // Set datasets + datasets.push({ + type: "bar", + label: "No Charge", + data: barZero, + order: 1, + }); + colors.push({ + // Dark Green + backgroundColor: "rgba(51,102,0,0.8)", + borderColor: "rgba(51,102,0,1)", + }); + + // Set dataset for ChargeGrid. + datasets.push({ + type: "bar", + label: "Surplus PV", + data: barSurplusPv, + order: 1, + }); + colors.push({ + // Sky blue + backgroundColor: "rgba(0, 204, 204,0.5)", + borderColor: "rgba(0, 204, 204,0.7)", + }); + + // Set dataset for buy from grid + datasets.push({ + type: "bar", + label: "Force Charge", + data: barForce, + order: 1, + }); + colors.push({ + // Black + backgroundColor: "rgba(0,0,0,0.8)", + borderColor: "rgba(0,0,0,0.9)", + }); + + const scheduleChartData: Controller_Evcs.ScheduleChartData = { + colors: colors, + datasets: datasets, + labels: labels, + }; + + return scheduleChartData; + } +} diff --git a/ui/src/app/edge/live/Controller/Evcs/administration/administration.component.ts b/ui/src/app/edge/live/Controller/Evcs/administration/administration.component.ts index 3e38be561f1..172a7e42cfa 100644 --- a/ui/src/app/edge/live/Controller/Evcs/administration/administration.component.ts +++ b/ui/src/app/edge/live/Controller/Evcs/administration/administration.component.ts @@ -7,6 +7,7 @@ import { Edge, EdgeConfig, Service, Websocket } from "../../../../../shared/shar @Component({ selector: AdministrationComponent.SELECTOR, templateUrl: "./administration.component.html", + standalone: false, }) export class AdministrationComponent implements OnInit { diff --git a/ui/src/app/edge/live/Controller/Evcs/flat/flat.html b/ui/src/app/edge/live/Controller/Evcs/flat/flat.html index 2ec4d329a1d..2466bb3e5d7 100644 --- a/ui/src/app/edge/live/Controller/Evcs/flat/flat.html +++ b/ui/src/app/edge/live/Controller/Evcs/flat/flat.html @@ -1,5 +1,5 @@ + [title]="evcsComponent.alias" [icon]="{name: 'oe-evcs', color: 'normal'}"> @@ -13,7 +13,7 @@ [channelAddress]="componentId + '/EnergySession'" [converter]="CONVERT_TO_KILO_WATTHOURS"> - + diff --git a/ui/src/app/edge/live/Controller/Evcs/flat/flat.ts b/ui/src/app/edge/live/Controller/Evcs/flat/flat.ts index ea589527b23..b72c031a05d 100644 --- a/ui/src/app/edge/live/Controller/Evcs/flat/flat.ts +++ b/ui/src/app/edge/live/Controller/Evcs/flat/flat.ts @@ -12,6 +12,7 @@ type ChargeMode = "FORCE_CHARGE" | "EXCESS_POWER" | "OFF"; @Component({ selector: "Controller_Evcs", templateUrl: "./flat.html", + standalone: false, }) export class FlatComponent extends AbstractFlatWidget { @@ -42,6 +43,7 @@ export class FlatComponent extends AbstractFlatWidget { protected chargeDischargePower: { name: string, value: number }; protected propertyMode: DefaultTypes.ManualOnOff | null = null; protected status: string; + protected isReadWrite: boolean; formatNumber(i: number) { const round = Math.ceil(i / 100) * 100; @@ -88,6 +90,7 @@ export class FlatComponent extends AbstractFlatWidget { this.evcsComponent = this.config.getComponent(this.component.id); this.isConnectionSuccessful = currentData.allComponents[this.component.id + "/State"] != 3 ? true : false; + this.isReadWrite = !this.component.properties["readOnly"]; this.status = this.getState(this.controller ? currentData.allComponents[this.controller.id + "/_PropertyEnabledCharging"] === 1 : null, currentData.allComponents[this.component.id + "/Status"], currentData.allComponents[this.component.id + "/Plug"]); // Check if Energy since beginning is allowed diff --git a/ui/src/app/edge/live/Controller/Evcs/jsonrpc/getScheduleRequest.ts b/ui/src/app/edge/live/Controller/Evcs/jsonrpc/getScheduleRequest.ts new file mode 100644 index 00000000000..a667977635c --- /dev/null +++ b/ui/src/app/edge/live/Controller/Evcs/jsonrpc/getScheduleRequest.ts @@ -0,0 +1,24 @@ +import { JsonrpcRequest } from "src/app/shared/jsonrpc/base"; + +/** + * Wraps a JSON-RPC Request for an OpenEMS Component that implements JsonApi + * + *
    + * {
    + *   "jsonrpc": "2.0",
    + *   "id": "UUID",
    + *   "method": "getSchedule",
    + *   "params": {}
    + * }
    + * 
    + */ +export class GetScheduleRequest extends JsonrpcRequest { + + private static METHOD: string = "getSchedule"; + + public constructor( + ) { + super(GetScheduleRequest.METHOD, {}); + } + +} diff --git a/ui/src/app/edge/live/Controller/Evcs/jsonrpc/getScheduleResponse.ts b/ui/src/app/edge/live/Controller/Evcs/jsonrpc/getScheduleResponse.ts new file mode 100644 index 00000000000..b836ba20842 --- /dev/null +++ b/ui/src/app/edge/live/Controller/Evcs/jsonrpc/getScheduleResponse.ts @@ -0,0 +1,36 @@ +import { JsonrpcResponseSuccess } from "src/app/shared/jsonrpc/base"; + +/** + * Wraps a JSON-RPC Response for a GetScheduleRequest. + * + *
    + * {
    + *   "jsonrpc": "2.0",
    + *   "id": UUID,
    + *   "result": {
    + *     "schedule": [{
    + *     	"timestamp": string,
    + *      "price": number,
    + *      "state": number,
    + *     }]
    + *   }
    + * }
    + * 
    + */ +export class GetScheduleResponse extends JsonrpcResponseSuccess { + + + public constructor( + public override readonly id: string, + public override readonly result: { + schedule: { + timestamp: string; + price: number; + state: number; + }[] + }, + ) { + super(id, result); + } + +} diff --git a/ui/src/app/edge/live/Controller/Evcs/modal/modal.html b/ui/src/app/edge/live/Controller/Evcs/modal/modal.html index da7866b9130..1af3217ccb0 100644 --- a/ui/src/app/edge/live/Controller/Evcs/modal/modal.html +++ b/ui/src/app/edge/live/Controller/Evcs/modal/modal.html @@ -1,7 +1,7 @@ - + @@ -10,16 +10,17 @@ - - - - - + + - + + + + + - @@ -31,33 +32,53 @@ - - - -
    General.chargePowerGeneral.CHARGE {{ data["_sum/EssDcChargeEnergy"] | unitvalue:'kWh' }}
    General.dischargePowerGeneral.DISCHARGE {{ data["_sum/EssDcDischargeEnergy"] | unitvalue:'kWh' }}
    - - - - -
    - General.mode - - - -
    - - - + + + + + + + + + + + + + + + + + + +
    + General.mode + + + +
    + + - - + { name: ('General.off' | translate), value: 'OFF', icon: {color:'danger', name: 'power-outline'}}]"> +
    +
    + + + + + + + + + @@ -141,7 +162,7 @@ - + @@ -149,9 +170,8 @@ - - + \ No newline at end of file diff --git a/ui/src/app/edge/live/Controller/Evcs/modal/modal.ts b/ui/src/app/edge/live/Controller/Evcs/modal/modal.ts index 0e92bbc6eda..07fd36f1ef1 100644 --- a/ui/src/app/edge/live/Controller/Evcs/modal/modal.ts +++ b/ui/src/app/edge/live/Controller/Evcs/modal/modal.ts @@ -4,6 +4,7 @@ import { FormBuilder, FormControl, FormGroup } from "@angular/forms"; import { ActivatedRoute } from "@angular/router"; import { ModalController, PopoverController } from "@ionic/angular"; import { TranslateService } from "@ngx-translate/core"; +import { EvcsUtils } from "src/app/shared/components/edge/utils/evcs-utils"; import { AbstractModal } from "src/app/shared/components/modal/abstractModal"; import { ChannelAddress, CurrentData, EdgeConfig, Service, Utils, Websocket } from "src/app/shared/shared"; @@ -13,6 +14,7 @@ import { PopoverComponent } from "../popover/popover"; type ChargeMode = "FORCE_CHARGE" | "EXCESS_POWER"; @Component({ templateUrl: "./modal.html", + standalone: false, }) export class ModalComponent extends AbstractModal { @@ -37,6 +39,8 @@ export class ModalComponent extends AbstractModal { protected isChargingEnabled: boolean = false; protected sessionLimit: number; protected helpKey: string; + protected awaitingHysteresis: boolean; + protected isReadWrite: boolean = true; constructor( @Inject(Websocket) protected override websocket: Websocket, @@ -104,7 +108,7 @@ export class ModalComponent extends AbstractModal { return [ // channels for modal component, subscribe here for better UX - new ChannelAddress(this.component.id, "ChargePower"), + new ChannelAddress(this.component.id, this.getPowerChannelId()), new ChannelAddress(this.component.id, "Phases"), new ChannelAddress(this.component.id, "Plug"), new ChannelAddress(this.component.id, "Status"), @@ -113,18 +117,22 @@ export class ModalComponent extends AbstractModal { new ChannelAddress(this.component.id, "MinimumHardwarePower"), new ChannelAddress(this.component.id, "MaximumHardwarePower"), new ChannelAddress(this.component.id, "SetChargePowerLimit"), + new ChannelAddress(this.component.id, "_PropertyReadOnly"), new ChannelAddress(this.controller?.id, "_PropertyChargeMode"), new ChannelAddress(this.controller?.id, "_PropertyEnabledCharging"), new ChannelAddress(this.controller?.id, "_PropertyDefaultChargeMinPower"), + new ChannelAddress(this.controller?.id, "AwaitingHysteresis"), ]; } protected override onCurrentData(currentData: CurrentData) { this.isConnectionSuccessful = currentData.allComponents[this.component.id + "/State"] !== 3 ? true : false; + this.awaitingHysteresis = currentData.allComponents[this.controller?.id + "/AwaitingHysteresis"]; + this.isReadWrite = !this.component.properties["readOnly"]; // Do not change values after touching formControls if (this.formGroup?.pristine) { this.status = this.getState(this.controller ? currentData.allComponents[this.controller.id + "/_PropertyEnabledCharging"] === 1 : null, currentData.allComponents[this.component.id + "/Status"], currentData.allComponents[this.component.id + "/Plug"]); - this.chargePower = Utils.convertChargeDischargePower(this.translate, currentData.allComponents[this.component.id + "/ChargePower"]); + this.chargePower = Utils.convertChargeDischargePower(this.translate, currentData.allComponents[this.component.id + "/" + this.getPowerChannelId()]); this.chargePowerLimit = Utils.CONVERT_TO_WATT(this.formatNumber(currentData.allComponents[this.component.id + "/SetChargePowerLimit"])); this.state = currentData.allComponents[this.component.id + "/Status"]; this.energySession = Utils.CONVERT_TO_WATTHOURS(currentData.allComponents[this.component.id + "/EnergySession"]); @@ -276,8 +284,12 @@ export class ModalComponent extends AbstractModal { } } + private getPowerChannelId(): string { + return EvcsUtils.getEvcsPowerChannelId(this.component, this.config, this.edge); + } } + enum ChargeState { UNDEFINED = -1, //Undefined STARTING, //Starting diff --git a/ui/src/app/edge/live/Controller/Evcs/modal/scheduleChart.ts b/ui/src/app/edge/live/Controller/Evcs/modal/scheduleChart.ts new file mode 100644 index 00000000000..813c1da9a53 --- /dev/null +++ b/ui/src/app/edge/live/Controller/Evcs/modal/scheduleChart.ts @@ -0,0 +1,164 @@ +// @ts-strict-ignore +import { Component, Input, OnChanges, OnDestroy, OnInit } from "@angular/core"; +import { TranslateService } from "@ngx-translate/core"; +import * as Chart from "chart.js"; +import { filter, take } from "rxjs/operators"; +import { AbstractHistoryChart } from "src/app/edge/history/abstracthistorychart"; +import { calculateResolution } from "src/app/edge/history/shared"; +import { ChartConstants } from "src/app/shared/components/chart/chart.constants"; +import { ComponentJsonApiRequest } from "src/app/shared/jsonrpc/request/componentJsonApiRequest"; +import { ChartAxis, HistoryUtils, TimeOfUseTariffUtils, YAxisType } from "src/app/shared/service/utils"; +import { ChannelAddress, Currency, Edge, EdgeConfig, Service, Websocket } from "src/app/shared/shared"; +import { ColorUtils } from "src/app/shared/utils/color/color.utils"; +import { Controller_Evcs } from "../Evcs"; +import { GetScheduleRequest } from "../jsonrpc/getScheduleRequest"; +import { GetScheduleResponse } from "../jsonrpc/getScheduleResponse"; + +@Component({ + selector: "scheduleChart", + templateUrl: "../../../../history/abstracthistorychart.html", + standalone: false, +}) +export class ScheduleChartComponent extends AbstractHistoryChart implements OnInit, OnChanges, OnDestroy { + + @Input({ required: true }) public refresh!: boolean; + @Input({ required: true }) public override edge!: Edge; + @Input({ required: true }) public component!: EdgeConfig.Component; + + protected evcsComponent: EdgeConfig.Component; + private currencyLabel: Currency.Label; // Default + private currencyUnit: Currency.Unit; // Default + + constructor( + protected override service: Service, + protected override translate: TranslateService, + private websocket: Websocket, + ) { + super("schedule-chart", service, translate); + } + + public getChartHeight(): number { + return TimeOfUseTariffUtils.getChartHeight(this.service.isSmartphoneResolution); + } + + public async ngOnChanges() { + this.edge.getConfig(this.websocket).pipe(filter(config => !!config), take(1)).subscribe(config => { + this.evcsComponent = config?.getComponentsByFactory("Controller.Evcs") + .find(element => "evcs.id" in element.properties && element.properties["evcs.id"] == this.component.id); + const meta: EdgeConfig.Component = config?.getComponent("_meta"); + const currency: string = config?.getPropertyFromComponent(meta, "currency"); + this.currencyLabel = Currency.getCurrencyLabelByCurrency(currency); + this.currencyUnit = Currency.getChartCurrencyUnitLabel(currency); + }); + this.updateChart(); + } + + public ngOnInit() { + this.service.startSpinner(this.spinnerId); + } + + public ngOnDestroy() { + this.unsubscribeChartRefresh(); + } + + protected override updateChart() { + this.autoSubscribeChartRefresh(); + this.service.startSpinner(this.spinnerId); + this.loading = true; + + this.edge.sendRequest( + this.websocket, + new ComponentJsonApiRequest({ componentId: this.evcsComponent.id, payload: new GetScheduleRequest() }), + ).then(response => { + const result = (response as GetScheduleResponse).result; + const schedule = result.schedule; + + // Extracting prices, states, timestamps from the schedule array + const { priceArray, stateArray, timestampArray } = { + priceArray: schedule.map(entry => entry.price), + stateArray: schedule.map(entry => entry.state), + timestampArray: schedule.map(entry => entry.timestamp), + }; + + const scheduleChartData = Controller_Evcs.getScheduleChartData(schedule.length, priceArray, + stateArray, timestampArray, this.translate); + + this.colors = scheduleChartData.colors; + this.labels = scheduleChartData.labels; + + this.datasets = scheduleChartData.datasets; + this.loading = false; + this.setLabel(); + this.stopSpinner(); + + }).catch((reason) => { + console.error(reason); + this.initializeChart(); + return; + + }).finally(async () => { + this.unit = YAxisType.CURRENCY; + await this.setOptions(this.options); + this.applyControllerSpecificOptions(); + }); + } + + protected setLabel() { + this.options = this.createDefaultChartOptions(); + } + + protected getChannelAddresses(): Promise { + return new Promise(() => { []; }); + } + + private applyControllerSpecificOptions() { + + this.options.scales.x["time"].unit = calculateResolution(this.service, this.service.historyPeriod.value.from, this.service.historyPeriod.value.to).timeFormat; + this.options.scales.x["ticks"] = { source: "auto", autoSkip: false }; + this.options.scales.x.ticks.maxTicksLimit = 30; + this.options.scales.x["offset"] = false; + this.options.scales.x.ticks.callback = function (value) { + const date = new Date(value); + + // Display the label only if the minutes are zero (full hour) + return date.getMinutes() === 0 ? date.getHours() + ":00" : ""; + }; + + // options.plugins. + this.options.plugins.tooltip.mode = "index"; + this.options.plugins.tooltip.callbacks.labelColor = (item: Chart.TooltipItem) => { + if (!item) { + return; + } + return { + borderColor: ColorUtils.changeOpacityFromRGBA(item.dataset.borderColor, 1), + backgroundColor: item.dataset.backgroundColor, + }; + }; + + this.options.plugins.tooltip.callbacks.label = (item: Chart.TooltipItem) => { + + const label = item.dataset.label; + const value = item.dataset.data[item.dataIndex]; + + return TimeOfUseTariffUtils.getLabel(value, label, this.translate, this.currencyLabel); + }; + + this.datasets = this.datasets.map((el) => { + const opacity = el.type === "line" ? 0.2 : 0.5; + + if (el.backgroundColor && el.borderColor) { + el.backgroundColor = ColorUtils.changeOpacityFromRGBA(el.backgroundColor.toString(), opacity); + el.borderColor = ColorUtils.changeOpacityFromRGBA(el.borderColor.toString(), 1); + } + return el; + }); + + const leftYAxis: HistoryUtils.yAxes = { position: "left", unit: this.unit, yAxisId: ChartAxis.LEFT, customTitle: this.currencyUnit, scale: { dynamicScale: true } }; + + this.options.scales[ChartAxis.LEFT] = { + ...this.options.scales[ChartAxis.LEFT], + ...ChartConstants.DEFAULT_Y_SCALE_OPTIONS(leftYAxis, this.translate, "bar", this.datasets.filter(el => el["yAxisID"] === ChartAxis.LEFT), true), + }; + } +} diff --git a/ui/src/app/edge/live/Controller/Evcs/popover/popover.ts b/ui/src/app/edge/live/Controller/Evcs/popover/popover.ts index 718bdb5d868..c7913cba287 100644 --- a/ui/src/app/edge/live/Controller/Evcs/popover/popover.ts +++ b/ui/src/app/edge/live/Controller/Evcs/popover/popover.ts @@ -5,6 +5,7 @@ import { AbstractModal } from "src/app/shared/components/modal/abstractModal"; type ChargeMode = "FORCE_CHARGE" | "EXCESS_POWER" | "OFF"; @Component({ templateUrl: "./popover.html", + standalone: false, }) export class PopoverComponent extends AbstractModal { diff --git a/ui/src/app/edge/live/Controller/Io/ChannelSingleThreshold/Io_ChannelSingleThreshold.html b/ui/src/app/edge/live/Controller/Io/ChannelSingleThreshold/Io_ChannelSingleThreshold.html index 4d5daaa2a78..59838eae56d 100644 --- a/ui/src/app/edge/live/Controller/Io/ChannelSingleThreshold/Io_ChannelSingleThreshold.html +++ b/ui/src/app/edge/live/Controller/Io/ChannelSingleThreshold/Io_ChannelSingleThreshold.html @@ -1,5 +1,6 @@ + [icon]="{name: icon.name, color: 'normal'}"> + diff --git a/ui/src/app/edge/live/Controller/Io/ChannelSingleThreshold/Io_ChannelSingleThreshold.ts b/ui/src/app/edge/live/Controller/Io/ChannelSingleThreshold/Io_ChannelSingleThreshold.ts index bbed2715a18..25648cf2dce 100644 --- a/ui/src/app/edge/live/Controller/Io/ChannelSingleThreshold/Io_ChannelSingleThreshold.ts +++ b/ui/src/app/edge/live/Controller/Io/ChannelSingleThreshold/Io_ChannelSingleThreshold.ts @@ -3,11 +3,13 @@ import { Component } from "@angular/core"; import { AbstractFlatWidget } from "src/app/shared/components/flat/abstract-flat-widget"; import { ChannelAddress, CurrentData, Utils } from "src/app/shared/shared"; import { Icon } from "src/app/shared/type/widget"; +import { StringUtils } from "src/app/shared/utils/string/string.utils"; import { Controller_Io_ChannelSingleThresholdModalComponent } from "./modal/modal.component"; @Component({ selector: "Controller_Io_ChannelSingleThresholdComponent", templateUrl: "./Io_ChannelSingleThreshold.html", + standalone: false, }) export class Controller_Io_ChannelSingleThresholdComponent extends AbstractFlatWidget { @@ -19,7 +21,7 @@ export class Controller_Io_ChannelSingleThresholdComponent extends AbstractFlatW public icon: Icon = { name: "", color: "", - size: "", + size: "large", }; public dependendOn: string; public dependendOnValue: any; @@ -143,9 +145,10 @@ export class Controller_Io_ChannelSingleThresholdComponent extends AbstractFlatW } // True when InputAddress doesnt match any of the following channelIds - this.isOtherInputAddress = this.inputChannel.toString() != (null && "_sum/EssSoc" && "_sum/GridActivePower" && "_sum/ProductionActivePower") ? false : true; + this.isOtherInputAddress = StringUtils.isNotIn(this.inputChannel.toString(), + [null, "_sum/EssSoc", "_sum/GridActivePower", "_sum/ProductionActivePower"]); - // Switch ON / OFF && BELOW / ABOVE + // Switch ON / OF,&& BELOW / ABOVE // Threshold greater 0 if (this.component.properties.threshold > 0) { diff --git a/ui/src/app/edge/live/Controller/Io/ChannelSingleThreshold/modal/modal.component.html b/ui/src/app/edge/live/Controller/Io/ChannelSingleThreshold/modal/modal.component.html index 157118d7b01..49b85982a56 100644 --- a/ui/src/app/edge/live/Controller/Io/ChannelSingleThreshold/modal/modal.component.html +++ b/ui/src/app/edge/live/Controller/Io/ChannelSingleThreshold/modal/modal.component.html @@ -1,5 +1,6 @@ - + {{ component.alias }} @@ -45,20 +46,23 @@ General.on - + General.automatic - + General.off - +
    @@ -84,10 +88,9 @@ - Edge.Index.Widgets.Singlethreshold.dependendOn - + (ionChange)="updateInputMode($event)" + [label]="'Edge.Index.Widgets.Singlethreshold.dependendOn' | translate"> General.soc General.production General.gridSell @@ -109,9 +112,8 @@
    + style="text-align: end;" label=" W" class="regularFont"> -  W
    + formControlName="threshold" + [label]="inputMode.value !== 'OTHER' + ? ' W' + : ((inputMode.value === 'OTHER' && inputChannelUnit) ? ' {{ inputChannelUnit }}' : '')" class="regularFont"> -  W - - -
    -  {{ inputChannelUnit }} -
    -
    + style="text-align: end;" label=" W"> -  W
    + style="text-align: end;" label=" W"> -  W
    + style="text-align: end;" label=" s" class="regularFont"> -  s
    + style="text-align: end;" type="number" label=" W"> -  W
    + type="number" label=" W"> -  W
    + style="text-align: end;" label=" W"> -  W
    + style="text-align: end;" label=" W"> -  W
    + style="text-align: end;" label=" W"> -  W
    + style="text-align: end;" label=" W"> -  W
    + style="text-align: end;" label=" W"> -  W
    General.chargePowerGeneral.CHARGE {{ (sum.effectivePower <= 0 ? (sum.effectivePower * -1) : null) | unitvalue:'W' }}
    General.dischargePowerGeneral.DISCHARGE {{ (sum.effectivePower > 0 ? sum.effectivePower : null) | unitvalue:'W' }}
    General.phase {{ phase }} General.dischargePower + translate>General.DISCHARGE {{ value | unitvalue:'W' }} @@ -61,7 +61,7 @@
    General.phase {{ phase }} General.chargePower + translate>General.CHARGE {{ (value * -1) | unitvalue:'W' }} @@ -76,7 +76,7 @@
    General.phase {{ phase }} General.dischargePower + translate>General.DISCHARGE {{ value | unitvalue:'W' }} @@ -85,7 +85,7 @@
    General.phase {{ phase }} General.chargePower + translate>General.CHARGE {{ (value * -1) | unitvalue:'W' }} @@ -132,7 +132,7 @@ - + Edge.Index.EmergencyReserve.INFO_FOR_EMERGENCY_RESERVE_SLIDER @@ -153,6 +153,22 @@ {{ 100 | unitvalue:'%' }} + + + + + + + + + + + + @@ -316,13 +332,13 @@
    General.chargePowerGeneral.CHARGE {{ sum.effectiveChargePower | unitvalue:'W' }}
    General.dischargePowerGeneral.DISCHARGE {{ sum.effectiveDischargePower | unitvalue:'W' }}
    General.phase {{ phase }} - General.dischargePower + General.DISCHARGE {{ value | unitvalue:'W' }} @@ -349,7 +365,7 @@
    General.phase {{ phase }} - General.chargePower + General.CHARGE {{ (value * -1) | unitvalue:'W' }} @@ -364,7 +380,7 @@
    General.phase {{ phase }} - General.dischargePower + General.DISCHARGE {{ value | unitvalue:'W' }} @@ -373,7 +389,7 @@
    General.phase {{ phase }} - General.chargePower + General.CHARGE {{ (value * -1) | unitvalue:'W' }} @@ -437,12 +453,12 @@ *ngIf="factory.natureIds.includes('io.openems.edge.ess.api.SymmetricEss') && value !== null && value !== undefined" class="full_width">
    General.chargePowerGeneral.CHARGE {{ (value <= 0 ? (value * -1) : null) | unitvalue:'W' }}
    General.dischargePowerGeneral.DISCHARGE {{ (value > 0 ? value : null) | unitvalue:'W' }} @@ -458,7 +474,7 @@ General.phase {{ phase }} - General.dischargePower + General.DISCHARGE {{ value | unitvalue:'W' }} @@ -467,7 +483,7 @@ General.phase {{ phase }} - General.chargePower + General.CHARGE {{ (value * -1) | unitvalue:'W' }} @@ -514,7 +530,7 @@ - + Edge.Index.EmergencyReserve.INFO_FOR_EMERGENCY_RESERVE_SLIDER @@ -696,14 +712,14 @@ {{ component.alias }}
    General.chargePowerGeneral.CHARGE {{ (value > 0 ? value : null) | unitvalue:'W' }}
    General.dischargePowerGeneral.DISCHARGE {{ (value <= 0 ? (value * -1) : null | unitvalue:'W') }} - + Edge.Index.EmergencyReserve.INFO_FOR_EMERGENCY_RESERVE_SLIDER diff --git a/ui/src/app/edge/live/common/storage/modal/modal.component.ts b/ui/src/app/edge/live/common/storage/modal/modal.component.ts index 6ce266a1401..fa3b4109b80 100644 --- a/ui/src/app/edge/live/common/storage/modal/modal.component.ts +++ b/ui/src/app/edge/live/common/storage/modal/modal.component.ts @@ -10,16 +10,14 @@ import { Role } from "src/app/shared/type/role"; @Component({ selector: "storage-modal", templateUrl: "./modal.component.html", + standalone: false, }) export class StorageModalComponent implements OnInit, OnDestroy { // TODO after refactoring of Model: subscribe to EssActivePowerL1/L2/L3 here instead of Flat Widget @Input({ required: true }) protected edge!: Edge; - @Input({ required: true }) protected config!: EdgeConfig; - @Input() protected essComponents: EdgeConfig.Component[] | null = null; - @Input({ required: true }) protected chargerComponents!: EdgeConfig.Component[]; - @Input() protected singleComponent: EdgeConfig.Component | null = null; + @Input() protected component: EdgeConfig.Component | null = null; // reference to the Utils method to access via html public isLastElement = Utils.isLastElement; @@ -28,6 +26,10 @@ export class StorageModalComponent implements OnInit, OnDestroy { protected isAtLeastInstaller: boolean; protected isTargetTimeInValid: Map = new Map(); protected controllerIsRequiredEdgeVersion: boolean = false; + protected hasRequiredEdgeVersion: boolean = false; + protected config: EdgeConfig; + protected essComponents: EdgeConfig.Component[] | null = null; + protected chargerComponents!: EdgeConfig.Component[]; constructor( public service: Service, @@ -38,120 +40,157 @@ export class StorageModalComponent implements OnInit, OnDestroy { ) { } ngOnInit() { + this.edge.getFirstValidConfig(this.websocket).then(config => { + this.config = config; + this.essComponents = this.config + .getComponentsImplementingNature("io.openems.edge.ess.api.SymmetricEss") + .filter(component => component.isEnabled && !this.config + .getNatureIdsByFactoryId(component.factoryId) + .includes("io.openems.edge.ess.api.MetaEss")); + + this.chargerComponents = this.config + .getComponentsImplementingNature("io.openems.edge.ess.dccharger.api.EssDcCharger") + .filter(component => component.isEnabled); + + // Future Work: Remove when all ems are at least at this version + this.controllerIsRequiredEdgeVersion = this.edge.isVersionAtLeast("2023.2.5"); + + this.isAtLeastInstaller = this.edge.roleIsAtLeast(Role.INSTALLER); + const emergencyReserveCtrl = this.config.getComponentsByFactory("Controller.Ess.EmergencyCapacityReserve"); + const prepareBatteryExtensionCtrl = this.config.getComponentsByFactory("Controller.Ess.PrepareBatteryExtension"); + this.hasRequiredEdgeVersion = this.edge.isVersionAtLeast("2024.12.3"); + const components = [...prepareBatteryExtensionCtrl, ...emergencyReserveCtrl].filter(component => component.isEnabled).reduce((result, component) => { + const essId = component.properties["ess.id"]; + if (result[essId] == null) { + result[essId] = []; + } + result[essId].push(component); + return result; + }, {}); - // Future Work: Remove when all ems are at least at this version - this.controllerIsRequiredEdgeVersion = this.edge.isVersionAtLeast("2023.2.5"); + const channelAddresses: ChannelAddress[] = []; + channelAddresses.push(...this.chargerComponents.map(comp => new ChannelAddress(comp.id, "ActualPower"))); - this.isAtLeastInstaller = this.edge.roleIsAtLeast(Role.INSTALLER); - const emergencyReserveCtrl = this.config.getComponentsByFactory("Controller.Ess.EmergencyCapacityReserve"); - const prepareBatteryExtensionCtrl = this.config.getComponentsByFactory("Controller.Ess.PrepareBatteryExtension"); - const components = [...prepareBatteryExtensionCtrl, ...emergencyReserveCtrl].filter(component => component.isEnabled).reduce((result, component) => { - const essId = component.properties["ess.id"]; - if (result[essId] == null) { - result[essId] = []; + if (this.hasRequiredEdgeVersion) { + channelAddresses.push(new ChannelAddress("_meta", "IsEssChargeFromGridAllowed")); } - result[essId].push(component); - return result; - }, {}); - - const channelAddresses: ChannelAddress[] = []; - for (const essId in prepareBatteryExtensionCtrl) { - const controller = prepareBatteryExtensionCtrl[essId]; - channelAddresses.push( - new ChannelAddress(controller.id, "_PropertyIsRunning"), - new ChannelAddress(controller.id, "_PropertyTargetTime"), - new ChannelAddress(controller.id, "_PropertyTargetTimeSpecified"), - new ChannelAddress(controller.id, "_PropertyTargetSoc"), - new ChannelAddress(controller.id, "_PropertyTargetTimeBuffer"), - new ChannelAddress(controller.id, "ExpectedStartEpochSeconds"), - ); - } - this.edge.subscribeChannels(this.websocket, "storage", channelAddresses); - - this.edge.currentData - .subscribe(currentData => { - - const controls: FormGroup = new FormGroup({}); - for (const essId of Object.keys(components)) { - const controllers = components[essId]; - - const controllerFrmGrp: FormGroup = new FormGroup({}); - for (const controller of (controllers as EdgeConfig.Component[])) { - - if (controller.factoryId == "Controller.Ess.EmergencyCapacityReserve") { - const reserveSoc = currentData.channel[controller.id + "/_PropertyReserveSoc"] ?? 20 /* default Reserve-Soc */; - const isReserveSocEnabled = currentData.channel[controller.id + "/_PropertyIsReserveSocEnabled"] == 1; - - controllerFrmGrp.addControl("emergencyReserveController", - this.formBuilder.group({ - controllerId: new FormControl(controller["id"]), - isReserveSocEnabled: new FormControl(isReserveSocEnabled), - reserveSoc: new FormControl(reserveSoc), - }), - ); - } else if (controller.factoryId == "Controller.Ess.PrepareBatteryExtension") { - - const isRunning = currentData.channel[controller.id + "/_PropertyIsRunning"] == 1; - - // Because of ionic segment buttons only accepting a string value, i needed to convert it - const targetTimeSpecified = (currentData.channel[controller.id + "/_PropertyTargetTimeSpecified"] == 1).toString(); - let targetTime = currentData.channel[controller.id + "/_PropertyTargetTime"]; - const targetSoc = currentData.channel[controller.id + "/_PropertyTargetSoc"]; - const targetTimeBuffer = currentData.channel[controller.id + "/_PropertyTargetTimeBuffer"]; - const epochSeconds = currentData.channel[controller.id + "/ExpectedStartEpochSeconds"]; - - const expectedStartOfPreparation = new Date(0); - expectedStartOfPreparation.setUTCSeconds(epochSeconds ?? 0); - - // If targetTime not set, not equals 0 or targetTime is no valid time, - // then set targetTime to null - if (!targetTime || targetTime == 0 || isNaN(Date.parse(targetTime))) { - targetTime = null; - } + for (const essId in prepareBatteryExtensionCtrl) { + const controller = prepareBatteryExtensionCtrl[essId]; + channelAddresses.push( + new ChannelAddress(controller.id, "_PropertyIsRunning"), + new ChannelAddress(controller.id, "_PropertyTargetTime"), + new ChannelAddress(controller.id, "_PropertyTargetTimeSpecified"), + new ChannelAddress(controller.id, "_PropertyTargetSoc"), + new ChannelAddress(controller.id, "_PropertyTargetTimeBuffer"), + new ChannelAddress(controller.id, "ExpectedStartEpochSeconds"), + ); + } + this.edge.subscribeChannels(this.websocket, "storage", channelAddresses); - // Channel "ExpectedStartEpochSeconds" is not set - if ((epochSeconds == null - || epochSeconds == 0)) { - this.isTargetTimeInValid.set(essId, true); - } else if - - // If expected expectedStartOfpreparation is after targetTime - // Guarantee, that the TargetSoc should be reached after the preparation to reach that Soc started - (isBefore(new Date(targetTime), expectedStartOfPreparation) - || isBefore(new Date(targetTime), new Date())) { - this.isTargetTimeInValid.set(essId, true); - } else { - this.isTargetTimeInValid.set(essId, false); - } + this.edge.currentData + .subscribe(currentData => { - controllerFrmGrp.addControl("prepareBatteryExtensionController", - this.formBuilder.group({ - controllerId: new FormControl(controller.id), - isRunning: new FormControl(isRunning), - targetTime: new FormControl(targetTime), - targetTimeSpecified: new FormControl(targetTimeSpecified), - targetSoc: new FormControl(targetSoc), - targetTimeBuffer: new FormControl(targetTimeBuffer), - expectedStartOfPreparation: new FormControl(expectedStartOfPreparation), - }), - ); + const controls: FormGroup = new FormGroup({}); + if (this.hasRequiredEdgeVersion) { + controls.addControl("_meta", this.formBuilder.group({ + isEssChargeFromGridAllowed: new FormControl(currentData.channel["_meta/IsEssChargeFromGridAllowed"]), + })); + } + for (const essId of Object.keys(components)) { + const controllers = components[essId]; + + const controllerFrmGrp: FormGroup = new FormGroup({}); + for (const controller of (controllers as EdgeConfig.Component[])) { + + if (controller.factoryId == "Controller.Ess.EmergencyCapacityReserve") { + const reserveSoc = currentData.channel[controller.id + "/_PropertyReserveSoc"] ?? 20 /* default Reserve-Soc */; + const isReserveSocEnabled = currentData.channel[controller.id + "/_PropertyIsReserveSocEnabled"] == 1; + + controllerFrmGrp.addControl("emergencyReserveController", + this.formBuilder.group({ + controllerId: new FormControl(controller["id"]), + isReserveSocEnabled: new FormControl(isReserveSocEnabled), + reserveSoc: new FormControl(reserveSoc), + }), + ); + } else if (controller.factoryId == "Controller.Ess.PrepareBatteryExtension") { + + const isRunning = currentData.channel[controller.id + "/_PropertyIsRunning"] == 1; + + // Because of ionic segment buttons only accepting a string value, i needed to convert it + const targetTimeSpecified = (currentData.channel[controller.id + "/_PropertyTargetTimeSpecified"] == 1).toString(); + let targetTime = currentData.channel[controller.id + "/_PropertyTargetTime"]; + const targetSoc = currentData.channel[controller.id + "/_PropertyTargetSoc"]; + const targetTimeBuffer = currentData.channel[controller.id + "/_PropertyTargetTimeBuffer"]; + const epochSeconds = currentData.channel[controller.id + "/ExpectedStartEpochSeconds"]; + + const expectedStartOfPreparation = new Date(0); + expectedStartOfPreparation.setUTCSeconds(epochSeconds ?? 0); + + // If targetTime not set, not equals 0 or targetTime is no valid time, + // then set targetTime to null + if (!targetTime || targetTime == 0 || isNaN(Date.parse(targetTime))) { + targetTime = null; + } + + // Channel "ExpectedStartEpochSeconds" is not set + if ((epochSeconds == null + || epochSeconds == 0)) { + this.isTargetTimeInValid.set(essId, true); + } else if + + // If expected expectedStartOfpreparation is after targetTime + // Guarantee, that the TargetSoc should be reached after the preparation to reach that Soc started + (isBefore(new Date(targetTime), expectedStartOfPreparation) + || isBefore(new Date(targetTime), new Date())) { + this.isTargetTimeInValid.set(essId, true); + } else { + this.isTargetTimeInValid.set(essId, false); + } + + controllerFrmGrp.addControl("prepareBatteryExtensionController", + this.formBuilder.group({ + controllerId: new FormControl(controller.id), + isRunning: new FormControl(isRunning), + targetTime: new FormControl(targetTime), + targetTimeSpecified: new FormControl(targetTimeSpecified), + targetSoc: new FormControl(targetSoc), + targetTimeBuffer: new FormControl(targetTimeBuffer), + expectedStartOfPreparation: new FormControl(expectedStartOfPreparation), + }), + ); + } } + controls.addControl(essId, controllerFrmGrp); } - controls.addControl(essId, controllerFrmGrp); - } - if (!this.formGroup.dirty) { - this.formGroup = controls; - } - }); + if (!this.formGroup.dirty) { + this.formGroup = controls; + } + }); + }, + ); + } applyChanges() { if (this.edge == null) { return; } - const updateArray: Map>> = new Map(); + if (this.hasRequiredEdgeVersion) { + const metaFormGroup = (this.formGroup.get("_meta") as FormGroup)?.controls ?? []; + for (const prop of Object.keys(metaFormGroup)) { + if (metaFormGroup[prop].dirty) { + if (updateArray.get("_meta")) { + updateArray.get("_meta").push(new Map().set(prop, metaFormGroup[prop].value)); + } else { + updateArray.set("_meta", [new Map().set(prop, metaFormGroup[prop].value)]); + } + } + } + } + for (const essId in this.formGroup.controls) { const essGroups = this.formGroup.controls[essId]; @@ -204,4 +243,5 @@ export class StorageModalComponent implements OnInit, OnDestroy { ngOnDestroy() { this.edge.unsubscribeChannels(this.websocket, "storage"); } + } diff --git a/ui/src/app/edge/live/common/storage/storage.component.html b/ui/src/app/edge/live/common/storage/storage.component.html index d7ffcb21081..47d39d8842e 100644 --- a/ui/src/app/edge/live/common/storage/storage.component.html +++ b/ui/src/app/edge/live/common/storage/storage.component.html @@ -15,10 +15,9 @@ - - + + @@ -31,20 +30,20 @@ - + - - - diff --git a/ui/src/app/edge/live/common/storage/storage.component.ts b/ui/src/app/edge/live/common/storage/storage.component.ts index ca1846f220f..4bcaad43273 100644 --- a/ui/src/app/edge/live/common/storage/storage.component.ts +++ b/ui/src/app/edge/live/common/storage/storage.component.ts @@ -2,15 +2,16 @@ import { formatNumber } from "@angular/common"; import { Component } from "@angular/core"; import { AbstractFlatWidget } from "src/app/shared/components/flat/abstract-flat-widget"; -import { CurrentData } from "src/app/shared/shared"; +import { ChannelAddress, CurrentData, EdgeConfig, Utils } from "src/app/shared/shared"; +import { Language } from "src/app/shared/type/language"; import { DateUtils } from "src/app/shared/utils/date/dateutils"; -import { ChannelAddress, EdgeConfig, Utils } from "../../../../shared/shared"; import { StorageModalComponent } from "./modal/modal.component"; @Component({ selector: "storage", templateUrl: "./storage.component.html", + standalone: false, }) export class StorageComponent extends AbstractFlatWidget { @@ -51,6 +52,7 @@ export class StorageComponent extends AbstractFlatWidget { * @returns only positive and 0 */ public convertPower(value: number, isCharge?: boolean) { + const locale: string = (Language.getByKey(localStorage.LANGUAGE) ?? Language.DEFAULT).i18nLocaleKey; if (value == null) { return "-"; } @@ -59,7 +61,7 @@ export class StorageComponent extends AbstractFlatWidget { // Round thisValue to Integer when decimal place equals 0 if (thisValue > 0) { - return formatNumber(thisValue, "de", "1.0-1") + " kW"; // TODO get locale dynamically + return formatNumber(thisValue, locale, "1.0-1") + " kW"; } else if (thisValue == 0 && isCharge) { // if thisValue is 0, then show only when charge and not discharge @@ -75,11 +77,7 @@ export class StorageComponent extends AbstractFlatWidget { component: StorageModalComponent, componentProps: { edge: this.edge, - config: this.config, component: this.component, - essComponents: this.essComponents, - chargerComponents: this.chargerComponents, - singleComponent: this.component, }, }); return await modal.present(); diff --git a/ui/src/app/edge/live/delayedselltogrid/delayedselltogrid.component.html b/ui/src/app/edge/live/delayedselltogrid/delayedselltogrid.component.html index 352d15d1357..0235fb3e696 100644 --- a/ui/src/app/edge/live/delayedselltogrid/delayedselltogrid.component.html +++ b/ui/src/app/edge/live/delayedselltogrid/delayedselltogrid.component.html @@ -1,6 +1,7 @@ - + + {{ component.alias }} diff --git a/ui/src/app/edge/live/delayedselltogrid/delayedselltogrid.component.ts b/ui/src/app/edge/live/delayedselltogrid/delayedselltogrid.component.ts index 89febc1abd0..4710015a124 100644 --- a/ui/src/app/edge/live/delayedselltogrid/delayedselltogrid.component.ts +++ b/ui/src/app/edge/live/delayedselltogrid/delayedselltogrid.component.ts @@ -9,6 +9,7 @@ import { DelayedSellToGridModalComponent } from "./modal/modal.component"; @Component({ selector: DelayedSellToGridComponent.SELECTOR, templateUrl: "./delayedselltogrid.component.html", + standalone: false, }) export class DelayedSellToGridComponent implements OnInit, OnDestroy { diff --git a/ui/src/app/edge/live/delayedselltogrid/modal/modal.component.html b/ui/src/app/edge/live/delayedselltogrid/modal/modal.component.html index 0327fd96b05..5d23d380fc2 100644 --- a/ui/src/app/edge/live/delayedselltogrid/modal/modal.component.html +++ b/ui/src/app/edge/live/delayedselltogrid/modal/modal.component.html @@ -1,5 +1,6 @@ - + {{ component.alias }} @@ -21,9 +22,8 @@ + style="text-align: end;" label=" W"> -  W
    + style="text-align: end;" label=" W"> -  W
    + -   {{ displayValue }} + {{displayName ? displayName + ": " + displayValue: '  ' + + displayValue}}
    +
    - diff --git a/ui/src/app/shared/components/flat/flat-widget-line/flat-widget-line.ts b/ui/src/app/shared/components/flat/flat-widget-line/flat-widget-line.ts index 288f0f5aced..1f06103ebaf 100644 --- a/ui/src/app/shared/components/flat/flat-widget-line/flat-widget-line.ts +++ b/ui/src/app/shared/components/flat/flat-widget-line/flat-widget-line.ts @@ -5,11 +5,9 @@ import { AbstractFlatWidgetLine } from "../abstract-flat-widget-line"; @Component({ selector: "oe-flat-widget-line", templateUrl: "./flat-widget-line.html", + standalone: false, }) export class FlatWidgetLineComponent extends AbstractFlatWidgetLine { - /** Name for parameter, displayed on the left side */ - @Input({ required: true }) - public name!: string; /** Width of left Column, right Column is (100 - width of left Column) */ @Input() diff --git a/ui/src/app/shared/components/flat/flat-widget-percentagebar/flat-widget-percentagebar.html b/ui/src/app/shared/components/flat/flat-widget-percentagebar/flat-widget-percentagebar.html index 5570abc71b8..bc881797a6a 100644 --- a/ui/src/app/shared/components/flat/flat-widget-percentagebar/flat-widget-percentagebar.html +++ b/ui/src/app/shared/components/flat/flat-widget-percentagebar/flat-widget-percentagebar.html @@ -1,10 +1,10 @@ - - - - {{ displayValue | unitvalue: '%' }} - - + + + + {{ displayPercent | unitvalue: '%' }} + + diff --git a/ui/src/app/shared/components/flat/flat-widget-percentagebar/flat-widget-percentagebar.ts b/ui/src/app/shared/components/flat/flat-widget-percentagebar/flat-widget-percentagebar.ts index 668caef9e60..63f1247b1f7 100644 --- a/ui/src/app/shared/components/flat/flat-widget-percentagebar/flat-widget-percentagebar.ts +++ b/ui/src/app/shared/components/flat/flat-widget-percentagebar/flat-widget-percentagebar.ts @@ -4,5 +4,13 @@ import { AbstractFlatWidgetLine } from "../abstract-flat-widget-line"; @Component({ selector: "oe-flat-widget-percentagebar", templateUrl: "./flat-widget-percentagebar.html", + standalone: false, }) -export class FlatWidgetPercentagebarComponent extends AbstractFlatWidgetLine { } +export class FlatWidgetPercentagebarComponent extends AbstractFlatWidgetLine { + + protected get displayPercent(): number | null { + return this.displayValue === null + ? null + : Math.round(Number.parseFloat(this.displayValue)); + } +} diff --git a/ui/src/app/shared/components/flat/flat-widget.component.html b/ui/src/app/shared/components/flat/flat-widget.component.html index a969eb76a61..e625b1fcd77 100644 --- a/ui/src/app/shared/components/flat/flat-widget.component.html +++ b/ui/src/app/shared/components/flat/flat-widget.component.html @@ -1,9 +1,9 @@ - - + + - - + + diff --git a/ui/src/app/shared/components/flat/flat.html b/ui/src/app/shared/components/flat/flat.html index 0e2ae2de349..72ebc04d451 100644 --- a/ui/src/app/shared/components/flat/flat.html +++ b/ui/src/app/shared/components/flat/flat.html @@ -1,9 +1,10 @@ - - + + + - - + + @@ -15,7 +16,7 @@ - + diff --git a/ui/src/app/shared/components/flat/flat.ts b/ui/src/app/shared/components/flat/flat.ts index a2642ad0b75..372d6abc8d1 100644 --- a/ui/src/app/shared/components/flat/flat.ts +++ b/ui/src/app/shared/components/flat/flat.ts @@ -1,15 +1,16 @@ // @ts-strict-ignore import { Component, Input } from "@angular/core"; -import { Icon } from "src/app/shared/type/widget"; +import { Icon, ImageIcon } from "src/app/shared/type/widget"; @Component({ selector: "oe-flat-widget", templateUrl: "./flat.html", + standalone: false, }) export class FlatWidgetComponent { /** Image in Header */ - @Input() public img?: string; + @Input() public img?: ImageIcon; /** Icon in Header */ @Input() public icon: Icon | null = null; diff --git a/ui/src/app/shared/components/footer/footer.html b/ui/src/app/shared/components/footer/footer.html index 1ff1460ea74..781d0067406 100644 --- a/ui/src/app/shared/components/footer/footer.html +++ b/ui/src/app/shared/components/footer/footer.html @@ -1,6 +1,6 @@ - - - + + + @@ -19,5 +19,5 @@ - + diff --git a/ui/src/app/shared/components/footer/footer.ts b/ui/src/app/shared/components/footer/footer.ts index 2183f132dea..bca45ba524e 100644 --- a/ui/src/app/shared/components/footer/footer.ts +++ b/ui/src/app/shared/components/footer/footer.ts @@ -32,6 +32,7 @@ import { Role } from "../../type/role"; } `], templateUrl: "footer.html", + standalone: false, }) export class FooterComponent implements OnInit { diff --git a/ui/src/app/shared/components/footer/subnavigation/footerNavigation.html b/ui/src/app/shared/components/footer/subnavigation/footerNavigation.html index bfa8f32655c..49b9a346fc8 100644 --- a/ui/src/app/shared/components/footer/subnavigation/footerNavigation.html +++ b/ui/src/app/shared/components/footer/subnavigation/footerNavigation.html @@ -1,10 +1,11 @@ - + - + @@ -14,12 +15,11 @@ - + {{button.alias ?? button.id}} - + ... ", + standalone: false, }) export class FormlyWrapperDefaultValueWithCasesComponent extends FieldWrapper implements OnInit { diff --git a/ui/src/app/shared/components/formly/form-field-multi-step/form-field-multi-step.html b/ui/src/app/shared/components/formly/form-field-multi-step/form-field-multi-step.html new file mode 100644 index 00000000000..07dbace6ba8 --- /dev/null +++ b/ui/src/app/shared/components/formly/form-field-multi-step/form-field-multi-step.html @@ -0,0 +1,66 @@ + + + + + + {{props.label}} + + + + + + + {{props.description}} + + + + + + + + + + +
    + {{props.attributes?.infoLine }} +
    +
    +
    + + + + {{step.label}} + + + + + + + + + + {{step.description}} + + + + + + + + + +
    + + + + + + +
    +
    +
    +
    +
    diff --git a/ui/src/app/shared/components/formly/form-field-multi-step/form-field-multi-step.ts b/ui/src/app/shared/components/formly/form-field-multi-step/form-field-multi-step.ts new file mode 100644 index 00000000000..4c155a5af8d --- /dev/null +++ b/ui/src/app/shared/components/formly/form-field-multi-step/form-field-multi-step.ts @@ -0,0 +1,71 @@ +import { Component, OnInit } from "@angular/core"; +import { FieldType } from "@ngx-formly/core"; + +@Component({ + selector: "form-field-multi-step", + templateUrl: "./form-field-multi-step.html", + standalone: false, +}) +export class FormlyFieldMultiStepComponent extends FieldType implements OnInit { + + public currentStep: number = 0; + + protected get steps() { + return this.props.steps || []; + } + + public ngOnInit() { + // Ensure the model has an array to track steps + const stepArray = this.formControl.value; + + if (!Array.isArray(stepArray)) { + this.formControl.setValue(Array(this.steps.length).fill(false)); + } + + // Listen to status changes to reset steps if disabled + this.formControl.statusChanges.subscribe(status => { + if (status === "DISABLED") { + this.resetSteps(); + } + }); + + // Determine the current step based on the array of steps + const lastFalseIndex = stepArray.lastIndexOf(false); + const lastTrueIndex = stepArray.lastIndexOf(true); + + if (lastFalseIndex === -1) { + // All steps are true, show the final step + this.currentStep = this.steps.length - 1; + } else if (lastTrueIndex === -1) { + // No true steps, show the first step + this.currentStep = 0; + } else { + // Show the last true step + this.currentStep = lastTrueIndex; + } + } + + + protected nextStep() { + if (this.currentStep < this.steps.length - 1) { + this.currentStep++; + } + } + + protected prevStep() { + if (this.currentStep > 0) { + this.currentStep--; + } + } + + protected onCheckboxChange(event: any, index: number) { + const updatedValue = this.formControl.value; + updatedValue[index] = event.detail.checked; + this.formControl.setValue(updatedValue); + } + + private resetSteps() { + this.formControl.setValue(Array(this.steps.length).fill(false)); + this.currentStep = 0; + } +} diff --git a/ui/src/app/shared/components/formly/form-field.wrapper.ts b/ui/src/app/shared/components/formly/form-field.wrapper.ts index 15d3c9a1b0d..b4bb24fc97d 100644 --- a/ui/src/app/shared/components/formly/form-field.wrapper.ts +++ b/ui/src/app/shared/components/formly/form-field.wrapper.ts @@ -5,5 +5,6 @@ import { FieldWrapper } from "@ngx-formly/core"; selector: "formly-wrapper-ion-form-field", templateUrl: "./form-field.wrapper.html", changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false, }) export class FormlyWrapperFormFieldComponent extends FieldWrapper { } diff --git a/ui/src/app/shared/components/formly/formly-field-checkbox-image/formly-field-checkbox-with-image.html b/ui/src/app/shared/components/formly/formly-field-checkbox-image/formly-field-checkbox-with-image.html index bd1c0ab4da9..64274e6732d 100644 --- a/ui/src/app/shared/components/formly/formly-field-checkbox-image/formly-field-checkbox-with-image.html +++ b/ui/src/app/shared/components/formly/formly-field-checkbox-image/formly-field-checkbox-with-image.html @@ -4,13 +4,20 @@ {{props.label}} -
    - + + + + {{props.description}} + + + + diff --git a/ui/src/app/shared/components/formly/formly-field-checkbox-image/formly-field-checkbox-with-image.ts b/ui/src/app/shared/components/formly/formly-field-checkbox-image/formly-field-checkbox-with-image.ts index c00fdafb5a2..d1487567a71 100644 --- a/ui/src/app/shared/components/formly/formly-field-checkbox-image/formly-field-checkbox-with-image.ts +++ b/ui/src/app/shared/components/formly/formly-field-checkbox-image/formly-field-checkbox-with-image.ts @@ -5,6 +5,7 @@ import { FieldWrapper } from "@ngx-formly/core"; selector: "formly-field-checkbox-with-image", templateUrl: "./formly-field-checkbox-with-image.html", changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false, }) export class FormlyFieldCheckboxWithImageComponent extends FieldWrapper implements OnInit { @@ -12,7 +13,15 @@ export class FormlyFieldCheckboxWithImageComponent extends FieldWrapper implemen public ngOnInit() { // If the default value is not set in beginning. - this.value = this.field.defaultValue; + this.value = this.formControl.value ?? this.field.defaultValue; + + // Listen to form control status changes to reset steps if disabled + this.formControl.statusChanges.subscribe(status => { + if (status === "DISABLED" && this.value !== false) { + this.value = false; + this.formControl.setValue(this.value); + } + }); } /** @@ -23,4 +32,13 @@ export class FormlyFieldCheckboxWithImageComponent extends FieldWrapper implemen this.formControl.setValue(this.value); } + /** + * Returns the show/hide value based on the properties. + * + * @returns boolean value representing "show" or "hide". + */ + protected showContent() { + return (!this.field.props?.disabled && !this.value) && this.field.props?.url !== undefined; + } + } diff --git a/ui/src/app/shared/components/formly/formly-field-modal/formlyfieldmodal.ts b/ui/src/app/shared/components/formly/formly-field-modal/formlyfieldmodal.ts index 2d934c9c8fa..4f19e2a5b45 100644 --- a/ui/src/app/shared/components/formly/formly-field-modal/formlyfieldmodal.ts +++ b/ui/src/app/shared/components/formly/formly-field-modal/formlyfieldmodal.ts @@ -4,5 +4,6 @@ import { FieldWrapper } from "@ngx-formly/core"; @Component({ selector: "formly-field-modal", templateUrl: "./formlyfieldmodal.html", + standalone: false, }) export class FormlyFieldModalComponent extends FieldWrapper { } diff --git a/ui/src/app/shared/components/formly/formly-field-radio-with-image/formly-field-radio-with-image.ts b/ui/src/app/shared/components/formly/formly-field-radio-with-image/formly-field-radio-with-image.ts index a104e13a949..f2d12b8526c 100644 --- a/ui/src/app/shared/components/formly/formly-field-radio-with-image/formly-field-radio-with-image.ts +++ b/ui/src/app/shared/components/formly/formly-field-radio-with-image/formly-field-radio-with-image.ts @@ -5,6 +5,7 @@ import { FieldWrapper } from "@ngx-formly/core"; selector: "formly-field-radio-with-image", templateUrl: "./formly-field-radio-with-image.html", changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false, }) export class FormlyFieldRadioWithImageComponent extends FieldWrapper implements OnInit { diff --git a/ui/src/app/shared/components/formly/formly-select-field-modal.component.ts b/ui/src/app/shared/components/formly/formly-select-field-modal.component.ts index 35bef389fd0..d818f2a97f4 100644 --- a/ui/src/app/shared/components/formly/formly-select-field-modal.component.ts +++ b/ui/src/app/shared/components/formly/formly-select-field-modal.component.ts @@ -4,6 +4,7 @@ import { ModalController } from "@ionic/angular"; @Component({ selector: "formly-select-modal", templateUrl: "./formly-select-field-modal.component.html", + standalone: false, }) export class FormlySelectFieldModalComponent implements OnInit { diff --git a/ui/src/app/shared/components/formly/formly-select-field.extended.ts b/ui/src/app/shared/components/formly/formly-select-field.extended.ts index ffcdb6ada79..80e3697c5ea 100644 --- a/ui/src/app/shared/components/formly/formly-select-field.extended.ts +++ b/ui/src/app/shared/components/formly/formly-select-field.extended.ts @@ -6,6 +6,7 @@ import { FormlySelectFieldModalComponent } from "./formly-select-field-modal.com @Component({ selector: "formly-select-extended-wrapper", templateUrl: "./formly-select-field.extended.html", + standalone: false, }) export class FormlySelectFieldExtendedWrapperComponent extends FieldWrapper { diff --git a/ui/src/app/shared/components/formly/formly-skeleton-wrapper.ts b/ui/src/app/shared/components/formly/formly-skeleton-wrapper.ts index 8dc332dcf29..d8b574c447d 100644 --- a/ui/src/app/shared/components/formly/formly-skeleton-wrapper.ts +++ b/ui/src/app/shared/components/formly/formly-skeleton-wrapper.ts @@ -10,7 +10,7 @@ import { FormlyFieldConfig } from "@ngx-formly/core"; * @input model the model */ @Component({ - selector: "formly-skeleton-wrapper", + selector: "oe-formly-skeleton-wrapper", template: `
    @@ -21,6 +21,7 @@ import { FormlyFieldConfig } from "@ngx-formly/core";
    `, + standalone: false, }) export class FormlyFieldWithLoadingAnimationComponent { @Input() public show: boolean = false; diff --git a/ui/src/app/shared/components/formly/input-serial-number-wrapper.ts b/ui/src/app/shared/components/formly/input-serial-number-wrapper.ts index 0df1760de05..f56015c1311 100644 --- a/ui/src/app/shared/components/formly/input-serial-number-wrapper.ts +++ b/ui/src/app/shared/components/formly/input-serial-number-wrapper.ts @@ -4,5 +4,6 @@ import { FieldWrapper } from "@ngx-formly/core"; @Component({ selector: "formly-input-serial-number", templateUrl: "./input-serial-number-wrapper.html", + standalone: false, }) export class FormlyInputSerialNumberWrapperComponent extends FieldWrapper { } diff --git a/ui/src/app/shared/components/formly/input.ts b/ui/src/app/shared/components/formly/input.ts index 3e547670e94..8635fb6bc79 100644 --- a/ui/src/app/shared/components/formly/input.ts +++ b/ui/src/app/shared/components/formly/input.ts @@ -4,5 +4,6 @@ import { FieldType } from "@ngx-formly/core"; @Component({ selector: "formly-input-section", templateUrl: "./input.html", + standalone: false, }) export class InputTypeComponent extends FieldType { } diff --git a/ui/src/app/shared/components/formly/panel-wrapper.component.ts b/ui/src/app/shared/components/formly/panel-wrapper.component.ts index 5af73ea9d87..f5b3cf1c5d6 100644 --- a/ui/src/app/shared/components/formly/panel-wrapper.component.ts +++ b/ui/src/app/shared/components/formly/panel-wrapper.component.ts @@ -15,6 +15,7 @@ import { FieldWrapper } from "@ngx-formly/core";
    `, + standalone: false, }) export class PanelWrapperComponent extends FieldWrapper { } diff --git a/ui/src/app/shared/components/formly/repeat.ts b/ui/src/app/shared/components/formly/repeat.ts index 5ec40c593a7..9d4db58885d 100644 --- a/ui/src/app/shared/components/formly/repeat.ts +++ b/ui/src/app/shared/components/formly/repeat.ts @@ -4,6 +4,7 @@ import { FieldArrayType } from "@ngx-formly/core"; @Component({ selector: "formly-repeat-section", templateUrl: "./repeat.html", + standalone: false, }) export class RepeatTypeComponent extends FieldArrayType { // TODO: add explicit constructor diff --git a/ui/src/app/shared/components/header/app-header.ts b/ui/src/app/shared/components/header/app-header.ts new file mode 100644 index 00000000000..3143213c849 --- /dev/null +++ b/ui/src/app/shared/components/header/app-header.ts @@ -0,0 +1,223 @@ +// @ts-strict-ignore +import { AfterViewChecked, ChangeDetectorRef, Component, Input, OnDestroy, OnInit, ViewChild } from "@angular/core"; +import { NavigationEnd, Router } from "@angular/router"; +import { MenuController, ModalController } from "@ionic/angular"; +import { Subject } from "rxjs"; +import { filter, takeUntil } from "rxjs/operators"; +import { environment } from "src/environments"; + +import { Edge, Service, Websocket } from "../../shared"; +import { PickDateComponent } from "../pickdate/pickdate.component"; +import { StatusSingleComponent } from "../status/single/status.component"; + +@Component({ + selector: "app-header", + templateUrl: "./header.component.html", + standalone: false, +}) +export class AppHeaderComponent implements OnInit, OnDestroy, AfterViewChecked { + + @ViewChild(PickDateComponent, { static: false }) public PickDateComponent: PickDateComponent; + + public environment = environment; + public backUrl: string | boolean = "/"; + public enableSideMenu: boolean; + public currentPage: "EdgeSettings" | "Other" | "IndexLive" | "IndexHistory" = "Other"; + public isSystemLogEnabled: boolean = false; + + protected isHeaderAllowed: boolean = false; + + private ngUnsubscribe: Subject = new Subject(); + private _customBackUrl: string | null = null; + + constructor( + private cdRef: ChangeDetectorRef, + public menu: MenuController, + public modalCtrl: ModalController, + public router: Router, + public service: Service, + public websocket: Websocket, + ) { } + + @Input() public set customBackUrl(url: string | null) { + if (!url) { + return; + } + this._customBackUrl = url; + this.updateBackUrl(url); + } + + ngOnInit() { + // set inital URL + this.updateUrl(this.router.routerState.snapshot.url); + // update backUrl on navigation events + this.router.events.pipe( + takeUntil(this.ngUnsubscribe), + filter(event => event instanceof NavigationEnd), + ).subscribe(event => { + window.scrollTo(0, 0); + this.updateUrl((event).urlAfterRedirects); + }); + + } + + // used to prevent 'Expression has changed after it was checked' error + ngAfterViewChecked() { + this.cdRef.detectChanges(); + } + + updateUrl(url: string) { + this.updateBackUrl(url); + this.updateEnableSideMenu(url); + this.updateCurrentPage(url); + this.isHeaderAllowed = this.isAllowedForView(url); + } + + updateEnableSideMenu(url: string) { + const urlArray = url.split("/"); + const file = urlArray.pop(); + + if (file == "user" || file == "settings" || file == "changelog" || file == "login" || file == "index" || urlArray.length > 3) { + // disable side-menu; show back-button instead + this.enableSideMenu = false; + } else { + // enable side-menu if back-button is not needed + this.enableSideMenu = true; + } + } + + updateBackUrl(url: string) { + + if (this._customBackUrl) { + this.backUrl = this._customBackUrl; + return; + } + + // disable backUrl & Segment Navigation on initial 'login' page + if (url === "/login" || url === "/overview" || url === "/index") { + this.backUrl = false; + return; + } + + + // set backUrl for user when an Edge had been selected before + const currentEdge: Edge = this.service.currentEdge.value; + if (url === "/user" && currentEdge != null) { + this.backUrl = "/device/" + currentEdge.id + "/live"; + return; + } + + // set backUrl for user if no edge had been selected + if (url === "/user") { + this.backUrl = "/overview"; + return; + } + + if (url === "/changelog" && currentEdge != null) { + // TODO this does not work if Changelog was opened from /user + this.backUrl = "/device/" + currentEdge.id + "/settings/profile"; + return; + } + + const urlArray = url.split("/"); + let backUrl: string | boolean = "/"; + const file = urlArray.pop(); + + // disable backUrl for History & EdgeIndex Component ++ Enable Segment Navigation + if ((file == "history" || file == "live") && urlArray.length == 3) { + this.backUrl = false; + return; + } + + // disable backUrl to first 'index' page from Edge index if there is only one Edge in the system + if (file === "live" && urlArray.length == 3 && this.environment.backend === "OpenEMS Edge") { + this.backUrl = false; + return; + } + + // remove one part of the url for 'index' + if (file === "live") { + urlArray.pop(); + } + + // fix url for App "settings/app/install" and "settings/app/update" + if (urlArray.slice(-3, -1).join("/") === "settings/app") { + urlArray.pop(); + } + + // re-join the url + backUrl = urlArray.join("/") || "/"; + + // correct path for '/device/[edgeId]/index' + if (backUrl === "/device") { + backUrl = "/"; + } + this.backUrl = backUrl; + } + + updateCurrentPage(url: string) { + const urlArray = url.split("/"); + let file = urlArray.pop(); + if (urlArray.length >= 4) { + file = urlArray[3]; + } + // Enable Segment Navigation for Edge-Index-Page + if ((file == "history" || file == "live") && urlArray.length == 3) { + if (file == "history") { + this.currentPage = "IndexHistory"; + } else { + this.currentPage = "IndexLive"; + } + } else if (file == "settings" && urlArray.length > 1) { + this.currentPage = "EdgeSettings"; + } + else { + this.currentPage = "Other"; + } + } + + public segmentChanged(event) { + if (event.detail.value == "IndexLive") { + this.router.navigate(["/device/" + this.service.currentEdge.value.id + "/live"], { replaceUrl: true }); + this.cdRef.detectChanges(); + } + if (event.detail.value == "IndexHistory") { + + /** Creates bug of being infinite forwarded betweeen live and history, if not relatively routed */ + // this.router.navigate(["../history"], { relativeTo: this.route }); + this.router.navigate(["/device/" + this.service.currentEdge.value.id + "/history"]); + this.cdRef.detectChanges(); + } + } + + async presentSingleStatusModal() { + const modal = await this.modalCtrl.create({ + component: StatusSingleComponent, + }); + return await modal.present(); + } + + ngOnDestroy() { + this.ngUnsubscribe.next(); + this.ngUnsubscribe.complete(); + } + + private isAllowedForView(url: string): boolean { + + // Strip queryParams + const cleanUrl = url.split("?")[0]; + + if (url.includes("/history/")) { + return false; + } + + switch (cleanUrl) { + case "/login": + case "/index": + case "/demo": + return false; + default: + return true; + } + } +} diff --git a/ui/src/app/shared/components/header/header.component.html b/ui/src/app/shared/components/header/header.component.html index b1e4714c831..46bf8be67b8 100644 --- a/ui/src/app/shared/components/header/header.component.html +++ b/ui/src/app/shared/components/header/header.component.html @@ -1,60 +1,68 @@ - - + + - + - + - + {{ service.currentPageTitle }} - + {{ service.currentPageTitle }} - - - - General.LIVE - - - General.HISTORY - - - + + + General.LIVE + + + General.HISTORY + + - + - + - + - - + style="font-size: 20px; filter: drop-shadow(0 1px 1px var(--ion-color-success));" + [name]="edge.roleIsAtLeast('admin') ? 'information-outline' : 'checkmark-circle-outline'" + size="medium">
    + + - + diff --git a/ui/src/app/shared/components/header/header.component.ts b/ui/src/app/shared/components/header/header.component.ts index fc97026c4e6..b6f68d9cb84 100644 --- a/ui/src/app/shared/components/header/header.component.ts +++ b/ui/src/app/shared/components/header/header.component.ts @@ -1,6 +1,6 @@ // @ts-strict-ignore -import { AfterViewChecked, ChangeDetectorRef, Component, OnDestroy, OnInit, ViewChild } from "@angular/core"; -import { ActivatedRoute, NavigationEnd, Router } from "@angular/router"; +import { AfterViewChecked, ChangeDetectorRef, Component, Input, OnDestroy, OnInit, ViewChild } from "@angular/core"; +import { NavigationEnd, Router } from "@angular/router"; import { MenuController, ModalController } from "@ionic/angular"; import { Subject } from "rxjs"; import { filter, takeUntil } from "rxjs/operators"; @@ -13,6 +13,7 @@ import { StatusSingleComponent } from "../status/single/status.component"; @Component({ selector: "header", templateUrl: "./header.component.html", + standalone: false, }) export class HeaderComponent implements OnInit, OnDestroy, AfterViewChecked { @@ -23,7 +24,11 @@ export class HeaderComponent implements OnInit, OnDestroy, AfterViewChecked { public enableSideMenu: boolean; public currentPage: "EdgeSettings" | "Other" | "IndexLive" | "IndexHistory" = "Other"; public isSystemLogEnabled: boolean = false; + + protected isHeaderAllowed: boolean = true; + private ngUnsubscribe: Subject = new Subject(); + private _customBackUrl: string | null = null; constructor( private cdRef: ChangeDetectorRef, @@ -32,9 +37,16 @@ export class HeaderComponent implements OnInit, OnDestroy, AfterViewChecked { public router: Router, public service: Service, public websocket: Websocket, - private route: ActivatedRoute, ) { } + @Input() public set customBackUrl(url: string | null) { + if (!url) { + return; + } + this._customBackUrl = url; + this.updateBackUrl(url); + } + ngOnInit() { // set inital URL this.updateUrl(this.router.routerState.snapshot.url); @@ -46,6 +58,7 @@ export class HeaderComponent implements OnInit, OnDestroy, AfterViewChecked { window.scrollTo(0, 0); this.updateUrl((event).urlAfterRedirects); }); + } // used to prevent 'Expression has changed after it was checked' error @@ -74,6 +87,11 @@ export class HeaderComponent implements OnInit, OnDestroy, AfterViewChecked { updateBackUrl(url: string) { + if (this._customBackUrl) { + this.backUrl = this._customBackUrl; + return; + } + // disable backUrl & Segment Navigation on initial 'login' page if (url === "/login" || url === "/overview" || url === "/index") { this.backUrl = false; @@ -165,7 +183,8 @@ export class HeaderComponent implements OnInit, OnDestroy, AfterViewChecked { if (event.detail.value == "IndexHistory") { /** Creates bug of being infinite forwarded betweeen live and history, if not relatively routed */ - this.router.navigate(["../history"], { relativeTo: this.route }); + // this.router.navigate(["../history"], { relativeTo: this.route }); + this.router.navigate(["/device/" + this.service.currentEdge.value.id + "/history"]); this.cdRef.detectChanges(); } } diff --git a/ui/src/app/shared/components/history-data-error/history-data-error.component.ts b/ui/src/app/shared/components/history-data-error/history-data-error.component.ts index de600575ce7..4361c648f55 100644 --- a/ui/src/app/shared/components/history-data-error/history-data-error.component.ts +++ b/ui/src/app/shared/components/history-data-error/history-data-error.component.ts @@ -14,6 +14,7 @@ import { JsonrpcResponseError } from "src/app/shared/jsonrpc/base";
    `, + standalone: false, }) export class HistoryDataErrorComponent { diff --git a/ui/src/app/shared/components/modal/abstract-modal-line.ts b/ui/src/app/shared/components/modal/abstract-modal-line.ts index f16f75a48f8..0db855672ea 100644 --- a/ui/src/app/shared/components/modal/abstract-modal-line.ts +++ b/ui/src/app/shared/components/modal/abstract-modal-line.ts @@ -6,8 +6,8 @@ import { ModalController } from "@ionic/angular"; import { TranslateService } from "@ngx-translate/core"; import { Subject } from "rxjs"; import { takeUntil } from "rxjs/operators"; -import { ChannelAddress, CurrentData, Edge, EdgeConfig, Service, Utils, Websocket } from "src/app/shared/shared"; import { v4 as uuidv4 } from "uuid"; +import { ChannelAddress, CurrentData, Edge, EdgeConfig, Service, Utils, Websocket } from "src/app/shared/shared"; import { Role } from "../../type/role"; import { Converter } from "../shared/converter"; diff --git a/ui/src/app/shared/components/modal/abstractModal.ts b/ui/src/app/shared/components/modal/abstractModal.ts index 225cf314e76..b6520994122 100644 --- a/ui/src/app/shared/components/modal/abstractModal.ts +++ b/ui/src/app/shared/components/modal/abstractModal.ts @@ -6,8 +6,8 @@ import { ModalController } from "@ionic/angular"; import { TranslateService } from "@ngx-translate/core"; import { Subject, Subscription } from "rxjs"; import { takeUntil } from "rxjs/operators"; -import { ChannelAddress, CurrentData, Edge, EdgeConfig, Service, Utils, Websocket } from "src/app/shared/shared"; import { v4 as uuidv4 } from "uuid"; +import { ChannelAddress, CurrentData, Edge, EdgeConfig, Service, Utils, Websocket } from "src/app/shared/shared"; import { Role } from "../../type/role"; import { Converter } from "../shared/converter"; diff --git a/ui/src/app/shared/components/modal/help-button/help-button.ts b/ui/src/app/shared/components/modal/help-button/help-button.ts index 86faa04d87f..82b825596ab 100644 --- a/ui/src/app/shared/components/modal/help-button/help-button.ts +++ b/ui/src/app/shared/components/modal/help-button/help-button.ts @@ -6,6 +6,7 @@ import { environment } from "src/environments"; @Component({ selector: "oe-help-button", templateUrl: "./help-button.html", + standalone: false, }) export class HelpButtonComponent { diff --git a/ui/src/app/shared/components/modal/modal-button/modal-button.html b/ui/src/app/shared/components/modal/modal-button/modal-button.html index 77f3b1a88b4..0b7b8fa0100 100644 --- a/ui/src/app/shared/components/modal/modal-button/modal-button.html +++ b/ui/src/app/shared/components/modal/modal-button/modal-button.html @@ -1,10 +1,11 @@
    - - {{ button.name }} - + + {{ button.name }} + diff --git a/ui/src/app/shared/components/modal/modal-button/modal-button.ts b/ui/src/app/shared/components/modal/modal-button/modal-button.ts index 9b72038989c..c20fd426d89 100644 --- a/ui/src/app/shared/components/modal/modal-button/modal-button.ts +++ b/ui/src/app/shared/components/modal/modal-button/modal-button.ts @@ -5,6 +5,7 @@ import { AbstractModalLine } from "../abstract-modal-line"; @Component({ selector: "oe-modal-buttons", templateUrl: "./modal-button.html", + standalone: false, }) export class ModalButtonsComponent extends AbstractModalLine { @@ -18,4 +19,5 @@ export type ButtonLabel = { /** Icons for Button, displayed above the corresponding name */ icons?: Icon; callback?: () => void; + style?: { [key: string]: string }; }; diff --git a/ui/src/app/shared/components/modal/modal-info-line/modal-info-line.ts b/ui/src/app/shared/components/modal/modal-info-line/modal-info-line.ts index d19b17030fe..4ab53d9a513 100644 --- a/ui/src/app/shared/components/modal/modal-info-line/modal-info-line.ts +++ b/ui/src/app/shared/components/modal/modal-info-line/modal-info-line.ts @@ -4,6 +4,7 @@ import { Icon } from "src/app/shared/type/widget"; @Component({ selector: "oe-modal-info-line", templateUrl: "./modal-info-line.html", + standalone: false, }) export class ModalInfoLineComponent { diff --git a/ui/src/app/shared/components/modal/modal-line/modal-line-item/modal-line-item.ts b/ui/src/app/shared/components/modal/modal-line/modal-line-item/modal-line-item.ts index c9e36569b7b..12332b3664b 100644 --- a/ui/src/app/shared/components/modal/modal-line/modal-line-item/modal-line-item.ts +++ b/ui/src/app/shared/components/modal/modal-line/modal-line-item/modal-line-item.ts @@ -5,5 +5,6 @@ import { AbstractModalLine } from "../../abstract-modal-line"; /** If multiple items in line use this */ selector: "oe-modal-line-item", templateUrl: "./modal-line-item.html", + standalone: false, }) export class ModalLineItemComponent extends AbstractModalLine { } diff --git a/ui/src/app/shared/components/modal/modal-line/modal-line.ts b/ui/src/app/shared/components/modal/modal-line/modal-line.ts index 76e9e187946..8fa75724a2f 100644 --- a/ui/src/app/shared/components/modal/modal-line/modal-line.ts +++ b/ui/src/app/shared/components/modal/modal-line/modal-line.ts @@ -5,6 +5,7 @@ import { ButtonLabel } from "../modal-button/modal-button"; @Component({ selector: "oe-modal-line", templateUrl: "./modal-line.html", + standalone: false, }) export class ModalLineComponent extends AbstractModalLine { diff --git a/ui/src/app/shared/components/modal/modal-phases/modal-phases.ts b/ui/src/app/shared/components/modal/modal-phases/modal-phases.ts index 8dfdd170ea4..774ef47d7b1 100644 --- a/ui/src/app/shared/components/modal/modal-phases/modal-phases.ts +++ b/ui/src/app/shared/components/modal/modal-phases/modal-phases.ts @@ -1,6 +1,7 @@ import { formatNumber } from "@angular/common"; import { Component, Input } from "@angular/core"; import { ChannelAddress, CurrentData, Utils } from "src/app/shared/shared"; +import { Language } from "src/app/shared/type/language"; import { AbstractModalLine } from "../abstract-modal-line"; import { TextIndentation } from "../modal-line/modal-line"; @@ -9,6 +10,7 @@ import { TextIndentation } from "../modal-line/modal-line"; /** If multiple items in line use this */ selector: "oe-modal-meter-phases", templateUrl: "./modal-phases.html", + standalone: false, }) export class ModalPhasesComponent extends AbstractModalLine { @@ -46,8 +48,8 @@ export class ModalPhasesComponent extends AbstractModalLine { * @returns converted value */ protected CONVERT_TO_POSITIVE_WATT = (value: number | null): string => { - + const locale: string = (Language.getByKey(localStorage.LANGUAGE) ?? Language.DEFAULT).i18nLocaleKey; value = Utils.absSafely(value) ?? 0; - return formatNumber(value, "de", "1.0-0") + " W"; + return formatNumber(value, locale, "1.0-0") + " W"; }; } diff --git a/ui/src/app/shared/components/modal/modal-value-line/modal-value-line.ts b/ui/src/app/shared/components/modal/modal-value-line/modal-value-line.ts index 992dd9261de..4e7c78e7444 100644 --- a/ui/src/app/shared/components/modal/modal-value-line/modal-value-line.ts +++ b/ui/src/app/shared/components/modal/modal-value-line/modal-value-line.ts @@ -7,6 +7,7 @@ import { AbstractModalLine } from "../abstract-modal-line"; @Component({ selector: "oe-modal-value-line", templateUrl: "./modal-value-line.html", + standalone: false, }) export class ModalValueLineComponent extends AbstractModalLine { diff --git a/ui/src/app/shared/components/modal/modal.html b/ui/src/app/shared/components/modal/modal.html index e4d22c8a660..e6d3a906a61 100644 --- a/ui/src/app/shared/components/modal/modal.html +++ b/ui/src/app/shared/components/modal/modal.html @@ -1,6 +1,6 @@ - - {{title}} + + {{title}} @@ -15,7 +15,7 @@ + [color]="toolbarButtons.icon?.color ?? 'dark'" [size]="toolbarButtons.icon?.size ??'medium'"> @@ -25,7 +25,7 @@ - + diff --git a/ui/src/app/shared/components/modal/modal.ts b/ui/src/app/shared/components/modal/modal.ts index 601283c75df..4d0d48e8ad7 100644 --- a/ui/src/app/shared/components/modal/modal.ts +++ b/ui/src/app/shared/components/modal/modal.ts @@ -22,6 +22,7 @@ export enum Status { font-size: 0.9em; } `], + standalone: false, }) export class ModalComponent { diff --git a/ui/src/app/shared/components/modal/model-horizontal-line/modal-horizontal-line.html b/ui/src/app/shared/components/modal/model-horizontal-line/modal-horizontal-line.html index 2a2f2922aaf..af396348aab 100644 --- a/ui/src/app/shared/components/modal/model-horizontal-line/modal-horizontal-line.html +++ b/ui/src/app/shared/components/modal/model-horizontal-line/modal-horizontal-line.html @@ -1,7 +1,7 @@
    + style="border-bottom: 1px solid lightgray; opacity: 0.2; width: 110%; margin-top: 2%; margin-bottom: 2%; margin-left: -5%;">
    @@ -9,6 +9,6 @@
    + style="border-bottom: 1px solid lightgray; opacity: 0.2; width: 110%; margin-top: 2%; margin-bottom: 2%; margin-left: -5%;">
    diff --git a/ui/src/app/shared/components/modal/model-horizontal-line/modal-horizontal-line.ts b/ui/src/app/shared/components/modal/model-horizontal-line/modal-horizontal-line.ts index 98664ae0144..3da19f6274a 100644 --- a/ui/src/app/shared/components/modal/model-horizontal-line/modal-horizontal-line.ts +++ b/ui/src/app/shared/components/modal/model-horizontal-line/modal-horizontal-line.ts @@ -6,6 +6,7 @@ import { Component, Input } from "@angular/core"; @Component({ selector: "oe-modal-horizontal-line", templateUrl: "./modal-horizontal-line.html", + standalone: false, }) export class ModalHorizontalLineComponent { diff --git a/ui/src/app/shared/components/percentagebar/percentagebar.component.ts b/ui/src/app/shared/components/percentagebar/percentagebar.component.ts index 0f636a02eba..7e47cc3d739 100644 --- a/ui/src/app/shared/components/percentagebar/percentagebar.component.ts +++ b/ui/src/app/shared/components/percentagebar/percentagebar.component.ts @@ -3,6 +3,7 @@ import { Component, Input } from "@angular/core"; @Component({ selector: "percentagebar", templateUrl: "./percentagebar.component.html", + standalone: false, }) export class PercentageBarComponent { diff --git a/ui/src/app/shared/components/pickdate/pickdate.component.html b/ui/src/app/shared/components/pickdate/pickdate.component.html index a3987add648..d3b8718a379 100644 --- a/ui/src/app/shared/components/pickdate/pickdate.component.html +++ b/ui/src/app/shared/components/pickdate/pickdate.component.html @@ -1,11 +1,13 @@ - + - + {{service.historyPeriod.value.getText(translate, service)}} - + diff --git a/ui/src/app/shared/components/pickdate/pickdate.component.spec.ts b/ui/src/app/shared/components/pickdate/pickdate.component.spec.ts index b2f9e4dec6f..865b8f885b8 100644 --- a/ui/src/app/shared/components/pickdate/pickdate.component.spec.ts +++ b/ui/src/app/shared/components/pickdate/pickdate.component.spec.ts @@ -1,6 +1,6 @@ import { endOfMonth, endOfWeek, endOfYear, startOfDay, startOfMonth, startOfWeek, startOfYear, subDays, subMonths, subWeeks, subYears } from "date-fns"; import { DefaultTypes } from "../../service/defaulttypes"; -import { TestContext, sharedSetup } from "../shared/testing/utils.spec"; +import { TestContext, TestingUtils } from "../shared/testing/utils.spec"; import { PickDateComponent } from "./pickdate.component"; @@ -16,7 +16,7 @@ describe("Pickdate", () => { let TEST_CONTEXT: TestContext; beforeEach(async () => - TEST_CONTEXT = await sharedSetup(), + TEST_CONTEXT = await TestingUtils.sharedSetup(), ); it("#isPreviousPeriodAllowed && #isNextPeriodAllowed - Day-View: firstSetupProtocol = today", () => { diff --git a/ui/src/app/shared/components/pickdate/pickdate.component.ts b/ui/src/app/shared/components/pickdate/pickdate.component.ts index 9d2e83fd813..90cfa356241 100644 --- a/ui/src/app/shared/components/pickdate/pickdate.component.ts +++ b/ui/src/app/shared/components/pickdate/pickdate.component.ts @@ -2,8 +2,7 @@ import { Component, Input, OnDestroy, OnInit } from "@angular/core"; import { PopoverController } from "@ionic/angular"; import { TranslateService } from "@ngx-translate/core"; -import { addMonths, addYears, differenceInDays, differenceInMilliseconds, endOfDay, endOfMonth, endOfYear, isAfter, isBefore, startOfDay, startOfMonth, startOfWeek, startOfYear, subMonths, subYears } from "date-fns"; -import { addDays, addWeeks, endOfWeek, isFuture, subDays, subWeeks } from "date-fns/esm"; +import { addDays, addMonths, addWeeks, addYears, differenceInDays, differenceInMilliseconds, endOfDay, endOfMonth, endOfWeek, endOfYear, isAfter, isBefore, isFuture, startOfDay, startOfMonth, startOfWeek, startOfYear, subDays, subMonths, subWeeks, subYears } from "date-fns"; import { DefaultTypes } from "../../service/defaulttypes"; import { Edge, Service } from "../../shared"; @@ -13,6 +12,7 @@ import { PickDatePopoverComponent } from "./popover/popover.component"; @Component({ selector: "pickdate", templateUrl: "./pickdate.component.html", + standalone: false, }) export class PickDateComponent implements OnInit, OnDestroy { diff --git a/ui/src/app/shared/components/pickdate/popover/popover.component.ts b/ui/src/app/shared/components/pickdate/popover/popover.component.ts index a4586947d2f..43d3f9e555a 100644 --- a/ui/src/app/shared/components/pickdate/popover/popover.component.ts +++ b/ui/src/app/shared/components/pickdate/popover/popover.component.ts @@ -3,16 +3,17 @@ import { Component, Input, OnInit } from "@angular/core"; import { PopoverController } from "@ionic/angular"; import { TranslateService } from "@ngx-translate/core"; import { CalAnimation, IAngularMyDpOptions, IMyDate, IMyDateRangeModel } from "@nodro7/angular-mydatepicker"; -import { endOfMonth, startOfMonth } from "date-fns"; -import { addDays, endOfWeek, endOfYear, getDate, getMonth, getYear, startOfWeek, startOfYear } from "date-fns/esm"; +import { addDays, endOfMonth, endOfWeek, endOfYear, getDate, getMonth, getYear, startOfMonth, startOfWeek, startOfYear } from "date-fns"; import { DefaultTypes } from "src/app/shared/service/defaulttypes"; import { EdgePermission, Service, Utils } from "src/app/shared/shared"; +import { Language } from "src/app/shared/type/language"; import { Edge } from "../../edge/edge"; @Component({ selector: "pickdatepopover", templateUrl: "./popover.component.html", + standalone: false, }) export class PickDatePopoverComponent implements OnInit { @@ -20,21 +21,70 @@ export class PickDatePopoverComponent implements OnInit { @Input() public edge: Edge | null = null; @Input() public historyPeriods: DefaultTypes.PeriodStringValues[] = []; - public locale: string = "de"; + public locale: string = Language.DEFAULT.key; public showCustomDate: boolean = false; protected periods: string[] = []; protected readonly TOMORROW = addDays(new Date(), 1); protected myDpOptions: IAngularMyDpOptions = { + stylesData: { selector: "dp1", styles: ` - .dp1 .myDpMarkCurrDay, - .dp1 .myDpMarkCurrMonth, - .dp1 .myDpMarkCurrYear { - border-bottom: 2px solid #2d8fab; - color: #2d8fab; + .dp1 { + overflow-x: hidden; + } + .myDpSelector{ + background-color: var(--ion-color-background); + color: var(--color); + background: var(--ion-color-background); + } + .dp1 .myDpIconLeftArrow, + .dp1 .myDpIconRightArrow, + .dp1 .myDpHeaderBtn { + color: var(--ion-color-primary); + } + .dp1 .myDpHeaderBtn:focus, + .dp1 .myDpMonthLabel:focus, + .dp1 .myDpYearLabel:focus { + color: #2d8fab; + } + .dp1 .myDpDaycell:focus, + .dp1 .myDpMonthcell:focus, + .dp1 .myDpYearcell:focus { + box-shadow: inset 0 0 0 1px #66afe9; + } + .dp1 .myDpSelectedDay, + .dp1 .myDpSelectedMonth, + .dp1 .myDpSelectedYear { + background-color: #93c47d; + } + .dp1 .myDpTableSingleDay:hover, + .dp1 .myDpTableSingleMonth:hover, + .dp1 .myDpTableSingleYear:hover { + background-color: #add8e6; + color: #3855c1; } + .dp1 .myDpMarkCurrDay, + .dp1 .myDpMarkCurrMonth, + .dp1 .myDpMarkCurrYear { + border-bottom: 2px solid #2d8fab; + color: #2d8fab; + } + .dp1 .myDpRangeColor { + background-color: #dbeaff; + } + + .ng-mydp * { + background-color: var(--ion-color-background); + color: var(--color); + border: 0; + } + + .myDpDisabled { + color: var(--color); + background: repeating-linear-gradient(-45deg, darkgrey 7px, darkgrey 8px, transparent 7px, transparent 14px) !important; + } `, }, calendarAnimation: { in: CalAnimation.FlipDiagonal, out: CalAnimation.ScaleCenter }, @@ -46,11 +96,11 @@ export class PickDatePopoverComponent implements OnInit { selectorHeight: "225px", selectorWidth: "251px", showWeekNumbers: true, + }; protected readonly DefaultTypes = DefaultTypes; private readonly TODAY = new Date(); - constructor( public service: Service, public popoverCtrl: PopoverController, @@ -64,6 +114,8 @@ export class PickDatePopoverComponent implements OnInit { } ngOnInit() { + + this.locale = (Language.getByKey(localStorage.LANGUAGE) ?? Language.DEFAULT).key; // Restrict user to pick date before ibn-date this.myDpOptions.disableUntil = { day: Utils.subtractSafely(getDate(this.edge?.firstSetupProtocol), 1) ?? 1, month: Utils.addSafely(getMonth(this.edge?.firstSetupProtocol), 1) ?? 1, year: this.edge?.firstSetupProtocol?.getFullYear() ?? 2013 }, this.locale = this.translate.getBrowserLang(); diff --git a/ui/src/app/shared/components/pickdate/popover/popover.spec.ts b/ui/src/app/shared/components/pickdate/popover/popover.spec.ts index fd6a8f9463c..d3174919708 100644 --- a/ui/src/app/shared/components/pickdate/popover/popover.spec.ts +++ b/ui/src/app/shared/components/pickdate/popover/popover.spec.ts @@ -43,6 +43,7 @@ describe("PickdatePopover", () => { const popoverBtn = debugElement.query(By.css("[testId=\"popover-button\"]")); popoverBtn.triggerEventHandler("click", null); fixture.detectChanges(); + expect(component).toBeDefined(); expect((debugElement?.nativeNode?.children as HTMLCollection)?.item(2)?.localName).toEqual("lib-angular-mydatepicker-calendar"); }); diff --git a/ui/src/app/shared/components/pull-to-refresh/pull-to-refresh.ts b/ui/src/app/shared/components/pull-to-refresh/pull-to-refresh.ts new file mode 100644 index 00000000000..53a52e08889 --- /dev/null +++ b/ui/src/app/shared/components/pull-to-refresh/pull-to-refresh.ts @@ -0,0 +1,71 @@ +import { CommonModule } from "@angular/common"; +import { Component, ElementRef, Input, Renderer2 } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { BrowserModule } from "@angular/platform-browser"; +import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; +import { IonicModule, RefresherCustomEvent } from "@ionic/angular"; +import { TranslateModule } from "@ngx-translate/core"; +import { NgxSpinnerModule } from "ngx-spinner"; + +/** + * Component used to indicate if live data is still updated + */ +@Component({ + standalone: true, + selector: "oe-refresh-view", + template: ` + + + + + + + + + + LIVE.PULL_TO_REFRESH + + + + + + + + + + `, + styles: ` + .ion-col-with-left-and-right-icon { + display: flex; + justify-content: center; + + ion-text { + padding-left: 1%; + padding-right: 1%; + align-self: center; + } + } + `, + imports: [ + BrowserModule, + BrowserAnimationsModule, + IonicModule, + TranslateModule, + CommonModule, + NgxSpinnerModule, + ReactiveFormsModule, + ], +}) +export class PullToRefreshComponent { + @Input({ required: true }) public show: boolean = false; + + constructor(private el: ElementRef, private renderer: Renderer2) { + + // Rerender ion-content to use full available height + const hostElement = this.el.nativeElement; + this.renderer.addClass(hostElement, "ion-page"); + } + + @Input({ required: true }) public refresh: (ev: RefresherCustomEvent) => void = (ev: RefresherCustomEvent) => { }; +} diff --git a/ui/src/app/shared/components/shared/converter.ts b/ui/src/app/shared/components/shared/converter.ts index b76ad1ace3f..16a72a5afc7 100644 --- a/ui/src/app/shared/components/shared/converter.ts +++ b/ui/src/app/shared/components/shared/converter.ts @@ -93,6 +93,20 @@ export namespace Converter { Formatter.FORMAT_WATT(value)); }; + /** + * Formats a Power value as Watt [W]. + * + * Value 1000 -> "1.000 W". + * Value null -> "-". + * + * @param value the power value + * @returns formatted value; '-' for null + */ + export const POWER_IN_KILO_WATT: Converter = (raw) => { + return IF_NUMBER(raw, value => + Formatter.FORMAT_KILO_WATT(Utils.divideSafely(value, 1000))); + }; + /** * Formats a Energy value as Kilo watt hours [kWh]. * @@ -164,6 +178,21 @@ export namespace Converter { Formatter.FORMAT_AMPERE(value / 1000)); }; + /** + * Converts a formatted current value to the absolute value. + * + * Value -1000 -> "1.000 A". + * Value 1000 -> "1.000 A". + * Value null -> "-". + * + * @param value the current value + * @returns formatted value; '-' for null + */ + export const CURRENT_IN_MILLIAMPERE_TO_ABSOLUTE_AMPERE: Converter = (raw) => { + return IF_NUMBER(raw, value => + Formatter.FORMAT_AMPERE(Math.abs(value) / 1000)); + }; + export const ONLY_POSITIVE_POWER_AND_NEGATIVE_AS_ZERO: Converter = (raw) => { return IF_NUMBER(raw, value => value <= 0 diff --git a/ui/src/app/shared/components/shared/dataservice.ts b/ui/src/app/shared/components/shared/dataservice.ts index b1d7a835af2..5f2986dca33 100644 --- a/ui/src/app/shared/components/shared/dataservice.ts +++ b/ui/src/app/shared/components/shared/dataservice.ts @@ -1,5 +1,5 @@ // @ts-strict-ignore -import { Injectable } from "@angular/core"; +import { Injectable, WritableSignal, signal } from "@angular/core"; import { RefresherCustomEvent } from "@ionic/angular"; import { BehaviorSubject, Subject } from "rxjs"; import { ChannelAddress, Edge } from "../../shared"; @@ -9,6 +9,7 @@ export abstract class DataService { /** Used to retrieve values */ public currentValue: BehaviorSubject<{ allComponents: {} }> = new BehaviorSubject({ allComponents: {} }); + public lastUpdated: WritableSignal = signal(new Date()); protected edge: Edge | null = null; protected stopOnDestroy: Subject = new Subject(); diff --git a/ui/src/app/shared/components/shared/formatter.ts b/ui/src/app/shared/components/shared/formatter.ts index 798818e17b6..8cfba6bba0b 100644 --- a/ui/src/app/shared/components/shared/formatter.ts +++ b/ui/src/app/shared/components/shared/formatter.ts @@ -1,39 +1,54 @@ import { formatNumber } from "@angular/common"; import { Currency } from "../../shared"; +import { Language } from "../../type/language"; export namespace Formatter { + + // Changes the number format based on the language selected. + const locale: string = (Language.getByKey(localStorage.LANGUAGE) ?? Language.DEFAULT).i18nLocaleKey; + export const FORMAT_WATT = (value: number) => { // TODO apply correct locale - return formatNumber(value, "de", "1.0-0") + " W"; + return formatNumber(value, locale, "1.0-0") + " W"; + }; + + export const FORMAT_KILO_WATT = (value: number) => { + // TODO apply correct locale + return formatNumber(value, locale, "1.0-2") + " kW"; }; export const FORMAT_KILO_WATT_HOURS = (value: number) => { // TODO apply correct locale - return formatNumber(value, "de", "1.0-0") + " kWh"; + return formatNumber(value, locale, "1.0-0") + " kWh"; }; export const FORMAT_VOLT = (value: number) => { // TODO apply correct locale - return formatNumber(value, "de", "1.0-0") + " V"; + return formatNumber(value, locale, "1.0-0") + " V"; }; export const FORMAT_AMPERE = (value: number) => { // TODO apply correct locale - return formatNumber(value, "de", "1.1-1") + " A"; + return formatNumber(value, locale, "1.1-1") + " A"; }; export const FORMAT_CELSIUS = (value: number) => { // TODO apply correct locale - return formatNumber(value, "de", "1.0-0") + " °C"; + return formatNumber(value, locale, "1.0-0") + " °C"; }; export const FORMAT_PERCENT = (value: number) => { // TODO apply correct locale - return formatNumber(value, "de", "1.0-0") + " %"; + return formatNumber(value, locale, "1.0-0") + " %"; + }; + + export const FORMAT_BAR = (value: number) => { + // TODO apply correct locale + return formatNumber(value, locale, "1.1-1") + " mbar"; }; export const FORMAT_CURRENCY_PER_KWH = (value: number | string, currency: string = Currency.Unit.CENT) => { // TODO apply correct locale - return formatNumber(parseInt(value.toString()), "de", "1.0-2") + " " + Currency.getCurrencyLabelByCurrency(currency); + return formatNumber(parseInt(value.toString()), locale, "1.0-2") + " " + Currency.getCurrencyLabelByCurrency(currency); }; } diff --git a/ui/src/app/shared/components/shared/notification/notification.ts b/ui/src/app/shared/components/shared/notification/notification.ts index 682dd9d33b4..8706336d31a 100644 --- a/ui/src/app/shared/components/shared/notification/notification.ts +++ b/ui/src/app/shared/components/shared/notification/notification.ts @@ -4,6 +4,7 @@ import { ToastController } from "@ionic/angular"; @Component({ selector: "oe-notification", template: "", + standalone: false, }) export class NotificationComponent implements OnInit, OnChanges { diff --git a/ui/src/app/shared/components/shared/testing/common.ts b/ui/src/app/shared/components/shared/testing/common.ts index 62f21fe345a..737ee68587c 100644 --- a/ui/src/app/shared/components/shared/testing/common.ts +++ b/ui/src/app/shared/components/shared/testing/common.ts @@ -13,7 +13,6 @@ export namespace OeTester { /** Always one value for each channel from a {@link QueryHistoricTimeseriesEnergyResponse} */ energyChannelWithValues: QueryHistoricTimeseriesEnergyResponse, - /** data from a {@link QueryHistoricTimeseriesEnergyPerPeriodResponse} */ energyPerPeriodChannelWithValues?: QueryHistoricTimeseriesEnergyPerPeriodResponse, /** data from a {@link QueryHistoricTimeseriesDataResponse} */ @@ -22,16 +21,40 @@ export namespace OeTester { } export namespace ChartOptions { - export const LINE_CHART_OPTIONS = (period: string, chartType: "line" | "bar", options: { [key: string]: { scale: { min?: number, max?: number, beginAtZero?: boolean }, ticks?: { stepSize: number; }; }; }, title?: string): OeChartTester.Dataset.Option => ({ + export const LINE_CHART_OPTIONS = (period: string, chartType: "line" | "bar", options: { [key: string]: { scale: { min?: number, max?: number, beginAtZero?: boolean }, ticks?: { stepSize: number; min?: number, max?: number }; }; }, title?: string): OeChartTester.Dataset.Option => ({ type: "option", options: { - "responsive": true, "maintainAspectRatio": false, "elements": { "point": { "radius": 0, "hitRadius": 0, "hoverRadius": 0 }, "line": { "stepped": false, "fill": true } }, "datasets": { "bar": {}, "line": {} }, "plugins": { - "colors": { "enabled": false }, "legend": { "display": true, "position": "bottom", "labels": { "color": "" } }, "tooltip": { "intersect": false, "mode": "index", "callbacks": {} }, "annotation": { "annotations": {} }, "datalabels": { + "responsive": true, + "maintainAspectRatio": false, + "elements": { + "point": { "radius": 0, "hitRadius": 0, "hoverRadius": 0 }, + "line": { "stepped": false, "fill": true }, + }, + "datasets": { "bar": {}, "line": {} }, + "plugins": { + "colors": { "enabled": false }, + "legend": { "display": true, "position": "bottom", "labels": { "color": "" } }, + "tooltip": { "intersect": false, "mode": "index", "callbacks": {}, "enabled": true }, + "annotation": { "annotations": {} }, "datalabels": { display: false, }, }, "scales": { - "x": { "stacked": true, "offset": false, "type": "time", "ticks": { "source": "auto", "maxTicksLimit": 31 }, "bounds": "ticks", "adapters": { "date": { "locale": { "code": "de", "formatLong": {}, "localize": {}, "match": {}, "options": { "weekStartsOn": 1, "firstWeekContainsDate": 4 } } } }, "time": { "unit": period as TimeUnit, "displayFormats": { "datetime": "yyyy-MM-dd HH:mm:ss", "millisecond": "SSS [ms]", "second": "HH:mm:ss a", "minute": "HH:mm", "hour": "HH:00", "day": "dd", "week": "ll", "month": "MM", "quarter": "[Q]Q - YYYY", "year": "yyyy" } } }, "left": { - "beginAtZero": false, ...options["left"]?.scale, ...(chartType === "line" ? { stacked: false } : {}), "title": { "text": "kW", "display": false, "padding": 5, "font": { "size": 11 } }, "position": "left", "grid": { "display": true }, + "x": { + "stacked": true, + "offset": false, + "type": "time", + "ticks": { "source": "auto", "maxTicksLimit": 31 }, + "bounds": "ticks", + "adapters": { "date": { "locale": { "code": "de", "formatLong": {}, "localize": {}, "match": {}, "options": { "weekStartsOn": 1, "firstWeekContainsDate": 4 } } } }, + "time": { "unit": period as TimeUnit, "displayFormats": { "datetime": "yyyy-MM-dd HH:mm:ss", "millisecond": "SSS [ms]", "second": "HH:mm:ss a", "minute": "HH:mm", "hour": "HH:00", "day": "dd", "week": "ll", "month": "MM", "quarter": "[Q]Q - YYYY", "year": "yyyy" } }, + }, + "left": { + "stacked": false, + "beginAtZero": false, + ...options["left"]?.scale, ...(chartType === "line" ? { stacked: false } : {}), + "title": { "text": "kW", "display": false, "padding": 5, "font": { "size": 11 } }, + "position": "left", + "grid": { "display": true }, "ticks": { ...options["left"]?.ticks, "color": "", @@ -45,14 +68,43 @@ export namespace OeTester { export const BAR_CHART_OPTIONS = (period: string, chartType: "line" | "bar", options: { [key: string]: { scale: { min: number, max: number; }, ticks?: { stepSize: number; }; }; }, title?: string): OeChartTester.Dataset.Option => ({ type: "option", options: { - "responsive": true, "maintainAspectRatio": false, "elements": { "point": { "radius": 0, "hitRadius": 0, "hoverRadius": 0 }, "line": { "stepped": false, "fill": true } }, "datasets": { "bar": { "barPercentage": 1 }, "line": {} }, "plugins": { - "colors": { "enabled": false }, "legend": { "display": true, "position": "bottom", "labels": { "color": "" } }, "tooltip": { "intersect": false, "mode": "x", "callbacks": {} }, "annotation": { "annotations": {} }, "datalabels": { + "responsive": true, + "maintainAspectRatio": false, + "elements": { + "point": { "radius": 0, "hitRadius": 0, "hoverRadius": 0 }, + "line": { "stepped": false, "fill": true }, + }, + "datasets": { + "bar": { "barPercentage": 1 }, + "line": {}, + }, + "plugins": { + "colors": { "enabled": false }, + "legend": { "display": true, "position": "bottom", "labels": { "color": "" } }, + "tooltip": { "intersect": false, "mode": "x", "callbacks": {}, "enabled": true }, + "annotation": { "annotations": {} }, + "datalabels": { display: false, }, - }, "scales": { - "x": { "stacked": true, "offset": true, "type": "time", "ticks": { "source": "auto", "maxTicksLimit": 31 }, "bounds": "ticks", "adapters": { "date": { "locale": { "code": "de", "formatLong": {}, "localize": {}, "match": {}, "options": { "weekStartsOn": 1, "firstWeekContainsDate": 4 } } } }, "time": { "unit": period as TimeUnit, "displayFormats": { "datetime": "yyyy-MM-dd HH:mm:ss", "millisecond": "SSS [ms]", "second": "HH:mm:ss a", "minute": "HH:mm", "hour": "HH:00", "day": "dd", "week": "ll", "month": "MM", "quarter": "[Q]Q - YYYY", "year": "yyyy" } } }, + }, + "scales": { + "x": { + "stacked": true, + "offset": true, + "type": "time", + "ticks": { "source": "auto", "maxTicksLimit": 31 }, + "bounds": "ticks", + "adapters": { "date": { "locale": { "code": "de", "formatLong": {}, "localize": {}, "match": {}, "options": { "weekStartsOn": 1, "firstWeekContainsDate": 4 } } } }, + "time": { "unit": period as TimeUnit, "displayFormats": { "datetime": "yyyy-MM-dd HH:mm:ss", "millisecond": "SSS [ms]", "second": "HH:mm:ss a", "minute": "HH:mm", "hour": "HH:00", "day": "dd", "week": "ll", "month": "MM", "quarter": "[Q]Q - YYYY", "year": "yyyy" } }, + }, "left": { - ...options["left"]?.scale, ...(chartType === "line" ? { stacked: false } : {}), "beginAtZero": true, "title": { "text": "kWh", "display": false, "padding": 5, "font": { "size": 11 } }, "position": "left", "grid": { "display": true }, + "stacked": true, + "beginAtZero": true, + ...options["left"]?.scale, + ...(chartType === "line" ? { stacked: false } : {}), + "title": { "text": "kWh", "display": false, "padding": 5, "font": { "size": 11 } }, + "position": "left", + "grid": { "display": true }, "ticks": { ...options["left"]?.ticks, "color": "", @@ -75,6 +127,7 @@ export namespace OeTester { "display": true, "position": "bottom", "labels": { "color": "" }, }, "tooltip": { "intersect": false, "mode": "index", "callbacks": {}, + "enabled": true, }, "annotation": { "annotations": {}, @@ -84,8 +137,9 @@ export namespace OeTester { }, }, "scales": { "x": { "stacked": true, "offset": false, "type": "time", "ticks": { "source": "auto", "maxTicksLimit": 31 }, "bounds": "ticks", "adapters": { "date": { "locale": { "code": "de", "formatLong": {}, "localize": {}, "match": {}, "options": { "weekStartsOn": 1, "firstWeekContainsDate": 4 } } } }, "time": { "unit": period as TimeUnit, "displayFormats": { "datetime": "yyyy-MM-dd HH:mm:ss", "millisecond": "SSS [ms]", "second": "HH:mm:ss a", "minute": "HH:mm", "hour": "HH:00", "day": "dd", "week": "ll", "month": "MM", "quarter": "[Q]Q - YYYY", "year": "yyyy" } } }, "left": { + "stacked": false, ...options["left"]?.scale, ...(chartType === "line" ? { stacked: false } : {}), "beginAtZero": true, - "title": { "text": "kW", "display": true, "padding": 5, "font": { "size": 11 } }, + "title": { "text": "kW", "display": false, "padding": 5, "font": { "size": 11 } }, "position": "left", "grid": { "display": true }, "ticks": { ...options["left"]?.ticks, @@ -95,8 +149,9 @@ export namespace OeTester { }, }, "right": { + "stacked": false, ...options["right"]?.scale, ...(chartType === "line" ? { stacked: false } : {}), "beginAtZero": true, - "title": { "text": "Zustand", "display": true, "padding": 5, "font": { "size": 11 } }, + "title": { "text": "Zustand", "display": false, "padding": 5, "font": { "size": 11 } }, "position": "right", "grid": { "display": false }, "ticks": { ...options["right"]?.ticks, @@ -112,13 +167,14 @@ export namespace OeTester { type: "option", options: { "responsive": true, "maintainAspectRatio": false, "elements": { "point": { "radius": 0, "hitRadius": 0, "hoverRadius": 0 }, "line": { "stepped": false, "fill": true } }, "datasets": { "bar": { "barPercentage": 1 }, "line": {} }, "plugins": { - "colors": { "enabled": false }, "legend": { "display": true, "position": "bottom", "labels": { "color": "" } }, "tooltip": { "intersect": false, "mode": "x", "callbacks": {} }, "annotation": { "annotations": {} }, "datalabels": { + "colors": { "enabled": false }, "legend": { "display": true, "position": "bottom", "labels": { "color": "" } }, "tooltip": { "intersect": false, "mode": "x", "callbacks": {}, "enabled": true }, "annotation": { "annotations": {} }, "datalabels": { display: false, }, }, "scales": { "x": { "stacked": true, "offset": true, "type": "time", "ticks": { "source": "auto", "maxTicksLimit": 31 }, "bounds": "ticks", "adapters": { "date": { "locale": { "code": "de", "formatLong": {}, "localize": {}, "match": {}, "options": { "weekStartsOn": 1, "firstWeekContainsDate": 4 } } } }, "time": { "unit": period as TimeUnit, "displayFormats": { "datetime": "yyyy-MM-dd HH:mm:ss", "millisecond": "SSS [ms]", "second": "HH:mm:ss a", "minute": "HH:mm", "hour": "HH:00", "day": "dd", "week": "ll", "month": "MM", "quarter": "[Q]Q - YYYY", "year": "yyyy" } } }, "left": { - ...options["left"]?.scale, ...(chartType === "line" ? { stacked: false } : {}), "beginAtZero": true, "title": { "text": "kWh", "display": true, "padding": 5, "font": { "size": 11 } }, "position": "left", "grid": { "display": true }, + "stacked": true, + ...options["left"]?.scale, ...(chartType === "line" ? { stacked: false } : {}), "beginAtZero": true, "title": { "text": "kWh", "display": false, "padding": 5, "font": { "size": 11 } }, "position": "left", "grid": { "display": true }, "ticks": { ...options["left"]?.ticks, "color": "", @@ -127,8 +183,9 @@ export namespace OeTester { }, }, "right": { - ...options["right"]?.scale, ...(chartType === "line" ? { stacked: false } : { min: 0 }), "beginAtZero": true, - "title": { "text": "Aktive Zeit", "display": true, "padding": 5, "font": { "size": 11 } }, + "stacked": true, + ...options["right"]?.scale, ...(chartType === "line" ? { stacked: false } : {}), "beginAtZero": true, + "title": { "text": "Aktive Zeit", "display": false, "padding": 5, "font": { "size": 11 } }, "position": "right", "grid": { "display": false }, "ticks": { "color": "", diff --git a/ui/src/app/shared/components/shared/testing/tester.ts b/ui/src/app/shared/components/shared/testing/tester.ts index 2e30117a4fa..a41579bde67 100644 --- a/ui/src/app/shared/components/shared/testing/tester.ts +++ b/ui/src/app/shared/components/shared/testing/tester.ts @@ -6,6 +6,7 @@ import { QueryHistoricTimeseriesEnergyPerPeriodResponse } from "src/app/shared/j import { HistoryUtils } from "src/app/shared/service/utils"; import { CurrentData, EdgeConfig } from "src/app/shared/shared"; +import { ObjectUtils } from "src/app/shared/utils/object/object.utils"; import { AbstractHistoryChart } from "../../chart/abstracthistorychart"; import { XAxisType } from "../../chart/chart.constants"; import { TextIndentation } from "../../modal/modal-line/modal-line"; @@ -233,6 +234,7 @@ export class OeChartTester { from: new Date(channelData.result.timestamps[0] ?? 0), to: new Date(channelData.result.timestamps.reverse()[0] ?? 0), getText: () => testContext.service.historyPeriod.value.getText(testContext.translate, testContext.service), + isWeekOrDay: () => testContext.service.historyPeriod.value.isWeekOrDay(), }); // Fill Data @@ -306,9 +308,12 @@ export class OeChartTester { legendOptions.push(AbstractHistoryChart.getLegendOptions(label, displayValue)); }); + let options: Chart.ChartOptions = AbstractHistoryChart.getOptions(chartData, chartType, testContext.service, testContext.translate, legendOptions, channelData.result, config, datasets, xAxisType, labels); + options = prepareOptionsForTesting(options, chartData); + return { type: "option", - options: AbstractHistoryChart.getOptions(chartData, chartType, testContext.service, testContext.translate, legendOptions, channelData.result, locale, config, datasets, xAxisType, labels), + options: options, }; } @@ -421,3 +426,18 @@ export namespace OeFormlyViewTester { }; } } + +/** Exclude properties that dont need to be tested */ +function prepareOptionsForTesting(options: Chart.ChartOptions, chartData: HistoryUtils.ChartData): Chart.ChartOptions { + options.scales["x"]["ticks"] = ObjectUtils.excludeProperties(options.scales["x"]["ticks"] as Chart.RadialTickOptions, ["color"]); + chartData.yAxes.filter(axis => axis.unit != null).forEach(axis => { + // Remove custom scale calculations from unittest, seperate unittest existing + options.scales[axis.yAxisId] = ObjectUtils.excludeProperties(options.scales[axis.yAxisId], ["min", "max"]) as Chart.ScaleOptionsByType<"radialLinear" | keyof Chart.CartesianScaleTypeRegistry>; + options.scales[axis.yAxisId].ticks = ObjectUtils.excludeProperties(options.scales[axis.yAxisId].ticks as Chart.RadialTickOptions, ["stepSize"]); + options.scales[axis.yAxisId]["title"] = ObjectUtils.excludeProperties(options.scales[axis.yAxisId]["title"] as Chart.RadialTickOptions, ["color"]); + }); + console.log("options", options); + return options; +} + + diff --git a/ui/src/app/shared/components/shared/testing/utils.spec.ts b/ui/src/app/shared/components/shared/testing/utils.spec.ts index 641d93f57cd..9e99eed6049 100644 --- a/ui/src/app/shared/components/shared/testing/utils.spec.ts +++ b/ui/src/app/shared/components/shared/testing/utils.spec.ts @@ -5,7 +5,6 @@ import localeDeExtra from "@angular/common/locales/extra/de"; import { LOCALE_ID } from "@angular/core"; import { TestBed, TestModuleMetadata } from "@angular/core/testing"; import { ActivatedRoute, RouterModule } from "@angular/router"; -import { RouterTestingModule } from "@angular/router/testing"; import { FORMLY_CONFIG } from "@ngx-formly/core"; import { TranslateLoader, TranslateModule, TranslateService } from "@ngx-translate/core"; import { routes } from "src/app/app-routing.module"; @@ -14,7 +13,6 @@ import { registerTranslateExtension } from "src/app/shared/translate.extension"; import { Language, MyTranslateLoader } from "src/app/shared/type/language"; export type TestContext = { translate: TranslateService, service: Service }; - export const BASE_TEST_BED: TestModuleMetadata = { imports: [ TranslateModule.forRoot({ loader: { provide: TranslateLoader, useClass: MyTranslateLoader }, defaultLanguage: Language.DEFAULT.key, useDefaultLang: false }), @@ -27,63 +25,120 @@ export const BASE_TEST_BED: TestModuleMetadata = { ], }; -export function setTranslateParams(): void { - const translateService = TestBed.inject(TranslateService); - translateService.addLangs(["de"]); - translateService.use("de"); - registerLocaleData(localDE, "de", localeDeExtra); +function setTranslateParams(): Promise { + return new Promise((res) => { + const translateService = TestBed.inject(TranslateService); + translateService.addLangs(["de"]); + translateService.use("de"); + registerLocaleData(localDE, "de", localeDeExtra); + res(); + }); } -export async function sharedSetup(): Promise { - await TestBed.configureTestingModule(BASE_TEST_BED) - .compileComponents() - .then(() => setTranslateParams()); +export namespace TestingUtils { - return { - translate: TestBed.inject(TranslateService), - service: TestBed.inject(Service), - }; -} + /** + * Sets up a basic testing environment setup + * + * @returns the injected translateService and service + */ + export async function sharedSetup(): Promise { + await TestBed.configureTestingModule(BASE_TEST_BED) + .compileComponents() + .then(() => setTranslateParams()); -export function removeFunctions(obj: any): any { - if (typeof obj !== "object" || obj === null) { - return obj; + return { + translate: TestBed.inject(TranslateService), + service: TestBed.inject(Service), + }; } - const result: any = {}; - for (const key in obj) { - if (typeof obj[key] !== "function") { - result[key] = removeFunctions(obj[key]); - } + // Main function that returns an object with service names and injected providers + /** + * Merges setups and injects additional services to the base setup - {@link sharedSetup} + * + * @param services the services to be injected + * @returns the merged testing environment setup + */ + export async function mergeSetup>( + services: { name: keyof T; provider: new (...args: any[]) => T[keyof T]; metadata: TestModuleMetadata }[], + ): Promise { + // Merge all TestModuleMetadata from services + const testModuleMetadata = services.reduce((arr, el) => { + arr.imports.push(...(el.metadata.imports || [])); + arr.providers.push(...(el.metadata.providers || [])); + return arr; + }, { + imports: BASE_TEST_BED.imports ?? [], + providers: BASE_TEST_BED.providers ?? [], + }); + + // Set up the TestBed + await TestBed.configureTestingModule(testModuleMetadata) + .compileComponents(); + await setTranslateParams(); + + // Inject services and return them as an object with service names as keys + const result = services.reduce((arr, el) => { + arr[el.name] = TestBed.inject(el.provider); + return arr; + }, {} as T); + + return { + translate: TestBed.inject(TranslateService), + service: TestBed.inject(Service), + ...result, + }; } - return result; -} -export async function sharedSetupWithComponentIdRoute(componentId: string): Promise { - await TestBed.configureTestingModule({ - imports: [ - ...(BASE_TEST_BED.imports as any[]), - RouterTestingModule.withRoutes(routes), - RouterModule.forRoot([]), - ], - providers: [ - ...(BASE_TEST_BED.providers as any[]), - { - provide: ActivatedRoute, - useValue: { - snapshot: { - params: { componentId: componentId }, + export namespace TestModuleMetadata { + + /** + * Sets the activatedRoute testmetadata + * + * @param componentId the component id + * @returns the test module data, needed for setting up testing environment + */ + export function setActivatedRoute(componentId: string): TestModuleMetadata { + return { + imports: [ + RouterModule.forRoot(routes), + ], + providers: [ + { + provide: ActivatedRoute, + useValue: { + snapshot: { + params: { componentId: componentId }, + }, + }, }, - }, - }, - ], - }) - .compileComponents() - .then(() => setTranslateParams()); + ], + }; + } + } + + export function removeFunctions(obj: any): any { + if (typeof obj !== "object" || obj === null) { + return obj; + } - return { - translate: TestBed.inject(TranslateService), - service: TestBed.inject(Service), - route: TestBed.inject(ActivatedRoute), - }; + const result: any = {}; + for (const key in obj) { + if (typeof obj[key] !== "function") { + result[key] = removeFunctions(obj[key]); + } + } + return result; + } + + /** + * Sets up the test environment for components with own route + * + * @param componentId the component id + * @returns the test context with the activatedRoute services injected + */ + export function setupWithActivatedRoute(componentId: string): Promise<(TestContext & { route: ActivatedRoute; })> { + return TestingUtils.mergeSetup([{ name: "route", provider: ActivatedRoute, metadata: TestingUtils.TestModuleMetadata.setActivatedRoute(componentId) }]); + } } diff --git a/ui/src/app/shared/components/status/single/status.component.html b/ui/src/app/shared/components/status/single/status.component.html index 296f7dda136..eff6cfe9f10 100644 --- a/ui/src/app/shared/components/status/single/status.component.html +++ b/ui/src/app/shared/components/status/single/status.component.html @@ -1,5 +1,6 @@ - + General.SYSTEM_STATE @@ -30,7 +31,7 @@ - + {{ category.category.title }} @@ -47,7 +48,7 @@

    {{ item.alias }}

    -

    {{ factory.name }}

    +

    {{ factory.name }}

    diff --git a/ui/src/app/shared/components/status/single/status.component.ts b/ui/src/app/shared/components/status/single/status.component.ts index 9d269b1ce59..0654caab35d 100644 --- a/ui/src/app/shared/components/status/single/status.component.ts +++ b/ui/src/app/shared/components/status/single/status.component.ts @@ -14,6 +14,7 @@ import { CategorizedComponents, EdgeConfig } from "../../edge/edgeconfig"; @Component({ selector: StatusSingleComponent.SELECTOR, templateUrl: "./status.component.html", + standalone: false, }) export class StatusSingleComponent implements OnInit, OnDestroy { private static readonly SELECTOR = "statussingle"; diff --git a/ui/src/app/shared/directive/autofill.ts b/ui/src/app/shared/directive/autofill.ts index ef1dfc539f4..fa3c384bd28 100644 --- a/ui/src/app/shared/directive/autofill.ts +++ b/ui/src/app/shared/directive/autofill.ts @@ -5,6 +5,7 @@ import { Logger } from "../shared"; @Directive({ selector: "[appAutofill]", + standalone: false, }) export class AutofillDirective implements OnInit { diff --git a/ui/src/app/shared/directive/ngvar.ts b/ui/src/app/shared/directive/ngvar.ts index 01923e9870a..c9b66a3b300 100644 --- a/ui/src/app/shared/directive/ngvar.ts +++ b/ui/src/app/shared/directive/ngvar.ts @@ -2,6 +2,7 @@ import { Directive, Input, TemplateRef, ViewContainerRef } from "@angular/core"; @Directive({ selector: "[ngVar]", + standalone: false, }) export class VarDirective { private context: { diff --git a/ui/src/app/shared/jsonrpc/jsonrpcutils.spec.ts b/ui/src/app/shared/jsonrpc/jsonrpcutils.spec.ts new file mode 100644 index 00000000000..0133cd573ed --- /dev/null +++ b/ui/src/app/shared/jsonrpc/jsonrpcutils.spec.ts @@ -0,0 +1,10 @@ +import { JsonRpcUtils } from "./jsonrpcutils"; + +describe("JsonRpcUtils", () => { + + const productionActivePowerData = [-0.01, -0.1, -0.49, -0.50, -1, null]; + const expectedOutput = [0, 0, 0, -0.5, -1, null]; + it("#normalizeQueryData", () => { + expect(JsonRpcUtils.normalizeQueryData(productionActivePowerData)).toEqual(expectedOutput); + }); +}); diff --git a/ui/src/app/shared/jsonrpc/jsonrpcutils.ts b/ui/src/app/shared/jsonrpc/jsonrpcutils.ts index 642ffee60c6..979d7be8b53 100644 --- a/ui/src/app/shared/jsonrpc/jsonrpcutils.ts +++ b/ui/src/app/shared/jsonrpc/jsonrpcutils.ts @@ -2,6 +2,26 @@ import { ChannelAddress } from "../type/channeladdress"; export class JsonRpcUtils { + private static THRESHOLD: number = -0.50; + + public static normalizeQueryData(data: (number | null)[]): (number | null)[] { + return data.map(el => JsonRpcUtils.roundSlightlyNegativeValues(el)); + } + + /** + * Rounds values between 0 and -1kW to 0 + * + * @param value the value to convert + */ + public static roundSlightlyNegativeValues(value: number | null): number | null { + if (value == null) { + return null; + } + + return (value > JsonRpcUtils.THRESHOLD && value < 0) ? 0 : value; + } + + /** * Converts an array of ChannelAddresses to a string array with unique values. */ diff --git a/ui/src/app/shared/legacy/chartoptions/chartoptions.component.html b/ui/src/app/shared/legacy/chartoptions/chartoptions.component.html index 02980ba1e0d..1f896f0ba84 100644 --- a/ui/src/app/shared/legacy/chartoptions/chartoptions.component.html +++ b/ui/src/app/shared/legacy/chartoptions/chartoptions.component.html @@ -1,3 +1,3 @@ - + diff --git a/ui/src/app/shared/legacy/chartoptions/chartoptions.component.ts b/ui/src/app/shared/legacy/chartoptions/chartoptions.component.ts index bb5399a749b..7d01b25555b 100644 --- a/ui/src/app/shared/legacy/chartoptions/chartoptions.component.ts +++ b/ui/src/app/shared/legacy/chartoptions/chartoptions.component.ts @@ -9,6 +9,7 @@ import { ChartOptionsPopoverComponent } from "./popover/popover.component"; @Component({ selector: "chartOptions", templateUrl: "./chartoptions.component.html", + standalone: false, }) export class ChartOptionsComponent { diff --git a/ui/src/app/shared/legacy/chartoptions/popover/popover.component.ts b/ui/src/app/shared/legacy/chartoptions/popover/popover.component.ts index 5cddb86fbc6..a8bb201f292 100644 --- a/ui/src/app/shared/legacy/chartoptions/popover/popover.component.ts +++ b/ui/src/app/shared/legacy/chartoptions/popover/popover.component.ts @@ -6,6 +6,7 @@ import { Service } from "src/app/shared/shared"; @Component({ selector: "chartoptionspopover", templateUrl: "./popover.component.html", + standalone: false, }) export class ChartOptionsPopoverComponent { diff --git a/ui/src/app/shared/ngrx-store/states.ts b/ui/src/app/shared/ngrx-store/states.ts index 33d8f0aeb0d..fd5110356a9 100644 --- a/ui/src/app/shared/ngrx-store/states.ts +++ b/ui/src/app/shared/ngrx-store/states.ts @@ -35,7 +35,7 @@ export class AppStateTracker { protected router: Router, protected pagination: Pagination, private websocket: Websocket, - private previousRouteService: PreviousRouteService, + private routeService: PreviousRouteService, ) { if (!localStorage.getItem("AppState")) { console.log(`${AppStateTracker.LOG_PREFIX} Log deactivated`); @@ -48,22 +48,25 @@ export class AppStateTracker { effect(() => { const state = this.websocket.state(); this.startStateHandler(state); - }, { allowSignalWrites: true }); + }); } /** * Handles navigation after authentication */ public navigateAfterAuthentication() { - const segments = this.router.routerState.snapshot.url.split("/"); - const previousUrl: string = this.previousRouteService.getPreviousUrl(); - if ((previousUrl === segments[segments.length - 1]) || previousUrl === "/") { - this.router.navigate(["./overview"]); - return; - } + this.router.navigate(["overview"]); + return; + // const segments = this.router.routerState.snapshot.url.split("/"); + // const previousUrl: string = this.routeService.getPreviousUrl(); + + // if ((previousUrl === segments[segments.length - 1]) || previousUrl === "/") { + // this.router.navigate(["./overview"]); + // return; + // } - this.router.navigate(previousUrl.split("/")); + // this.router.navigate(previousUrl.split("/")); } private startStateHandler(state: States): void { diff --git a/ui/src/app/shared/pipe/classname/classname.pipe.ts b/ui/src/app/shared/pipe/classname/classname.pipe.ts index dbc78248e44..cbc453b92f5 100644 --- a/ui/src/app/shared/pipe/classname/classname.pipe.ts +++ b/ui/src/app/shared/pipe/classname/classname.pipe.ts @@ -3,6 +3,7 @@ import { Pipe, PipeTransform } from "@angular/core"; @Pipe({ name: "classname", + standalone: false, }) export class ClassnamePipe implements PipeTransform { transform(value, args: string[]): any { diff --git a/ui/src/app/shared/pipe/converter/converter.ts b/ui/src/app/shared/pipe/converter/converter.ts index 34cc2fc6649..a03b8d9c4f6 100644 --- a/ui/src/app/shared/pipe/converter/converter.ts +++ b/ui/src/app/shared/pipe/converter/converter.ts @@ -4,6 +4,7 @@ import { Converter } from "../../components/shared/converter"; @Pipe({ name: "converter", + standalone: false, }) export class ConverterPipe implements PipeTransform { diff --git a/ui/src/app/shared/pipe/formatSecondsToDuration/formatSecondsToDuration.pipe.ts b/ui/src/app/shared/pipe/formatSecondsToDuration/formatSecondsToDuration.pipe.ts index 85dcbb36b85..33ff8389b7d 100644 --- a/ui/src/app/shared/pipe/formatSecondsToDuration/formatSecondsToDuration.pipe.ts +++ b/ui/src/app/shared/pipe/formatSecondsToDuration/formatSecondsToDuration.pipe.ts @@ -5,6 +5,7 @@ import { Converter } from "../../components/shared/converter"; @Pipe({ name: "formatSecondsToDuration", + standalone: false, }) export class FormatSecondsToDurationPipe implements PipeTransform { diff --git a/ui/src/app/shared/pipe/isclass/isclass.pipe.ts b/ui/src/app/shared/pipe/isclass/isclass.pipe.ts index 878c61e1d53..39fe3448c17 100644 --- a/ui/src/app/shared/pipe/isclass/isclass.pipe.ts +++ b/ui/src/app/shared/pipe/isclass/isclass.pipe.ts @@ -6,6 +6,7 @@ import { Pipe, PipeTransform } from "@angular/core"; */ @Pipe({ name: "isclass", + standalone: false, }) export class IsclassPipe implements PipeTransform { transform(object: any, classname: string): boolean { diff --git a/ui/src/app/shared/pipe/keys/keys.pipe.ts b/ui/src/app/shared/pipe/keys/keys.pipe.ts index 41d220c89af..ea47b26d222 100644 --- a/ui/src/app/shared/pipe/keys/keys.pipe.ts +++ b/ui/src/app/shared/pipe/keys/keys.pipe.ts @@ -3,6 +3,7 @@ import { Pipe, PipeTransform } from "@angular/core"; @Pipe({ name: "keys", + standalone: false, }) export class KeysPipe implements PipeTransform { transform(value, args: string[]): any { diff --git a/ui/src/app/shared/pipe/sign/sign.pipe.ts b/ui/src/app/shared/pipe/sign/sign.pipe.ts index 3929ad4997c..69c33c28fd4 100644 --- a/ui/src/app/shared/pipe/sign/sign.pipe.ts +++ b/ui/src/app/shared/pipe/sign/sign.pipe.ts @@ -3,6 +3,7 @@ import { Pipe, PipeTransform } from "@angular/core"; @Pipe({ name: "sign", + standalone: false, }) export class SignPipe implements PipeTransform { transform(value, args: string[]): any { diff --git a/ui/src/app/shared/pipe/typeof/typeof.pipe.ts b/ui/src/app/shared/pipe/typeof/typeof.pipe.ts index 288d771169c..50e8827b3b7 100644 --- a/ui/src/app/shared/pipe/typeof/typeof.pipe.ts +++ b/ui/src/app/shared/pipe/typeof/typeof.pipe.ts @@ -2,6 +2,7 @@ import { Pipe, PipeTransform } from "@angular/core"; @Pipe({ name: "typeof", + standalone: false, }) export class TypeofPipe implements PipeTransform { diff --git a/ui/src/app/shared/pipe/unitvalue/unitvalue.pipe.ts b/ui/src/app/shared/pipe/unitvalue/unitvalue.pipe.ts index 994ae841eaf..f6f548f8c42 100644 --- a/ui/src/app/shared/pipe/unitvalue/unitvalue.pipe.ts +++ b/ui/src/app/shared/pipe/unitvalue/unitvalue.pipe.ts @@ -5,6 +5,7 @@ import { Language } from "../../type/language"; @Pipe({ name: "unitvalue", + standalone: false, }) export class UnitvaluePipe implements PipeTransform { diff --git a/ui/src/app/shared/pipe/version/version.pipe.ts b/ui/src/app/shared/pipe/version/version.pipe.ts index 01a7a757398..9e06a04f640 100644 --- a/ui/src/app/shared/pipe/version/version.pipe.ts +++ b/ui/src/app/shared/pipe/version/version.pipe.ts @@ -4,6 +4,7 @@ import { Role } from "../../type/role"; @Pipe({ name: "version", + standalone: false, }) export class VersionPipe implements PipeTransform { diff --git a/ui/src/app/shared/service/arrayutils.ts b/ui/src/app/shared/service/arrayutils.ts deleted file mode 100644 index 5fc4e31b654..00000000000 --- a/ui/src/app/shared/service/arrayutils.ts +++ /dev/null @@ -1,27 +0,0 @@ -export namespace ArrayUtils { - export function equalsCheck(a: T[], b: T[]) { - return a.length === b.length && - a.every((v, i) => v === b[i]); - } - - /** - * Sort arrays alphabetically, according to the string returned by fn. - * Elements for which fn returns null or undefined are sorted to the end in an undefined order. - * - * @param array to sort - * @param fn to get a string to sort by - * @returns sorted array - */ - export function sortedAlphabetically(array: Type[], fn: (arg: Type) => string): Type[] { - return array.sort((a: Type, b: Type) => { - const aVal = fn(a); - const bVal = fn(b); - if (!aVal) { - return !bVal ? 0 : 1; - } else if (!bVal) { - return -1; - } - return aVal.localeCompare(bVal, undefined, { sensitivity: "accent" }); - }); - } -} diff --git a/ui/src/app/shared/service/defaulttypes.spec.ts b/ui/src/app/shared/service/defaulttypes.spec.ts new file mode 100644 index 00000000000..b5a01f05370 --- /dev/null +++ b/ui/src/app/shared/service/defaulttypes.spec.ts @@ -0,0 +1,17 @@ +// @ts-strict-ignore +import { RGBColor } from "./defaulttypes"; + +describe("Defaulttypes", () => { + + it("#RgbColor.toString()", () => { + const black = new RGBColor(0, 0, 0); + expect(black.toString()).toEqual("rgb(0,0,0)"); + }); + + it("#RgbColor.toString() - invalid values", () => { + const allInvalid = new RGBColor(null, null, null); + expect(() => allInvalid.toString()).toThrow(Error("All values need to be valid")); + const oneInvalid = new RGBColor(0, 0, null); + expect(() => oneInvalid.toString()).toThrow(Error("All values need to be valid")); + }); +}); diff --git a/ui/src/app/shared/service/defaulttypes.ts b/ui/src/app/shared/service/defaulttypes.ts index b01b9875627..9fa63b0f4ae 100644 --- a/ui/src/app/shared/service/defaulttypes.ts +++ b/ui/src/app/shared/service/defaulttypes.ts @@ -1,6 +1,6 @@ // @ts-strict-ignore import { TranslateService } from "@ngx-translate/core"; -import { endOfMonth, endOfYear, format, getDay, getMonth, getYear, isSameDay, isSameMonth, isSameYear, startOfMonth, startOfYear, subDays } from "date-fns"; +import { differenceInDays, endOfMonth, endOfYear, format, getDay, getMonth, getYear, isSameDay, isSameMonth, isSameYear, startOfMonth, startOfYear, subDays } from "date-fns"; import { QueryHistoricTimeseriesEnergyResponse } from "../jsonrpc/response/queryHistoricTimeseriesEnergyResponse"; import { ChannelAddress, Service } from "../shared"; @@ -157,6 +157,7 @@ export namespace DefaultTypes { public to: Date = new Date(), ) { } + /** * Returns a translated weekday name. * @@ -236,6 +237,15 @@ export namespace DefaultTypes { }); } } + + /** + * Checks if current period is week or day + * + * @returns true if period is week or day, false if not + */ + public isWeekOrDay(): boolean { + return Math.abs(differenceInDays(this.to, this.from)) <= 6; + } } } @@ -246,3 +256,41 @@ export type TKeyValue = { }; /** */ export type PropType = TObj[TProp]; + +type Range = Acc["length"] extends N + ? Acc[number] + : Range; + +export type RGBValue = Range<256>; // 0 to 255 + +export class RGBColor { + private static INVALID_RGB_VALUES_ERROR = new Error("All values need to be valid"); + private readonly red: T; + private readonly green: T; + private readonly blue: T; + + constructor(red: T, green: T, blue: T) { + this.red = red; + this.green = green; + this.blue = blue; + } + + public static fromString(rgbString: string) { + const rgb: string[] = rgbString.split(",").map(el => el.trim()); + const red: RGBValue = parseInt(rgb[0]) as RGBValue; + const green: RGBValue = parseInt(rgb[1]) as RGBValue; + const blue: RGBValue = parseInt(rgb[2]) as RGBValue; + + if (!red || !green || !blue) { + throw RGBColor.INVALID_RGB_VALUES_ERROR; + } + return new RGBColor(red, green, blue); + } + + public toString(): string { + if (this.red == null || this.green == null || this.blue == null) { + throw RGBColor.INVALID_RGB_VALUES_ERROR; + } + return `rgb(${this.red},${this.green},${this.blue})`; + } +} diff --git a/ui/src/app/shared/service/pagination.ts b/ui/src/app/shared/service/pagination.ts index f75a43c2f71..bef8d9d1e77 100644 --- a/ui/src/app/shared/service/pagination.ts +++ b/ui/src/app/shared/service/pagination.ts @@ -2,9 +2,9 @@ import { Directive } from "@angular/core"; import { Router } from "@angular/router"; import { SubscribeEdgesRequest } from "../jsonrpc/request/subscribeEdgesRequest"; +import { States } from "../ngrx-store/states"; import { ChannelAddress, Edge } from "../shared"; import { Service } from "./service"; -import { States } from "../ngrx-store/states"; @Directive() export class Pagination { diff --git a/ui/src/app/shared/service/previousRouteService.ts b/ui/src/app/shared/service/previousRouteService.ts index f6087a0ea2c..6b1b468066a 100644 --- a/ui/src/app/shared/service/previousRouteService.ts +++ b/ui/src/app/shared/service/previousRouteService.ts @@ -26,4 +26,14 @@ export class PreviousRouteService { public getPreviousUrl() { return this.previousUrl; } + + + /** + * Gets the current url + * + * @returns the current url + */ + public getCurrentUrl() { + return this.currentUrl; + } } diff --git a/ui/src/app/shared/service/service.ts b/ui/src/app/shared/service/service.ts index e25fa22fac3..73e2b1ea3d3 100644 --- a/ui/src/app/shared/service/service.ts +++ b/ui/src/app/shared/service/service.ts @@ -1,6 +1,6 @@ // @ts-strict-ignore import { registerLocaleData } from "@angular/common"; -import { Injectable } from "@angular/core"; +import { Injectable, WritableSignal, signal } from "@angular/core"; import { ActivatedRoute, Router } from "@angular/router"; import { ToastController } from "@ionic/angular"; import { LangChangeEvent, TranslateService } from "@ngx-translate/core"; @@ -79,7 +79,7 @@ export class Service extends AbstractService { user: User, edges: { [edgeId: string]: Edge } }> = new BehaviorSubject(null); - public currentUser: User | null = null; + public currentUser: WritableSignal = signal(null); /** * Holds the current Activated Route @@ -199,6 +199,7 @@ export class Service extends AbstractService { public onLogout() { this.currentEdge.next(null); this.metadata.next(null); + this.currentUser.set(null); this.websocket.state.set(States.NOT_AUTHENTICATED); this.router.navigate(["/login"]); } diff --git a/ui/src/app/shared/service/utils.spec.ts b/ui/src/app/shared/service/utils.spec.ts index 3034ab95019..1e470ba930b 100644 --- a/ui/src/app/shared/service/utils.spec.ts +++ b/ui/src/app/shared/service/utils.spec.ts @@ -1,6 +1,6 @@ // @ts-strict-ignore import { DummyConfig } from "../components/edge/edgeconfig.spec"; -import { EdgeConfig } from "../shared"; +import { Currency, EdgeConfig } from "../shared"; import { HistoryUtils, Utils } from "./utils"; describe("Utils", () => { @@ -54,4 +54,18 @@ describe("Utils", () => { const expectedResult4 = [null, null, null, 565, 560, 561, 573]; expect(Utils.calculateOtherConsumption(channelData, [], [])).toEqual(expectedResult4); }); + + it("+CONVERT_PRICE_TO_CENT_PER_KWH", () => { + let currencyLabel: string = Currency.getCurrencyLabelByCurrency("EUR"); + expect(Utils.CONVERT_PRICE_TO_CENT_PER_KWH(2, currencyLabel)(0)).toEqual("0 Cent/kWh"); + expect(Utils.CONVERT_PRICE_TO_CENT_PER_KWH(2, currencyLabel)(null)).toEqual("- Cent/kWh"); + expect(Utils.CONVERT_PRICE_TO_CENT_PER_KWH(2, currencyLabel)(undefined)).toEqual("- Cent/kWh"); + expect(Utils.CONVERT_PRICE_TO_CENT_PER_KWH(2, currencyLabel)(1)).toEqual("0,1 Cent/kWh"); + + currencyLabel = Currency.getCurrencyLabelByCurrency("CHF"); + expect(Utils.CONVERT_PRICE_TO_CENT_PER_KWH(2, currencyLabel)(0)).toEqual("0 Rp./kWh"); + expect(Utils.CONVERT_PRICE_TO_CENT_PER_KWH(2, currencyLabel)(null)).toEqual("- Rp./kWh"); + expect(Utils.CONVERT_PRICE_TO_CENT_PER_KWH(2, currencyLabel)(undefined)).toEqual("- Rp./kWh"); + expect(Utils.CONVERT_PRICE_TO_CENT_PER_KWH(2, currencyLabel)(1)).toEqual("0,1 Rp./kWh"); + }); }); diff --git a/ui/src/app/shared/service/utils.ts b/ui/src/app/shared/service/utils.ts index 9e237060345..bae81131bc9 100644 --- a/ui/src/app/shared/service/utils.ts +++ b/ui/src/app/shared/service/utils.ts @@ -4,7 +4,7 @@ import { TranslateService } from "@ngx-translate/core"; import { ChartDataset } from "chart.js"; import { saveAs } from "file-saver-es"; import { DefaultTypes } from "src/app/shared/service/defaulttypes"; - +import { Language } from "src/app/shared/type/language"; import { JsonrpcResponseSuccess } from "../jsonrpc/base"; import { Base64PayloadResponse } from "../jsonrpc/response/base64PayloadResponse"; import { QueryHistoricTimeseriesEnergyResponse } from "../jsonrpc/response/queryHistoricTimeseriesEnergyResponse"; @@ -252,10 +252,11 @@ export class Utils { * @returns converted value */ public static CONVERT_TO_WATT = (value: number | null): string => { + const locale: string = (Language.getByKey(localStorage.LANGUAGE) ?? Language.DEFAULT).i18nLocaleKey; if (value == null) { return "-"; } else if (value >= 0) { - return formatNumber(value, "de", "1.0-0") + " W"; + return formatNumber(value, locale, "1.0-0") + " W"; } else { return "0 W"; } @@ -268,13 +269,14 @@ export class Utils { * @returns converted value */ public static CONVERT_WATT_TO_KILOWATT = (value: number | null): string => { + const locale: string = (Language.getByKey(localStorage.LANGUAGE) ?? Language.DEFAULT).i18nLocaleKey; if (value == null) { return "-"; } const thisValue: number = (value / 1000); if (thisValue >= 0) { - return formatNumber(thisValue, "de", "1.0-1") + " kW"; + return formatNumber(thisValue, locale, "1.0-1") + " kW"; } else { return "0 kW"; } @@ -307,7 +309,8 @@ export class Utils { * @returns converted value */ public static CONVERT_TO_WATTHOURS = (value: number): string => { - return formatNumber(value, "de", "1.0-1") + " Wh"; + const locale: string = (Language.getByKey(localStorage.LANGUAGE) ?? Language.DEFAULT).i18nLocaleKey; + return formatNumber(value, locale, "1.0-1") + " Wh"; }; /** @@ -317,7 +320,8 @@ export class Utils { * @returns converted value */ public static CONVERT_TO_KILO_WATTHOURS = (value: number): string => { - return formatNumber(Utils.divideSafely(value, 1000), "de", "1.0-1") + " kWh"; + const locale: string = (Language.getByKey(localStorage.LANGUAGE) ?? Language.DEFAULT).i18nLocaleKey; + return formatNumber(Utils.divideSafely(value, 1000), locale, "1.0-1") + " kWh"; }; /** @@ -347,9 +351,9 @@ export class Utils { */ public static convertChargeDischargePower(translate: TranslateService, power: number): { name: string, value: number } { if (power >= 0) { - return { name: translate.instant("General.dischargePower"), value: power }; + return { name: translate.instant("General.DISCHARGE"), value: power }; } else { - return { name: translate.instant("General.chargePower"), value: power * -1 }; + return { name: translate.instant("General.CHARGE"), value: power * -1 }; } } @@ -396,8 +400,9 @@ export class Utils { * @returns converted value */ public static CONVERT_PRICE_TO_CENT_PER_KWH = (decimal: number, label: string) => { - return (value: number | null): string => - (!value ? "-" : formatNumber(value / 10, "de", "1.0-" + decimal)) + " " + label; + const locale: string = (Language.getByKey(localStorage.LANGUAGE) ?? Language.DEFAULT).i18nLocaleKey; + return (value: number | null | undefined): string => + (value == null ? "-" : formatNumber(value / 10, locale, "1.0-" + decimal)) + " " + label; }; /** @@ -522,7 +527,7 @@ export class Utils { * * @param value the value to convert */ - public static roundSlightlyNegativeValues(value: number) { + public static roundSlightlyNegativeValues(value: number | null): number | null { return (value > -0.49 && value < 0) ? 0 : value; } @@ -626,16 +631,17 @@ export class Utils { } export enum YAxisType { + CURRENCY, + CURRENT, + ENERGY, + LEVEL, NONE, - POWER, PERCENTAGE, - RELAY, - ENERGY, - VOLTAGE, + POWER, REACTIVE, - CURRENT, + RELAY, TIME, - CURRENCY, + VOLTAGE, } export enum ChartAxis { @@ -758,13 +764,17 @@ export namespace HistoryUtils { /** Input Channels that need to be queried from the database */ input: InputChannel[], /** Output Channels that will be shown in the chart */ - output: (data: ChannelData, labels?: Date[]) => DisplayValue[], + output: (data: ChannelData, labels?: (string | Date)[]) => DisplayValue[], tooltip: { /** Format of Number displayed */ formatNumber: string, afterTitle?: (stack: string) => string, + /** Defaults to true */ + enabled?: boolean, }, yAxes: yAxes[], + /** Rounds slightly negative values, defaults to false */ + normalizeOutputData?: boolean, }; export type yAxes = { @@ -774,8 +784,12 @@ export namespace HistoryUtils { yAxisId: ChartAxis, /** YAxis title -> {@link https://www.chartjs.org/docs/latest/samples/scale-options/titles.html Chartjs Title} */ customTitle?: string - /** Default: true */ - displayGrid?: boolean + /** Default: true _> {@link https://www.chartjs.org/docs/latest/axes/styling.html#grid-line-configuration Chartjs Grid Display} */ + displayGrid?: boolean, + scale?: { + /** Default: false, if true scale starts at minimum value of all datasets */ + dynamicScale?: boolean, + } }; export namespace ValueConverter { @@ -855,7 +869,7 @@ export namespace TimeOfUseTariffUtils { * @returns The formatted label, or exits if the value is not valid. */ export function getLabel(value: number, label: string, translate: TranslateService, currencyLabel?: Currency.Label): string { - + const locale: string = (Language.getByKey(localStorage.LANGUAGE) ?? Language.DEFAULT).i18nLocaleKey; // Error handling: Return undefined if value is not valid if (value === undefined || value === null || Number.isNaN(Number.parseInt(value.toString()))) { return; @@ -870,18 +884,18 @@ export namespace TimeOfUseTariffUtils { // Switch case to handle different labels switch (label) { case socLabel: - return label + ": " + formatNumber(value, "de", "1.0-0") + " %"; + return label + ": " + formatNumber(value, locale, "1.0-0") + " %"; case dischargeLabel: case chargeConsumptionLabel: case balancingLabel: // Show floating point number for values between 0 and 1 - return label + ": " + formatNumber(value, "de", "1.0-4") + " " + currencyLabel; + return label + ": " + formatNumber(value, locale, "1.0-4") + " " + currencyLabel; default: case gridBuyLabel: // Power values - return label + ": " + formatNumber(value, "de", "1.0-2") + " kW"; + return label + ": " + formatNumber(value, locale, "1.0-2") + " kW"; } } diff --git a/ui/src/app/shared/service/websocket.ts b/ui/src/app/shared/service/websocket.ts index 6df2b0d368b..a10012290a4 100644 --- a/ui/src/app/shared/service/websocket.ts +++ b/ui/src/app/shared/service/websocket.ts @@ -1,10 +1,11 @@ // @ts-strict-ignore -import { Injectable, WritableSignal, signal } from "@angular/core"; +import { Injectable, signal, WritableSignal } from "@angular/core"; import { Router } from "@angular/router"; import { TranslateService } from "@ngx-translate/core"; import { CookieService } from "ngx-cookie-service"; import { delay, retryWhen } from "rxjs/operators"; -import { WebSocketSubject, webSocket } from "rxjs/webSocket"; +import { webSocket, WebSocketSubject } from "rxjs/webSocket"; +import { UserComponent } from "src/app/user/user.component"; import { environment } from "src/environments"; import { JsonrpcMessage, JsonrpcNotification, JsonrpcRequest, JsonrpcResponse, JsonrpcResponseError, JsonrpcResponseSuccess } from "../jsonrpc/base"; @@ -82,7 +83,8 @@ export class Websocket implements WebsocketInterface { // received login token -> save in cookie this.cookieService.set("token", authenticateResponse.token, { expires: 365, path: "/", sameSite: "Strict", secure: location.protocol === "https:" }); - this.service.currentUser = authenticateResponse.user; + this.service.currentUser.set(authenticateResponse.user); + UserComponent.applyUserSettings(authenticateResponse.user); // Metadata this.service.metadata.next({ diff --git a/ui/src/app/shared/shared.module.ts b/ui/src/app/shared/shared.module.ts index 2ff830daf99..c4ac3d7797c 100644 --- a/ui/src/app/shared/shared.module.ts +++ b/ui/src/app/shared/shared.module.ts @@ -8,13 +8,14 @@ import { IonicModule } from "@ionic/angular"; import { FormlyFieldConfig, FormlyModule } from "@ngx-formly/core"; import { FormlyIonicModule } from "@ngx-formly/ionic"; import { TranslateModule } from "@ngx-translate/core"; -import { NgChartsModule } from "ng2-charts"; +import { BaseChartDirective } from "ng2-charts"; import { NgxSpinnerModule } from "ngx-spinner"; import { appRoutingProviders } from "../app-routing.module"; import { ComponentsModule } from "./components/components.module"; import { MeterModule } from "./components/edge/meter/meter.module"; import { FormlyCheckBoxHyperlinkWrapperComponent } from "./components/formly/form-field-checkbox-hyperlink/form-field-checkbox-hyperlink.wrapper"; import { FormlyWrapperDefaultValueWithCasesComponent } from "./components/formly/form-field-default-cases.wrapper"; +import { FormlyFieldMultiStepComponent } from "./components/formly/form-field-multi-step/form-field-multi-step"; import { FormlyWrapperFormFieldComponent } from "./components/formly/form-field.wrapper"; import { FormlyFieldCheckboxWithImageComponent } from "./components/formly/formly-field-checkbox-image/formly-field-checkbox-with-image"; import { FormlyFieldModalComponent } from "./components/formly/formly-field-modal/formlyfieldmodal"; @@ -26,6 +27,7 @@ import { InputTypeComponent } from "./components/formly/input"; import { FormlyInputSerialNumberWrapperComponent as FormlyWrapperInputSerialNumber } from "./components/formly/input-serial-number-wrapper"; import { PanelWrapperComponent } from "./components/formly/panel-wrapper.component"; import { RepeatTypeComponent } from "./components/formly/repeat"; +import { AppHeaderComponent } from "./components/header/app-header"; import { HeaderComponent } from "./components/header/header.component"; import { HistoryDataErrorModule } from "./components/history-data-error/history-data-error.module"; import { PercentageBarComponent } from "./components/percentagebar/percentagebar.component"; @@ -55,20 +57,12 @@ export function SubnetmaskValidatorMessage(err, field: FormlyFieldConfig) { return `"${field.formControl.value}" is not a valid Subnetmask`; } - @NgModule({ imports: [ BrowserAnimationsModule, - NgChartsModule, CommonModule, + ComponentsModule, DirectiveModule, - FormsModule, - IonicModule, - NgxSpinnerModule.forRoot({ - type: "ball-clip-rotate-multiple", - }), - ReactiveFormsModule, - RouterModule, FormlyModule.forRoot({ wrappers: [ { name: "form-field", component: FormlyWrapperFormFieldComponent }, @@ -84,6 +78,7 @@ export function SubnetmaskValidatorMessage(err, field: FormlyFieldConfig) { types: [ { name: "input", component: InputTypeComponent }, { name: "repeat", component: RepeatTypeComponent }, + { name: "multi-step", component: FormlyFieldMultiStepComponent }, ], validators: [ { name: "ip", validation: IpValidator }, @@ -94,55 +89,61 @@ export function SubnetmaskValidatorMessage(err, field: FormlyFieldConfig) { { name: "subnetmask", message: SubnetmaskValidatorMessage }, ], }), - PipeModule, - ComponentsModule, - TranslateModule, + FormsModule, HistoryDataErrorModule, + IonicModule, MeterModule, + BaseChartDirective, + NgxSpinnerModule.forRoot({ + type: "ball-clip-rotate-multiple", + }), + PipeModule, + ReactiveFormsModule, + RouterModule, + TranslateModule, ], declarations: [ - // components + AppHeaderComponent, ChartOptionsComponent, - HeaderComponent, - PercentageBarComponent, - // formly - InputTypeComponent, - FormlyWrapperFormFieldComponent, - RepeatTypeComponent, - FormlyWrapperInputSerialNumber, + FormlyCheckBoxHyperlinkWrapperComponent, + FormlyFieldCheckboxWithImageComponent, + FormlyFieldModalComponent, + FormlyFieldMultiStepComponent, + FormlyFieldRadioWithImageComponent, + FormlyFieldWithLoadingAnimationComponent, FormlySelectFieldExtendedWrapperComponent, FormlySelectFieldModalComponent, - FormlyFieldRadioWithImageComponent, - FormlyCheckBoxHyperlinkWrapperComponent, FormlyWrapperDefaultValueWithCasesComponent, - FormlyFieldModalComponent, + FormlyWrapperFormFieldComponent, + FormlyWrapperInputSerialNumber, + HeaderComponent, + InputTypeComponent, PanelWrapperComponent, - FormlyFieldWithLoadingAnimationComponent, - FormlyFieldCheckboxWithImageComponent, + PercentageBarComponent, + RepeatTypeComponent, ], exports: [ - // modules + AppHeaderComponent, BrowserAnimationsModule, - NgChartsModule, + ChartOptionsComponent, CommonModule, + ComponentsModule, DirectiveModule, + FormlyFieldWithLoadingAnimationComponent, FormlyIonicModule, FormlyModule, FormsModule, + HeaderComponent, + HistoryDataErrorModule, IonicModule, + MeterModule, + BaseChartDirective, NgxSpinnerModule, + PercentageBarComponent, + PipeModule, ReactiveFormsModule, RouterModule, TranslateModule, - PipeModule, - ComponentsModule, - MeterModule, - HistoryDataErrorModule, - // components - ChartOptionsComponent, - HeaderComponent, - PercentageBarComponent, - FormlyFieldWithLoadingAnimationComponent, ], providers: [ AppStateTracker, diff --git a/ui/src/app/shared/shared.ts b/ui/src/app/shared/shared.ts index 6cf1168edc6..26bc0432659 100644 --- a/ui/src/app/shared/shared.ts +++ b/ui/src/app/shared/shared.ts @@ -1,4 +1,5 @@ // @ts-strict-ignore +export { ChartConstants } from "./components/chart/chart.constants"; export { Edge } from "./components/edge/edge"; export { EdgeConfig } from "./components/edge/edgeconfig"; export { Logger } from "./service/logger"; @@ -122,43 +123,55 @@ export class UserPermission { } } +export enum Producttype { +} + export namespace Currency { /** - * Gets the currencylabel for a edgeId + * This method returns the corresponding label based on the user-selected currency in "core.meta." * - * @param edgeId the edgeId - * @returns the Currencylabel dependent on edgeId + * @param currency The currency enum. + * @returns the Currencylabel */ - export function getCurrencyLabelByEdgeId(edgeId: string): Label { - switch (edgeId) { + export function getCurrencyLabelByCurrency(currency: string): Label { + switch (currency) { + case "SEK": + return Label.OERE_PER_KWH; + case "CHF": + return Label.RAPPEN_PER_KWH; default: return Label.CENT_PER_KWH; } } /** - * This method returns the corresponding label based on the user-selected currency in "core.meta." + * This method returns the corresponding label for the chart based on the user-selected currency. * * @param currency The currency enum. - * @returns the Currencylabel + * @returns the Currency Unit label */ - export function getCurrencyLabelByCurrency(currency: string): Label { + export function getChartCurrencyUnitLabel(currency: string) { switch (currency) { case "SEK": - return Label.OERE_PER_KWH; + return Unit.OERE; + case "CHF": + return Unit.RAPPEN; default: - return Label.CENT_PER_KWH; + return Unit.CENT; } } export enum Label { OERE_PER_KWH = "Öre/kWh", CENT_PER_KWH = "Cent/kWh", + RAPPEN_PER_KWH = "Rp./kWh", } export enum Unit { CENT = "Cent", + OERE = "Öre", + RAPPEN = "Rp.", } } diff --git a/ui/src/app/shared/type/language.ts b/ui/src/app/shared/type/language.ts index 4858ceca062..693a81d2b6e 100644 --- a/ui/src/app/shared/type/language.ts +++ b/ui/src/app/shared/type/language.ts @@ -119,7 +119,7 @@ export class Language { * @returns translations params */ public static async setAdditionalTranslationFile(translationFile: any, translate: TranslateService): Promise<{ lang: string; translations: {}; shouldMerge?: boolean; }> { - const lang = (await translate.onLangChange.pipe(filter(lang => !!lang), take(1)).toPromise()).lang; + const lang = (await translate.onLangChange.pipe(filter(lang => !!lang), take(1)).toPromise())?.lang ?? Language.DEFAULT.key; let translationKey: string = lang; if (!(lang in translationFile)) { diff --git a/ui/src/app/shared/type/widget.ts b/ui/src/app/shared/type/widget.ts index 5f72e25e0ce..a6f73311e93 100644 --- a/ui/src/app/shared/type/widget.ts +++ b/ui/src/app/shared/type/widget.ts @@ -47,6 +47,11 @@ export type Icon = { name: string; }; +export type ImageIcon = { + src: string; + large: boolean; +}; + export class Widget { public name: WidgetNature | WidgetFactory | string; public componentId: string; diff --git a/ui/src/app/shared/utils/array/array.utils.ts b/ui/src/app/shared/utils/array/array.utils.ts index 6026afa4579..1f00f0d723f 100644 --- a/ui/src/app/shared/utils/array/array.utils.ts +++ b/ui/src/app/shared/utils/array/array.utils.ts @@ -19,10 +19,10 @@ export namespace ArrayUtils { /** * Finds the biggest number in a array. * null, undefined, NaN, +-Infinity are ignored in this method. - * - * @param arr the arr - * @returns a number if arr not empty, else null - */ + * + * @param arr the arr + * @returns a number if arr not empty, else null + */ export function findBiggestNumber(arr: (number | null | undefined)[]): number | null { const filteredArr = arr.filter((el): el is number => Number.isFinite(el)); return filteredArr.length > 0 ? Math.max(...filteredArr) : null; @@ -48,4 +48,26 @@ export namespace ArrayUtils { return aVal.localeCompare(bVal, undefined, { sensitivity: "accent" }); }); } + + /** + * Checks if array contains at least one of the passed strings + * + * @param strings the strings + * @param arr the array + * @returns true if arr contains at least one of the strings + */ + export function containsStrings(strings: (number | string | null)[], arr: (number | string | null)[]): boolean { + return arr.filter(el => strings.includes(el)).length > 0; + } + + /** + * Checks if array contains at least one of the passed strings + * + * @param strings the strings + * @param arr the array + * @returns true if arr contains all of the strings + */ + export function containsAllStrings(strings: (number | string | null)[], arr: (number | string | null)[]): boolean { + return arr.every(el => strings.includes(el)); + } } diff --git a/ui/src/app/shared/utils/assertions/assertions-utils.ts b/ui/src/app/shared/utils/assertions/assertions-utils.ts new file mode 100644 index 00000000000..d6b9f1ec983 --- /dev/null +++ b/ui/src/app/shared/utils/assertions/assertions-utils.ts @@ -0,0 +1,19 @@ + +export namespace AssertionUtils { + + export function assertIsDefined(value: T, message: string = "Value is undefined"): asserts value is NonNullable { + if (value === undefined || value === null) { + throw new Error(message); + } + } + + export function assertHasMaxLength( + value: string, + maxLength: number, + message: string = `String exceeds maximum length of ${maxLength}`, + ): asserts value { + if (value.length > maxLength) { + throw new Error(message); + } + } +} diff --git a/ui/src/app/shared/utils/datetime/datetime-utils.spec.ts b/ui/src/app/shared/utils/datetime/datetime-utils.spec.ts new file mode 100644 index 00000000000..51a5ff48aca --- /dev/null +++ b/ui/src/app/shared/utils/datetime/datetime-utils.spec.ts @@ -0,0 +1,20 @@ +import { subSeconds } from "date-fns"; +import { DateTimeUtils } from "./datetime-utils"; + +describe("DateTimeUtils", () => { + it("#isDifferenceInSecondsGreaterThan - expected true", () => { + const currDate: Date = new Date(); + const lastUpdate: Date = subSeconds(new Date(), 11); + expect(DateTimeUtils.isDifferenceInSecondsGreaterThan(10, currDate, lastUpdate)).toEqual(true); + }); + it("#isDifferenceInSecondsGreaterThan - expected false", () => { + const currDate: Date = new Date(); + const lastUpdate: Date = subSeconds(new Date(), 9); + expect(DateTimeUtils.isDifferenceInSecondsGreaterThan(10, currDate, lastUpdate)).toEqual(false); + }); + it("#isDifferenceInSecondsGreaterThan - invalid Dates", () => { + const currDate: Date = new Date(); + const lastUpdate: Date | null = null; + expect(DateTimeUtils.isDifferenceInSecondsGreaterThan(10, currDate, lastUpdate)).toEqual(false); + }); +}); diff --git a/ui/src/app/shared/utils/datetime/datetime-utils.ts b/ui/src/app/shared/utils/datetime/datetime-utils.ts index 70bb0b7cc58..b008ba8e340 100644 --- a/ui/src/app/shared/utils/datetime/datetime-utils.ts +++ b/ui/src/app/shared/utils/datetime/datetime-utils.ts @@ -1,6 +1,9 @@ // @ts-strict-ignore -import { format, startOfMonth, startOfYear } from "date-fns"; +/* eslint-disable import/no-duplicates */ +// cf. https://github.com/import-js/eslint-plugin-import/issues/1479 +import { differenceInMilliseconds, format, startOfMonth, startOfYear } from "date-fns"; import { de } from "date-fns/locale"; +/* eslint-enable import/no-duplicates */ import { ChronoUnit } from "src/app/edge/history/shared"; import { QueryHistoricTimeseriesDataResponse } from "../../jsonrpc/response/queryHistoricTimeseriesDataResponse"; @@ -54,6 +57,13 @@ export class DateTimeUtils { default: return energyPerPeriodResponse; } + } + public static isDifferenceInSecondsGreaterThan(seconds: number, currentDate: Date, dateToCompare: Date | null) { + if (dateToCompare == null) { + return false; + } + const milliSeconds = seconds * 1000; + return differenceInMilliseconds(currentDate, dateToCompare) > milliSeconds; } } diff --git a/ui/src/app/shared/utils/object/object.utils.ts b/ui/src/app/shared/utils/object/object.utils.ts new file mode 100644 index 00000000000..2b0bd31d86d --- /dev/null +++ b/ui/src/app/shared/utils/object/object.utils.ts @@ -0,0 +1,14 @@ +import { ArrayUtils } from "../array/array.utils"; + +export class ObjectUtils { + + public static excludeProperties, K extends keyof T>(obj: T, keys: K[]): Omit { + const result = { ...obj }; + keys.forEach(key => delete result[key]); + return result; + } + + public static hasKeys>(obj: T, keys: string[]): boolean { + return ArrayUtils.containsAllStrings(Object.keys(obj), keys); + } +} diff --git a/ui/src/app/shared/utils/string/string.utils.ts b/ui/src/app/shared/utils/string/string.utils.ts new file mode 100644 index 00000000000..e5f804b7fc8 --- /dev/null +++ b/ui/src/app/shared/utils/string/string.utils.ts @@ -0,0 +1,13 @@ +export namespace StringUtils { + + /** + * Checks if the value does not occur in array + * + * @param val the value + * @param arr the array + * @returns true if passed value is not contained by the array + */ + export function isNotIn(val: string, arr: string[]): boolean { + return arr.some(el => val != el); + } +} diff --git a/ui/src/app/shared/utils/time/timeutils.ts b/ui/src/app/shared/utils/time/timeutils.ts index 2998327d2ab..d2ac1d7d981 100644 --- a/ui/src/app/shared/utils/time/timeutils.ts +++ b/ui/src/app/shared/utils/time/timeutils.ts @@ -1,6 +1,8 @@ // @ts-strict-ignore import { DecimalPipe } from "@angular/common"; +import { TranslateService } from "@ngx-translate/core"; +import { Utils } from "../../shared"; import { Language } from "../../type/language"; export class TimeUtils { @@ -54,4 +56,18 @@ export class TimeUtils { return decimalPipe.transform(seconds, "1.0-0") + " s"; } } + + public static getDaysFromMilliSeconds(ms: number) { + return Utils.floorSafely(Utils.divideSafely(ms, 24 * 60 * 60 * 1000)); + } + public static getHoursFromMilliSeconds(ms: number) { + return Utils.floorSafely(Utils.divideSafely(ms, 60 * 60 * 1000)); + } + public static getMinutesFromMilliSeconds(ms: number) { + return Utils.floorSafely(Utils.divideSafely(ms, 60 * 1000)); + } + + public static getDurationText(ms: number, translate: TranslateService, singular: string, plural: string) { + return `${ms} ${translate.instant(ms > 1 ? plural : singular)}`; + } } diff --git a/ui/src/app/user/user.component.html b/ui/src/app/user/user.component.html index bc5370ebca3..7a6f64e37c4 100644 --- a/ui/src/app/user/user.component.html +++ b/ui/src/app/user/user.component.html @@ -1,4 +1,3 @@ -
    @@ -45,7 +44,7 @@
    - + Menu.logout @@ -75,12 +74,12 @@ - - + + - {{isEditModeDisabled ?'bearbeiten':'zurücksetzen'}} @@ -95,18 +94,16 @@ - + - - Register.Form.contactDetails - - (Register.Form.company) - + + @@ -124,7 +121,7 @@

    About.developed

    About.build

    @@ -132,18 +129,14 @@

    About.build

    - - - - Debug-Mode -
    @@ -153,19 +146,36 @@
    - About.language - + {{ language.title }} - - - - The language can not be changed permanently in the local online-monitoring for technical - reasons. - + +
    + + + + {{ theme.key }} + + + +
    + + Debug-Mode + + + + + The language can not be changed permanently in the local online-monitoring for technical + reasons. + +
    diff --git a/ui/src/app/user/user.component.ts b/ui/src/app/user/user.component.ts index b42945d1521..23d299fcb18 100644 --- a/ui/src/app/user/user.component.ts +++ b/ui/src/app/user/user.component.ts @@ -1,18 +1,23 @@ // @ts-strict-ignore -import { Component, OnInit } from "@angular/core"; +import { KeyValue } from "@angular/common"; +import { Component, OnInit, effect } from "@angular/core"; import { FormGroup, Validators } from "@angular/forms"; import { ActivatedRoute } from "@angular/router"; import { FormlyFieldConfig } from "@ngx-formly/core"; import { TranslateService } from "@ngx-translate/core"; import { Changelog } from "src/app/changelog/view/component/changelog.constants"; import { environment } from "../../environments"; +import { Theme } from "../edge/history/shared"; import { GetUserInformationRequest } from "../shared/jsonrpc/request/getUserInformationRequest"; import { SetUserInformationRequest } from "../shared/jsonrpc/request/setUserInformationRequest"; import { UpdateUserLanguageRequest } from "../shared/jsonrpc/request/updateUserLanguageRequest"; +import { UpdateUserSettingsRequest } from "../shared/jsonrpc/request/updateUserSettingsRequest"; import { GetUserInformationResponse } from "../shared/jsonrpc/response/getUserInformationResponse"; +import { User } from "../shared/jsonrpc/shared"; import { Service, Websocket } from "../shared/shared"; import { COUNTRY_OPTIONS } from "../shared/type/country"; import { Language } from "../shared/type/language"; +import { Role } from "../shared/type/role"; type CompanyUserInformation = UserInformation & { companyName: string }; @@ -29,9 +34,17 @@ type UserInformation = { @Component({ templateUrl: "./user.component.html", + standalone: false, }) export class UserComponent implements OnInit { + private static readonly DEFAULT_THEME: Theme = Theme.LIGHT; + public currentTheme: string; + public readonly themes: KeyValue[] = [ + { key: "Light", value: "light" }, + { key: "Dark", value: "dark" }, + { key: "System", value: "system" }, + ]; protected readonly environment = environment; protected readonly uiVersion = Changelog.UI_VERSION; protected readonly languages: Language[] = Language.ALL; @@ -55,100 +68,67 @@ export class UserComponent implements OnInit { disabled: true, }, }]; - protected readonly companyInformationFields: FormlyFieldConfig[] = []; + protected companyInformationFields: FormlyFieldConfig[] = []; + + protected isAtLeastAdmin: boolean = false; constructor( public translate: TranslateService, public service: Service, private route: ActivatedRoute, private websocket: Websocket, - ) { } + ) { + effect(() => { + const user = this.service.currentUser(); + + if (user) { + this.isAtLeastAdmin = Role.isAtLeast(user.globalRole, Role.ADMIN); + this.updateUserInformation(); + } + }); + } + + public static applyUserSettings(user: User): void { + const theme = UserComponent.getCurrentTheme(user); + let attr: Exclude<`${Theme}`, Theme.SYSTEM> = theme; + localStorage.setItem("THEME", theme); + if (theme === Theme.SYSTEM) { + attr = window.matchMedia("(prefers-color-scheme: dark)").matches ? Theme.DARK : Theme.LIGHT; + } + document.documentElement.setAttribute("data-theme", attr); + } + + public static getPreferedColorSchemeFromTheme(theme: Theme) { + return theme === Theme.SYSTEM ? (window.matchMedia("(prefers-color-scheme: dark)").matches ? Theme.DARK : Theme.LIGHT) : theme; + } + + public static getCurrentTheme(user: User): Theme { + return user?.settings["theme"] ?? localStorage.getItem("THEME") ?? UserComponent.DEFAULT_THEME; + } ngOnInit() { // Set currentLanguage to this.currentLanguage = Language.getByKey(localStorage.LANGUAGE) ?? Language.DEFAULT; - this.getUserInformation().then((userInformation) => { - this.form = { - formGroup: new FormGroup({}), - model: userInformation, - }; - const baseInformationFields: FormlyFieldConfig[] = [{ - key: "street", - type: "input", - props: { - label: this.translate.instant("Register.Form.street"), - disabled: true, - }, - }, - { - key: "zip", - type: "input", - props: { - label: this.translate.instant("Register.Form.zip"), - disabled: true, - }, - }, - { - key: "city", - type: "input", - props: { - label: this.translate.instant("Register.Form.city"), - disabled: true, - }, - }, - { - key: "country", - type: "select", - props: { - label: this.translate.instant("Register.Form.country"), - options: COUNTRY_OPTIONS(this.translate), - disabled: true, - }, - }, - { - key: "email", - type: "input", - props: { - label: this.translate.instant("Register.Form.email"), - disabled: true, - }, - validators: { - validation: [Validators.email], - }, - }, - { - key: "phone", - type: "input", - props: { - label: this.translate.instant("Register.Form.phone"), - disabled: true, - }, - }]; - - if (Object.prototype.hasOwnProperty.call(userInformation, "companyName")) { - this.companyInformationFields.push( - { - key: "companyName", - type: "input", - props: { - label: this.translate.instant("Register.Form.companyName"), - disabled: true, - }, - }, - ...baseInformationFields, - ); - } else { - this.userInformationFields.push(...baseInformationFields); - } - - }).then(() => { + this.updateUserInformation().then(() => { this.service.metadata.subscribe(entry => { this.showInformation = true; }); }); } + public setTheme(theme: Theme): void { + this.service.websocket.sendSafeRequest(new UpdateUserSettingsRequest({ + settings: { theme: theme }, + })).then(() => { + const currentUser = this.service.currentUser(); + if (currentUser) { + currentUser.settings["theme"] = theme; + UserComponent.applyUserSettings(currentUser); + } + }); + } + public applyChanges() { const params: SetUserInformationRequest["params"] = { @@ -263,4 +243,82 @@ export class UserComponent implements OnInit { this.currentLanguage = language; this.translate.use(language.key); } + + private updateUserInformation(): Promise { + return this.getUserInformation().then((userInformation) => { + this.form = { + formGroup: new FormGroup({}), + model: userInformation, + }; + + const baseInformationFields: FormlyFieldConfig[] = [{ + key: "street", + type: "input", + props: { + label: this.translate.instant("Register.Form.street"), + disabled: true, + }, + }, + { + key: "zip", + type: "input", + props: { + label: this.translate.instant("Register.Form.zip"), + disabled: true, + }, + }, + { + key: "city", + type: "input", + props: { + label: this.translate.instant("Register.Form.city"), + disabled: true, + }, + }, + { + key: "country", + type: "select", + props: { + label: this.translate.instant("Register.Form.country"), + options: COUNTRY_OPTIONS(this.translate), + disabled: true, + }, + }, + { + key: "email", + type: "input", + props: { + label: this.translate.instant("Register.Form.email"), + disabled: true, + }, + validators: { + validation: [Validators.email], + }, + }, + { + key: "phone", + type: "input", + props: { + label: this.translate.instant("Register.Form.phone"), + disabled: true, + }, + + }]; + + if (Object.prototype.hasOwnProperty.call(userInformation, "companyName")) { + this.companyInformationFields = [{ + key: "companyName", + type: "input", + props: { + label: this.translate.instant("Register.Form.companyName"), + disabled: true, + }, + }, + ...baseInformationFields, + ]; + } else { + this.userInformationFields = baseInformationFields; + } + }); + } } diff --git a/ui/src/assets/i18n/cz.json b/ui/src/assets/i18n/cz.json index 04ac9dd63ec..1d948ec5469 100644 --- a/ui/src/assets/i18n/cz.json +++ b/ui/src/assets/i18n/cz.json @@ -10,7 +10,6 @@ "changeAccepted": "Změna byla přijata", "changeFailed": "Změna se nezdařila", "chargeDischarge": "Debetní/vybíjení", - "chargePower": "Nabíjecí výkon", "componentCount": "Počet komponentů", "componentInactive": "Komponenta je neaktivní!", "connectionLost": "Spojení ztraceno. Pokouší se znovu připojit.", @@ -22,7 +21,6 @@ "digitalInputs": "Digitální vstupy", "numberOfComponents": "Počet komponentů", "directConsumption": "Přímá spotřeba", - "dischargePower": "Vybíjecí výkon", "fault": "Chyba", "grid": "Síť", "gridBuy": "Nákup ze sítě", @@ -96,7 +94,9 @@ "MINUTES": "Minuty", "DAY": "Den", "DAYS": "Dny" - } + }, + "DISCHARGE": "Vypouštění", + "CHARGE": "Načítání" }, "Menu": { "accessLevel": "Úroveň přístupu", @@ -537,7 +537,7 @@ "currentDevelopments": "Aktuální vývoj", "developed": "Toto uživatelské rozhraní bylo vyvinuto jako open-source software.", "faq": "Často kladené otázky (FAQ)", - "language": "Zvolte jazyk:", + "language": "Select language:", "openEMS": "Více o OpenEMS", "patchnotes": "Změny v monitorování tohoto sestavení", "info": "Jazyk nemůže být trvale změněn v místním online monitorování z technických důvodů." diff --git a/ui/src/assets/i18n/de.json b/ui/src/assets/i18n/de.json index 457f5cb15ab..ef0490e7b76 100644 --- a/ui/src/assets/i18n/de.json +++ b/ui/src/assets/i18n/de.json @@ -200,7 +200,9 @@ "name": "Erzwungene Beladung", "shortName": "Manuell" }, - "Uncontrollable": "Diese Ladesäule kann nicht gesteuert werden." + "Uncontrollable": "Diese Ladesäule kann nicht gesteuert werden.", + "HYSTERESIS": "Mindestumschaltzeit der Ladestation aktiv", + "HYSTERESIS_INFO": "Um Ladeabbrüche aufgrund zu häufigen Startens/Pausierens des Ladevorgangs zu verhindern, wird der aktuell eingestellte Lademodus für die nächsten Minuten fortgesetzt." }, "Heatingelement": { "activeForced": "Aktiv (Mindestlaufzeit)", @@ -235,14 +237,14 @@ "CONTROL_MODE_DESCRIPTION": { "CHARGE_CONSUMPTION": "" }, - "CHARGE_FROM_GRID_ACTIVATE": "Aktive Beladung aus dem Netz aktivieren (BETA-Test)", + "CHARGE_FROM_GRID_ACTIVATE": "Aktive Beladung aus dem Netz aktivieren", "PRICE": "Aktueller Bezugsstrompreis", "STATE": { "DELAY_DISCHARGE": "Entladung verzögert", "BALANCING": "Eigenverbrauchsoptimierung", "CHARGE_GRID": "Beladung aus dem Netz freigegeben" }, - "CHART_TITLE": "Aktueller Fahrplan (BETA-Test)", + "CHART_TITLE": "Aktueller Fahrplan", "CHART_WARNING_NOTE": "Die Grafik zeigt die vergangenen drei Stunden, sowie die zukünftig geplante Betriebsweise für den Zeitraum, für den die dynamischen Netzbezugspreise zur Verfügung stehen. Bitte beachten Sie, dass der Fahrplan kontinuierlich neu berechnet wird und sich somit im Tagesverlauf ändern kann.", "POWER_SOC_CHART_TITLE": "Vorhersagen (Nur für Admins)" }, @@ -252,7 +254,14 @@ }, "OVERALL_SYSTEM": "Gesamtsystem" }, - "FIX_DIGITAL_OUTPUT": "Digitale Ausgangscontroller" + "FIX_DIGITAL_OUTPUT": "Digitale Ausgangscontroller", + "SCHEDULE": { + "CHART_TITLE_ADMIN": "Aktueller Fahrplan (Nur für Admins)" + }, + "STORAGE": { + "GRID_CHARGE": "Erhalt der Notstromreserve aus dem Netz aktivieren", + "GRID_CHARGE_WARNING": "Das Speichersystem kann zur Aufrechterhaltung der eingestellten Notstromreserve aktiv aus dem Netz nachladen um bei längerem Ausbleiben von Erzeugung Verluste durch internen Eigenverbrauch auszugleichen.

    ACHTUNG: Die aktive Beladung der Batterie aus dem Netz über die technisch notwendige Erhaltungsladung hinaus erfordert eine entsprechende Anmeldung des Speichersystems. Im Zweifel wenden Sie sich hierzu bitte an Ihren Installateur. Mit Betätigung des blauen \"Speichern\"-Buttons bestätigen Sie, dass Sie diese Prüfung durchgeführt haben.

    Beachten Sie: Voraussetzung für die Inanspruchnahme einer Förderung nach dem Gesetz für den Ausbau erneuerbarer Energien (EEG) ist, dass im Speicher ausschließlich Strom zwischengespeichert wird, der aus erneuerbaren Energien und/oder Grubengas stammt. Bei Aktivierung dieser Funktion wird der Speicher mit Netzstrom geladen. Dieser Netzstrom wird, je nach Stromlieferungsvertrag, ggf. auch aus fossilen Energieträgern und/oder Atomkraft gewonnen. Daher können wir nicht gewährleisten, dass der Speicher ausschließlich mit Strom aus erneuerbaren Energien und/oder Grubengas geladen wird" + } }, "RETROFITTING": { "UPDATE_TO_NEW_VERSION": "Um die Kapazitätserweiterung nutzen zu können, ist ein Update auf die neueste Version erforderlich.", @@ -271,6 +280,7 @@ "SYSTEMUPDATE": "Systemupdate" }, "History": { + "PHASE_ACCURATE": "Phasengenau", "CURRENT_AND_VOLTAGE": "Strom & Spannung", "beginDate": "Startdatum wählen", "day": "Tag", @@ -312,6 +322,7 @@ "nov": "Nov", "dec": "Dez", "activeDuration": "Einschaltdauer", + "ACTIVE_DURATION_WITH_LEVEL": "Einschaltdauer Level {{level}}", "CURRENT": "Strom", "VOLTAGE": "Spannung" }, @@ -398,6 +409,7 @@ "modifyApp": "App bearbeiten", "createApp": "App installieren", "deleteApp": "App entfernen", + "DELETE_CONFIRM": "entfernen", "updateApp": "App aktualisieren", "errorInstallable": "Installierungs fehler", "errorCompatible": "Kompatibilitäts fehler", @@ -427,7 +439,9 @@ "successUpdate": "App erfolgreich aktualisiert", "failUpdate": "Fehler während der Aktualisierung: {{error}}", "successDelete": "App erfolgreich entfernt", - "failDelete": "Entfernen der App fehlgeschlagen: {{error}}" + "failDelete": "Entfernen der App fehlgeschlagen: {{error}}", + "DELETE_CONFIRM_HEADLINE": "Sind Sie sicher, dass die App entfernt werden soll?", + "DELETE_CONFIRM_DESCRIPTION": "Beim Entfernen werden alle Komponenten und Einstellungen gelöscht, die von dieser App angelegt wurden." } }, "Service": { @@ -467,7 +481,6 @@ "changeAccepted": "Änderung übernommen", "changeFailed": "Änderung fehlgeschlagen", "chargeDischarge": "Be-/Entladung", - "chargePower": "Beladung", "componentCount": "Anzahl Komponenten", "componentInactive": "Komponente ist inaktiv!", "connectionLost": "Verbindung unterbrochen. Versuche die Verbindung wiederherzustellen.", @@ -479,7 +492,6 @@ "digitalInputs": "Digitaleingänge", "numberOfComponents": "Anzahl der Komponenten", "directConsumption": "Direktverbrauch", - "dischargePower": "Entladung", "energyLimit": "Energielimit", "fault": "Fehler", "grid": "Netz", @@ -570,6 +582,19 @@ "MINUTES": "Minuten", "DAY": "Tag", "DAYS": "Tage" + }, + "CHARGE": "Beladung", + "DISCHARGE": "Entladung" + }, + "OFFLINE": { + "EDGE": { + "LOCAL_ACCESS_CURRENTLY_NOT_POSSIBLE": "Lokalzugriff derzeit nicht möglich", + "DESCRIPTION": "Sollte das {{edgeShortName}}-Gerät neugestartet worden sein, warten Sie bitte einen Moment, bis der Neustart vollkommen durchgeführt wurde." + }, + "GENERAL": { + "AVAILABILITY": "Der Onlinezugriff auf das Speichersystem ist aktuell nicht möglich.
    Die Daten werden lokal gespeichert und gehen nicht verloren.", + "MORE_INFO": "mehr Infos", + "EMS_OFFLINE": "{{edgeShortName}} ist offline" } }, "Index": { @@ -856,7 +881,16 @@ }, "CONVERTER_RATIO": "Primärstrom (200–5000A)" }, - "CONFIGURATION_MPPT_SELECTION_NOTE": "Weitere Erzeuger können im Anschluss über das App Center konfiguriert werden." + "CONFIGURATION_MPPT_SELECTION_NOTE": "Weitere Erzeuger können im Anschluss über das App Center konfiguriert werden.", + "CONFIGURATION_ESS_LIMITER": { + "TITLE": "Dimmung nach §14a EnWG", + "IS_ACTIVATE": "Möglichkeit der Dimmung nach §14a EnWG aktivieren", + "INFO_NOTE": "Um eine Überlastung des Netzes zu vermeiden, erhält der Netzbetreiber bei allen, seit dem 01.01.2024 installierten Verbrauchseinrichtungen, zukünftig die Möglichkeit, diese auf eine Netzbezugsleistung des Stromspeichers von 4,2 kW zu dimmen.", + "IS_ACTIVATE_LABEL": "Die Verdrahtung ist entsprechend der Montageanleitung anzubringen." + } + }, + "LIVE": { + "PULL_TO_REFRESH": "Hier ziehen, um zu aktualisieren" }, "LOADING_SCREEN": { "LOADING": "Wird geladen", @@ -1006,4 +1040,4 @@ "APP": { "FUNCTIONALITY_TEMPORARILY_NOT_AVAILABLE": "Diese Funktion ist vorübergehend in der App nicht verfügbar, verwenden Sie bitte dafür die Web-App." } -} +} \ No newline at end of file diff --git a/ui/src/assets/i18n/en.json b/ui/src/assets/i18n/en.json index b3f33f87876..ad5ccfb119f 100644 --- a/ui/src/assets/i18n/en.json +++ b/ui/src/assets/i18n/en.json @@ -201,7 +201,9 @@ "name": "Force charging", "shortName": "Manually" }, - "Uncontrollable": "This charging station can not be controlled." + "Uncontrollable": "This charging station can not be controlled.", + "HYSTERESIS": "Minimum switching time of the charging station active", + "HYSTERESIS_INFO": "For preventing charging interruptions due to frequent starting/pausing of the charing process, the charging mode currently selected will continue for the next few minutes." }, "Heatingelement": { "activeForced": "Active (Minimum runtime)", @@ -236,14 +238,14 @@ "CONTROL_MODE_DESCRIPTION": { "CHARGE_CONSUMPTION": "" }, - "CHARGE_FROM_GRID_ACTIVATE": "Activate charge from the grid (BETA test)", + "CHARGE_FROM_GRID_ACTIVATE": "Activate charge from the grid", "PRICE": "Current price", "STATE": { "DELAY_DISCHARGE": "Delayed discharge", "BALANCING": "Self-Consumption optmization", "CHARGE_GRID": "Charge from grid allowed" }, - "CHART_TITLE": "Planned Schedule (BETA test)", + "CHART_TITLE": "Planned Schedule", "CHART_WARNING_NOTE": "The graphic shows the past three hours as well as the future planned operating mode for the period for which the dynamic grid purchase prices are available. Please note that the planned schedule is subject to continuous recalculation and may change throughout the day.", "POWER_SOC_CHART_TITLE": "Forecasts (Only for Admins)" }, @@ -253,7 +255,14 @@ }, "OVERALL_SYSTEM": "Overall system" }, - "FIX_DIGITAL_OUTPUT": "Digital Outputcontroller" + "FIX_DIGITAL_OUTPUT": "Digital Outputcontroller", + "SCHEDULE": { + "CHART_TITLE_ADMIN": "Planned Schedule (Only for Admins)" + }, + "STORAGE": { + "GRID_CHARGE": "Activate maintenance of the emergency power reserve from the grid", + "GRID_CHARGE_WARNING": "The storage system can actively recharge from the grid to maintain the set emergency power reserve in order to compensate for losses due to internal self-consumption in the event of a prolonged absence of generation.

    ATTENTION: Actively charging the battery from grid beyond the technically necessary maintenance charging, requires an appropriate registration of the storage system. If in doubt, please verify with your installer. By pressing the blue \"Save\" button you confirm that this verification has been done.

    Be aware: precondition for German \"Gesetz für den Ausbau erneuerbarer Energien (EEG)\" grant is, that only energy from renewable sources and/or pit gas is stored in the battery. By activating this function, the battery is charged with energy from grid. This energy is, depending on the individual power-supply contract, probably made from fossil sources and/or nuclear power plants. Because of this we can not guarantee, that the battery is only charged with energy from renewable sources and/or pit gas." + } }, "RETROFITTING": { "OPTIMAL": "Capacity expansion", @@ -311,10 +320,12 @@ "oct": "Oxt", "nov": "Nov", "dec": "Dec", - "activeDuration": "active duration", + "activeDuration": "Active duration", + "ACTIVE_DURATION_WITH_LEVEL": "Active duration level {{level}}", "CURRENT_AND_VOLTAGE": "Current & Voltage", "CURRENT": "Current", - "VOLTAGE": "Voltage" + "VOLTAGE": "Voltage", + "PHASE_ACCURATE": "Phasengenau" }, "Config": { "Index": { @@ -398,7 +409,8 @@ "buyApp": "Buy licence key", "modifyApp": "Modify app", "createApp": "Install app", - "deleteApp": "Delete app", + "deleteApp": "Uninstall app", + "DELETE_CONFIRM": "uninstall", "updateApp": "Update app", "errorInstallable": "Installation errors", "errorCompatible": "Compatibility errors", @@ -428,7 +440,9 @@ "successUpdate": "Successfully updated App", "failUpdate": "Error updating App '{{error}}'", "successDelete": "Successfully deleted App", - "failDelete": "Error deleting App '{{error}}'" + "failDelete": "Error deleting App '{{error}}'", + "DELETE_CONFIRM_HEADLINE": "Are you sure you wish to uninstall the app?", + "DELETE_CONFIRM_DESCRIPTION": "Uninstalling the app will remove all of its components and delete all settings made by it." } }, "Service": { @@ -467,8 +481,7 @@ "capacity": "Capacity", "changeAccepted": "Change accepted", "changeFailed": "Change failed", - "chargeDischarge": "Charge/Discharge power", - "chargePower": "Charge power", + "chargeDischarge": "Charge/Discharge", "componentCount": "Number of components", "componentInactive": "Component is not active!", "connectionLost": "Connection lost. Trying to reconnect.", @@ -480,7 +493,6 @@ "digitalInputs": "Digital Inputs", "numberOfComponents": "Number of Components", "directConsumption": "Direct consumption", - "dischargePower": "Discharge power", "fault": "Fault", "FAILED": "failed", "WILL_BE_EXECUTED": "will be executed", @@ -572,7 +584,9 @@ "MINUTES": "Minutes", "DAY": "Day", "DAYS": "Days" - } + }, + "CHARGE": "Charge", + "DISCHARGE": "Discharge" }, "Index": { "allConnected": "All connections established.", @@ -591,6 +605,17 @@ "VISIBLE_HERE_AFTER_INSTALLATION": "After your {{value}} has been commissioned by an installer, you will see it at this point.", "FIRST_SETUP_PROTOCOL": "Initial commissioning" }, + "OFFLINE": { + "EDGE": { + "LOCAL_ACCESS_CURRENTLY_NOT_POSSIBLE": "Local access currently not possible", + "DESCRIPTION": "If the {{edgeShortName}} has been restarted, please wait a moment until the restart has been completed." + }, + "GENERAL": { + "AVAILABILITY": "Online access to the energy storage system is currently not available.
    The data is stored locally and will not be lost.", + "MORE_INFO": "More Info", + "EMS_OFFLINE": "{{edgeShortName}} is offline" + } + }, "INSTALLATION": { "ATTENTION_MESSAGE": "Note that the selection cannot be undone afterwards.", "BACK": "Back", @@ -860,6 +885,9 @@ }, "CONFIGURATION_MPPT_SELECTION_NOTE": "Additional generators can then be configured via the App Center." }, + "LIVE": { + "PULL_TO_REFRESH": "Pull this banner to refresh" + }, "LOADING_SCREEN": { "LOADING": "Loading", "SERVER_NOT_ACCESSIBLE": "Server not accessible", @@ -1009,4 +1037,4 @@ "APP": { "FUNCTIONALITY_TEMPORARILY_NOT_AVAILABLE": "This function is temporarily not available in the app, please use the web app instead." } -} +} \ No newline at end of file diff --git a/ui/src/assets/i18n/es.json b/ui/src/assets/i18n/es.json index 251dd41029d..68f7cbe885b 100644 --- a/ui/src/assets/i18n/es.json +++ b/ui/src/assets/i18n/es.json @@ -9,7 +9,6 @@ "changeAccepted": "Cambio aceptado", "changeFailed": "Cambio fallido", "chargeDischarge": "Débito/Descarga", - "chargePower": "Carga", "componentCount": "Numero de componentes", "componentInactive": "El componente está inactivo!", "connectionLost": "Conexión perdida. Intentando reconectar.", @@ -20,7 +19,6 @@ "digitalInputs": "Entradas digitales", "numberOfComponents": "número de componentes", "directConsumption": "Consumo directo", - "dischargePower": "Descarga", "fault": "Error", "grid": "Red", "gridBuy": "Relación", @@ -91,7 +89,9 @@ "MINUTES": "Minutos", "DAY": "Día", "DAYS": "Días" - } + }, + "DISCHARGE": "Descargar", + "CHARGE": "Cargando" }, "Menu": { "accessLevel": "Nivel de acceso", diff --git a/ui/src/assets/i18n/fr.json b/ui/src/assets/i18n/fr.json index 2ead4d8e8c0..2d3c86ae82d 100644 --- a/ui/src/assets/i18n/fr.json +++ b/ui/src/assets/i18n/fr.json @@ -11,7 +11,6 @@ "changeAccepted": "Changement accepté", "changeFailed": "Changement échoué", "chargeDischarge": "Puissance de Charge/Décharge", - "chargePower": "Puissance de charge", "componentInactive": "Component is not active!", "connectionLost": "Connection lost. Trying to reconnect.", "consumption": "Consommation", @@ -19,7 +18,6 @@ "currentName": "current name", "currentValue": "current value", "dateFormat": "yyyy-MM-dd", - "dischargePower": "Puissance de décharge", "digitalInputs": "Entrées numériques", "fault": "Fault", "grid": "Réseau", @@ -93,7 +91,9 @@ "MINUTES": "Minutes", "DAY": "Journée", "DAYS": "Journées" - } + }, + "DISCHARGE": "Décharge", + "CHARGE": "Charge" }, "Menu": { "accessLevel": "Niveau d'accès", diff --git a/ui/src/assets/i18n/ja.json b/ui/src/assets/i18n/ja.json index 06a7294eb4f..5ec3a322dd5 100644 --- a/ui/src/assets/i18n/ja.json +++ b/ui/src/assets/i18n/ja.json @@ -410,7 +410,6 @@ "changeAccepted": "変更が承認されました", "changeFailed": "変更が失敗しました", "chargeDischarge": "充電・放電パワー", - "chargePower": "充電", "componentCount": "部品数", "componentInactive": "無効化中", "connectionLost": "接続が切断されました。再接続を試みます。", @@ -422,7 +421,6 @@ "digitalInputs": "デジタル入力", "numberOfComponents": "部品数", "directConsumption": "直接消費", - "dischargePower": "放電", "fault": "障害", "grid": "グリッド", "gridBuy": "買電", @@ -503,7 +501,9 @@ "yes": "はい", "no": "いいえ", "value": "値", - "SUM_STATE": "システム状態" + "SUM_STATE": "システム状態", + "DISCHARGE": "放電", + "CHARGE": "充電" }, "Index": { "allConnected": "すべての接続が確立されています。", diff --git a/ui/src/assets/i18n/nl.json b/ui/src/assets/i18n/nl.json index 77d013c0482..8104de8fd3d 100644 --- a/ui/src/assets/i18n/nl.json +++ b/ui/src/assets/i18n/nl.json @@ -9,7 +9,6 @@ "changeAccepted": "Wijziging geaccepteerd", "changeFailed": "Wijziging mislukt", "chargeDischarge": "Debet/ontlaad", - "chargePower": "Laad vermogen", "componentCount": "Aantal componenten", "componentInactive": "Component is inactief!", "connectionLost": "Verbinding verbroken. Probeer opnieuw verbinding te maken.", @@ -20,7 +19,6 @@ "digitalInputs": "Digitale Ingangen", "numberOfComponents": "aantal componenten", "directConsumption": "Directe consumptie", - "dischargePower": "Ontlaad vermogen", "fault": "Fout", "grid": "Net", "gridBuy": "Netafname", @@ -88,7 +86,9 @@ "MINUTES": "Minuten", "DAY": "Dag", "DAYS": "Dagen" - } + }, + "DISCHARGE": "Afvoer", + "CHARGE": "Laden" }, "Menu": { "accessLevel": "Toegangsniveau", diff --git a/ui/src/assets/img/fenecon-logo-dark.png b/ui/src/assets/img/fenecon-logo-dark.png new file mode 100644 index 00000000000..aa662b4593a Binary files /dev/null and b/ui/src/assets/img/fenecon-logo-dark.png differ diff --git a/ui/src/assets/img/fenecon-logo-light.png b/ui/src/assets/img/fenecon-logo-light.png new file mode 100644 index 00000000000..18748c78131 Binary files /dev/null and b/ui/src/assets/img/fenecon-logo-light.png differ diff --git a/ui/src/environments/index.scss b/ui/src/environments/index.scss new file mode 100644 index 00000000000..77d40a07e3d --- /dev/null +++ b/ui/src/environments/index.scss @@ -0,0 +1,261 @@ +@use "variables" as *; + +:root { + + /** font **/ + --ion-font-family: "Roboto", "Helvetica Neue", sans-serif; + + /** Box edges shadow **/ + --ion-color-shadow: #47577a; + /** Custom Colors **/ + --ion-color-normal: #7692a4; + --ion-color-normal: #7692a4; //normal / general icon color + --ion-background-color: #fff; + + /** environmental **/ + --ion-color-environmental: #8abd46; + --ion-color-environmental-rgb: 138, 189, 70; + --ion-color-environmental-contrast: #fff; + --ion-color-environmental-contrast-rgb: 255, 255, 255; + --ion-color-environmental-shade: #527029; + --ion-color-environmental-tint: #bbff5d; + + /** production **/ + --ion-color-production: #36aed1; + --ion-color-production-rgb: 54, 174, 209; + --ion-color-production-contrast: #fff; + --ion-color-production-contrast-rgb: 255, 255, 255; + --ion-color-production-shade: #226e84; + --ion-color-production-tint: #41d4ff; + + .ion-color-environmental { + --ion-color-base: var(--ion-color-environmental); + --ion-color-base-rgb: var(--ion-color-environmental-rgb); + --ion-color-contrast: var(--ion-color-environmental-contrast); + --ion-color-contrast-rgb: var(--ion-color-environmental-contrast-rgb); + --ion-color-shade: var(--ion-color-environmental-shade); + --ion-color-tint: var(--ion-color-environmental-tint); + } + + + .ion-color-production { + --ion-color-base: var(--ion-color-production); + --ion-color-base-rgb: var(--ion-color-production-rgb); + --ion-color-contrast: var(--ion-color-production-contrast); + --ion-color-contrast-rgb: var(--ion-color-production-contrast-rgb); + --ion-color-shade: var(--ion-color-production-shade); + --ion-color-tint: var(--ion-color-production-tint); + } + + .ion-color-normal { + --ion-color-base: var(--ion-color-normal); + } + + .ion-color-white { + --ion-color-base: var(--ion-color-white); + } + + .ion-color-background { + --ion-color-base: var(--ion-item-background); + } + + --ion-color-pickdate-toolbar: var(--ion-menu-color); + --ion-color-header-contrast: #fff; + --ion-color-active-percentagebar: var(--ion-color-primary); +} + +:root[data-theme="light"] { + + /** success **/ + --ion-color-success-contrast-rgb: 255, 255, 255; + --ion-color-success-contrast: #fff; + --ion-color-success-rgb: 16, 220, 96; + --ion-color-success-shade: #0ec254; + --ion-color-success-tint: #28e070; + --ion-color-success: #0ebe54; + + /** warning **/ + --ion-color-warning-contrast-rgb: 0, 0, 0; + --ion-color-warning-contrast: #000; + --ion-color-warning-rgb: 255, 206, 0; + --ion-color-warning-shade: #e0b500; + --ion-color-warning-tint: #ffd31a; + --ion-color-warning: #fdc507; + + /** danger **/ + --ion-color-danger-contrast-rgb: 255, 255, 255; + --ion-color-danger-contrast: #fff; + --ion-color-danger-rgb: 245, 61, 61; + --ion-color-danger-shade: #d83636; + --ion-color-danger-tint: #f65050; + --ion-color-danger: #f53d3d; + + /** dark **/ + --ion-color-dark-contrast-rgb: 255, 255, 255; + --ion-color-dark-contrast: #fff; + --ion-color-dark-rgb: 34, 34, 34; + --ion-color-dark-shade: #1e1e1e; + --ion-color-dark-tint: #383838; + --ion-color-dark: #222; + + /** medium **/ + --ion-color-medium-contrast-rgb: 0, 0, 0; + --ion-color-medium-contrast: #575757; + --ion-color-medium-rgb: 152, 154, 162; + --ion-color-medium-shade: #86888f; + --ion-color-medium-soft: #d8d8d8; + --ion-color-medium-tint: #a2a4ab; + --ion-color-medium: #aeb0b9; + + /** light **/ + --ion-color-light-contrast-rgb: 0, 0, 0; + --ion-color-light-contrast: #000; + --ion-color-light-rgb: 244, 244, 244; + --ion-color-light-shade: #d7d7d7; + --ion-color-light-tint: #f5f5f5; + --ion-color-light: #f4f4f4; + + /** text **/ + --ion-color-text-channels: #4d4d4d; + --ion-color-text-menu-dark: #656565; + --ion-color-text-menu-light: #91939a; + --ion-color-text-menu: #01000c; + --ion-color-text-percentage-bar: #000000; + --ion-color-text: #444444; + --ion-text-color-reverse: #000000; // lightMode > Black && darkMode > White + --ion-text-color-shade: #a0a0a0; + --ion-text-color: #444444; + + /** item **/ + --ion-item-background: var(--ion-background-color); + + --ion-color-toolbar-primary: var(--ion-color-primary); + --ion-color-toolbar-segment: #fff; + --ion-menu-color: #ebf4ff; + --ion-segment-button-color: var(--ion-color-white); + --ion-title-color: white; + --ion-segment-button-color: #fff; + --ion-color-white: #000; + + /** chart **/ + --ion-color-chart-primary: #444444; + --ion-color-chart-buy-tint: rgba(0, 0, 0, 0.2); + --ion-color-chart-buy: rgb(0, 0, 0); + --ion-color-chart-primary: #444444; + + /** background **/ + --ion-background-percentagebar-color: #f4f4f4; + --ion-background-color-card-header: #f4f4f4; + --ion-background-color: #fff; + --ion-background-percentagebar-color: #f4f4f4; + --ion-color-background-menu: #fff; + + --ion-color-emergencyreserve-primary: rgba(1, 1, 1, 1); + --ion-color-emergencyreserve-rgba: rgba(1, 1, 1, 0); + --ion-color-grey-primary: rgba(128, 128, 128, 1); + --ion-color-grey-rgba: rgba(128, 128, 128, 0.05); + --ion-card-content-color: #737373; // #f4f4f4 in case the percentage-style class is applied. + --ion-color-charge-primary: rgba(0, 223, 0, 1); // Base // + --ion-color-charge-rgba: rgba(0, 223, 0, 0.05); // semi-transparent // + --ion-color-custom-links: var(--ion-color-primary); + --ion-color-toggle-background: var(--ion-color-primary-tint); +} + + +:root[data-theme="dark"] { + + /** success **/ + --ion-color-success: #20bf6b; + --ion-color-success-rgb: 32, 191, 107; + --ion-color-success-contrast: #03130b; + --ion-color-success-contrast-rgb: 3, 19, 11; + --ion-color-success-shade: #4dcc89; + --ion-color-success-tint: #1a9956; + + /** warning **/ + --ion-color-warning: #f7b731; + --ion-color-warning-rgb: 247, 183, 49; + --ion-color-warning-contrast: #191205; + --ion-color-warning-contrast-rgb: 25, 18, 5; + --ion-color-warning-shade: #f9c55a; + --ion-color-warning-tint: #c69227; + + /** danger **/ + --ion-color-danger: #eb3b5a; + --ion-color-danger-rgb: 235, 59, 90; + --ion-color-danger-contrast: #170609; + --ion-color-danger-contrast-rgb: 23, 6, 9; + --ion-color-danger-shade: #ef627b; + --ion-color-danger-tint: #bc2f48; + + /** dark **/ + --ion-color-dark: #f4f5f8; + --ion-color-dark-rgb: 244, 245, 248; + --ion-color-dark-contrast: #000000; + --ion-color-dark-contrast-rgb: 0, 0, 0; + --ion-color-dark-shade: #f5f6f9; + --ion-color-dark-tint: #d7d8da; + + /** medium **/ + --ion-color-medium: #989aa2; + --ion-color-medium-rgb: 152, 154, 162; + --ion-color-medium-contrast: hsla(0, 0%, 0%, 0.801); + --ion-color-medium-contrast-rgb: 0, 0, 0; + --ion-color-medium-shade: #a2a4ab; + --ion-color-medium-tint: #86888f; + + /** light **/ + --ion-color-light: var(--ion-color-toolbar-primary); // Background color for header of each widget. + --ion-color-light-rgb: 50, 63, 75; + --ion-color-light-contrast: #F5F7FA; // Font color in the title of all widgets. + --ion-color-light-contrast-rgb: 255, 255, 255; + --ion-color-light-shade: #383a3e; + --ion-color-light-tint: #1e2023; + + /** Custom Colors **/ + --ion-item-color: white; // this sets the color for text in History, Menu bar and Menu Page sections // + --ion-item-background: #4f5f75; // background color for widgets. + --ion-title-color: #dff0ff; // Color of the Page Title. + --ion-menu-color: #ebf4ff; + --ion-color-text-menu: #e2e2e2; + --ion-color-background-menu: #1d2c42; + + /** Charts Colors **/ + --ion-color-chart-primary: #ffffff; + + /** Text Color **/ + --ion-text-color: #d3e3e3; + --ion-text-color-rgb: rgb(211, 227, 227); + --ion-text-color-dark: #000; + --ion-text-color-shade: #a0a0a0; + --ion-color-text: #c8c8c8; + --ion-color-text-percentage-bar: white; + + /** Percentage bar Color **/ + --ion-background-percentagebar-color: white; + --ion-segment-button-color: var(--ion-color-primary); + + /** toolbar **/ + --ion-color-toolbar-primary: #353a50; + --ion-color-toolbar-segment: var(--ion-color-primary); + + /** Chart **/ + --ion-color-chart-primary: #c9c9c9; + --ion-color-chart-xAxis-ticks: #c8c8c8; + --ion-color-chart-xLines: #545454; + + --ion-item-background: #2b2f42; + --ion-color-emergencyreserve-primary: rgb(160, 160, 160); // Base // + --ion-color-emergencyreserve-rgba: rgba(160, 160, 160, 0); // Transparent // + --ion-color-charge-primary: rgba(0, 223, 0, 1); + --ion-color-charge-rgba: rgba(0, 223, 0, 0.05); + --ion-color-grey-primary: rgba(128, 128, 128, 1); + --ion-color-grey-rgba: rgba(128, 128, 128, 0.05); // semi-transparent // + --ion-background-percentagebar-color: #384352; + --ion-color-white: #fff; + + /** Card **/ + --ion-card-content: #cccccc; + --ion-card-content-color: #cccccc; + --ion-color-toggle-background: #9c9c9c; +} diff --git a/ui/src/environments/index.ts b/ui/src/environments/index.ts index e10181d51ce..a5790f059f5 100644 --- a/ui/src/environments/index.ts +++ b/ui/src/environments/index.ts @@ -96,3 +96,10 @@ export interface Environment { }, readonly PRODUCT_TYPES: (translate: TranslateService) => Filter | null } + +/* + * Return the proper websocket scheme (WS or WSS) depending on whether the page is accessed via HTTP or HTTPS. + */ +export function getWebsocketScheme(): string { + return window.location.protocol === "https:" ? "wss://" : "ws://"; +} diff --git a/ui/src/global-ion-custom.scss b/ui/src/global-ion-custom.scss new file mode 100644 index 00000000000..c5157763744 --- /dev/null +++ b/ui/src/global-ion-custom.scss @@ -0,0 +1,47 @@ +@use "variables"; + +.ion-font-size-medium { + font-size: medium !important; +} + +.ion-font-size-smaller { + font-size: small !important; +} + +.ion-font-weight-bolder { + font-weight: bolder; +} + +.ion-link { + color: var(--ion-color-primary); +} + +.ion-full-width { + width: 100%; +} + +.ion-text-align-center { + text-align: center; +} + +.ion-col-with-icon { + display: flex; + justify-content: center; +} + +.ion-item-min-content-height { + height: min-content; +} + +.ion-toolbar-button::part(native) { + --background: var(--ion-item-background) !important; +} + +/* Functions */ +@function tint($color, $percentage) { + @return mix(white, $color, $percentage); +} + +@function shade($color, $percentage) { + @return mix(black, $color, $percentage); +} diff --git a/ui/src/global.scss b/ui/src/global.scss index 59a24c31264..9e8063f82d6 100644 --- a/ui/src/global.scss +++ b/ui/src/global.scss @@ -10,21 +10,31 @@ */ /* Core CSS required for Ionic components to work properly */ -@import "~@ionic/angular/css/core.css"; -@import "~@ionic/angular/css/display.css"; -@import "~@ionic/angular/css/flex-utils.css"; -@import "~@ionic/angular/css/float-elements.css"; +@use "node_modules/@ionic/angular/css/core.css" as *; +@use "node_modules/@ionic/angular/css/display.css" as *; +@use "node_modules/@ionic/angular/css/flex-utils.css" as *; +@use "node_modules/@ionic/angular/css/float-elements.css" as *; /* Basic CSS for apps built with Ionic */ -@import "~@ionic/angular/css/normalize.css"; +@use "node_modules/@ionic/angular/css/normalize.css" as *; +@use "node_modules/@ionic/angular/css/structure.css" as *; +@use "node_modules/@ionic/angular/css/typography.css" as *; + /* Optional CSS utils that can be commented out */ -@import "~@ionic/angular/css/padding.css"; -@import "~@ionic/angular/css/structure.css"; -@import "~@ionic/angular/css/text-alignment.css"; -@import "~@ionic/angular/css/text-transformation.css"; -@import "~@ionic/angular/css/typography.css"; +@use "node_modules/@ionic/angular/css/padding.css" as *; +@use "node_modules/@ionic/angular/css/structure.css" as *; +@use "node_modules/@ionic/angular/css/text-alignment.css" as *; +@use "node_modules/@ionic/angular/css/text-transformation.css" as *; +@use "node_modules/@ionic/angular/css/typography.css" as *; /* ngx-spinner */ -@import "node_modules/ngx-spinner/animations/ball-clip-rotate-multiple.css"; -@import "variables"; +@use "node_modules/ngx-spinner/animations/ball-clip-rotate-multiple.css" as *; +@use "./environments/index.scss" as *; +@use "variables" as *; +@use "./global-ion-custom.scss" as *; + +ion-alert { + color: green; + --color: red; +} /* Live- and HistoryComponent*/ ion-refresher-content { @@ -33,12 +43,12 @@ ion-refresher-content { } } -.custom-ion-popover { - - white-space: inherit; +ion-card-content { + color: var(--ion-card-content-color); +} - // Overwrites default width of popover - --min-width: min-content; +.footer-color { + background-color: var(--ion-color-toolbar-secondary) !important; } .hide-empty-list-header { @@ -54,6 +64,13 @@ ion-refresher-content { } +.disabled { + color: gray; + pointer-events: none; + opacity: 0.5; + /* Makes it semi-transparent */ +} + formly-wrapper-ion-form-field, formly-input-serial-number, formly-field-ion-radio { @@ -81,6 +98,81 @@ formly-field-ion-radio { } } +.normal-icon { + color: var(--ion-color-primary-shade) !important; + + ion-icon { + color: var(--ion-color-primary-shade) !important; + } +} + +.percentage-style { + fill: var(--ion-color-text-percentage-bar); + text-shadow: var(--ion-text-shadow); +} + +.ion-card-style { + ion-item { + --background: var(--ion-color-primary); + + ion-icon { + color: var(--ion-color-primary-contrast); + } + } +} + +.custom-channels-text { + color: var(--ion-color-text-channels) !important; +} + +//* Color of Menu button *// +.custom-menu-button { + color: var(--ion-menu-color); +} + +.custom-theme-text { + color: var(--ion-color-text-menu) !important; +} + +.custom-menu-text-dark { + color: var(--ion-color-text-menu-dark) !important; +} + +.item-devider { + border-color: white; +} + +.custom-ion-popover { + + --border-width: 0; + --background: var(--ion-background-color); + + // Floating label + &::part(label) { + color: var(--ion-color-primary) !important; + } + + // Overwrites default width of popover + white-space: inherit; + --min-width: min-content; + + [aria-checked="true"] { + .alert-radio-label { + color: var(--ion-color-text) !important; + } + } + + .alert-radio-label { + color: var(--ion-color-text); + } + + // Color of the selected Language from the list. + .item-radio-checked { + --background: var(--ion-color-primary-tint); + --background-opacity: 0.9; + } +} + .line-break { formly-wrapper-ion-form-field>ion-item>ion-label>span>small, @@ -98,7 +190,7 @@ formly-field-ion-radio { .command-textbox { font-family: monospace; - color: rgb(52, 52, 52); + color: var(--ion-color-text); } .margin-top { @@ -108,7 +200,7 @@ formly-field-ion-radio { } .active { - color: $primary-color; + color: var(--ion-color-dark); } .primary-color { @@ -197,16 +289,41 @@ table { } .underline { - border-bottom: 1pt solid lightgray; + border-bottom: 1pt solid var(--ion-color-medium); } .gray_color { - color: gray; + color: var(--ion-color-medium); } button.activated { - color: black; - background-color: lightgreen; + color: var(--ion-color-dark); + background-color: var(--ion-color-success); +} + +svg::part('container') { + --border-color: white; + --checkmark-color: red; +} + +ion-item, +ion-checkbox { + .checkbox-icon { + display: none; + } +} + +#theme-switcher { + --transition: checkmark-color 5s ease-in-out; + + :host { + --checkmark-color: red; + } + + ion-checkbox::part('mark') { + --border-color: white; + --checkmark-color: red; + } } #toast-container { @@ -268,6 +385,13 @@ formly-input-section { :root { + --ion-color-primary-item: shade($primary-color, #000); + + ion-segment { + --color: var(--ion-text-color); + color: var(--ion-text-color); + } + // Used in IBN, dynamicFeedInLimitation formly-field-ion-select { ion-select::part(text) { @@ -299,6 +423,22 @@ formly-input-section { ion-select { font-family: $font-family; font-size: 14px; + color: var(--ion-text-color); + } + + ion-alert { + color: var(--ion-text-color) !important; + --color: red; + + [aria-checked="true"] { + .alert-radio-label { + color: var(--ion-color-primary) !important; + } + } + + .alert-radio-label { + color: var(--ion-text-color); + } } .alert-wrapper { @@ -312,7 +452,8 @@ formly-input-section { } ion-toggle { - --background-checked: var(--ion-color-primary-tint); + --background-checked: var(--ion-color-toggle-background); + --background: #9C9C9C; } ion-spinner { @@ -403,4 +544,22 @@ ion-modal.full-width { --storage-segment-2: block; --storage-segment-3: block; --storage-segment-4: block; -} \ No newline at end of file +} + +.card-with-primary-border { + border: 2px solid $primary-color; +} + +ion-avatar.iconify { + margin-inline-end: 32px; + margin-top: 12px; + margin-bottom: 12px; + --border-radius: 0; + max-height: 24px; + max-width: 24px; +} + +ion-avatar.iconify.icon-large { + max-height: 32px; + max-width: 32px; +} diff --git a/ui/src/index.html b/ui/src/index.html index cfd90f2e9be..cd523172f27 100644 --- a/ui/src/index.html +++ b/ui/src/index.html @@ -1,5 +1,5 @@ - + diff --git a/ui/src/themes/openems/environments/backend-dev.ts b/ui/src/themes/openems/environments/backend-dev.ts index a6726d2b8c5..962ce0115cd 100644 --- a/ui/src/themes/openems/environments/backend-dev.ts +++ b/ui/src/themes/openems/environments/backend-dev.ts @@ -1,11 +1,11 @@ -import { Environment } from "src/environments"; +import { Environment , getWebsocketScheme } from "src/environments"; import { theme } from "./theme"; export const environment: Environment = { ...theme, ...{ backend: "OpenEMS Backend", - url: "ws://" + location.hostname + ":8082", + url: getWebsocketScheme() + location.hostname + ":8082", production: false, debugMode: true, diff --git a/ui/src/themes/openems/environments/backend-prod.ts b/ui/src/themes/openems/environments/backend-prod.ts index ebfe2b1ecf2..70ec8f4e578 100644 --- a/ui/src/themes/openems/environments/backend-prod.ts +++ b/ui/src/themes/openems/environments/backend-prod.ts @@ -1,11 +1,11 @@ -import { Environment } from "src/environments"; +import { Environment , getWebsocketScheme } from "src/environments"; import { theme } from "./theme"; export const environment: Environment = { ...theme, ...{ backend: "OpenEMS Backend", - url: "ws://" + location.hostname + ":8082", + url: getWebsocketScheme() + location.hostname + ":8082", production: true, debugMode: false, diff --git a/ui/src/themes/openems/environments/edge-dev.ts b/ui/src/themes/openems/environments/edge-dev.ts index b36d202ef27..30ee3318fef 100644 --- a/ui/src/themes/openems/environments/edge-dev.ts +++ b/ui/src/themes/openems/environments/edge-dev.ts @@ -1,11 +1,11 @@ -import { Environment } from "src/environments"; +import { Environment , getWebsocketScheme } from "src/environments"; import { theme } from "./theme"; export const environment: Environment = { ...theme, ...{ backend: "OpenEMS Edge", - url: "ws://" + location.hostname + ":8085", + url: getWebsocketScheme() + location.hostname + ":8085", production: false, debugMode: true, diff --git a/ui/src/themes/openems/environments/edge-prod.ts b/ui/src/themes/openems/environments/edge-prod.ts index 183a4f0fef9..32dda90b29d 100644 --- a/ui/src/themes/openems/environments/edge-prod.ts +++ b/ui/src/themes/openems/environments/edge-prod.ts @@ -1,11 +1,11 @@ -import { Environment } from "src/environments"; +import { Environment , getWebsocketScheme } from "src/environments"; import { theme } from "./theme"; export const environment: Environment = { ...theme, ...{ backend: "OpenEMS Edge", - url: "ws://" + location.hostname + ":8075", + url: getWebsocketScheme() + location.hostname + ":8075", production: true, debugMode: false, diff --git a/ui/src/themes/openems/scss/variables.scss b/ui/src/themes/openems/scss/variables.scss index abf1541b28c..f9052326054 100644 --- a/ui/src/themes/openems/scss/variables.scss +++ b/ui/src/themes/openems/scss/variables.scss @@ -1,14 +1,14 @@ -// Ionic Variables and Theming. For more info, please see: -// http://ionicframework.com/docs/theming/ - -/** Ionic CSS Variables **/ $primary-color: var(--ion-color-primary); $primary-color-contrast: var(--ion-color-primary-contrast); $secondary-color: var(--ion-color-dark); $secondary-color-contrast: var(--ion-color-secondary-contrast); $font-family: var(--ion-font-family); -:root { +#theme-switcher { + transform: --default-transition; +} + +:root[data-theme="light"] { /** primary **/ --ion-color-primary: #e8ae6a; --ion-color-primary-rgb: 232, 174, 106; @@ -34,95 +34,8 @@ $font-family: var(--ion-font-family); --ion-color-tertiary-shade: #c6bbac; --ion-color-tertiary-tint: #e4d9ca; - /** success **/ - --ion-color-success: #2dd36f; - --ion-color-success-rgb: 45, 211, 111; - --ion-color-success-contrast: #000000; - --ion-color-success-contrast-rgb: 0, 0, 0; - --ion-color-success-shade: #28ba62; - --ion-color-success-tint: #42d77d; - - /** warning **/ - --ion-color-warning: #ffc409; - --ion-color-warning-rgb: 255, 196, 9; - --ion-color-warning-contrast: #000000; - --ion-color-warning-contrast-rgb: 0, 0, 0; - --ion-color-warning-shade: #e0ac08; - --ion-color-warning-tint: #ffca22; - - /** danger **/ - --ion-color-danger: #eb445a; - --ion-color-danger-rgb: 235, 68, 90; - --ion-color-danger-contrast: #ffffff; - --ion-color-danger-contrast-rgb: 255, 255, 255; - --ion-color-danger-shade: #cf3c4f; - --ion-color-danger-tint: #ed576b; - - /** light **/ - --ion-color-light: #f4f4f4; - --ion-color-light-rgb: 244, 244, 244; - --ion-color-light-contrast: #000; - --ion-color-light-contrast-rgb: 0, 0, 0; - --ion-color-light-shade: #d7d7d7; - --ion-color-light-tint: #f5f5f5; - - /** medium **/ - --ion-color-medium: #92949c; - --ion-color-medium-rgb: 146, 148, 156; - --ion-color-medium-contrast: #000000; - --ion-color-medium-contrast-rgb: 0, 0, 0; - --ion-color-medium-shade: #808289; - --ion-color-medium-tint: #9d9fa6; - - /** dark **/ - --ion-color-dark: #222; - --ion-color-dark-rgb: 34, 34, 34; - --ion-color-dark-contrast: #fff; - --ion-color-dark-contrast-rgb: 255, 255, 255; - --ion-color-dark-shade: #1e1e1e; - --ion-color-dark-tint: #383838; - - /** font **/ - --ion-font-family: "Roboto", "Helvetica Neue", sans-serif; - --ion-color-background: #fff; - --ion-color-text: #444444; -} - -/** Custom Colors **/ -:root { - --ion-color-environmental: #8abd46; - --ion-color-environmental-rgb: 138, 189, 70; - --ion-color-environmental-contrast: #fff; - --ion-color-environmental-contrast-rgb: 255, 255, 255; - --ion-color-environmental-shade: #527029; - --ion-color-environmental-tint: #bbff5d; - - .ion-color-environmental { - --ion-color-base: var(--ion-color-environmental); - --ion-color-base-rgb: var(--ion-color-environmental-rgb); - --ion-color-contrast: var(--ion-color-environmental-contrast); - --ion-color-contrast-rgb: var(--ion-color-environmental-contrast-rgb); - --ion-color-shade: var(--ion-color-environmental-shade); - --ion-color-tint: var(--ion-color-environmental-tint); - } - - --ion-color-production: #36aed1; - --ion-color-production-rgb: 54, - 174, - 209; - --ion-color-production-contrast: #fff; - --ion-color-production-contrast-rgb: 255, - 255, - 255; - --ion-color-production-shade: #226e84; - --ion-color-production-tint: #41d4ff; - - .ion-color-production { - --ion-color-base: var(--ion-color-production); - --ion-color-base-rgb: var(--ion-color-production-rgb); - --ion-color-contrast: var(--ion-color-production-contrast); - --ion-color-contrast-rgb: var(--ion-color-production-contrast-rgb); - --ion-color-shade: var(--ion-color-production-shade); - --ion-color-tint: var(--ion-color-production-tint); - } + --ion-background-color: #fff; + --ion-color-header-contrast: #000; + --ion-menu-color: #000; + --ion-title-color: #000; } diff --git a/ui/tsconfig.json b/ui/tsconfig.json index 64acad31696..65543d6d36a 100644 --- a/ui/tsconfig.json +++ b/ui/tsconfig.json @@ -31,6 +31,6 @@ "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": false, "strictInputAccessModifiers": true, - "strictTemplates": false + "strictTemplates": false, } }
    - {{ name }} + {{ displayName }} + {{ displayValue }}

    * Day ahead prices retrieved from ENTSO-E are usually in EUR and might have to * be converted to the user's currency using the exchange rates provided by - * Exchange Rate API. For more information on the ExchangeRate API, visit: - * https://exchangerate.host/#/docs + * European Central Bank. */ // TODO this should be extracted to a Exchange-Rate API + Provider public class ExchangeRateApi { - private static final String BASE_URL = "http://api.exchangerate.host/live?access_key=%s&source=%s¤cies=%s"; + private static final String ECB_URL = "https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml"; + + // ECB gives exchange rates based on the EUR. + private static final Currency BASE_CURRENCY = Currency.EUR; private static final OkHttpClient client = new OkHttpClient(); /** - * Fetches the exchange rate from exchangerate.host. + * Fetches the exchange rate from ECB API. + * + * @param source the source currency (e.g. EUR) + * @param target the target currency (e.g. SEK) + * @param orElse the default value + * @return the exchange rate. + */ + public static double getExchangeRateOrElse(String source, Currency target, double orElse) { + try { + return getExchangeRate(source, target); + } catch (Exception e) { + e.printStackTrace(); + return orElse; + } + } + + /** + * Fetches the exchange rate from ECB API. * - * @param accessKey personal API access key. - * @param source the source currency (e.g. EUR) - * @param target the target currency (e.g. SEK) + * @param source the source currency (e.g. EUR) + * @param target the target currency (e.g. SEK) * @return the exchange rate. - * @throws IOException on error. - * @throws OpenemsNamedException on error + * @throws IOException on error + * @throws OpenemsNamedException on error + * @throws SAXException on error + * @throws ParserConfigurationException on error */ - public static double getExchangeRate(String accessKey, String source, Currency target) - throws IOException, OpenemsNamedException { + public static double getExchangeRate(String source, Currency target) + throws IOException, OpenemsNamedException, ParserConfigurationException, SAXException { + if (target == Currency.UNDEFINED) { + throw new OpenemsException("Global Currency is UNDEFINED. Please configure it in Core.Meta component"); + } + + if (target.name().equals(source) || target.equals(BASE_CURRENCY)) { + return 1.; // No need to fetch exchange rate from API + } + var request = new Request.Builder() // - .url(String.format(BASE_URL, accessKey, source, target.name())) // + .url(ECB_URL) // .build(); try (var response = client.newCall(request).execute()) { @@ -49,25 +81,34 @@ public static double getExchangeRate(String accessKey, String source, Currency t throw new IOException("Failed to fetch exchange rate. HTTP status code: " + response.code()); } - return parseResponse(response.body().string(), source, target); + return parseResponse(response.body().string(), target); } } /** - * Parses the response string from exchangerate.host. + * Parses the response string from ECB API. * * @param response the response string - * @param source the source currency (e.g. EUR) * @param target the target currency (e.g. SEK) * @return the exchange rate. - * @throws OpenemsNamedException on error. + * @throws IOException on error. + * @throws SAXException on error. + * @throws ParserConfigurationException on error. */ - protected static double parseResponse(String response, String source, Currency target) - throws OpenemsNamedException { - var json = parseToJsonObject(response); - var quotes = getAsJsonObject(json, "quotes"); - var result = getAsDouble(quotes, source + target.name()); - return result; + protected static double parseResponse(String response, Currency target) + throws ParserConfigurationException, SAXException, IOException { + var root = getXmlRootDocument(response); + return stream(root) // + .filter(n -> n.getNodeName() == "Cube") // Filter all elements + .flatMap(XmlUtils::stream) // Stream the child nodes of each + .filter(n -> n.getNodeName().equals("Cube")) // + .flatMap(XmlUtils::stream) // + .filter(n -> n.getNodeName().equals("Cube")) // + .filter(n -> n.getAttributes().getNamedItem("currency").getNodeValue().equals(target.name())) // + .map(n -> n.getAttributes().getNamedItem("rate").getNodeValue()) // + .mapToDouble(Double::parseDouble) // + .findFirst() // + .getAsDouble(); } } diff --git a/io.openems.edge.timeofusetariff.api/test/io/openems/edge/timeofusetariff/api/TimeOfUsePricesTest.java b/io.openems.edge.timeofusetariff.api/test/io/openems/edge/timeofusetariff/api/TimeOfUsePricesTest.java index c105ed48d9a..81771e4ee41 100644 --- a/io.openems.edge.timeofusetariff.api/test/io/openems/edge/timeofusetariff/api/TimeOfUsePricesTest.java +++ b/io.openems.edge.timeofusetariff.api/test/io/openems/edge/timeofusetariff/api/TimeOfUsePricesTest.java @@ -13,6 +13,8 @@ import org.junit.Test; +import com.google.common.collect.ImmutableSortedMap; + public class TimeOfUsePricesTest { private static final ZonedDateTime TIME = ZonedDateTime.of(2024, 1, 2, 3, 4, 5, 6, ZoneId.of("UTC")); @@ -20,57 +22,49 @@ public class TimeOfUsePricesTest { @Test public void testEmpty() { assertEquals(EMPTY_PRICES, TimeOfUsePrices.from(TIME)); - assertEquals(EMPTY_PRICES, TimeOfUsePrices.from(new TreeMap<>())); - assertTrue(EMPTY_PRICES.pricePerQuarter.isEmpty()); + assertEquals(EMPTY_PRICES, TimeOfUsePrices.from(ImmutableSortedMap.of())); assertTrue(EMPTY_PRICES.isEmpty()); - { - var map = new TreeMap(); - map.put(TIME, null); - assertEquals(EMPTY_PRICES, TimeOfUsePrices.from(map)); - } } @Test public void testFromDoubles() { var sut = TimeOfUsePrices.from(TIME, 0.1, 0.2, 0.3, null, 0.5, null); + // Holds 4 values + assertArrayEquals(new Double[] { 0.1, 0.2, 0.3, 0.5 }, sut.asArray()); + } + + @Test(expected = NullPointerException.class) + public void testValidateQuarterKeys() { var base = roundDownToQuarter(TIME); - // Holds 5 keys - assertArrayEquals(new ZonedDateTime[] { // - base, // - base.plusMinutes(15), // - base.plusMinutes(30), // - base.plusMinutes(45), // - base.plusMinutes(60), // - }, sut.pricePerQuarter.keySet().toArray(ZonedDateTime[]::new)); - // Holds 5 values - assertArrayEquals(new Double[] { 0.1, 0.2, 0.3, null, 0.5 }, sut.asArray()); + TimeOfUsePrices.from(ImmutableSortedMap.of(// + base, 0.1, // + base.plusMinutes(1), null, // + base.plusMinutes(15), 0.2, // + base.plusMinutes(30), 0.3, // + base.plusMinutes(60), 0.5, // + base.plusMinutes(75), null)); } @Test public void testFromMap() { var base = roundDownToQuarter(TIME); - - var map = new TreeMap(); - map.put(base, 0.1); - map.put(base.plusMinutes(1), null); - map.put(base.plusMinutes(15), 0.2); - map.put(base.plusMinutes(30), 0.3); - map.put(base.plusMinutes(60), 0.5); - map.put(base.plusMinutes(75), null); + var map = ImmutableSortedMap.of(// + base, 0.1, // + base.plusMinutes(15), 0.2, // + base.plusMinutes(30), 0.3, // + base.plusMinutes(60), 0.5); var sut = TimeOfUsePrices.from(map); - // Holds 5 keys - assertArrayEquals(new ZonedDateTime[] { // - base, // - base.plusMinutes(15), // - base.plusMinutes(30), // - base.plusMinutes(45), // added automatically - base.plusMinutes(60), // - }, sut.pricePerQuarter.keySet().toArray(ZonedDateTime[]::new)); - // Holds 5 values - assertArrayEquals(new Double[] { null, 0.2, 0.3, null, 0.5 }, sut.asArray()); + // Holds 4 native keys + assertEquals(4, sut.asArray().length); + + var x = new TreeMap(); + x.put(base, null); + + // Fills up to 5 keys + assertEquals(5, sut.toMapWithAllQuarters().size()); } @Test @@ -81,8 +75,7 @@ public void testFromOther() { assertEquals(other, TimeOfUsePrices.from(TIME.plusMinutes(5), other)); // submap - assertArrayEquals(new Double[] { 0.2, 0.3, null, 0.5 }, - TimeOfUsePrices.from(TIME.plusMinutes(16), other).asArray()); + assertArrayEquals(new Double[] { 0.2, 0.3, 0.5 }, TimeOfUsePrices.from(TIME.plusMinutes(16), other).asArray()); // empty assertEquals(EMPTY_PRICES, TimeOfUsePrices.from(TIME.plusDays(1), other)); @@ -96,7 +89,15 @@ public void testFromOther() { @Test public void testGetFirst() { assertEquals(0.1, TimeOfUsePrices.from(TIME, 0.1, 0.2, 0.3).getFirst(), 0.001); - assertNull(TimeOfUsePrices.from(TIME, null, 0.2, 0.3).getFirst()); + assertEquals(0.2, TimeOfUsePrices.from(TIME, null, 0.2, 0.3).getFirst(), 0.001); assertNull(TimeOfUsePrices.EMPTY_PRICES.getFirst()); } + + @Test + public void testGet() { + final var sut = TimeOfUsePrices.from(TIME, 0.1, 0.2, 0.3); + var base = roundDownToQuarter(TIME); + assertEquals(0.1, sut.getAt(base), 0.001); + assertEquals(0.1, sut.getAt(base.withZoneSameInstant(ZoneId.of("Europe/Berlin"))), 0.001); + } } diff --git a/io.openems.edge.timeofusetariff.api/test/io/openems/edge/timeofusetariff/api/utils/ExchangeRateApiTest.java b/io.openems.edge.timeofusetariff.api/test/io/openems/edge/timeofusetariff/api/utils/ExchangeRateApiTest.java index 81657b127c0..8c167f54485 100644 --- a/io.openems.edge.timeofusetariff.api/test/io/openems/edge/timeofusetariff/api/utils/ExchangeRateApiTest.java +++ b/io.openems.edge.timeofusetariff.api/test/io/openems/edge/timeofusetariff/api/utils/ExchangeRateApiTest.java @@ -5,40 +5,73 @@ import java.io.IOException; +import javax.xml.parsers.ParserConfigurationException; + import org.junit.Ignore; import org.junit.Test; +import org.xml.sax.SAXException; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.common.oem.DummyOpenemsEdgeOem; import io.openems.edge.common.currency.Currency; public class ExchangeRateApiTest { private static final String RESPONSE = """ - { - "success": true, - "terms": "https://currencylayer.com/terms", - "privacy": "https://currencylayer.com/privacy", - "timestamp": 1699605243, - "source": "EUR", - "quotes": { - "EURSEK": 11.649564 - } - } - """; + + Reference rates + + European Central Bank + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + """; // Remove '@Ignore' tag to test this API call. @Ignore @Test - public void testGetExchangeRate() throws IOException, OpenemsNamedException { - var oem = new DummyOpenemsEdgeOem(); - var rate = getExchangeRate(oem.getExchangeRateAccesskey(), "EUR", Currency.SEK); + public void testGetExchangeRate() + throws IOException, OpenemsNamedException, ParserConfigurationException, SAXException { + var rate = getExchangeRate("EUR", Currency.SEK); System.out.println(rate); } @Test - public void testParseResponse() throws OpenemsNamedException { - var rate = ExchangeRateApi.parseResponse(RESPONSE, "EUR", Currency.SEK); - assertEquals(11.649564, rate, 0.0001); + public void testParseResponse() + throws OpenemsNamedException, ParserConfigurationException, SAXException, IOException { + var rate = ExchangeRateApi.parseResponse(RESPONSE, Currency.SEK); + assertEquals(11.4475, rate, 0.0001); } } diff --git a/io.openems.edge.timeofusetariff.awattar/.classpath b/io.openems.edge.timeofusetariff.awattar/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.timeofusetariff.awattar/.classpath +++ b/io.openems.edge.timeofusetariff.awattar/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.timeofusetariff.awattar/src/io/openems/edge/timeofusetariff/awattar/TimeOfUseTariffAwattarImpl.java b/io.openems.edge.timeofusetariff.awattar/src/io/openems/edge/timeofusetariff/awattar/TimeOfUseTariffAwattarImpl.java index 21a489c625d..e41470ee018 100644 --- a/io.openems.edge.timeofusetariff.awattar/src/io/openems/edge/timeofusetariff/awattar/TimeOfUseTariffAwattarImpl.java +++ b/io.openems.edge.timeofusetariff.awattar/src/io/openems/edge/timeofusetariff/awattar/TimeOfUseTariffAwattarImpl.java @@ -12,7 +12,6 @@ import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; -import java.util.TreeMap; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @@ -26,6 +25,8 @@ import org.osgi.service.component.annotations.Reference; import org.osgi.service.metatype.annotations.Designate; +import com.google.common.collect.ImmutableSortedMap; + import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.utils.ThreadPoolUtils; import io.openems.edge.common.component.AbstractOpenemsComponent; @@ -139,7 +140,7 @@ public TimeOfUsePrices getPrices() { * @throws OpenemsNamedException on error */ public static TimeOfUsePrices parsePrices(String jsonData) throws OpenemsNamedException { - var result = new TreeMap(); + var result = ImmutableSortedMap.naturalOrder(); var data = getAsJsonArray(parseToJsonObject(jsonData), "data"); for (var element : data) { var marketPrice = getAsDouble(element, "marketprice"); @@ -156,7 +157,7 @@ public static TimeOfUsePrices parsePrices(String jsonData) throws OpenemsNamedEx result.put(startTimeStamp.plusMinutes(30), marketPrice); result.put(startTimeStamp.plusMinutes(45), marketPrice); } - return TimeOfUsePrices.from(result); + return TimeOfUsePrices.from(result.build()); } @Override diff --git a/io.openems.edge.timeofusetariff.awattar/test/io/openems/edge/timeofusetariff/awattar/TimeOfUseTariffAwattarImplTest.java b/io.openems.edge.timeofusetariff.awattar/test/io/openems/edge/timeofusetariff/awattar/TimeOfUseTariffAwattarImplTest.java index eaef278769f..dd329cfe509 100644 --- a/io.openems.edge.timeofusetariff.awattar/test/io/openems/edge/timeofusetariff/awattar/TimeOfUseTariffAwattarImplTest.java +++ b/io.openems.edge.timeofusetariff.awattar/test/io/openems/edge/timeofusetariff/awattar/TimeOfUseTariffAwattarImplTest.java @@ -1,5 +1,6 @@ package io.openems.edge.timeofusetariff.awattar; +import static io.openems.edge.timeofusetariff.awattar.Zone.GERMANY; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.fail; @@ -11,15 +12,13 @@ public class TimeOfUseTariffAwattarImplTest { - private static final String CTRL_ID = "ctrl0"; - @Test public void test() throws Exception { var awattar = new TimeOfUseTariffAwattarImpl(); new ComponentTest(awattar) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setZone(Zone.GERMANY) // + .setId("ctrl0") // + .setZone(GERMANY) // .build()) // ; } diff --git a/io.openems.edge.timeofusetariff.corrently/.classpath b/io.openems.edge.timeofusetariff.corrently/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.timeofusetariff.corrently/.classpath +++ b/io.openems.edge.timeofusetariff.corrently/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.timeofusetariff.corrently/src/io/openems/edge/timeofusetariff/corrently/TimeOfUseTariffCorrentlyImpl.java b/io.openems.edge.timeofusetariff.corrently/src/io/openems/edge/timeofusetariff/corrently/TimeOfUseTariffCorrentlyImpl.java index d97f80b7c5a..0bf22480391 100644 --- a/io.openems.edge.timeofusetariff.corrently/src/io/openems/edge/timeofusetariff/corrently/TimeOfUseTariffCorrentlyImpl.java +++ b/io.openems.edge.timeofusetariff.corrently/src/io/openems/edge/timeofusetariff/corrently/TimeOfUseTariffCorrentlyImpl.java @@ -12,7 +12,6 @@ import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; -import java.util.TreeMap; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @@ -28,6 +27,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.collect.ImmutableSortedMap; + import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.utils.ThreadPoolUtils; import io.openems.edge.common.component.AbstractOpenemsComponent; @@ -142,7 +143,7 @@ public TimeOfUsePrices getPrices() { * @throws OpenemsNamedException on error */ public static TimeOfUsePrices parsePrices(String jsonData) throws OpenemsNamedException { - var result = new TreeMap(); + var result = ImmutableSortedMap.naturalOrder(); var data = getAsJsonArray(parseToJsonObject(jsonData), "data"); for (var element : data) { var marketPrice = getAsDouble(element, "marketprice"); @@ -154,7 +155,7 @@ public static TimeOfUsePrices parsePrices(String jsonData) throws OpenemsNamedEx // Adding the values in the Map. result.put(startTimeStamp, marketPrice); } - return TimeOfUsePrices.from(result); + return TimeOfUsePrices.from(result.build()); } @Override diff --git a/io.openems.edge.timeofusetariff.corrently/test/io/openems/edge/timeofusetariff/corrently/TimeOfUseTariffCorrentlyImplTest.java b/io.openems.edge.timeofusetariff.corrently/test/io/openems/edge/timeofusetariff/corrently/TimeOfUseTariffCorrentlyImplTest.java index 3516d5f8378..83c69c1e2eb 100644 --- a/io.openems.edge.timeofusetariff.corrently/test/io/openems/edge/timeofusetariff/corrently/TimeOfUseTariffCorrentlyImplTest.java +++ b/io.openems.edge.timeofusetariff.corrently/test/io/openems/edge/timeofusetariff/corrently/TimeOfUseTariffCorrentlyImplTest.java @@ -12,14 +12,12 @@ public class TimeOfUseTariffCorrentlyImplTest { - private static final String CTRL_ID = "ctrl0"; - @Test public void test() throws Exception { var corrently = new TimeOfUseTariffCorrentlyImpl(); new ComponentTest(corrently) // .activate(MyConfig.create() // - .setId(CTRL_ID) // + .setId("ctrl0") // .setZipcode("94469" /* Deggendorf, Germany */) // .build()) // ; diff --git a/io.openems.edge.timeofusetariff.entsoe/.classpath b/io.openems.edge.timeofusetariff.entsoe/.classpath index bbfbdbe40e7..b4cffd0fe60 100644 --- a/io.openems.edge.timeofusetariff.entsoe/.classpath +++ b/io.openems.edge.timeofusetariff.entsoe/.classpath @@ -1,7 +1,7 @@ - + diff --git a/io.openems.edge.timeofusetariff.entsoe/readme.adoc b/io.openems.edge.timeofusetariff.entsoe/readme.adoc index aea3aee6223..83b1738c11c 100644 --- a/io.openems.edge.timeofusetariff.entsoe/readme.adoc +++ b/io.openems.edge.timeofusetariff.entsoe/readme.adoc @@ -6,6 +6,6 @@ To request a (free) authentication token, please see chapter "2. Authentication Prices retrieved from ENTSO-E are subsequently converted to the user's currency (defined in Core.Meta Component) using the Exchange Rates API. -For detailed information about the Exchange Rates API, please refer to: https://exchangerate.host/#/docs +For detailed information about the Exchange Rates API, please refer to: https://www.ecb.europa.eu/stats/policy_and_exchange_rates/euro_reference_exchange_rates/html/index.en.html https://github.com/OpenEMS/openems/tree/develop/io.openems.edge.timeofusetariff.entsoe[Source Code icon:github[]] \ No newline at end of file diff --git a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/Config.java b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/Config.java index 73a451dee78..ae9d004ee20 100644 --- a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/Config.java +++ b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/Config.java @@ -21,11 +21,11 @@ @AttributeDefinition(name = "Security Token", description = "Security token for the ENTSO-E Transparency Platform", type = AttributeType.PASSWORD) String securityToken() default ""; - @AttributeDefinition(name = "Exchangerate.host API Access Key", description = "Access key for Exchangerate.host: Please register at https://exchangerate.host/ to get your personal access key", type = AttributeType.PASSWORD) - String exchangerateAccesskey() default ""; - @AttributeDefinition(name = "Bidding Zone", description = "Zone corresponding to the customer's location") BiddingZone biddingZone(); + @AttributeDefinition(name = "Resolution", description = "Resolution corresponding to the price interval") + Resolution resolution() default Resolution.HOURLY; + String webconsole_configurationFactory_nameHint() default "Time-Of-Use Tariff ENTSO-E [{id}]"; } \ No newline at end of file diff --git a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/Resolution.java b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/Resolution.java new file mode 100644 index 00000000000..918e094cb7a --- /dev/null +++ b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/Resolution.java @@ -0,0 +1,24 @@ +package io.openems.edge.timeofusetariff.entsoe; + +import java.time.Duration; + +public enum Resolution { + + /** + * Prices every Hour. + */ + HOURLY("PT60M"), // + /** + * Prices every Quarter. + */ + QUARTERLY("PT15M"); + + public final String resolutionCode; + public final Duration duration; + + private Resolution(String resolutionCode) { + this.resolutionCode = resolutionCode; + this.duration = Duration.parse(resolutionCode); + } + +} diff --git a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoeImpl.java b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoeImpl.java index 873b6357fa7..248caf0677a 100644 --- a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoeImpl.java +++ b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoeImpl.java @@ -1,7 +1,7 @@ package io.openems.edge.timeofusetariff.entsoe; import static io.openems.common.utils.StringUtils.definedOrElse; -import static io.openems.edge.timeofusetariff.api.utils.ExchangeRateApi.getExchangeRate; +import static io.openems.edge.timeofusetariff.api.utils.ExchangeRateApi.getExchangeRateOrElse; import static io.openems.edge.timeofusetariff.api.utils.TimeOfUseTariffUtils.generateDebugLog; import static io.openems.edge.timeofusetariff.entsoe.Utils.parseCurrency; import static io.openems.edge.timeofusetariff.entsoe.Utils.parsePrices; @@ -30,14 +30,11 @@ import org.slf4j.LoggerFactory; import org.xml.sax.SAXException; -import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.common.exceptions.OpenemsException; import io.openems.common.oem.OpenemsEdgeOem; import io.openems.common.utils.ThreadPoolUtils; import io.openems.edge.common.channel.value.Value; import io.openems.edge.common.component.AbstractOpenemsComponent; import io.openems.edge.common.component.OpenemsComponent; -import io.openems.edge.common.currency.Currency; import io.openems.edge.common.meta.Meta; import io.openems.edge.timeofusetariff.api.TimeOfUsePrices; import io.openems.edge.timeofusetariff.api.TimeOfUseTariff; @@ -64,7 +61,6 @@ public class TouEntsoeImpl extends AbstractOpenemsComponent implements TouEntsoe private Config config = null; private String securityToken = null; - private String exchangerateAccesskey = null; private ScheduledFuture future = null; public TouEntsoeImpl() { @@ -92,11 +88,6 @@ private void activate(ComponentContext context, Config config) { return; } - this.exchangerateAccesskey = definedOrElse(config.exchangerateAccesskey(), this.oem.getExchangeRateAccesskey()); - if (this.exchangerateAccesskey == null) { - this.logError(this.log, "Please configure personal Access key to access Exchange rate host API"); - return; - } this.config = config; // React on updates to Currency. @@ -127,27 +118,22 @@ private synchronized void scheduleTask(long seconds) { private final Runnable task = () -> { var token = this.securityToken; - var exchangerateAccesskey = this.exchangerateAccesskey; var areaCode = this.config.biddingZone().code; var fromDate = ZonedDateTime.now().truncatedTo(ChronoUnit.HOURS); var toDate = fromDate.plusDays(1); var unableToUpdatePrices = false; + var preferredResolution = this.config.resolution(); try { final var result = EntsoeApi.query(token, areaCode, fromDate, toDate); final var entsoeCurrency = parseCurrency(result); final var globalCurrency = this.meta.getCurrency(); - if (globalCurrency == Currency.UNDEFINED) { - throw new OpenemsException("Global Currency is UNDEFINED. Please configure it in Core.Meta component"); - } + final double exchangeRate = getExchangeRateOrElse(entsoeCurrency, globalCurrency, 1.); - final var exchangeRate = globalCurrency.name().equals(entsoeCurrency) // - ? 1. // No need to fetch exchange rate from API. - : getExchangeRate(exchangerateAccesskey, entsoeCurrency, globalCurrency); // Parse the response for the prices - this.prices.set(parsePrices(result, "PT60M", exchangeRate)); + this.prices.set(parsePrices(result, exchangeRate, preferredResolution)); - } catch (IOException | ParserConfigurationException | SAXException | OpenemsNamedException e) { + } catch (IOException | ParserConfigurationException | SAXException e) { this.logWarn(this.log, "Unable to Update Entsoe Time-Of-Use Price: " + e.getMessage()); e.printStackTrace(); unableToUpdatePrices = true; diff --git a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/Utils.java b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/Utils.java index f5025debb6f..effd2527734 100644 --- a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/Utils.java +++ b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/Utils.java @@ -1,83 +1,78 @@ package io.openems.edge.timeofusetariff.entsoe; +import static io.openems.common.utils.XmlUtils.getXmlRootDocument; import static io.openems.common.utils.XmlUtils.stream; import static java.lang.Double.parseDouble; +import static java.lang.Integer.parseInt; import java.io.IOException; import java.io.StringReader; +import java.time.Duration; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.List; -import java.util.TreeMap; +import java.util.Comparator; +import java.util.function.Function; +import java.util.stream.Stream; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; +import org.w3c.dom.Element; +import org.w3c.dom.Node; import org.xml.sax.InputSource; import org.xml.sax.SAXException; +import com.google.common.collect.ImmutableSortedMap; +import com.google.common.collect.ImmutableTable; + import io.openems.common.utils.XmlUtils; import io.openems.edge.timeofusetariff.api.TimeOfUsePrices; public class Utils { - private static final DateTimeFormatter FORMATTER_MINUTES = DateTimeFormatter.ofPattern("u-MM-dd'T'HH:mmX"); - - private static record QueryResult(ZonedDateTime start, List prices) { - protected static class Builder { - private ZonedDateTime start; - private List prices = new ArrayList<>(); - - public Builder start(ZonedDateTime start) { - this.start = start; - return this; - } - - public Builder prices(List prices) { - this.prices.addAll(prices); - return this; - } - - public TimeOfUsePrices toTimeOfUsePrices() { - var result = new TreeMap(); - var timestamp = this.start.withZoneSameInstant(ZoneId.systemDefault()); - var quarterHourIncrements = this.prices.size() * 4; - - for (int i = 0; i < quarterHourIncrements; i++) { - result.put(timestamp, this.prices.get(i / 4)); - timestamp = timestamp.plusMinutes(15); - } - return TimeOfUsePrices.from(result); - } - } - - public static Builder create() { - return new Builder(); - } - } + protected static final DateTimeFormatter FORMATTER_MINUTES = DateTimeFormatter.ofPattern("u-MM-dd'T'HH:mmX"); /** * Parses the XML response from the Entso-E API to get the Day-Ahead prices. * - * @param xml The XML string to be parsed. - * @param resolution PT15M or PT60M - * @param exchangeRate The exchange rate of user currency to EUR. + * @param xml The XML string to be parsed. + * @param exchangeRate The exchange rate of user currency to EUR. + * @param preferredResolution The user preferred resolution. * @return The {@link TimeOfUsePrices} * @throws ParserConfigurationException on error. * @throws SAXException on error * @throws IOException on error */ - protected static TimeOfUsePrices parsePrices(String xml, String resolution, double exchangeRate) + protected static TimeOfUsePrices parsePrices(String xml, double exchangeRate, Resolution preferredResolution) throws ParserConfigurationException, SAXException, IOException { - var dbFactory = DocumentBuilderFactory.newInstance(); - var dBuilder = dbFactory.newDocumentBuilder(); - var is = new InputSource(new StringReader(xml)); - var doc = dBuilder.parse(is); - var root = doc.getDocumentElement(); - var result = QueryResult.create(); + var root = getXmlRootDocument(xml); + + var allPrices = parseXml(root, exchangeRate); + + if (allPrices.isEmpty()) { + return TimeOfUsePrices.EMPTY_PRICES; + } + + final var duration = getDuration(allPrices, preferredResolution); + final var prices = ImmutableSortedMap.copyOf(allPrices.row(duration)); + final var minTimestamp = prices.firstKey(); + final var maxTimestamp = prices.lastKey().plus(duration); + + var result = Stream // + .iterate(minTimestamp, // + t -> t.isBefore(maxTimestamp), // + t -> t.plusMinutes(15)) // + .collect(ImmutableSortedMap.toImmutableSortedMap(// + Comparator.naturalOrder(), // + Function.identity(), // + t -> prices.floorEntry(t).getValue())); + + return TimeOfUsePrices.from(result); + } + protected static ImmutableTable parseXml(Element root, double exchangeRate) { + var result = ImmutableTable.builder(); stream(root) // // .filter(n -> n.getNodeName() == "TimeSeries") // @@ -85,40 +80,53 @@ protected static TimeOfUsePrices parsePrices(String xml, String resolution, doub // .filter(n -> n.getNodeName() == "Period") // // Find Period with correct resolution - .filter(p -> stream(p) // - .filter(n -> n.getNodeName() == "resolution") // - .map(XmlUtils::getContentAsString) // - .anyMatch(r -> r.equals(resolution))) // .forEach(period -> { - - var start = ZonedDateTime.parse(// - stream(period) // - // - .filter(n -> n.getNodeName() == "timeInterval") // - .flatMap(XmlUtils::stream) // - // - .filter(n -> n.getNodeName() == "start") // - .map(XmlUtils::getContentAsString) // - .findFirst().get(), - FORMATTER_MINUTES).withZoneSameInstant(ZoneId.of("UTC")); - - if (result.start == null) { - // Avoiding overwriting of start due to multiple periods. - result.start(start); + try { + parsePeriod(result, period, exchangeRate); + } catch (Exception e) { + e.printStackTrace(); } + }); + return result.build(); + } - result.prices(stream(period) // - // - .filter(n -> n.getNodeName() == "Point") // - .flatMap(XmlUtils::stream) // + protected static void parsePeriod(ImmutableTable.Builder result, Node period, + double exchangeRate) throws Exception { + final var duration = Duration.parse(stream(period) // + // + .filter(n -> n.getNodeName() == "resolution") // + .map(XmlUtils::getContentAsString) // + .findFirst().get() /* "PT15M" or "PT60M" */); + final var start = ZonedDateTime.parse(// + stream(period) // + // + .filter(n -> n.getNodeName() == "timeInterval") // + .flatMap(XmlUtils::stream) // + // + .filter(n -> n.getNodeName() == "start") // + .map(XmlUtils::getContentAsString) // + .findFirst().get(), + FORMATTER_MINUTES).withZoneSameInstant(ZoneId.of("UTC")); + + stream(period) // + // + .filter(n -> n.getNodeName() == "Point") // + .forEach(point -> { + final var position = stream(point) // + // + .filter(n -> n.getNodeName() == "position") // + .map(XmlUtils::getContentAsString) // + .map(s -> parseInt(s)) // + .findFirst().get(); + final var timestamp = start.plusMinutes((position - 1) * duration.toMinutes()); + final var price = stream(point) // // .filter(n -> n.getNodeName() == "price.amount") // .map(XmlUtils::getContentAsString) // .map(s -> parseDouble(s) * exchangeRate) // - .toList()); + .findFirst().get(); + result.put(duration, timestamp, price); }); - - return result.toTimeOfUsePrices(); } /** @@ -150,4 +158,33 @@ protected static String parseCurrency(String xml) throws ParserConfigurationExce return result; } + /** + * Determines the appropriate {@link Duration} from the given + * {@link ImmutableTable}. + * + *