├── .devcontainer ├── post-install.sh ├── recommended-Dockerfile └── recommended-devcontainer.json ├── .eslintignore ├── .eslintrc.js ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ └── feature_request.yml ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml ├── release.yml └── workflows │ ├── HACS.yml │ ├── pull_requests.yaml │ ├── release.yml │ └── update_docs.yml ├── .gitignore ├── .idea └── .gitignore ├── .nojekyll ├── .prettierrc ├── .yarn └── install-state.gz ├── LICENSE ├── README.md ├── docs ├── Makefile ├── _templates │ └── layout.html ├── conf.py ├── configuration.md ├── contribute │ ├── bugs.rst │ ├── devcontainer.rst │ ├── devcycle.rst │ └── docs.rst ├── examples │ ├── foxess.rst │ ├── goodwe.rst │ ├── huawei.rst │ ├── huawei_packages │ │ ├── huawei_derived_sensors.yaml │ │ ├── huawei_solar_electricity_costs_AU_Red_Energy_TOU.yaml │ │ ├── huawei_solar_electricity_costs_AU_Synerg_Energy_Flate_Rate.yaml │ │ ├── huawei_solar_electricity_costs_AU_Synerg_Energy_Flate_with_ToU_ExportRate.yaml │ │ └── sunsynk_power_flow_card_derived_sensors.yaml │ ├── lux.rst │ ├── powmr.rst │ ├── solax.rst │ ├── solis-codes.csv │ ├── solis.rst │ ├── sunsynk.rst │ └── victron.rst ├── index.rst ├── requirements.txt └── toc.rst ├── eslint.config.mjs ├── hacs.json ├── package.json ├── rollup.config.mjs ├── src ├── cards │ ├── compact-card.ts │ └── full-card.ts ├── components │ ├── compact │ │ ├── bat │ │ │ └── bat-elements.ts │ │ ├── grid │ │ │ └── grid-elements.ts │ │ ├── inverter │ │ │ └── inverter-elements.ts │ │ ├── load │ │ │ └── load-elements.ts │ │ └── pv │ │ │ └── pv-elements.ts │ ├── full │ │ ├── auxload │ │ │ ├── aux-elements.ts │ │ │ ├── icon-configs.ts │ │ │ └── render-static-aux-icons.ts │ │ ├── bat │ │ │ └── bat-elements.ts │ │ ├── grid │ │ │ └── grid-elements.ts │ │ ├── inverter │ │ │ └── inverter-elements.ts │ │ ├── load │ │ │ └── load-elements.ts │ │ └── pv │ │ │ └── pv_elements.ts │ └── shared │ │ ├── grid │ │ ├── icon-configs.ts │ │ └── render-static-grid-icon.ts │ │ ├── load │ │ ├── icon-configs.ts │ │ └── render-static-load-icon.ts │ │ └── pv │ │ ├── render-pv-flow.ts │ │ └── render-pv.ts ├── const.ts ├── defaults.ts ├── editor.ts ├── helpers │ ├── battery-icon-manager.ts │ ├── globals.ts │ ├── icons.ts │ ├── render-circle.ts │ ├── render-icon.ts │ ├── render-path.ts │ ├── text-utils.ts │ └── utils.ts ├── index.ts ├── inverters │ ├── brands │ │ ├── azzurro.ts │ │ ├── ces-battery-box.ts │ │ ├── deye.ts │ │ ├── e3dc.ts │ │ ├── easun.ts │ │ ├── ferroamp.ts │ │ ├── fox-ess.ts │ │ ├── fronius.ts │ │ ├── goodwe-grid.ts │ │ ├── goodwe.ts │ │ ├── growatt.ts │ │ ├── huawei.ts │ │ ├── linky.ts │ │ ├── lux.ts │ │ ├── makeskyblue.ts │ │ ├── mpp-solar.ts │ │ ├── powmr.ts │ │ ├── sigenergy.ts │ │ ├── sma-solar.ts │ │ ├── sofar.ts │ │ ├── solar-edge.ts │ │ ├── solax.ts │ │ ├── solis.ts │ │ ├── sungrow.ts │ │ ├── sunsynk.ts │ │ └── victron.ts │ ├── dto │ │ ├── custom-entity.ts │ │ └── inverter-settings.dto.ts │ └── inverter-factory.ts ├── localize │ ├── languages │ │ ├── ca.json │ │ ├── cs.json │ │ ├── da.json │ │ ├── de.json │ │ ├── en.json │ │ ├── es.json │ │ ├── et.json │ │ ├── fr.json │ │ ├── it.json │ │ ├── nl.json │ │ ├── pt-br.json │ │ ├── ru.json │ │ ├── sk.json │ │ ├── sl.json │ │ ├── sv.json │ │ └── uk.json │ └── localize.ts ├── style.ts └── types.ts └── tsconfig.json /.devcontainer/post-install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -ex 3 | 4 | # Convenience workspace directory for later use 5 | WORKSPACE_DIR=$(pwd) 6 | 7 | # Now install all dependencies 8 | yarn install 9 | 10 | # Install documentation dependencies 11 | pip3 install -r docs/requirements.txt 12 | pip3 install sphinx-autobuild 13 | 14 | echo "Done!" 15 | -------------------------------------------------------------------------------- /.devcontainer/recommended-Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/devcontainers/typescript-node:1-20-bullseye 2 | 3 | RUN apt update && apt upgrade -y 4 | 5 | RUN apt install -y zsh python3 python3-sphinx python3-pip 6 | 7 | RUN wget https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh -O - | zsh || true 8 | 9 | CMD ["zsh"] 10 | -------------------------------------------------------------------------------- /.devcontainer/recommended-devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sunsynk-power-flow-card", 3 | "dockerFile": "Dockerfile", 4 | "context": "..", 5 | "customizations": { 6 | "vscode": { 7 | "settings": { 8 | "files.eol": "\n", 9 | "editor.tabSize": 4, 10 | "terminal.integrated.shell.linux": "/bin/zsh", 11 | "editor.formatOnPaste": false, 12 | "editor.formatOnSave": true, 13 | "editor.formatOnType": true, 14 | "files.trimTrailingWhitespace": true 15 | }, 16 | "extensions": [ 17 | "github.vscode-pull-request-github", 18 | "yzhang.markdown-all-in-one", 19 | "bierner.lit-html", 20 | "runem.lit-plugin", 21 | "davidanson.vscode-markdownlint", 22 | "redhat.vscode-yaml", 23 | "eamodio.gitlens", 24 | "ms-python.python", 25 | "tht13.html-preview-vscode", 26 | "sourcery.sourcery", 27 | "tabnine.tabnine-vscode", 28 | "trond-snekvik.simple-rst" 29 | ] 30 | } 31 | }, 32 | "postCreateCommand": "zsh ./.devcontainer/post-install.sh" 33 | } 34 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/** 2 | dist/** -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', // Specifies the ESLint parser 3 | plugins: [ 4 | "@typescript-eslint" 5 | ], 6 | //extends: [ 7 | // 'prettier', // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier 8 | // 'plugin:prettier/recommended', // Enables eslint-plugin-prettier and displays prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array. 9 | //], 10 | parserOptions: { 11 | ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features 12 | sourceType: 'module', // Allows for the use of imports 13 | experimentalDecorators: true, 14 | }, 15 | rules: { 16 | "@typescript-eslint/camelcase": 0, 17 | "@typescript-eslint/no-explicit-any": 0, 18 | //'prettier/prettier': 'warn' 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: File a bug report 3 | labels: ["type/bug"] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | **NOTE:** Before you start, the following should be completed. 9 | 10 | - Read the documentation to ensure the correct setup. 11 | - Make sure no [similar issues(including closed ones)](https://github.com/slipx06/sunsynk-power-flow-card/issues?q=is%3Aissue+is%3Aopen+label%3Atype%2Fbug) exists. 12 | - Make sure the request is based on the latest release. 13 | 14 | Thanks for taking the time to assist with improving this project! 15 | - type: checkboxes 16 | attributes: 17 | label: Is there an existing issue for this? 18 | description: Please search to see if an issue already exists for the bug you encountered. 19 | options: 20 | - label: I have searched the existing issues 21 | required: true 22 | - type: textarea 23 | id: current-behavior 24 | attributes: 25 | label: Current Behavior 26 | description: A concise description of what you're experiencing. 27 | placeholder: Tell us what you see! 28 | validations: 29 | required: true 30 | - type: textarea 31 | attributes: 32 | label: Steps To Reproduce 33 | description: Steps to reproduce the behavior. 34 | placeholder: | 35 | 1. In this environment... 36 | 2. With this config... 37 | 3. Run '...' 38 | 4. See error... 39 | validations: 40 | required: false 41 | - type: textarea 42 | id: expected-behaviour 43 | attributes: 44 | label: Expected behaviour 45 | description: A concise description of what you expected to happen. 46 | placeholder: Tell us what you should see! 47 | validations: 48 | required: true 49 | - type: input 50 | id: card-version 51 | attributes: 52 | label: Card Version 53 | description: The version of the card you have installed 54 | validations: 55 | required: true 56 | - type: input 57 | id: ha-version 58 | attributes: 59 | label: Home Assistant Version 60 | description: The version of Home Assistant you have installed 61 | validations: 62 | required: true 63 | - type: textarea 64 | id: config 65 | attributes: 66 | label: Configuration 67 | description: Please copy and paste your configuration. This will be automatically formatted into code, so no need for backticks. 68 | render: shell 69 | - type: textarea 70 | id: logs 71 | attributes: 72 | label: Relevant log output 73 | description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. 74 | render: shell 75 | validations: 76 | required: false 77 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | blank_issues_enabled: false 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | description: Suggest an idea for this project 3 | labels: ['type/feature'] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thanks for taking the time to assist with improving this project! 9 | - type: checkboxes 10 | attributes: 11 | label: Is there an existing issue for this? 12 | description: Please search to see if an issue already exists for the feature you are requesting. 13 | options: 14 | - label: I have searched the existing issues 15 | required: true 16 | - type: textarea 17 | id: current-behavior 18 | attributes: 19 | label: Current Behavior 20 | description: A concise description of what you're experiencing. 21 | placeholder: Tell us what you see! 22 | validations: 23 | required: true 24 | - type: textarea 25 | id: expected-behaviour 26 | attributes: 27 | label: Expected behaviour 28 | description: A concise description of what you expected to happen. 29 | placeholder: Tell us what you should see! 30 | validations: 31 | required: true 32 | - type: textarea 33 | attributes: 34 | label: Possible Solutions 35 | description: If you have an idea on how to implement this please let us know. 36 | validations: 37 | required: false 38 | - type: dropdown 39 | id: mode 40 | attributes: 41 | label: Mode 42 | description: What card style does this feature apply to? 43 | options: 44 | - lite 45 | - full 46 | - Both 47 | validations: 48 | required: true 49 | - type: textarea 50 | attributes: 51 | label: Context / Reason 52 | description: Providing context helps us come up with a solution that is most useful in the real world. 53 | validations: 54 | required: true 55 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Description 4 | 5 | 6 | 7 | ## Related issues 8 | 9 | 10 | 11 | 12 | 13 | ## Motivation and Context 14 | 15 | 16 | ## How has this been tested 17 | 18 | 19 | 20 | ## Type of change 21 | 22 | Please delete options that are not relevant. 23 | 24 | - [ ] Bug fix (non-breaking change which fixes an issue) 25 | - [ ] New feature (non-breaking change which adds functionality) 26 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 27 | - [ ] This change requires a documentation update 28 | 29 | ## Checklist 30 | 31 | 32 | - [ ] My code follows the style guidelines of this project 33 | - [ ] I have performed a self-review of my code 34 | - [ ] I have commented my code, particularly in hard-to-understand areas 35 | - [ ] I have made corresponding changes to the documentation 36 | - [ ] My changes generate no new warnings 37 | - [ ] Any dependent changes have been merged and published in downstream modules 38 | - [ ] I have updated the version in `package.json` following [semver](https://semver.org/) 39 | 40 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | # Maintain dependencies for npm 8 | - package-ecosystem: "npm" 9 | directory: "/" 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | changelog: 2 | categories: 3 | - title: 💥 Breaking Changes 4 | labels: 5 | - semver/major 6 | - flag/breaking changes 7 | - title: 🚀 Exciting New Features 8 | labels: 9 | - semver/minor 10 | - type/feat 11 | - enhancement 12 | - title: 🐛 Patches & Bug Fixes 13 | labels: 14 | - semver/patch 15 | - type/bug 16 | - bug 17 | - title: 📚 Documentation 18 | labels: 19 | - type/docs 20 | - documentation 21 | - title: 📔 Language 22 | labels: 23 | - type/language 24 | - language 25 | - title: ⬆️ Dependencies 26 | labels: 27 | - type/dependencies 28 | - dependencies 29 | - title: Other Changes 30 | labels: 31 | - "*" 32 | -------------------------------------------------------------------------------- /.github/workflows/HACS.yml: -------------------------------------------------------------------------------- 1 | name: HACS Action 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | validate-hacs: 9 | runs-on: "ubuntu-latest" 10 | steps: 11 | - uses: "actions/checkout@v4" 12 | - name: HACS validation 13 | uses: "hacs/action@main" 14 | with: 15 | category: "plugin" 16 | -------------------------------------------------------------------------------- /.github/workflows/pull_requests.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | name: Pull Requests 4 | 5 | on: 6 | pull_request: 7 | branches: 8 | - main 9 | workflow_dispatch: 10 | jobs: 11 | checkVersion: 12 | if: github.event_name == 'pull_request' 13 | 14 | name: Check version updated 15 | runs-on: ubuntu-latest 16 | env: 17 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 18 | steps: 19 | - uses: actions/checkout@v4 20 | # Use this GitHub Action 21 | - name: Check package version 22 | uses: dudo/tag_check@master 23 | with: 24 | git_tag_prefix: v 25 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | workflow_dispatch: 8 | 9 | jobs: 10 | release: 11 | name: Create release 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | with: 16 | fetch-depth: 0 17 | 18 | - name: Set up Node.js 19 | uses: actions/setup-node@v4 20 | with: 21 | node-version: '20' # Specify node version to match project requirements 22 | 23 | - name: Get latest tag or fail 24 | id: get-latest-tag 25 | run: | 26 | latest_tag=$(git for-each-ref --sort=version:refname --format '%(refname)' refs/tags/ | grep -v 'latest' | tail -1 | sed 's/^refs\/tags\///; s/^v//') 27 | if [ -z "$latest_tag" ]; then 28 | echo "No tags found for the commit." 29 | exit 1 30 | fi 31 | echo "tag=$latest_tag" >> $GITHUB_OUTPUT 32 | 33 | - name: Get current version 34 | id: get-version 35 | run: echo "version=$(node -pe "require('./package.json').version")" >> $GITHUB_ENV 36 | 37 | - name: Install semver 38 | run: npm install semver 39 | 40 | - name: Check if version is higher 41 | id: check-version 42 | run: | 43 | is_higher=$(node -pe "const semver = require('semver'); semver.gt('${{ env.version }}', '${{ steps.get-latest-tag.outputs.tag }}')") 44 | if [ "$is_higher" != "true" ]; then 45 | echo "is_higher_version=false" >> $GITHUB_ENV 46 | echo "The version is not higher than the latest release" 47 | exit 1 48 | fi 49 | 50 | echo "is_higher_version=true" >> $GITHUB_ENV 51 | 52 | - name: Remove package-lock.json to prevent conflicts 53 | if: env.is_higher_version == 'true' 54 | run: | 55 | if [ -f package-lock.json ]; then 56 | rm package-lock.json 57 | fi 58 | 59 | - name: Build project 60 | if: env.is_higher_version == 'true' 61 | run: | 62 | npm install --legacy-peer-deps 63 | npm run build 64 | 65 | - name: 🛎️ Create release 66 | if: env.is_higher_version == 'true' 67 | uses: softprops/action-gh-release@v2 68 | with: 69 | files: dist/sunsynk-power-flow-card.js 70 | tag_name: v${{ env.version }} 71 | name: v${{ env.version }} 72 | token: ${{ secrets.GITHUB_TOKEN }} 73 | generate_release_notes: true 74 | 75 | - name: 🏷️ Update latest tag 76 | if: env.is_higher_version == 'true' 77 | uses: EndBug/latest-tag@latest -------------------------------------------------------------------------------- /.github/workflows/update_docs.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | name: Generate and Deploy Documentation 4 | 5 | on: 6 | push: 7 | branches: 8 | - main 9 | 10 | jobs: 11 | build-and-deploy-docs: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout code 16 | uses: actions/checkout@v4 17 | 18 | - name: Deploy to GitHub Pages 19 | uses: totaldebug/sphinx-publish-action@master 20 | with: 21 | token: ${{ secrets.GITHUB_TOKEN }} 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist/* 2 | !dist/sunsynk-power-flow-card.js 3 | 4 | .devcontainer/* 5 | !.devcontainer/recommended-devcontainer.json 6 | !.devcontainer/recommended-Dockerfile 7 | !.devcontainer/post-install.sh 8 | 9 | .vscode/* 10 | 11 | # Logs 12 | logs 13 | *.log 14 | npm-debug.log* 15 | yarn-debug.log* 16 | yarn-error.log* 17 | *.map 18 | package-lock.json 19 | 20 | # Runtime data 21 | pids 22 | *.pid 23 | *.seed 24 | *.pid.lock 25 | 26 | # Directory for instrumented libs generated by jscoverage/JSCover 27 | lib-cov 28 | 29 | # Coverage directory used by tools like istanbul 30 | coverage 31 | 32 | # nyc test coverage 33 | .nyc_output 34 | 35 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 36 | .grunt 37 | 38 | # Bower dependency directory (https://bower.io/) 39 | bower_components 40 | 41 | # node-waf configuration 42 | .lock-wscript 43 | 44 | # Compiled binary addons (https://nodejs.org/api/addons.html) 45 | build/Release 46 | 47 | # Dependency directories 48 | node_modules/ 49 | jspm_packages/ 50 | 51 | # TypeScript v1 declaration files 52 | typings/ 53 | 54 | # Optional npm cache directory 55 | .npm 56 | 57 | # Optional eslint cache 58 | .eslintcache 59 | 60 | # Optional REPL history 61 | .node_repl_history 62 | 63 | # Output of 'npm pack' 64 | *.tgz 65 | 66 | # Yarn Integrity file 67 | .yarn-integrity 68 | 69 | # dotenv environment variables file 70 | .env 71 | .env.test 72 | 73 | # parcel-bundler cache (https://parceljs.org/) 74 | .cache 75 | undefined/ 76 | 77 | # next.js build output 78 | .next 79 | 80 | # nuxt.js build output 81 | .nuxt 82 | 83 | # vuepress build output 84 | .vuepress/dist 85 | 86 | # Serverless directories 87 | .serverless/ 88 | 89 | # FuseBox cache 90 | .fusebox/ 91 | 92 | # DynamoDB Local files 93 | .dynamodb/ 94 | 95 | docs/_build 96 | _build 97 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | /misc.xml 10 | /modules.xml 11 | /sunsynk-power-flow-card.iml 12 | /vcs.xml 13 | -------------------------------------------------------------------------------- /.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slipx06/sunsynk-power-flow-card/dab08da41e72b5b180874d16caf35154b77fc261/.nojekyll -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | semi: true, 3 | trailingComma: 'all', 4 | singleQuote: true, 5 | printWidth: 120, 6 | useTabs: true, 7 | tabWidth: 4, 8 | bracketSpacing: false 9 | } 10 | -------------------------------------------------------------------------------- /.yarn/install-state.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slipx06/sunsynk-power-flow-card/dab08da41e72b5b180874d16caf35154b77fc261/.yarn/install-state.gz -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 slipx06 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sunsynk Power Flow Card 2 | 3 | An animated Home Assistant card to emulate the power flow that's shown on the Sunsynk Inverter screen. You can use this to display data from many inverters e.g. Sunsynk, Deye, Solis, Lux, FoxESS, Goodwe, Huawei etc as long as you have the required sensor data. See the [wiki](https://github.com/slipx06/sunsynk-power-flow-card/wiki) for integration methods and examples. 4 | 5 | [![Open your Home Assistant instance and open a repository inside the Home Assistant Community Store.](https://my.home-assistant.io/badges/hacs_repository.svg)](https://my.home-assistant.io/redirect/hacs_repository/?owner=slipx06&repository=sunsynk-power-flow-card&category=plugin) 6 | ![GitHub release (latest by date)](https://img.shields.io/github/v/release/slipx06/sunsynk-power-flow-card?style=for-the-badge) 7 | [![Community Forum](https://img.shields.io/badge/community-forum-brightgreen.svg?style=for-the-badge)](https://community.home-assistant.io/t/sunsynk-deye-inverter-power-flow-card/562933/1) 8 | Buy Me A Coffee 9 | ## Documentation 10 | 11 | Refer to [https://slipx06.github.io/sunsynk-power-flow-card/index.html](https://slipx06.github.io/sunsynk-power-flow-card/index.html) 12 | 13 | ## Features 14 | 15 | * Option to switch between three card styles: `compact`, `lite` or `full`. 16 | * Wide view for 16:9 layout. 17 | * Animated power flow based on positive/negative/zero sensor values with configurable dynamic speed. (Supports inverted battery, AUX and grid power). 18 | * Dynamic battery image based on SOC. 19 | * Grid connected status. 20 | * Configurable battery size and shutdown SOC to calculate and display remaining battery runtime based on current battery usage and system time slot setting i.e. SOC, Grid Charge. Can be toggled off. 21 | * Daily Totals that can be toggled on or off. 22 | * Hide all solar data if not installed or specify number of mppts in use. Set custom MPPT labels. 23 | * "Use Timer" setting and "Energy Pattern" setting (Priority Load or Priority Battery) shown as dynamic icons, with the ability to hide if not required. If setup as switches can be toggled by clicking on the card. 24 | * Card can be scaled by setting the card_height and card_width attributes. 25 | * AUX and Non-essential can be hidden from the full card or assigned configurable labels. 26 | * Customisable - Change colours and images. 27 | * Most entities can be clicked to show more-info dialog. 28 | * Optional data points include self sufficiency and ratio percentages, battery temperature, AC and DC temperature. 29 | * Display additional non-essential, essential and AUX loads. 30 | * Display energy cost per kWh and solar sell status. 31 | * Select your inverter model for custom inverter status and battery status messages i.e. Sunsynk, Lux, Goodwe, Solis. 32 | 33 | ## Screenshots 34 | *Compact Version* 35 | 36 | ![image](https://github.com/slipx06/sunsynk-power-flow-card/assets/7227275/b1e437a8-d1f7-4d6a-a549-1cc908950002) 37 | ![image](https://github.com/slipx06/sunsynk-power-flow-card/assets/7227275/49c499c5-9d2b-43e7-8f5d-5b9da5e07fb9) 38 | 39 | 40 | 41 | 42 | *Lite Version* 43 | 44 | ![image](https://github.com/slipx06/sunsynk-power-flow-card/assets/7227275/d25c621c-2607-445f-b3a3-865930387a05) 45 | ![image](https://github.com/slipx06/sunsynk-power-flow-card/assets/7227275/5a9078ee-7375-4f1c-affa-6fe291d62f8a) 46 | ![image](https://github.com/slipx06/sunsynk-power-flow-card/assets/7227275/73d6fae3-3e6b-4891-acc2-deb29156cd2d) 47 | ![image](https://github.com/slipx06/sunsynk-power-flow-card/assets/7227275/54ae290d-aa5c-428e-8a00-2a75e11c2de8) 48 | 49 | 50 | *Full Version* 51 | 52 | ![image](https://github.com/slipx06/sunsynk-power-flow-card/assets/7227275/fdcce257-e7b5-4874-926c-17e911e83eba) 53 | ![image](https://github.com/slipx06/sunsynk-power-flow-card/assets/7227275/12af5b02-c456-4685-a50f-bd0044b9e9b0) 54 | 55 | 56 | *Wide Full Version (2 batteries)* 57 | 58 | ![{4D3F02C5-3DC5-4995-AD99-7478E6DE5557}](https://github.com/user-attachments/assets/af169593-c73f-469e-bc8b-62fb72b8af43) 59 | 60 | 61 | *Wide Lite Version (2 batteries)* 62 | 63 | ![{F448EFB0-5549-470B-BAE0-13F9DF2E3769}](https://github.com/user-attachments/assets/100c80d2-1d5f-46f4-ae83-f48c923cadf6) 64 | 65 | 66 | *Wide Compact Version (2 batteries)* 67 | 68 | ![{B8CBC3C3-0E0A-4E37-B489-C41CB8EA4E7E}](https://github.com/user-attachments/assets/1cd5508d-33a0-4df9-9665-5a4d9e753178) 69 | 70 | 71 | ## Installation 72 | 73 | The card can be installed via HACS (recommended) or manually. 74 | 75 | ### Installation using HACS 76 | [![hacs_badge](https://img.shields.io/badge/HACS-Default-blue.svg)](https://github.com/custom-components/hacs) 77 | 78 | 79 | 1. Install HACS. 80 | 2. Search & Install sunsynk-power-flow-card or click the button below. 81 | 82 | [![Open your Home Assistant instance and open a repository inside the Home Assistant Community Store.](https://my.home-assistant.io/badges/hacs_repository.svg)](https://my.home-assistant.io/redirect/hacs_repository/?owner=slipx06&repository=sunsynk-power-flow-card&category=plugin) 83 | 84 | ### Manual Installation 85 | 86 | 1. Create a new directory under `www` and name it `sunsynk-power-flow-card` e.g `www/sunsynk-power-flow-card/`. 87 | 2. Copy the `sunsynk-power-flow-card.js` into the directory. 88 | 3. Add the resource to your Dashboard. You can append the filename with a `?ver=x` and increment x each time you download a new version to force a reload and avoid using a cached version. It is also a good idea to clear your browser cache. 89 | 90 | ![image](https://user-images.githubusercontent.com/7227275/235441241-93ab0c7d-341d-428f-8ca8-60ec932dde2d.png) 91 | 92 | 93 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SOURCEDIR = . 8 | BUILDDIR = _build 9 | 10 | # Put it first so that "make" without argument is like "make help". 11 | help: 12 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 13 | 14 | .PHONY: help Makefile 15 | 16 | # Catch-all target: route all unknown targets to Sphinx using the new 17 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 18 | %: Makefile 19 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 20 | -------------------------------------------------------------------------------- /docs/_templates/layout.html: -------------------------------------------------------------------------------- 1 | {% extends "!layout.html" %} 2 | {% block footer %} {{ super() }} 3 | 4 | 21 | 22 | {% endblock %} 23 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import re 4 | import os 5 | import sys 6 | import json 7 | 8 | sys.path.insert(0, os.path.abspath("..")) 9 | 10 | project = "sunsynk-power-flow-card" 11 | slug = re.sub(r"\W+", "-", project.lower()) 12 | copyright = "2024, slipx06" 13 | author = "slipx06" 14 | 15 | # Get version from package.json file 16 | with open('../package.json') as json_file: 17 | data = json.load(json_file) 18 | version = data['version'] 19 | 20 | # The full version, including alpha/beta/rc tags 21 | release = "" 22 | 23 | extensions = [ 24 | "sphinx.ext.autodoc", 25 | "sphinx.ext.coverage", 26 | "sphinx.ext.viewcode", 27 | "sphinx_rtd_theme", 28 | "sphinx.ext.napoleon", 29 | "sphinx.ext.autosectionlabel", 30 | "myst_parser", 31 | ] 32 | 33 | # -- Napoleon Settings ----------------------------------------------------- 34 | napoleon_google_docstring = True 35 | napoleon_numpy_docstring = False 36 | napoleon_include_init_with_doc = False 37 | napoleon_include_private_with_doc = False 38 | napoleon_include_special_with_doc = False 39 | napoleon_use_admonition_for_examples = False 40 | napoleon_use_admonition_for_notes = False 41 | napoleon_use_admonition_for_references = False 42 | napoleon_use_ivar = True 43 | napoleon_use_param = True 44 | napoleon_use_rtype = True 45 | napoleon_use_keyword = True 46 | autodoc_member_order = "bysource" 47 | 48 | 49 | templates_path = ["_templates"] 50 | source_suffix = ".rst" 51 | 52 | master_doc = "index" 53 | language = "en" 54 | gettext_compact = False 55 | 56 | exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] 57 | 58 | pygments_style = "default" 59 | 60 | 61 | html_theme = "sphinx_rtd_theme" 62 | html_theme_options = { 63 | "logo_only": True, 64 | "navigation_depth": 5, 65 | "display_version": True, 66 | } 67 | 68 | htmlhelp_basename = slug 69 | 70 | 71 | # -- Options for LaTeX output ------------------------------------------------ 72 | 73 | latex_elements = { 74 | # The paper size ('letterpaper' or 'a4paper'). 75 | # 76 | # 'papersize': 'letterpaper', 77 | # The font size ('10pt', '11pt' or '12pt'). 78 | # 79 | # 'pointsize': '10pt', 80 | # Additional stuff for the LaTeX preamble. 81 | # 82 | # 'preamble': '', 83 | # Latex figure (float) alignment 84 | # 85 | # 'figure_align': 'htbp', 86 | } 87 | 88 | latex_documents = [ 89 | ("index", "{0}.tex".format(slug), project, author, "manual"), 90 | ] 91 | 92 | 93 | man_pages = [("index", slug, project, [author], 1)] 94 | 95 | 96 | # -- Options for Texinfo output ---------------------------------------------- 97 | 98 | # Grouping the document tree into Texinfo files. List of tuples 99 | # (source start file, target name, title, author, 100 | # dir menu entry, description, category) 101 | texinfo_documents = [ 102 | ( 103 | master_doc, 104 | author, 105 | "slipx06", 106 | "", 107 | "Miscellaneous", 108 | ), 109 | ] 110 | 111 | 112 | # -- Options for Epub output ------------------------------------------------- 113 | 114 | # Bibliographic Dublin Core info. 115 | epub_title = project 116 | 117 | # The unique identifier of the text. This can be a ISBN number 118 | # or the project homepage. 119 | # 120 | # epub_identifier = '' 121 | 122 | # A unique identification for the text. 123 | # 124 | # epub_uid = '' 125 | 126 | # A list of files that should not be packed into the epub file. 127 | epub_exclude_files = ["search.html"] 128 | 129 | 130 | # -- Extension configuration ------------------------------------------------- 131 | -------------------------------------------------------------------------------- /docs/contribute/bugs.rst: -------------------------------------------------------------------------------- 1 | ############## 2 | Reporting Bugs 3 | ############## 4 | 5 | This section guides you through submitting a bug report for the Sunsynk Power Flow Card. 6 | Following these guidelines helps maintainers and the community understand your report, 7 | reproduce the behaviour, and find related reports. 8 | 9 | Before creating bug reports, please check the below information as you might find out 10 | that you don't need to create one. When you are creating a bug report, 11 | please include as many details as possible, the information it asks for helps 12 | us resolve issues faster. 13 | 14 | .. note:: 15 | 16 | If you find a **Closed** issue that seems like it is the same thing that you're 17 | experiencing, open a new issue and include a link to the original issue in the 18 | body of your new one. 19 | 20 | 21 | ****************************** 22 | Before Submitting A Bug Report 23 | ****************************** 24 | 25 | * **Perform a** `cursory search `_ 26 | to see if the problem has already been reported. If it has **and the issue is still open**, add a comment to 27 | the existing issue instead of opening a new one. 28 | 29 | ***************************** 30 | How do i submit a bug report? 31 | ***************************** 32 | 33 | Bugs are tracked as `GitHub issues `_. 34 | After you've determined this is not a configuration issue, create an issue on github 35 | and provide the following information by filling in `the template `_. 36 | 37 | Explain the problem and include additional details to help maintainers reproduce the problem: 38 | 39 | - **Use a clear and descriptive title** for the issue to identify the problem. 40 | - **Describe the exact steps which reproduce the problem** in as many details as possible. For example, start by explaining how you installed the plugin. When listing steps, **don't just say what you did, but explain how you did it**. For example, did you use the Lovelace Editor or do the change in YAML directly. 41 | - **Provide specific examples to demonstrate the steps**. Include screenshots, or copy/pasteable configuration snippets, which you use in those examples. If you're providing snippets in the issue, use `Markdown code blocks `_. 42 | - **Describe the behaviour you observed after following the steps** and point out what exactly is the problem with that behaviour. 43 | - **Explain which behaviour you expected to see instead and why.** 44 | - **Include screenshots and animated GIFs** which show you following the described steps and clearly demonstrate the problem. 45 | - **If Chrome's developer tools pane is showing errors**, include these in your report 46 | - **If the problem wasn't triggered by a specific action**, describe what you were doing before the problem happened and share more information using the guidelines below. 47 | 48 | Provide more context by answering these questions: 49 | 50 | - **Did the problem start happening recently** (e.g. after updating to a new version of HA / Sunsynk Power Flow Card) or was this always a problem? 51 | - If the problem started happening recently, **can you reproduce the problem in an older version of the card?** What's the most recent version in which the problem doesn't happen? You can install older versions of Atomic Calendar Revive via HACS or from the releases page on github 52 | - **Can you reliably reproduce the issue?** If not, provide details about how often the problem happens and under which conditions it normally happens. 53 | -------------------------------------------------------------------------------- /docs/contribute/devcontainer.rst: -------------------------------------------------------------------------------- 1 | ###################### 2 | VS Code - DevContainer 3 | ###################### 4 | 5 | The easiest way to get your development environment setup is by using VS Code 6 | with Dev Containers, this utilises Docker containers to setup a development 7 | environment that guarantees a match with all other developers, removing any 8 | potential headaches from incompatibilities. 9 | 10 | ************ 11 | Requirements 12 | ************ 13 | 14 | * VS Code 15 | * Docker 16 | * Remote - Containers (VS Code extension) 17 | 18 | 19 | ************* 20 | Configuration 21 | ************* 22 | 23 | #. Copy the ``recommended`` files inside ``.devcontainer`` 24 | #. Paste them in the same folder, remove the ``recommended-`` from the filename 25 | #. In most cases no other changes will be required with these files 26 | 27 | .. note:: 28 | Please ensure that the ``recommended-xxx`` files are not removed as this would remove 29 | them from the repository when committed. 30 | 31 | When you open the repository with VS Code, a prompt to "Reopen in container" should 32 | now appear. This will start the build of the development container with all components 33 | and extensions pre-installed. 34 | 35 | .. note:: 36 | If you don't see the notification, open the command pallet and select 37 | ``Dev Containers: Open Folder in Container`` 38 | -------------------------------------------------------------------------------- /docs/contribute/devcycle.rst: -------------------------------------------------------------------------------- 1 | ################# 2 | Development Cycle 3 | ################# 4 | 5 | The below will provide a guide on how you should develop for this plugin to have your 6 | code reviewed and accepted. 7 | 8 | **************** 9 | Setup Repository 10 | **************** 11 | 12 | * Fork the repo in `github `_ 13 | * Clone the project to your development machine 14 | 15 | .. code-block:: bash 16 | 17 | git clone https://github.com/your-username/sunsynk-power-flow-card.git 18 | 19 | ******************* 20 | Create Topic Branch 21 | ******************* 22 | 23 | You should always work on a new topic branch for each feature / bug you are working on. 24 | Also you must ensure that you have pulled the latest version from upstream see below. 25 | 26 | Start by setting up an upstream remote, this will be used to pull the latest version 27 | from the main repository: 28 | 29 | .. code-block:: bash 30 | 31 | git remote add upstream https://github.com/slipx06/sunsynk-power-flow-card 32 | 33 | 34 | Checkout the master branch and pull the latest upstream version: 35 | 36 | .. code-block:: bash 37 | 38 | git checkout master 39 | git fetch upstream 40 | git merge upstream/master 41 | git push 42 | 43 | Your fork should now be in sync with the main repository, now a new branch 44 | is required for development. 45 | 46 | .. code-block:: bash 47 | 48 | git checkout -b _ 49 | git checkout -b 100_Fix-the-bug 50 | 51 | 52 | .. note:: 53 | 54 | The branch should have a relevant short name starting with the issue number 55 | and then having a name for the fix / feature as shown in the example above. 56 | 57 | ******************** 58 | Install Dependencies 59 | ******************** 60 | 61 | From the cloned repository, run the command to install the requirements: 62 | 63 | .. code-block:: bash 64 | 65 | yarn install 66 | 67 | ******************** 68 | Make changes & Build 69 | ******************** 70 | 71 | #. Any changes to the card should be made in the folder ``src`` 72 | #. Update the version number in ``package.json`` 73 | #. Run the command ``yarn run build`` to create the latest distribution file 74 | 75 | ******* 76 | Testing 77 | ******* 78 | 79 | There are no automated tests for this project, however it is expected that any 80 | development work is tested against a HA Server with working inverter integration, 81 | this ensures no adverse impact is added with the feature or bugfix. 82 | 83 | ********** 84 | Versioning 85 | ********** 86 | 87 | This project follows `Semantic Versioning `_ 88 | 89 | **MAJOR.MINOR.PATCH** 90 | 91 | In the context of semantic versioning, the following should apply: 92 | 93 | * **Major** - A breaking change that requires user invervention, or a change to a 94 | default value. 95 | * **Minor** - A change that does not require intervention, or adds additional 96 | functionality in a backwards compatible manner. 97 | * **Patch** - A change that resolves a specific bug. 98 | 99 | All changes are tracked in the `release notes `_ 100 | 101 | 102 | ************** 103 | Commit Changes 104 | ************** 105 | 106 | Once you are happy with the changes, these can be committed: 107 | 108 | .. code-block:: bash 109 | 110 | git add . 111 | git commit -v -m "feat: 100 Added new feature" 112 | 113 | .. note:: 114 | 115 | Commit messages should follow `conventional commits `_ 116 | this ensures clear commit messages within the repository. 117 | 118 | 119 | ******************* 120 | Submit Pull Request 121 | ******************* 122 | 123 | Once development & testing are completed a pull request can be submitted for 124 | the change that is required, ensure that all tests are passing and once they 125 | are a member of the team will review the request, test and merge if appropriate 126 | -------------------------------------------------------------------------------- /docs/contribute/docs.rst: -------------------------------------------------------------------------------- 1 | ############# 2 | Documentation 3 | ############# 4 | 5 | The documentation for this repo is built using Sphinx, the site is re-built 6 | on each release of the card and pushed to github pages. 7 | 8 | ***************** 9 | How to Contribute 10 | ***************** 11 | 12 | There are two ways of contributing to the documentation: 13 | 14 | #. Editing the files within the ``docs`` folder. 15 | #. Raising an issue for something to be fixed on the next release. 16 | 17 | The documentation utilises `Sphinx RestructuredText `_ 18 | 19 | ************ 20 | Adding pages 21 | ************ 22 | 23 | To add new pages, add a new file in the appropriate directory, and then add a reference 24 | to the ``toc.rst`` file under the correct heading. 25 | 26 | You can test the pages added by running the command ``yarn run docs-build`` 27 | -------------------------------------------------------------------------------- /docs/examples/foxess.rst: -------------------------------------------------------------------------------- 1 | ################ 2 | FoxESS Inverter 3 | ################ 4 | 5 | ****************************************************************************************** 6 | Example 1 - Integration via https://github.com/nathanmarlor/foxess_modbus 7 | ****************************************************************************************** 8 | 9 | .. code-block:: yaml 10 | :linenos: 11 | 12 | type: custom:sunsynk-power-flow-card 13 | cardstyle: full 14 | card_width: 80% 15 | show_solar: true 16 | inverter: 17 | auto_scale: true 18 | colour: '#b6baa9' 19 | modern: true 20 | model: foxess 21 | autarky: power 22 | battery: 23 | energy: 33000 24 | shutdown_soc: 35 25 | colour: pink 26 | max_power: 9000 27 | show_daily: true 28 | auto_scale: true 29 | solar: 30 | show_daily: true 31 | mppts: 3 32 | auto_scale: true 33 | max_power: 5000 34 | pv1_name: 1 South F 35 | pv2_name: 2 South R 36 | pv3_name: 3 North 37 | load: 38 | show_daily: false 39 | auto_scale: true 40 | additional_loads: 2 41 | load1_name: HW 42 | load1_icon: mdi:water-boiler 43 | load2_name: Kitchen 44 | load2_icon: mdi:stove 45 | show_aux: true 46 | aux_name: Climate 47 | aux_loads: 2 48 | aux_load1_name: Mitsu AC/Heat 49 | aux_load1_icon: mdi:heat-pump-outline 50 | aux_load2_name: Mirror Heater 51 | aux_load2_icon: mdi:mirror-rectangle 52 | essential_name: Main 53 | grid: 54 | show_daily_buy: true 55 | show_daily_sell: true 56 | max_power: 12000 57 | auto_scale: true 58 | show_nonessential: true 59 | nonessential_name: EV 60 | nonessential_icon: mdi:ev-station 61 | invert_grid: true 62 | entities: 63 | use_timer_248: 'no' 64 | priority_load_243: 'no' 65 | inverter_voltage_154: sensor.emontx4_vrms 66 | inverter_current_164: sensor.foxess_rcurrent 67 | inverter_power_175: sensor.foxess_rpower 68 | grid_connected_status_194: sensor.foxess_inverter_state 69 | inverter_status_59: sensor.foxess_inverter_state 70 | day_battery_charge_70: sensor.foxess_battery_charge_today 71 | day_battery_discharge_71: sensor.foxess_battery_discharge_today 72 | battery_voltage_183: sensor.foxess_batvolt 73 | battery_soc_184: sensor.foxess_battery_soc 74 | battery_power_190: sensor.foxess_invbatpower 75 | battery_current_191: sensor.foxess_invbatcurrent 76 | day_grid_import_76: sensor.foxess_grid_consumption_energy_today 77 | day_grid_export_77: sensor.foxess_feed_in_energy_today 78 | grid_power_169: sensor.foxess_load_power 79 | grid_ct_power_172: sensor.foxess_grid_ct 80 | day_load_energy_84: sensor.foxess_load_energy_today 81 | essential_power: sensor.essential_total_power 82 | nonessential_power: sensor.emontx4_p3 83 | aux_power_166: sensor.aux_total_power 84 | day_pv_energy_108: sensor.foxess_solar_energy_today 85 | pv1_power_186: sensor.foxess_pv1_power 86 | pv2_power_187: sensor.foxess_pv2_power 87 | pv3_power_188: sensor.foxess_pv3_power 88 | pv1_voltage_109: sensor.foxess_pv1_voltage 89 | pv1_current_110: sensor.foxess_pv1_current 90 | pv2_voltage_111: sensor.foxess_pv2_voltage 91 | pv2_current_112: sensor.foxess_pv2_current 92 | pv3_voltage_113: sensor.foxess_pv3_voltage 93 | pv3_current_114: sensor.foxess_pv3_current 94 | nonessential_load1: sensor.emontx4_p3 95 | essential_load1: sensor.emontx4_p8 96 | essential_load2: sensor.kitchen_power 97 | aux_load1: sensor.emontx4_p12 98 | aux_load2: sensor.shlyclkrm_heater_power 99 | aux_load2_extra: sensor.shlycloakroom_temperature_2 100 | energy_cost_buy: sensor.octopus_energy_electricity_xxx_yyy_current_rate 101 | energy_cost_sell: sensor.octopus_energy_electricity_xxx_yyy_export_current_rate 102 | -------------------------------------------------------------------------------- /docs/examples/goodwe.rst: -------------------------------------------------------------------------------- 1 | ################ 2 | Goodwe Inverter 3 | ################ 4 | 5 | ********* 6 | Example 1 7 | ********* 8 | 9 | .. code-block:: yaml 10 | :linenos: 11 | 12 | type: custom:sunsynk-power-flow-card 13 | cardstyle: lite 14 | large_font: true 15 | title: Goodwe 16 | title_colour: grey 17 | title_size: 32px 18 | show_solar: true 19 | show_battery: true 20 | show_grid: true 21 | inverter: 22 | modern: false 23 | colour: grey 24 | autarky: energy 25 | model: goodwe_gridmode 26 | three_phase: true 27 | battery: 28 | energy: 10650 29 | shutdown_soc: sensor.goodwe_shutdown_soc 30 | invert_power: false 31 | show_daily: true 32 | max_power: 5400 33 | show_absolute: false 34 | auto_scale: true 35 | solar: 36 | show_daily: true 37 | display_mode: 3 38 | mppts: 2 39 | animation_speed: 9 40 | max_power: 5400 41 | pv1_name: East 42 | pv2_name: West 43 | load: 44 | show_daily: true 45 | grid: 46 | show_nonessential: true 47 | invert_grid: false 48 | export_colour: green 49 | entities: 50 | day_battery_discharge_71: sensor.today_battery_discharge 51 | day_battery_charge_70: sensor.today_battery_charge 52 | day_load_energy_84: sensor.today_load 53 | day_pv_energy_108: sensor.today_s_pv_generation 54 | inverter_voltage_154: sensor.on_grid_l1_voltage 55 | inverter_voltage_L2: sensor.on_grid_l2_voltage 56 | inverter_voltage_L3: sensor.on_grid_l3_voltage 57 | load_frequency_192: sensor.meter_frequency 58 | inverter_current_164: sensor.on_grid_l1_current 59 | inverter_current_L2: sensor.on_grid_l2_current 60 | inverter_current_L3: sensor.on_grid_l3_current 61 | inverter_power_175: zero 62 | pv1_power_186: sensor.pv1_power 63 | pv2_power_187: sensor.pv2_power 64 | pv_total: sensor.pv_power 65 | battery_voltage_183: sensor.battery_voltage 66 | battery_soc_184: sensor.battery_state_of_charge 67 | battery_power_190: sensor.battery_power 68 | battery_current_191: sensor.battery_current 69 | essential_power: sensor.house_consumption 70 | load_power_L1: sensor.load_l1 71 | load_power_L2: sensor.load_l2 72 | load_power_L3: sensor.load_l3 73 | grid_ct_power_172: sensor.active_power_l1 74 | grid_ct_power_L2: sensor.active_power_l2 75 | grid_ct_power_L3: sensor.active_power_l3 76 | grid_ct_power_total: sensor.active_power 77 | pv1_voltage_109: sensor.pv1_voltage 78 | pv1_current_110: sensor.pv1_current 79 | pv2_voltage_111: sensor.pv2_voltage 80 | pv2_current_112: sensor.pv2_current 81 | grid_connected_status_194: sensor.grid_mode_code 82 | inverter_status_59: sensor.work_mode_code 83 | battery_status: sensor.battery_mode_code 84 | total_pv_generation: sensor.total_pv_generation 85 | battery_temp_182: sensor.battery_temperature 86 | radiator_temp_91: sensor.inverter_temperature_radiator 87 | dc_transformer_temp_90: sensor.inverter_temperature_air 88 | day_grid_import_76: sensor.energy_buy_daily 89 | day_grid_export_77: sensor.energy_sell_daily 90 | remaining_solar: sensor.energy_production_today_total 91 | energy_cost_buy: sensor.spot_price_buy 92 | energy_cost_sell: sensor.spot_price_sell 93 | 94 | .. note:: 95 | 96 | The Goodwe integration does not provide a sensor for ``shutdown_soc``. 97 | A template sensor can be created using the provided depth of discharge (DOD) sensor i.e ``number.depth_of_discharge_on_grid``. 98 | See example below. Note that the depth of discharge sensor name may vary depending on your HA language. 99 | 100 | .. code-block:: bash 101 | 102 | - sensor: 103 | - name: GoodWe Shutdown SOC 104 | unique_id: goodwe_shutdown_soc 105 | unit_of_measurement: "%" 106 | icon: mdi:battery-arrow-down 107 | state: "{{100 - states('number.depth_of_discharge_on_grid') | int }}" -------------------------------------------------------------------------------- /docs/examples/huawei_packages/huawei_solar_electricity_costs_AU_Synerg_Energy_Flate_Rate.yaml: -------------------------------------------------------------------------------- 1 | # Based upon Western Australian energy retailer 'Synergy Energy' and their Flat Rate Tariff Plan. 2 | # v1.0 - Updated 4 January 2024 for Synergy Energy Rates 3 | 4 | template: 5 | - sensor: 6 | 7 | # If you set the prices here, remember to ALSO set them in the 'electricity_import_rate_???' and daily supply charge sensors. 8 | # 9 | # Grid Import 24x7 : $0.30812 p/kWh. 10 | # Grid Export (FIT) : $0.07 p/kWh. 11 | # 12 | # This sensor says if the power_meter_active_power is <=0 (Importing from Grid) then the sensor reports the RATE1 price 13 | # Alternatively if the power_meter_active_power >0 (Exporting to Grid) then the sensor reports the RATE2 price, that is set to ZERO. 14 | # This allows the electricity_price sensor to then be used as a source to calculate things like 'How much did the HVAC cost' that reflects 15 | # the HVAC using power that can change between paid for grid power, and free power from your solar plant. 16 | # 17 | - name: "Electricity - Price" 18 | unique_id: electricity_price 19 | unit_of_measurement: "$/kWh" 20 | device_class: monetary 21 | state: > 22 | {## Enter cost per kWh rates below. Rate 1 ##} 23 | {% set rate1 = 0.30812 %} 24 | {% set rate2 = 0.0 %} 25 | {% set power_meter_active_power = states('sensor.power_meter_active_power') | float %} 26 | {% if power_meter_active_power <= 0 %} {{rate1}} 27 | {% else %} {{rate2}} 28 | {% endif %} 29 | 30 | # Current FIT rate, rate 1 & 2 allows setting a second rate if variable FIT rates (i.e. $0.15 for first 10kWh, then $0.07 thereafter) 31 | 32 | - name: "Electricity - FIT" 33 | unique_id: electricity_fit 34 | unit_of_measurement: "$/kWh" 35 | device_class: monetary 36 | icon: mdi:cash-plus 37 | state: > 38 | {## Enter compensation per kWh rates below ##} 39 | {% set rate1 = 0.07 %} 40 | {% set rate2 = 0.07 %} 41 | {% set fit_exported_today = states('sensor.hs_grid_exported_daily') | float %} 42 | {% if fit_exported_today <= 10 %} {{rate1}} 43 | {% else %} {{rate2}} 44 | {% endif %} 45 | 46 | 47 | #################################### 48 | # IMPORT UTILITY METERS 49 | # See: https://github.com/zeronounours/HA-custom-component-energy-meter 50 | 51 | energy_meter: 52 | 53 | hs_grid_imported_daily: 54 | unique_id: hs_grid_imported_daily 55 | name: HS Grid - Imported Daily 56 | source: sensor.power_meter_consumption 57 | source_type: from_grid 58 | cycle: daily 59 | price_entity: &electricity-price sensor.electricity_price 60 | 61 | hs_grid_imported_monthly: 62 | unique_id: hs_grid_imported_monthly 63 | name: HS Grid - Imported Monthly 64 | source: sensor.power_meter_consumption 65 | source_type: from_grid 66 | cycle: monthly 67 | price_entity: *electricity-price 68 | 69 | hs_grid_imported_yearly: 70 | unique_id: hs_grid_imported_yearly 71 | name: HS Grid - Imported Yearly 72 | source: sensor.power_meter_consumption 73 | source_type: from_grid 74 | cycle: yearly 75 | price_entity: *electricity-price 76 | 77 | 78 | ######################### 79 | # EXPORT UTILITY METERS 80 | # See: https://github.com/zeronounours/HA-custom-component-energy-meter 81 | 82 | hs_grid_exported_daily: 83 | unique_id: hs_grid_exported_daily 84 | name: HS Grid - Exported Daily 85 | source: sensor.power_meter_exported 86 | source_type: to_grid 87 | cycle: daily 88 | price_entity: &electricity_fit sensor.electricity_fit 89 | 90 | hs_grid_exported_monthly: 91 | unique_id: hs_grid_exported_monthly 92 | name: HS Grid - Exported Monthly 93 | source: sensor.power_meter_exported 94 | source_type: to_grid 95 | cycle: monthly 96 | price_entity: *electricity_fit 97 | 98 | hs_grid_exported_yearly: 99 | unique_id: hs_grid_exported_yearly 100 | name: HS Grid - Exported Yearly 101 | source: sensor.power_meter_exported 102 | source_type: to_grid 103 | cycle: yearly 104 | price_entity: *electricity_fit 105 | 106 | ####################### 107 | 108 | -------------------------------------------------------------------------------- /docs/examples/huawei_packages/huawei_solar_electricity_costs_AU_Synerg_Energy_Flate_with_ToU_ExportRate.yaml: -------------------------------------------------------------------------------- 1 | # Based upon Western Australian energy retailer 'Synergy Energy' and their Flat Rate Tariff Plan. 2 | # v1.0 - Updated 31 March 2024 for Synergy Energy Rates with Flat Rate for Usage, but 3-9pm Peak Export and 'other times' export rate. 3 | 4 | template: 5 | - sensor: 6 | 7 | # If you set the prices here, remember to ALSO set them in the 'electricity_import_rate_???' and daily supply charge sensors. 8 | # 9 | # Grid Import 24x7 : $0.30812 p/kWh. 10 | # Grid Export (FIT) : $0.07 p/kWh. 11 | # 12 | # This sensor says if the power_meter_active_power is <=0 (Importing from Grid) then the sensor reports the RATE1 price 13 | # Alternatively if the power_meter_active_power >0 (Exporting to Grid) then the sensor reports the RATE2 price, that is set to ZERO. 14 | # This allows the electricity_price sensor to then be used as a source to calculate things like 'How much did the HVAC cost' that reflects 15 | # the HVAC using power that can change between paid for grid power, and free power from your solar plant. 16 | # 17 | - name: "Electricity - Price" 18 | unique_id: electricity_price 19 | unit_of_measurement: "$/kWh" 20 | device_class: monetary 21 | state: > 22 | {## Enter cost per kWh rates below. Rate 1 ##} 23 | {% set rate1 = 0.30812 %} 24 | {% set rate2 = 0.0 %} 25 | {% set power_meter_active_power = states('sensor.power_meter_active_power') | float %} 26 | {% if power_meter_active_power <= 0 %} {{rate1}} 27 | {% else %} {{rate2}} 28 | {% endif %} 29 | 30 | # Current FIT rate, rate 1 & 2 allows setting a second rate if variable FIT rates (i.e. $0.15 for first 10kWh, then $0.07 thereafter) 31 | 32 | - name: "Electricity - FIT" 33 | unique_id: electricity_fit 34 | unit_of_measurement: "$/kWh" 35 | device_class: monetary 36 | icon: mdi:cash-plus 37 | state: 38 | {%set Peak_Export = 0.10 %} 39 | {%set Base_Export = 0.0225 %} 40 | {%set H = as_timestamp(now())|timestamp_custom ('%-H')|float(0) %} {##Hour (24-hour clock) as a decimal number. 0, 1, 2, ... 23##} 41 | {##Peak FIT 3-9pm##} 42 | {% if (H >= 15) and (H < 21) %} {{Peak_Export}} 43 | {##Base FIT all other timesk##} 44 | {% else %} {{Base_Export}} 45 | {% endif %} 46 | 47 | 48 | #################################### 49 | # IMPORT UTILITY METERS 50 | # See: https://github.com/zeronounours/HA-custom-component-energy-meter 51 | 52 | energy_meter: 53 | 54 | hs_grid_imported_daily: 55 | unique_id: hs_grid_imported_daily 56 | name: HS Grid - Imported Daily 57 | source: sensor.power_meter_consumption 58 | source_type: from_grid 59 | cycle: daily 60 | price_entity: &electricity-price sensor.electricity_price 61 | 62 | hs_grid_imported_monthly: 63 | unique_id: hs_grid_imported_monthly 64 | name: HS Grid - Imported Monthly 65 | source: sensor.power_meter_consumption 66 | source_type: from_grid 67 | cycle: monthly 68 | price_entity: *electricity-price 69 | 70 | hs_grid_imported_yearly: 71 | unique_id: hs_grid_imported_yearly 72 | name: HS Grid - Imported Yearly 73 | source: sensor.power_meter_consumption 74 | source_type: from_grid 75 | cycle: yearly 76 | price_entity: *electricity-price 77 | 78 | 79 | ######################### 80 | # EXPORT UTILITY METERS 81 | # See: https://github.com/zeronounours/HA-custom-component-energy-meter 82 | 83 | hs_grid_exported_daily: 84 | unique_id: hs_grid_exported_daily 85 | name: HS Grid - Exported Daily 86 | source: sensor.power_meter_exported 87 | source_type: to_grid 88 | cycle: daily 89 | price_entity: &electricity_fit sensor.electricity_fit 90 | 91 | hs_grid_exported_monthly: 92 | unique_id: hs_grid_exported_monthly 93 | name: HS Grid - Exported Monthly 94 | source: sensor.power_meter_exported 95 | source_type: to_grid 96 | cycle: monthly 97 | price_entity: *electricity_fit 98 | 99 | hs_grid_exported_yearly: 100 | unique_id: hs_grid_exported_yearly 101 | name: HS Grid - Exported Yearly 102 | source: sensor.power_meter_exported 103 | source_type: to_grid 104 | cycle: yearly 105 | price_entity: *electricity_fit 106 | 107 | ####################### 108 | -------------------------------------------------------------------------------- /docs/examples/huawei_packages/sunsynk_power_flow_card_derived_sensors.yaml: -------------------------------------------------------------------------------- 1 | template: 2 | 3 | ############################# 4 | # 5 | # Sunsynk Power Flow Card - https://github.com/slipx06/sunsynk-power-flow-card 6 | # Derived sensors that are used upon the card 7 | # 8 | 9 | # Sensor to show the House Active Power Consumption less the sunsynk_card_aux_active_power and non_essential_active_power sensors. 10 | # Max 0 has been added, as delays in other sensors updating can mean this sensor suddenly returns a negative output for a short time. 11 | # Used by card entry: essential_power 12 | - sensor: 13 | - name: "House Consumption - Power - Less AUX Non-Essential" 14 | unique_id: house_consumption_power_less_aux_non_essential 15 | unit_of_measurement: "W" 16 | device_class: "power" 17 | state_class: measurement 18 | state: >- 19 | {{ max(0, (states('sensor.house_consumption_power')|float - 20 | states('sensor.sunsynk_card_aux_active_power')|float - 21 | states('sensor.sunsynk_card_non_essential_active_power')|float) | round(2)) }} 22 | availability: >- 23 | {{ (states('sensor.house_consumption_power')|is_number) 24 | and (states('sensor.sunsynk_card_aux_active_power')|is_number) 25 | and (states('sensor.sunsynk_card_non_essential_active_power')|is_number) }} 26 | 27 | 28 | # Sensor to show the Active Power for all the GPO's in the house, less the Network Rack and Fan, Servers and Study UPS sensors (or other sensors you've factored in already). 29 | # Used card entry: essential_load2 30 | - sensor: 31 | - name: "GPO ALL - Active Power Less Known" 32 | unique_id: gpo_all_active_power_less_known 33 | unit_of_measurement: "W" 34 | device_class: "power" 35 | state_class: measurement 36 | state: >- 37 | {% set gpo_total_active_power = states('sensor.gpo_total')|float(0) %} 38 | {% set all_it_active_power = states('sensor.it_hardware_active_power')|float(0) %} 39 | {% set whitegoods = states('sensor.gpo_clothes_dryer_power')|float(0) %} 40 | {{ max(0, (gpo_total_active_power - ( all_it_active_power + whitegoods ))|float(0)|round(0)) }} 41 | availability: >- 42 | {{ (states('sensor.gpo_total')|is_number) 43 | and (states('sensor.it_hardware_active_power')|is_number) 44 | and (states('sensor.gpo_clothes_dryer_power')|is_number) }} 45 | 46 | 47 | # Queries the group sensor.sunsynk_card_aux_active_power and if the ouput of that sensor is >0 (showing power being used) 48 | # then outputs 'on', else 'off' 49 | # Used card entry: aux_connected_status 50 | - binary_sensor: 51 | - name: "Sunsynk Card - AUX - Connected Status" 52 | unique_id: sunsynk_card_aux_connected_status 53 | device_class: power 54 | state: >- 55 | {{ 'on' if states('sensor.sunsynk_card_aux_active_power') | float(default=0) > 0 else 'off' }} 56 | 57 | 58 | ################################ 59 | 60 | energy_meter: 61 | 62 | # Used to convert the above Riemann sum sensor into an ENERGY sensor that RESETS to ZERO DAILY. 63 | house_consumption_energy_meter_less_aux_non_essential: 64 | unique_id: house_consumption_energy_meter_less_aux_non_essential 65 | name: "House Consumption - Energy Meter - Less AUX Non-Essential" 66 | source: sensor.house_consumption_energy_less_aux_non_essential 67 | cycle: daily 68 | 69 | # Optional: Used to create and energy_meter with total kWh and cost sensor, to track big energy users. 70 | # Alternatives or additional could be to use for items such as EV Charger. 71 | # hvac_energy_imported_daily: 72 | # unique_id: hvac_energy_imported_daily 73 | # name: HVAC Energy Imported - Daily 74 | # source: sensor.hvac_energy 75 | # source_type: from_grid 76 | # cycle: daily 77 | # price_entity: &electricity-price sensor.electricity_price 78 | 79 | # hvac_energy_imported_monthly: 80 | # unique_id: hvac_energy_imported_monthly 81 | # name: HVAC Energy Imported - Monthly 82 | # source: sensor.hvac_energy 83 | # source_type: from_grid 84 | # cycle: monthly 85 | # price_entity: *electricity-price 86 | 87 | # hvac_energy_imported_yearly: 88 | # unique_id: hvac_energy_imported_yearly 89 | # name: HVAC Energy Imported - Yearly 90 | # source: sensor.hvac_energy 91 | # source_type: from_grid 92 | # cycle: yearly 93 | # price_entity: *electricity-price 94 | 95 | 96 | ########### Start: Helpers - Riemann Sum to conver Power (W) to Energy (kWh) ########### 97 | 98 | # Riemann Sum sensor to take "House Consumption Less Known - Power" and convert it into ENERGY sensor, 99 | # this sensor is then used by the above energy_meter sensors. 100 | sensor: 101 | - platform: integration 102 | name: "House Consumption - Energy - Less AUX Non-Essential" 103 | unique_id: house_consumption_energy_less_aux_non_essential 104 | source: sensor.house_consumption_power_less_aux_non_essential 105 | round: 3 106 | unit_prefix: k 107 | unit_time: h 108 | method: left 109 | 110 | 111 | ######################### End of File ######################### 112 | -------------------------------------------------------------------------------- /docs/examples/lux.rst: -------------------------------------------------------------------------------- 1 | ############## 2 | Lux Inverter 3 | ############## 4 | 5 | ********* 6 | Example 1 7 | ********* 8 | 9 | .. code-block:: yaml 10 | :linenos: 11 | 12 | type: custom:sunsynk-power-flow-card 13 | cardstyle: lite 14 | show_solar: true 15 | inverter: 16 | model: lux 17 | battery: 18 | show: true 19 | energy: 12800 20 | shutdown_soc: 1 21 | show_daily: true 22 | invert_power: true 23 | solar: 24 | show_daily: true 25 | mppts: 2 26 | pv1_name: Rear 27 | pv2_name: Front 28 | load: 29 | show_daily: true 30 | grid: 31 | show_daily_buy: true 32 | show_daily_sell: true 33 | show_nonessential: false 34 | invert_grid: true 35 | additional_loads: 2 36 | entities: 37 | inverter_voltage_154: sensor.lux_grid_voltage_live 38 | load_frequency_192: sensor.lux_grid_frequency_live 39 | inverter_current_164: sensor.inverter_output_current 40 | inverter_status_59: sensor.lux_status 41 | inverter_power_175: sensor.lux_battery_flow_live 42 | day_battery_charge_70: sensor.lux_battery_charge_daily 43 | day_battery_discharge_71: sensor.lux_battery_discharge_daily 44 | battery_voltage_183: sensor.lux_battery_voltage_live 45 | battery_soc_184: sensor.lux_battery 46 | battery_power_190: sensor.lux_battery_flow_live 47 | battery_current_191: sensor.lux_battery_capacity_ah 48 | grid_power_169: sensor.lux_grid_flow_live 49 | day_grid_import_76: sensor.lux_power_from_grid_daily 50 | day_grid_export_77: sensor.lux_power_to_grid_daily 51 | grid_ct_power_172: sensor.lux_grid_flow_live 52 | day_load_energy_84: sensor.lux_power_from_inverter_to_home_daily 53 | essential_power: sensor.lux_home_consumption_live 54 | nonessential_power: none 55 | aux_power_166: sensor.aux_output_power 56 | day_pv_energy_108: sensor.lux_solar_output_daily 57 | pv_total: sensor.lux_solar_output_live 58 | pv1_power_186: sensor.lux_solar_output_array_1_live 59 | pv2_power_187: sensor.lux_solar_output_array_2_live 60 | pv1_voltage_109: sensor.lux_solar_voltage_array_1_live 61 | pv1_current_110: none 62 | pv2_voltage_111: sensor.lux_solar_voltage_array_2_live 63 | pv2_current_112: none 64 | radiator_temp_91: sensor.lux_radiator_1_temperature_live 65 | dc_transformer_temp_90: sensor.lux_radiator_2_temperature_live 66 | remaining_solar: sensor.forecast_remaining_today 67 | energy_cost: sensor.octopus_energy_electricity_20e5081533_2380002009185_current_rate 68 | 69 | ************************************************************************************ 70 | Example 2 using the lxp-bridge integration (https://github.com/celsworth/lxp-bridge) 71 | ************************************************************************************ 72 | 73 | .. code-block:: yaml 74 | :linenos: 75 | 76 | type: custom:sunsynk-power-flow-card 77 | cardstyle: lite 78 | show_solar: true 79 | inverter: 80 | model: lux 81 | battery: 82 | energy: 12800 83 | shutdown_soc: 20 84 | show_daily: true 85 | invert_power: true 86 | solar: 87 | show_daily: true 88 | mppts: 2 89 | pv1_name: PV1 90 | pv2_name: PV2 91 | load: 92 | show_daily: true 93 | grid: 94 | show_daily_buy: true 95 | show_daily_sell: true 96 | show_nonessential: false 97 | invert_grid: true 98 | additional_loads: 0 99 | entities: 100 | inverter_voltage_154: sensor.lxp_baXXXXXXXX_grid_voltage 101 | load_frequency_192: sensor.lxp_baXXXXXXXX_eps_frequency 102 | inverter_current_164: NONE 103 | inverter_status_59: NONE 104 | inverter_power_175: sensor.lxp_baXXXXXXXX_inverter_power 105 | day_battery_charge_70: sensor.lxp_baXXXXXXXX_battery_charge_today 106 | day_battery_discharge_71: sensor.lxp_baXXXXXXXX_battery_discharge_today 107 | battery_voltage_183: sensor.lxp_baXXXXXXXX_battery_voltage 108 | battery_soc_184: sensor.lxp_baXXXXXXXX_battery_percentage 109 | battery_power_190: sensor.lxp_baXXXXXXXX_battery_power_discharge_is_negative 110 | battery_current_191: NONE 111 | grid_power_169: sensor.lxp_baXXXXXXXX_grid_power_export_is_negative 112 | day_grid_import_76: sensor.lxp_baXXXXXXXX_energy_from_grid_today 113 | day_grid_export_77: sensor.lxp_baXXXXXXXX_energy_to_grid_today 114 | grid_ct_power_172: NONE 115 | day_load_energy_84: sensor.lxp_baXXXXXXXX_energy_of_inverter_today 116 | essential_power: sensor.lxp_baXXXXXXXX_inverter_power 117 | nonessential_power: NONE 118 | aux_power_166: NONE 119 | day_pv_energy_108: sensor.lxp_baXXXXXXXX_pv_generation_today 120 | pv_total: sensor.lxp_baXXXXXXXX_power_pv_array 121 | pv1_power_186: sensor.lxp_baXXXXXXXX_power_pv_string_1 122 | pv2_power_187: sensor.lxp_baXXXXXXXX_power_pv_string_2 123 | pv1_voltage_109: sensor.lxp_baXXXXXXXX_voltage_pv_string_1 124 | pv1_current_110: NONE 125 | pv2_voltage_111: sensor.lxp_baXXXXXXXX_voltage_pv_string_2 126 | pv2_current_112: NONE 127 | radiator_temp_91: sensor.lxp_baXXXXXXXX_radiator_1_temperature 128 | dc_transformer_temp_90: sensor.lxp_baXXXXXXXX_radiator_2_temperature 129 | remaining_solar: sensor.forecast_remaining_today 130 | energy_cost: NONE 131 | 132 | .. note:: 133 | 134 | Replace ``baXXXXXXXX`` with your wifi dongle number -------------------------------------------------------------------------------- /docs/examples/powmr.rst: -------------------------------------------------------------------------------- 1 | ################# 2 | PowMr Inverters 3 | ################# 4 | 5 | Integration via https://github.com/odya/esphome-powmr-hybrid-inverter 6 | 7 | ***************************************************************************************************** 8 | Example - PowMr OW-HVM2.0H-12V inverter with 2.4kW Battery, 1.8kW Solar and Grid (used in a Caravan) 9 | ***************************************************************************************************** 10 | 11 | .. code-block:: yaml 12 | :linenos: 13 | 14 | type: custom:sunsynk-power-flow-card 15 | cardstyle: lite 16 | large_font: false 17 | title: PowMr Inverter - Power Monitor 18 | title_size: 12px 19 | show_solar: true 20 | show_battery: true 21 | show_grid: true 22 | decimal_places: 2 23 | dynamic_line_width: true 24 | max_line_width: 8 25 | inverter: 26 | modern: false 27 | colour: grey 28 | autarky: power 29 | auto_scale: true 30 | model: powmr 31 | three_phase: false 32 | battery: 33 | energy: 2400 34 | max_power: 2000 35 | shutdown_soc: 0 36 | colour: '#9A64A0' 37 | show_daily: true 38 | invert_power: true 39 | show_absolute: true 40 | hide_soc: false 41 | show_remaining_energy: true 42 | dynamic_colour: true 43 | linear_gradient: true 44 | solar: 45 | show_daily: true 46 | mppts: 1 47 | maxpower: your-panel-total-watts-here 48 | pv1_name: Solar PV 49 | auto_scale: true 50 | display_mode: 2 51 | dynamic_colour: true 52 | load: 53 | show_daily: true 54 | max_power: 2000 55 | show_daily_aux: true 56 | show_aux: false 57 | invert_aux: false 58 | show_absolute_aux: false 59 | aux_name: Generator 60 | aux_type: gen 61 | aux_colour: '#5490c2' 62 | aux_off_colour: brown 63 | aux_loads: 0 64 | aux_load1_icon: '' 65 | aux_load2_icon: '' 66 | animation_speed: 4 67 | essential_name: Caravan 68 | additional_loads: 3 69 | load1_name: Kitchen 70 | load2_name: Bedroom 71 | load3_name: Lights 72 | load1_icon: mdi:gas-burner 73 | load2_icon: mdi:bed-outline 74 | load3_icon: mdi:light-flood-down 75 | load4_icon: '' 76 | auto_scale: true 77 | dynamic_icon: true 78 | dynamic_colour: true 79 | invert_load: false 80 | aux_dynamic_colour: true 81 | grid: 82 | grid_name: Utility Power 83 | max_power: 2000 84 | colour: '#FF2400' 85 | export_colour: green 86 | no_grid_colour: null 87 | grid_off_colour: '#e7d59f' 88 | show_daily_buy: true 89 | show_daily_sell: false 90 | show_nonessential: true 91 | invert_grid: false 92 | nonessential_name: Non Essential 93 | nonessential_icon: none 94 | additional_loads: 1 95 | load1_name: AirCon 96 | load2_name: EV 97 | load1_icon: mdi:fan 98 | load2_icon: mdi:car 99 | animation_speed: 7 100 | auto_scale: true 101 | dynamic_icon: true 102 | dynamic_colour: true 103 | energy_cost_decimals: 3 104 | entities: 105 | day_battery_charge_70: sensor.battery_charge_daily 106 | day_battery_discharge_71: sensor.battery_discharge_daily 107 | day_load_energy_84: sensor.powmr_inverter_load_consumed_daily 108 | day_grid_import_76: sensor.powmr_inverter_grid_imported_daily 109 | day_pv_energy_108: sensor.powmr_inverter_pv_yield_daily 110 | day_aux_energy: sensor.aircon_energy_daily_kwh 111 | inverter_voltage_154: sensor.powmr_inverter_load_voltage 112 | load_frequency_192: sensor.powmr_inverter_load_frequency 113 | grid_power_169: sensor.powmr_inverter_load_consumed_daily 114 | total_pv_generation: sensor.powmr_inverter_pv_yield_daily 115 | inverter_current_164: sensor.powmr_inverter_load_current 116 | inverter_power_175: sensor.powmr_inverter_load_power 117 | inverter_status_59: sensor.powmr_inverter_charger_status 118 | pv1_power_186: sensor.powmr_inverter_pv_power 119 | environment_temp: sensor._temp 120 | remaining_solar: sensor.energy_production_today_remaining 121 | pv1_voltage_109: sensor.powmr_inverter_pv_voltage 122 | pv1_current_110: sensor.powmr_inverter_pv_current 123 | battery_voltage_183: sensor.powmr_inverter_battery_voltage 124 | battery_soc_184: sensor.powmr_inverter_battery_soc 125 | battery_power_190: sensor.powmr_inverter_battery_power 126 | battery_current_191: sensor.powmr_inverter_battery_current 127 | essential_power: sensor.powmr_inverter_load_power 128 | essential_load1: sensor.kitchen_active_power 129 | essential_load2: sensor.bed_av__active_power 130 | essential_load1_extra: sensor.kitchen_temperature 131 | essential_load2_extra: sensor.bedroom_temperature 132 | load_power_L1: sensor.powmr_inverter_load_power 133 | nonessential_power: sensor.sunsynk_card_non_essential_active_power 134 | non_essential_load1: null 135 | non_essential_load2: null 136 | non_essential_load3: null 137 | grid_ct_power_172: sensor.powmr_inverter_grid_power 138 | grid_connected_status_194: sensor.powmr_inverter_grid_active 139 | aux_power_166: sensor.aircon_aux_active_power 140 | aux_load1_extra: sensor.caravan_internal_temperature 141 | aux_load2_extra: sensor.caravan_external_temperature 142 | grid_voltage: sensor.powmr_inverter_grid_voltage 143 | -------------------------------------------------------------------------------- /docs/examples/solax.rst: -------------------------------------------------------------------------------- 1 | ################ 2 | SolaX Inverter 3 | ################ 4 | 5 | ****************************************************************************************** 6 | Example 1 7 | ****************************************************************************************** 8 | 9 | .. code-block:: yaml 10 | :linenos: 11 | 12 | type: custom:sunsynk-power-flow-card 13 | cardstyle: compact 14 | show_solar: true 15 | show_grid: true 16 | show_battery: true 17 | large_font: false 18 | inverter: 19 | auto_scale: false 20 | modern: false 21 | model: solax 22 | battery: 23 | energy: 17600 24 | shutdown_soc: 11 25 | show_daily: true 26 | invert_power: true 27 | max_power: 3750 28 | auto_scale: false 29 | show_absolute: false 30 | hide_soc: false 31 | solar: 32 | show_daily: true 33 | mppts: 1 34 | max_power: 7000 35 | display_mode: 2 36 | animation_speed: 9 37 | dynamic_colour: false 38 | load: 39 | show_daily: true 40 | max_power: 15000 41 | show_aux: false 42 | show_nonessential: true 43 | additional_loads: 2 44 | load1_name: Water 45 | load2_name: EV 46 | load1_icon: mdi:thermometer-water 47 | load2_icon: mdi:ev-station 48 | animation_speed: 9 49 | grid: 50 | show_daily_buy: true 51 | show_daily_sell: true 52 | show_nonessential: false 53 | animation_speed: 9 54 | auto_scale: false 55 | export_colour: 56 | - 194 57 | - 6 58 | - 219 59 | no_grid_colour: 60 | - 189 61 | - 188 62 | - 188 63 | entities: 64 | inverter_status_59: sensor.solax_run_mode 65 | inverter_voltage_154: sensor.solax_inverter_voltage 66 | inverter_current_164: sensor.solax_inverter_current 67 | inverter_power_175: sensor.solax_inverter_power 68 | radiator_temp_91: sensor.solax_inverter_temperature 69 | day_battery_charge_70: sensor.solax_battery_input_energy_today 70 | day_battery_discharge_71: sensor.solax_battery_output_energy_today 71 | battery_voltage_183: sensor.solax_battery_voltage_charge 72 | battery_soc_184: sensor.solax_battery_capacity 73 | battery_power_190: sensor.solax_battery_power_charge 74 | battery_current_191: sensor.solax_battery_current_charge 75 | battery_temp_182: sensor.solax_battery_temperature 76 | day_grid_import_76: sensor.solax_today_s_import_energy 77 | day_grid_export_77: sensor.solax_today_s_export_energy 78 | grid_power_169: sensor.solax_grid_export_import_sum2 79 | grid_ct_power_172: sensor.solax_grid_export_import_sum2 80 | day_load_energy_84: sensor.powerflow_today_house_load 81 | day_pv_energy_108: sensor.solax_today_s_solar_energy 82 | pv1_power_186: sensor.solax_pv_power_1 83 | pv1_voltage_109: sensor.solax_pv_voltage_1 84 | pv1_current_110: sensor.solax_pv_current_1 85 | remaining_solar: sensor.solcast_pv_forecast_forecast_remaining_today 86 | essential_load1: sensor.immersion_current_consumption 87 | essential_load1_extra: sensor.immersion_energy_day 88 | essential_load2: sensor.ev_power_consumption 89 | essential_load2_extra: sensor.ev_fast_charge_day 90 | -------------------------------------------------------------------------------- /docs/examples/solis-codes.csv: -------------------------------------------------------------------------------- 1 | HexDecimal,Status,Inverter display,Status Category,Decimal 2 | 0000H,Normal operation,Generating,Normal,0 3 | 0001H,Open loop operation,OpenRun,Normal,1 4 | 0002H,Waiting,Waiting,Standby,2 5 | 0003H,Initialization,Initializing,SelfTest,3 6 | 1004H,Control off-Grid,Off-Grid,Fault,4100 7 | 1010H,Grid overvoltage,OV-G-V,Alarm,4112 8 | 1011H,Grid undervoltage,UN-G-V,Alarm,4113 9 | 1012H,Grid overfrequency,OV-G-F,Alarm,4114 10 | 1013H,Grid underfrequency,UN-G-F,Alarm,4115 11 | 1014H,Grid impedance is too large,G-IMP,Alarm,4116 12 | 1015H,No Grid,NO-Grid,Alarm,4117 13 | 1016H,Grid imbalance,G-PHASE,Alarm,4118 14 | 1017H,Grid frequency jitter,G-F-FLU,Alarm,4119 15 | 1018H,Grid overcurrent,OV-G-I,Alarm,4120 16 | 1019H,Grid current tracking fault,IGFOL-F,Alarm,4121 17 | 1020H,DC overvoltage,OV-DC,Alarm,4122 18 | 1021H,DC bus overvoltage,OV-BUS,Alarm,4123 19 | 1022H,DC busbar uneven voltage,UNB-BUS,Alarm,4124 20 | 1023H,DC bus undervoltage,UN-BUS,Alarm,4125 21 | 1024H,DC busbar uneven voltage 2,UNB2-BUS,Alarm,4126 22 | 1025H,DC A way overcurrent,OV-DCA-I,Alarm,4127 23 | 1026H,DC B path overcurrent,OV-DCB-I,Alarm,4128 24 | 1027H,DC input disturbance,DC-INTF.,Alarm,4129 25 | 1030H,Grid disturbance,GRID-INTF.,Alarm,4130 26 | 1031H,DSP initialization malfunction protection,INI-FAULT,Fault,4131 27 | 1032H,Temperature protection,OV-TEM,Fault,4132 28 | 1033H,Ground protection,GROUND-FAULT,Fault,4133 29 | 1034H,Leakage current fault,ILeak-FAULT,Fault,4134 30 | 1035H,Relay failure,Relay-FAULT,Fault,4135 31 | 1036H,DSP_B failure protection,DSP-B-FAULT,Fault,4136 32 | 1037H,DC component is too large,DCInj-FAULT,Fault,4137 33 | 1038H,12V undervoltage fault protection,12Power-FAULT,Fault,4138 34 | 1039H,Leakage current self-test protection,ILeak-Check,Fault,4139 35 | 103AH,Under temperature protection,UN-TEM,Fault,4140 36 | 1040H,Arc self-test protection,AFCI-Check,Fault,4140 37 | 1041H,Arc malfunction protection,ARC-FAULT,Fault,4141 38 | 1042H,DSP on-chip SRAM exception,RAM-FAULT,Fault,4142 39 | 1043H,DSP on-chip FLASH exception,FLASH-FAULT,Fault,4143 40 | 1044H,DSP on-chip PC pointer is abnormal,PC-FAULT,Fault,4144 41 | 1045H,DSP key register exception,REG-FAULT,Fault,4145 42 | 1046H,Grid disturbance 02,GRID-INTF02,Alarm,4146 43 | 1047H,Grid current sampling abnormality,IG-AD,Alarm,4147 44 | 1048H,IGBT overcurrent,IGBT-OV-I,Alarm,4148 45 | 1050H,Network side current transient,OV-IgTr,Alarm,4150 46 | 1051H,Battery overvoltage hardware failure,OV-Vbatt-H,Fault,4151 47 | 1052H,LLC hardware overcurrent,OV-ILLC,Fault,4152 48 | 1053H,Battery overvoltage detection,OV-Vbatt,Fault,4153 49 | 1054H,Battery undervoltage detection,UN-Vbatt,Fault,4154 50 | 1055H,Battery no connected,NO-Battery,Fault,4155 51 | 1056H,Bypass overvoltage fault,OV-VBackup,Fault,4156 52 | 1057H,Bypass overload fault,Over-Load,Fault,4157 53 | -------------------------------------------------------------------------------- /docs/examples/victron.rst: -------------------------------------------------------------------------------- 1 | ################# 2 | Victron Inverters 3 | ################# 4 | 5 | .. note:: 6 | PAGE UNDER DEVELOPMENT 7 | 8 | ****************************************************************************************** 9 | Example 1 - Victron with Battery / Solar / No Grid 10 | ****************************************************************************************** 11 | 12 | Integration via https://github.com/sfstar/hass-victron 13 | 14 | .. code-block:: yaml 15 | :linenos: 16 | 17 | type: custom:sunsynk-power-flow-card 18 | cardstyle: full 19 | large_font: false 20 | title: Victron - Power Monitor 21 | title_colour: White 22 | title_size: 18px 23 | show_solar: true 24 | show_grid: true 25 | show_battery: true 26 | decimal_places: 2 27 | dynamic_line_width: true 28 | inverter: 29 | modern: false 30 | colour: grey 31 | autarky: power 32 | auto_scale: true 33 | model: huawei 34 | three_phase: false 35 | battery: 36 | energy: 14850 37 | shutdown_soc: sensor.battery_end_of_discharge_soc 38 | invert_power: true 39 | colour: '#fc8d83' 40 | show_daily: true 41 | animation_speed: 5 42 | max_power: 5000 43 | show_absolute: true 44 | auto_scale: true 45 | hide_soc: false 46 | show_remaining_energy: true 47 | dynamic_colour: true 48 | linear_gradient: true 49 | solar: 50 | colour: '#F7BC00' 51 | show_daily: true 52 | mppts: 2 53 | animation_speed: 8 54 | max_power: 6600 55 | pv1_name: Inv1.S1 56 | pv2_name: Inv2.S1 57 | display_mode: 2 58 | auto_scale: true 59 | load: 60 | colour: magenta 61 | show_daily: true 62 | show_daily_aux: true 63 | show_aux: true 64 | invert_aux: false 65 | show_absolute_aux: false 66 | aux_name: Generator 67 | aux_type: gen 68 | aux_colour: '#5490c2' 69 | aux_off_colour: brown 70 | aux_loads: 2 71 | aux_load1_name: IT - Servers 72 | aux_load2_name: IT - Network 73 | aux_load1_icon: mdi:server-network 74 | aux_load2_icon: mdi:network 75 | animation_speed: 4 76 | essential_name: Essential 77 | max_power: 4000 78 | additional_loads: 2 79 | load1_name: Lights 80 | load2_name: All GPO 81 | load3_name: Spare 82 | load4_name: Spare 83 | load1_icon: mdi:lightbulb 84 | load2_icon: mdi:power-plug 85 | load3_icon: mdi:water-boiler 86 | load4_icon: mdi:kettle 87 | auto_scale: true 88 | dynamic_icon: true 89 | dynamic_colour: true 90 | grid: 91 | grid_name: Your-Grid-Name 92 | colour: '#FF2400' 93 | export_colour: green 94 | no_grid_colour: '#a40013' 95 | grid_off_colour: '#e7d59f' 96 | show_daily_buy: true 97 | show_daily_sell: true 98 | show_nonessential: true 99 | invert_grid: true 100 | nonessential_name: Non Essential 101 | nonessential_icon: none 102 | additional_loads: 2 103 | load1_name: HVAC 104 | load2_name: EV 105 | load1_icon: mdi:fan 106 | load2_icon: mdi:car 107 | animation_speed: 7 108 | max_power: 15000 109 | auto_scale: true 110 | dynamic_icon: true 111 | dynamic_colour: true 112 | energy_cost_decimals: 3 113 | entities: 114 | use_timer_248: null 115 | priority_load_243: null 116 | day_battery_charge_70: sensor.batteries_day_charge 117 | day_battery_discharge_71: sensor.batteries_day_discharge 118 | day_load_energy_84: sensor.house_consumption_energy_daily 119 | day_grid_import_76: sensor.hs_grid_imported_daily 120 | day_grid_export_77: sensor.hs_grid_exported_daily 121 | day_pv_energy_108: sensor.inverters_daily_yield 122 | day_aux_energy: sensor.sunsynk_card_aux_energy_daily 123 | inverter_voltage_154: sensor.power_meter_voltage 124 | load_frequency_192: sensor.power_meter_frequency 125 | grid_power_169: sensor.house_consumption_power 126 | inverter_current_164: sensor.inverter_phase_a_current 127 | inverter_power_175: sensor.inverters_active_power 128 | inverter_status_59: sensor.inverters_state 129 | radiator_temp_91: null 130 | dc_transformer_temp_90: sensor.inverters_internal_temperature 131 | pv1_power_186: sensor.inverter_1_pv_1_power 132 | pv2_power_187: sensor.inverter_1_pv_2_power 133 | environment_temp: sensor._temp 134 | remaining_solar: sensor.energy_production_today_remaining 135 | pv1_voltage_109: sensor.inverter_pv_1_voltage 136 | pv1_current_110: sensor.inverter_pv_1_current 137 | pv2_voltage_111: sensor.inverter_pv_2_voltage 138 | pv2_current_112: sensor.inverter_pv_2_current 139 | battery_voltage_183: sensor.batteries_bus_voltage 140 | battery_soc_184: sensor.batteries_state_of_capacity 141 | battery_power_190: sensor.batteries_charge_discharge_power 142 | battery_current_191: sensor.batteries_bus_current 143 | battery_temp_182: sensor.batteries_temperature 144 | battery_status: sensor.batteries_status 145 | essential_power: sensor.house_consumption_power_less_aux_non_essential 146 | essential_load1: sensor.lights_all_active_power 147 | essential_load2: sensor.gpo_all_active_power_less_known 148 | essential_load1_extra: null 149 | essential_load2_extra: null 150 | nonessential_power: sensor.sunsynk_card_non_essential_active_power 151 | non_essential_load1: sensor.hvac_active_power 152 | non_essential_load2: sensor.ev_charger_active_power 153 | grid_ct_power_172: sensor.power_meter_active_power 154 | grid_ct_power_total: sensor.power_meter_active_power 155 | grid_connected_status_194: sensor.inverters_off_grid_status 156 | aux_power_166: sensor.sunsynk_card_aux_active_power 157 | aux_connected_status: binary_sensor.sunsynk_card_aux_connected_status 158 | energy_cost_buy: sensor.electricity_price 159 | energy_cost_sell: sensor.electricity_fit 160 | solar_sell_247: switch.null 161 | aux_load1: sensor.it_hardware_network_active_power 162 | aux_load2: sensor.it_hardware_servers_active_power 163 | aux_load1_extra: sensor.env_network_rack_bme280_temperature 164 | aux_load2_extra: sensor.garage_controller_bme280_temperature 165 | grid_voltage: sensor.power_meter_voltage 166 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../README.md 2 | :parser: myst_parser.sphinx_ 3 | 4 | ***************** 5 | Table of Contents 6 | ***************** 7 | 8 | .. include:: toc.rst 9 | 10 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx_rtd_theme>=^1.2.2 2 | myst-parser>=^2.0.0 3 | -------------------------------------------------------------------------------- /docs/toc.rst: -------------------------------------------------------------------------------- 1 | .. toctree:: 2 | :caption: Configuration 3 | :titlesonly: 4 | 5 | configuration 6 | 7 | .. toctree:: 8 | :caption: Examples 9 | :titlesonly: 10 | 11 | examples/sunsynk 12 | examples/foxess 13 | examples/goodwe 14 | examples/huawei 15 | examples/lux 16 | examples/powmr 17 | examples/solax 18 | examples/solis 19 | examples/victron 20 | 21 | .. toctree:: 22 | :caption: Contribute 23 | :titlesonly: 24 | 25 | contribute/bugs 26 | contribute/devcontainer 27 | contribute/devcycle 28 | contribute/docs 29 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import globals from "globals"; 2 | import pluginJs from "@eslint/js"; 3 | import tseslint from "typescript-eslint"; 4 | 5 | 6 | export default [ 7 | {languageOptions: { globals: globals.browser }}, 8 | pluginJs.configs.recommended, 9 | ...tseslint.configs.recommended, 10 | ]; -------------------------------------------------------------------------------- /hacs.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Sunsynk Power Flow Card", 3 | "render_readme": true, 4 | "content_in_root": false, 5 | "filename": "sunsynk-power-flow-card.js" 6 | } 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sunsynk-power-flow-card", 3 | "version": "6.8.0", 4 | "description": "A customizable Home Assistant card to emulate the Sunsynk System flow that's displayed on the Inverter screen.", 5 | "main": "sunsynk-power-flow-card.js", 6 | "scripts": { 7 | "lint": "eslint src/*.ts | more ", 8 | "lintindex": "eslint src/index.ts | more", 9 | "lintindexfix": "eslint src/index.ts --fix", 10 | "lintfixall": "eslint src/*.ts --fix", 11 | "rollup": "rollup -c", 12 | "build": "rollup -c --bundleConfigAsCjs", 13 | "watch": "rollup -c --watch --config rollup-dev.config.js", 14 | "docs-build": "sphinx-autobuild docs docs/_build/html" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/slipx06/sunsynk-power-flow-card.git" 19 | }, 20 | "author": "slipx06 ", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/slipx06/sunsynk-power-flow-card/issues" 24 | }, 25 | "homepage": "https://github.com/slipx06/sunsynk-power-flow-card#readme", 26 | "dependencies": { 27 | "@lit-labs/scoped-registry-mixin": "^1.0.3", 28 | "custom-card-helpers": "^1.9.0", 29 | "lit": "^3.1.3", 30 | "lodash.merge": "^4.6.2" 31 | }, 32 | "devDependencies": { 33 | "@babel/cli": "^7.24.6", 34 | "@babel/core": "^7.24.6", 35 | "@babel/plugin-proposal-class-properties": "^7.18.6", 36 | "@babel/plugin-proposal-decorators": "^7.24.1", 37 | "@babel/plugin-transform-template-literals": "^7.24.1", 38 | "@babel/preset-env": "^7.24.6", 39 | "@eslint/js": "^9.3.0", 40 | "@rollup/plugin-babel": "^6.0.4", 41 | "@rollup/plugin-commonjs": "^28.0.0", 42 | "@rollup/plugin-eslint": "^9.0.5", 43 | "@rollup/plugin-json": "^6.1.0", 44 | "@rollup/plugin-node-resolve": "^16.0.0", 45 | "@rollup/plugin-terser": "^0.4.4", 46 | "@types/lodash.merge": "^4.6.9", 47 | "@typescript-eslint/eslint-plugin": "^8.1.0", 48 | "@typescript-eslint/parser": "^8.2.0", 49 | "babel-preset-minify": "^0.5.2", 50 | "eslint": "^9.11.0", 51 | "eslint-config-prettier": "^10.0.1", 52 | "eslint-plugin-prettier": "^5.1.3", 53 | "globals": "^16.0.0", 54 | "npm": "^11.0.0", 55 | "prettier": "^3.2.5", 56 | "rollup": "^4.17.2", 57 | "rollup-plugin-serve": "^3.0.0", 58 | "rollup-plugin-typescript2": "^0.36.0", 59 | "typescript": "^5.4.5", 60 | "typescript-eslint": "^8.1.0" 61 | }, 62 | "resolutions": { 63 | "rollup": "^3.24.0" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /rollup.config.mjs: -------------------------------------------------------------------------------- 1 | import typescript from 'rollup-plugin-typescript2'; 2 | import commonjs from '@rollup/plugin-commonjs'; 3 | import {nodeResolve} from '@rollup/plugin-node-resolve'; 4 | import babel from '@rollup/plugin-babel'; 5 | import terser from "@rollup/plugin-terser"; 6 | import json from '@rollup/plugin-json'; 7 | import eslint from '@rollup/plugin-eslint'; 8 | 9 | const plugins = [ 10 | nodeResolve({ 11 | jsnext: true, 12 | main: true, 13 | }), 14 | eslint(), 15 | commonjs(), 16 | typescript(), 17 | json(), 18 | babel({ 19 | exclude: 'node_modules/**', 20 | babelHelpers: 'bundled', 21 | compact: true, 22 | extensions: [ 23 | '.js', 24 | '.ts', 25 | ], 26 | presets: [ 27 | [ 28 | '@babel/env', 29 | { 30 | "modules": false, 31 | "targets": "> 2.5%, not dead" 32 | } 33 | ], 34 | ], 35 | plugins: [ 36 | [ 37 | "@babel/plugin-proposal-decorators", 38 | { 39 | "legacy": true 40 | } 41 | ], 42 | [ 43 | "@babel/plugin-proposal-class-properties" 44 | ], 45 | [ 46 | "@babel/plugin-transform-template-literals" 47 | ] 48 | ] 49 | }), 50 | terser() 51 | ]; 52 | 53 | export default { 54 | input: ['./src/index.ts'], 55 | output: { 56 | file: 'dist/sunsynk-power-flow-card.js', 57 | format: 'esm', 58 | name: 'SunsynkPowerFlowCard', 59 | inlineDynamicImports: true, 60 | }, 61 | watch: { 62 | clearScreen: false, 63 | }, 64 | plugins: [...plugins], 65 | onwarn: function (warning, handler) { 66 | if (warning.code === 'THIS_IS_UNDEFINED') { 67 | return; 68 | } 69 | 70 | // console.warn everything else 71 | handler(warning); 72 | } 73 | }; 74 | -------------------------------------------------------------------------------- /src/cards/compact-card.ts: -------------------------------------------------------------------------------- 1 | import {html} from 'lit'; 2 | import {DataDto, sunsynkPowerFlowCardConfig} from '../types'; 3 | import {renderSolarElements} from '../components/compact/pv/pv-elements'; 4 | import {renderBatteryElements} from '../components/compact/bat/bat-elements'; 5 | import {renderGridElements} from '../components/compact/grid/grid-elements'; 6 | import {renderLoadElements} from '../components/compact/load/load-elements'; 7 | import {renderInverterElements} from '../components/compact/inverter/inverter-elements'; 8 | import {getDynamicStyles } from '../style'; 9 | 10 | export const compactCard = (config: sunsynkPowerFlowCardConfig, inverterImg: string, data: DataDto) => { 11 | return html` 12 | 13 | ${getDynamicStyles(data)} 14 |
15 | ${config.title ? html`

17 | ${config.title}

` : ''} 18 | 24 | 25 | 26 | ${renderSolarElements(data, config)} 27 | 28 | 29 | ${renderBatteryElements(data, config)} 30 | 31 | 32 | ${renderGridElements(data, config)} 33 | 34 | 35 | ${renderLoadElements(data, config)} 36 | 37 | 38 | ${renderInverterElements(data, inverterImg, config)} 39 | 40 | 41 |
42 |
43 | ` 44 | } -------------------------------------------------------------------------------- /src/cards/full-card.ts: -------------------------------------------------------------------------------- 1 | import {html} from 'lit'; 2 | import {DataDto, sunsynkPowerFlowCardConfig} from '../types'; 3 | import {getDynamicStyles } from '../style'; 4 | import {renderSolarElements} from '../components/full/pv/pv_elements'; 5 | import {renderBatteryElements} from '../components/full/bat/bat-elements'; 6 | import {renderGridElements} from '../components/full/grid/grid-elements'; 7 | import {renderLoadElements} from '../components/full/load/load-elements'; 8 | import {renderAuxLoadElements} from '../components/full/auxload/aux-elements'; 9 | import {renderInverterElements} from '../components/full/inverter/inverter-elements'; 10 | 11 | 12 | export const fullCard = (config: sunsynkPowerFlowCardConfig, inverterImg: string, data: DataDto) => { 13 | return html` 14 | 15 | ${getDynamicStyles(data)} 16 |
17 | ${config.title ? html`

19 | ${config.title}

` : ''} 20 | 26 | 27 | 28 | ${renderSolarElements(data, config)} 29 | 30 | 31 | ${renderBatteryElements(data, config)} 32 | 33 | 34 | ${renderGridElements(data, config)} 35 | 36 | 37 | ${renderLoadElements(data, config)} 38 | 39 | 40 | ${renderAuxLoadElements(data, config)} 41 | 42 | 43 | ${renderInverterElements(data, inverterImg, config)} 44 | 45 | 46 |
47 |
48 | `; 49 | } 50 | -------------------------------------------------------------------------------- /src/components/full/auxload/icon-configs.ts: -------------------------------------------------------------------------------- 1 | import {DataDto} from '../../../types'; 2 | import {AuxIconConfig} from './render-static-aux-icons'; 3 | 4 | export const getAuxIconConfigs = (data: DataDto): AuxIconConfig[] => [ 5 | { 6 | id: 'aux_default', 7 | x: 371, 8 | y: 5, 9 | width: 83, 10 | height: 83, 11 | displayCondition: !data.showAux || [1, 2].includes(data.additionalAuxLoad), 12 | opacityCondition: data.auxType === 'default', 13 | iconType: 'aux', 14 | prefix: 'aux', 15 | dynamicColor: data.auxStatus === 'on' || data.auxStatus === '1' ? data.auxDynamicColour : data.auxOffColour, 16 | viewBoxSize: 24, 17 | }, 18 | { 19 | id: 'aux_generator', 20 | x: 374, 21 | y: 5, 22 | width: 74, 23 | height: 74, 24 | displayCondition: !data.showAux || [1, 2].includes(data.additionalAuxLoad), 25 | opacityCondition: data.auxType === 'gen', 26 | iconType: 'generator', 27 | prefix: 'aux', 28 | dynamicColor: data.auxStatus === 'on' || data.auxStatus === '1' ? data.auxDynamicColour : data.auxOffColour, 29 | viewBoxSize: 24, 30 | }, 31 | { 32 | id: 'aux_oven', 33 | x: 375, 34 | y: 8, 35 | width: 70, 36 | height: 70, 37 | displayCondition: !data.showAux || [1, 2].includes(data.additionalAuxLoad), 38 | opacityCondition: data.auxType === 'oven', 39 | iconType: 'oven', 40 | prefix: 'aux', 41 | dynamicColor: data.auxStatus === 'on' || data.auxStatus === '1' ? data.auxDynamicColour : data.auxOffColour, 42 | viewBoxSize: 32, 43 | }, 44 | { 45 | id: 'aux_boiler', 46 | x: 375, 47 | y: 8, 48 | width: 70, 49 | height: 70, 50 | displayCondition: !data.showAux || [1, 2].includes(data.additionalAuxLoad), 51 | opacityCondition: data.auxType === 'boiler', 52 | iconType: 'boiler', 53 | prefix: 'aux', 54 | dynamicColor: data.auxStatus === 'on' || data.auxStatus === '1' ? data.auxDynamicColour : data.auxOffColour, 55 | viewBoxSize: 24, 56 | }, 57 | { 58 | id: 'aux_ac', 59 | x: 380, 60 | y: 12, 61 | width: 60, 62 | height: 60, 63 | displayCondition: !data.showAux || [1, 2].includes(data.additionalAuxLoad), 64 | opacityCondition: data.auxType === 'aircon', 65 | iconType: 'aircon', 66 | prefix: 'aux', 67 | dynamicColor: data.auxStatus === 'on' || data.auxStatus === '1' ? data.auxDynamicColour : data.auxOffColour, 68 | viewBoxSize: 24, 69 | }, 70 | { 71 | id: 'aux_pump', 72 | x: 380, 73 | y: 15, 74 | width: 60, 75 | height: 70, 76 | displayCondition: !data.showAux || [1, 2].includes(data.additionalAuxLoad), 77 | opacityCondition: data.auxType === 'pump', 78 | iconType: 'pump', 79 | prefix: 'aux', 80 | dynamicColor: data.auxStatus === 'on' || data.auxStatus === '1' ? data.auxDynamicColour : data.auxOffColour, 81 | viewBoxSize: 24, 82 | }, 83 | ]; -------------------------------------------------------------------------------- /src/components/full/auxload/render-static-aux-icons.ts: -------------------------------------------------------------------------------- 1 | import {html} from 'lit'; 2 | import {icons} from '../../../helpers/icons'; 3 | 4 | export interface AuxIconConfig { 5 | id: string; 6 | x: number; 7 | y: number; 8 | width: number; 9 | height: number; 10 | displayCondition: boolean; 11 | opacityCondition: boolean; 12 | iconType: 'boiler' | 'aircon' | 'pump' | 'oven' | 'generator' | 'inverter' | 'aux'; 13 | prefix: string; 14 | dynamicColor: string; 15 | viewBoxSize: number; 16 | } 17 | 18 | export const renderStaticAuxIcon = (config: AuxIconConfig) => { 19 | 20 | const iconPath = icons[config.iconType]; 21 | 22 | return html` 23 | 31 | 35 | 36 | `; 37 | }; -------------------------------------------------------------------------------- /src/components/shared/grid/render-static-grid-icon.ts: -------------------------------------------------------------------------------- 1 | import {html} from 'lit'; 2 | import {icons} from '../../../helpers/icons'; 3 | 4 | export interface LoadIconConfig { 5 | id: string; 6 | x: number; 7 | y: number; 8 | width: number; 9 | height: number; 10 | displayCondition: boolean; 11 | opacityCondition: boolean; 12 | iconType: 'boiler' | 'aircon' | 'pump' | 'oven' | 'nonEss'; 13 | prefix: string; 14 | viewBoxSize?: number; 15 | dynamicColor?: string; 16 | } 17 | 18 | /** 19 | * Renders a static load icon (boiler, aircon, pump, or oven) with the specified configuration 20 | */ 21 | export const renderStaticGridIcon = (config: LoadIconConfig) => { 22 | 23 | const iconPath = icons[config.iconType]; 24 | 25 | return html` 26 | 34 | 38 | 39 | `; 40 | }; -------------------------------------------------------------------------------- /src/components/shared/load/render-static-load-icon.ts: -------------------------------------------------------------------------------- 1 | import { html } from 'lit'; 2 | import { DataDto } from '../../../types'; 3 | import { icons } from '../../../helpers/icons'; 4 | 5 | export interface LoadIconConfig { 6 | id: string; 7 | x: number; 8 | y: number; 9 | width: number; 10 | height: number; 11 | loadNumber: 1 | 2; 12 | displayCondition: boolean; 13 | opacityCondition: boolean; 14 | iconType: 'boiler' | 'aircon' | 'pump' | 'oven'; 15 | prefix: string; 16 | viewBoxSize?: number; // Optional parameter, defaults to 24 if not specified 17 | } 18 | 19 | /** 20 | * Renders a static load icon (boiler, aircon, pump, or oven) with the specified configuration 21 | * @param data The data object containing dynamic colors and state 22 | * @param config The configuration object for the icon 23 | * @returns A lit-html template for the SVG icon 24 | */ 25 | export const renderStaticLoadIcon = (data: DataDto, config: LoadIconConfig) => { 26 | const dynamicColor = config.loadNumber === 1 27 | ? data.dynamicColourEssentialLoad1 28 | : data.dynamicColourEssentialLoad2; 29 | 30 | const iconPath = icons[config.iconType]; 31 | const viewBoxSize = config.viewBoxSize || 24; // Default to 24 if not specified 32 | 33 | return html` 34 | 42 | 46 | 47 | `; 48 | }; -------------------------------------------------------------------------------- /src/components/shared/pv/render-pv-flow.ts: -------------------------------------------------------------------------------- 1 | import { html } from 'lit'; 2 | import { Utils } from '../../../helpers/utils'; 3 | 4 | export function renderPVFlow( 5 | id: string, 6 | path: string, 7 | color: string, 8 | lineWidth: number, 9 | powerWatts: number, 10 | duration: number, 11 | invertFlow: boolean, 12 | minLineWidth: number, 13 | className: string = "", 14 | keyPoints: string = "0;1" 15 | ) { 16 | const lineId = `${id}-line`; 17 | const finalKeyPoints = invertFlow === true ? Utils.invertKeyPoints(keyPoints) : keyPoints; 18 | 19 | return html` 20 | 21 | 26 | 29 | 32 | 33 | 34 | 35 | 36 | `; 37 | } -------------------------------------------------------------------------------- /src/components/shared/pv/render-pv.ts: -------------------------------------------------------------------------------- 1 | import { html } from 'lit'; 2 | import { DataDto, sunsynkPowerFlowCardConfig } from '../../../types'; 3 | 4 | export function renderPV(id: string, x: string, y: string, data: DataDto, config: sunsynkPowerFlowCardConfig) { 5 | const gradientId = `${id}LG-${data.timestamp_id}`; 6 | const efficiencyMap = { 7 | pvtotal: 'totalPVEfficiency', 8 | pv1: 'PV1Efficiency', 9 | pv2: 'PV2Efficiency', 10 | pv3: 'PV3Efficiency', 11 | pv4: 'PV4Efficiency', 12 | pv5: 'PV5Efficiency', 13 | pv6: 'PV6Efficiency', 14 | }; 15 | const efficiencyPropertyName = efficiencyMap[id] || 'totalPVEfficiency'; 16 | const efficiency = data[efficiencyPropertyName] || 0; 17 | const solarColour = data.solarColour; 18 | const useGradient = [1, 3].includes(config.solar.efficiency); 19 | const gradientUrl = useGradient ? `url(#${gradientId})` : solarColour; 20 | let className = ''; 21 | 22 | if (id === 'pv2' && config.solar.mppts === 1) { 23 | className = 'st12'; 24 | } else if (id === 'pv3' && [1, 2].includes(config.solar.mppts)) { 25 | className = 'st12'; 26 | } else if (id === 'pv4' && [1, 2, 3].includes(config.solar.mppts)) { 27 | className = 'st12'; 28 | } else if (id === 'pv5' && [1, 2, 3, 4].includes(config.solar.mppts)) { 29 | className = 'st12'; 30 | } else if (id === 'pv6' && [1, 2, 3, 4, 5].includes(config.solar.mppts)) { 31 | className = 'st12'; 32 | } 33 | 34 | const style = id === 'pvtotal' && config.solar.mppts === 1 ? 'display: none;' : ''; 35 | 36 | return html` 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 51 | 52 | `; 53 | } -------------------------------------------------------------------------------- /src/const.ts: -------------------------------------------------------------------------------- 1 | import {version} from '../package.json'; 2 | 3 | export const CARD_VERSION = version; 4 | 5 | export const validLoadValues = [0, 1, 2, 3, 4, 5, 6] 6 | export const validnonLoadValues = [0, 1, 2, 3] 7 | export const valid3phase = [true, false] 8 | export const validaux = [true, false] 9 | export const validauxLoads = [0, 1, 2] 10 | export const validGridDisconnected = ['off', '0', 'off-grid', 'off grid', 'offgrid'] 11 | export const validGridConnected = ['on', '1', 'on-grid', 'on grid', 'ongrid'] 12 | 13 | export const EDITOR_NAME = 'sunsynk-content-card-editor'; 14 | export const MAIN_NAME = 'sunsynk-power-flow-card'; 15 | 16 | export const enum SensorDeviceClass { 17 | DATE = "date", 18 | ENUM = "enum", 19 | TIMESTAMP = "timestamp", 20 | APPARENT_POWER = "apparent_power", 21 | ATMOSPHERIC_PRESSURE = "atmospheric_pressure", 22 | BATTERY = "battery", 23 | CO = "carbon_monoxide", 24 | CO2 = "carbon_dioxide", 25 | CURRENT = "current", 26 | ENERGY = "energy", 27 | ENERGY_STORAGE = "energy_storage", 28 | FREQUENCY = "frequency", 29 | IRRADIANCE = "irradiance", 30 | MONETARY = "monetary", 31 | POWER_FACTOR = "power_factor", 32 | POWER = "power", 33 | REACTIVE_POWER = "reactive_power", 34 | TEMPERATURE = "temperature", 35 | VOLTAGE = "voltage" 36 | } 37 | 38 | export const enum UnitOfPower { 39 | WATT = "W", 40 | KILO_WATT = "kW", 41 | MEGA_WATT = "MW", 42 | BTU_PER_HOUR = "BTU/h", 43 | } 44 | 45 | export const enum UnitOfEnergy { 46 | GIGA_JOULE = "GJ", 47 | KILO_WATT_HOUR = "kWh", 48 | MEGA_JOULE = "MJ", 49 | MEGA_WATT_HOUR = "MWh", 50 | WATT_HOUR = "Wh", 51 | } 52 | 53 | export const enum UnitOfElectricalCurrent { 54 | MILLIAMPERE = "mA", 55 | AMPERE = "A" 56 | } 57 | 58 | export const enum UnitOfElectricPotential { 59 | MILLIVOLT = "mV", 60 | VOLT = "V" 61 | } 62 | 63 | export const enum Percentage { 64 | PERCENTAGE = "%" 65 | } 66 | 67 | export type UnitOfEnergyOrPower = UnitOfEnergy | UnitOfPower; 68 | 69 | type ConversionRule = { 70 | threshold: number; 71 | divisor: number; 72 | targetUnit: UnitOfEnergyOrPower; 73 | decimal?: number; 74 | }; 75 | 76 | export const unitOfEnergyConversionRules: Record = { 77 | [UnitOfEnergy.WATT_HOUR]: [{threshold: 1e6, divisor: 1e6, targetUnit: UnitOfEnergy.MEGA_WATT_HOUR}, { 78 | threshold: 1e3, 79 | divisor: 1e3, 80 | targetUnit: UnitOfEnergy.KILO_WATT_HOUR, 81 | decimal: 1 82 | }], 83 | [UnitOfEnergy.KILO_WATT_HOUR]: [{ 84 | threshold: 1e3, 85 | divisor: 1e3, 86 | targetUnit: UnitOfEnergy.MEGA_WATT_HOUR, 87 | decimal: 2 88 | }], 89 | [UnitOfEnergy.MEGA_WATT_HOUR]: [], 90 | [UnitOfEnergy.GIGA_JOULE]: [{threshold: 1e3, divisor: 1e3, targetUnit: UnitOfEnergy.MEGA_JOULE}], 91 | [UnitOfEnergy.MEGA_JOULE]: [], 92 | [UnitOfPower.WATT]: [{threshold: 1e6, divisor: 1e6, targetUnit: UnitOfPower.MEGA_WATT}, { 93 | threshold: 1e3, 94 | divisor: 1e3, 95 | targetUnit: UnitOfPower.KILO_WATT, 96 | }], 97 | [UnitOfPower.KILO_WATT]: [{ 98 | threshold: 1e3, 99 | divisor: 1e3, 100 | targetUnit: UnitOfPower.MEGA_WATT, 101 | }], 102 | [UnitOfPower.MEGA_WATT]: [], 103 | [UnitOfPower.BTU_PER_HOUR]: [], 104 | }; 105 | -------------------------------------------------------------------------------- /src/defaults.ts: -------------------------------------------------------------------------------- 1 | import {localize} from './localize/localize'; 2 | import {InverterModel} from './types'; 3 | 4 | export default { 5 | cardstyle: 'lite', 6 | wide: false, 7 | large_font: false, 8 | show_solar: true, 9 | show_battery: true, 10 | show_grid: true, 11 | card_height: '100%', 12 | card_width: '100%', 13 | decimal_places: 2, 14 | decimal_places_energy: 1, 15 | dynamic_line_width: true, 16 | max_line_width: 4, 17 | min_line_width: 1, 18 | inverter: { 19 | modern: true, 20 | colour: 'grey', 21 | autarky: 'power', 22 | model: InverterModel.Sunsynk, 23 | auto_scale: true, 24 | three_phase: false, 25 | navigate:'', 26 | }, 27 | battery: { 28 | count: 1, 29 | energy: 0, 30 | shutdown_soc: 20, 31 | soc_end_of_charge: 100, 32 | invert_power: false, 33 | hide_soc: false, 34 | colour: 'pink', 35 | show_daily: false, 36 | show_remaining_energy: true, 37 | remaining_energy_to_shutdown: false, 38 | animation_speed: 6, 39 | max_power: 4500, 40 | show_absolute: false, 41 | auto_scale: true, 42 | dynamic_colour: true, 43 | linear_gradient: true, 44 | animate: true, 45 | path_threshold: 100, 46 | navigate:'', 47 | invert_flow: false, 48 | }, 49 | battery2: { 50 | energy: 0, 51 | shutdown_soc: 20, 52 | soc_end_of_charge: 100, 53 | invert_power: false, 54 | hide_soc: false, 55 | colour: 'pink', 56 | show_remaining_energy: true, 57 | remaining_energy_to_shutdown: false, 58 | show_absolute: false, 59 | auto_scale: true, 60 | dynamic_colour: true, 61 | linear_gradient: true, 62 | animate: true, 63 | path_threshold: 100, 64 | navigate:'', 65 | invert_flow: false, 66 | }, 67 | solar: { 68 | colour: 'orange', 69 | show_daily: false, 70 | mppts: 2, 71 | animation_speed: 9, 72 | max_power: 8000, 73 | pv1_name: localize('common.pv1_name'), 74 | pv2_name: localize('common.pv2_name'), 75 | pv3_name: localize('common.pv3_name'), 76 | pv4_name: localize('common.pv4_name'), 77 | pv5_name: localize('common.pv5_name'), 78 | pv6_name: localize('common.pv6_name'), 79 | auto_scale: true, 80 | display_mode: 1, 81 | dynamic_colour: true, 82 | efficiency: 3, 83 | off_threshold: 10, 84 | navigate:'', 85 | invert_flow: false, 86 | }, 87 | load: { 88 | colour: '#5fb6ad', 89 | off_colour: 'grey', 90 | dynamic_colour: true, 91 | dynamic_icon: true, 92 | aux_dynamic_colour: true, 93 | off_threshold: 0, 94 | show_daily: false, 95 | show_aux: false, 96 | show_daily_aux: false, 97 | invert_aux: false, 98 | invert_load: false, 99 | show_absolute_aux: false, 100 | animation_speed: 4, 101 | max_power: 8000, 102 | aux_name: localize('common.aux_name'), 103 | aux_daily_name: localize('common.daily_aux'), 104 | aux_type: 'default', 105 | additional_loads: 0, 106 | aux_loads: 0, 107 | aux_load1_name: '', 108 | aux_load2_name: '', 109 | essential_name: localize('common.essential'), 110 | load1_icon: 'default', 111 | load2_icon: 'default', 112 | load1_name: '', 113 | load2_name: '', 114 | load3_name: '', 115 | load4_name: '', 116 | load5_name: '', 117 | load6_name: '', 118 | auto_scale: true, 119 | path_threshold: 100, 120 | navigate:'', 121 | invert_flow: false, 122 | label_daily_load: localize('common.daily_load'), 123 | }, 124 | grid: { 125 | colour: '#5490c2', 126 | grid_name: localize('common.grid_name'), 127 | label_daily_grid_buy: localize('common.daily_grid_buy'), 128 | label_daily_grid_sell: localize('common.daily_grid_sell'), 129 | show_daily_buy: false, 130 | show_daily_sell: false, 131 | show_nonessential: true, 132 | nonessential_icon: 'default', 133 | nonessential_name: localize('common.nonessential_name'), 134 | additional_loads: 0, 135 | load1_name: '', 136 | load2_name: '', 137 | load3_name: '', 138 | load1_icon: 'default', 139 | load2_icon: 'default', 140 | load3_icon: 'default', 141 | invert_grid: false, 142 | animation_speed: 8, 143 | max_power: 8000, 144 | auto_scale: true, 145 | energy_cost_decimals: 2, 146 | show_absolute: false, 147 | off_threshold: 0, 148 | navigate:'', 149 | invert_flow: false, 150 | }, 151 | }; 152 | -------------------------------------------------------------------------------- /src/helpers/globals.ts: -------------------------------------------------------------------------------- 1 | import { HomeAssistant } from 'custom-card-helpers'; 2 | 3 | // Globalise the hass object so that localize can utilise it. 4 | 5 | export const globalData = { 6 | hass: null as HomeAssistant | null, 7 | }; 8 | 9 | export function setHass(hass: HomeAssistant) { 10 | globalData.hass = hass; 11 | } -------------------------------------------------------------------------------- /src/helpers/render-circle.ts: -------------------------------------------------------------------------------- 1 | import {svg} from 'lit'; 2 | import {Utils} from './utils'; 3 | 4 | /** 5 | * Renders an animated circle element. 6 | * @param id - The ID of the circle. 7 | * @param radius - The radius of the circle. 8 | * @param fill - The fill color of the circle. 9 | * @param duration - The duration of the animation in seconds. 10 | * @param keyPoints - The key points for the animation (e.g., "1;0" or "0;1"). 11 | * @param mpathHref - The ID of the path to follow (e.g., "#bat-line"). 12 | * @param invertFlow - Whether to invert the animation flow (optional, default: false). 13 | * @returns A Lit SVG template for the animated circle element. 14 | */ 15 | export const renderCircle = ( 16 | id: string, 17 | radius: number, 18 | fill: string, 19 | duration: number, 20 | keyPoints: string, 21 | mpathHref: string, 22 | invertFlow: boolean = false 23 | ) => { 24 | return svg` 25 | 26 | 29 | 30 | 31 | 32 | `; 33 | }; -------------------------------------------------------------------------------- /src/helpers/render-icon.ts: -------------------------------------------------------------------------------- 1 | import {svg} from 'lit'; 2 | import {Utils} from './utils'; 3 | 4 | /** 5 | * Renders a load icon with optional popup functionality. 6 | * @param entity - The entity to trigger the popup (optional). 7 | * @param icon - The icon name (e.g., "mdi:home"). 8 | * @param className - The CSS class to apply to the icon. 9 | * @param x - The x-coordinate of the icon. 10 | * @param y - The y-coordinate of the icon. 11 | * @param width - The width of the icon container (default: 30). 12 | * @param height - The height of the icon container (default: 30). 13 | * @param show - Whether the icon should be visible (default: true). 14 | * @returns A Lit SVG template or an empty string if no icon is provided. 15 | */ 16 | export function renderIcon( 17 | entity: string | undefined, 18 | icon: string | undefined, 19 | className: string, 20 | x: number | string, 21 | y: number | string, 22 | width: number = 30, 23 | height: number = 30, 24 | show: boolean = true 25 | ) { 26 | if (icon && entity) { 27 | return svg` 28 | Utils.handlePopup(e, entity)}> 29 | 30 |
31 | 32 |
33 |
34 |
`; 35 | } else if (icon) { 36 | return svg` 37 | 38 |
39 | 40 |
41 |
`; 42 | } 43 | return ''; 44 | } -------------------------------------------------------------------------------- /src/helpers/render-path.ts: -------------------------------------------------------------------------------- 1 | import { svg } from 'lit'; 2 | 3 | /** 4 | * Renders an SVG path element with display and stroke attributes. 5 | * @param id - The ID of the path. 6 | * @param d - The path data (e.g., "M 409 143 L 409 135"). 7 | * @param display - Whether the path should be displayed (true = visible, false = 'none'). 8 | * @param color - The stroke color of the path. 9 | * @param lineWidth - The stroke width of the path. 10 | * @returns A Lit SVG template for the path element. 11 | */ 12 | export const renderPath = ( 13 | id: string, 14 | d: string, 15 | display: boolean, 16 | color: string, 17 | lineWidth: number, 18 | ) => { 19 | return svg` 20 | 23 | `; 24 | }; -------------------------------------------------------------------------------- /src/helpers/text-utils.ts: -------------------------------------------------------------------------------- 1 | import {svg} from 'lit'; 2 | 3 | export const createTextWithPopup = ( 4 | id: string, 5 | x: number | string, 6 | y: number | string, 7 | displayCondition: boolean, 8 | className: string, 9 | fill: string, 10 | text: string, 11 | onClick: (e: Event) => void, 12 | hideOnCondition: boolean = false // Default behavior: hide when condition is false 13 | ) => { 14 | return svg` 15 | 16 | 20 | ${text} 21 | 22 | 23 | `; 24 | }; 25 | 26 | export const renderText = ( 27 | id: string, 28 | x: number | string, 29 | y: number | string, 30 | displayCondition: boolean, 31 | className: string, 32 | fill: string, 33 | text: string, 34 | hideOnCondition: boolean = false // Default behavior: hide when condition is false 35 | ) => { 36 | return svg` 37 | 41 | ${text} 42 | 43 | `; 44 | }; -------------------------------------------------------------------------------- /src/helpers/utils.ts: -------------------------------------------------------------------------------- 1 | import {unitOfEnergyConversionRules, UnitOfEnergyOrPower, UnitOfPower, UnitOfEnergy} from '../const'; 2 | import { navigate } from 'custom-card-helpers'; 3 | 4 | export class Utils { 5 | static toNum(val: string | number, decimals: number = -1, invert: boolean = false): number { 6 | let numberValue = Number(val); 7 | if (Number.isNaN(numberValue)) { 8 | return 0; 9 | } 10 | if (decimals >= 0) { 11 | numberValue = parseFloat(numberValue.toFixed(decimals)); 12 | } 13 | if (invert) { 14 | numberValue *= -1; 15 | } 16 | return numberValue; 17 | } 18 | 19 | static invertKeyPoints(keyPoints: string) { 20 | return keyPoints.split(';').reverse().join(';'); 21 | } 22 | 23 | static convertValue(value, decimal = 2) { 24 | decimal = Number.isNaN(decimal) ? 2 : decimal; 25 | if (Math.abs(value) >= 1000000) { 26 | return `${(value / 1000000).toFixed(decimal)} MW`; 27 | } else if (Math.abs(value) >= 1000) { 28 | return `${(value / 1000).toFixed(decimal)} kW`; 29 | } else { 30 | return `${Math.round(value)} W`; 31 | } 32 | } 33 | 34 | static convertValueNew(value: string | number, unit: UnitOfEnergyOrPower | string = '', decimal: number = 2) { 35 | decimal = isNaN(decimal) ? 2 : decimal; 36 | const numberValue = Number(value); 37 | if (isNaN(numberValue)) return 0; 38 | 39 | const rules = unitOfEnergyConversionRules[unit]; 40 | if (!rules) return `${numberValue.toFixed(decimal)} ${unit}`; 41 | 42 | if (unit === UnitOfEnergy.WATT_HOUR && Math.abs(numberValue) < 1000) { 43 | return `${Math.round(numberValue)} ${unit}`; 44 | }; 45 | 46 | if (unit === UnitOfPower.WATT && Math.abs(numberValue) < 1000) { 47 | return `${Math.round(numberValue)} ${unit}`; 48 | }; 49 | 50 | if (unit === UnitOfPower.KILO_WATT && Math.abs(numberValue) < 1) { 51 | return `${Math.round(numberValue * 1000)} W`; 52 | }; 53 | 54 | if (unit === UnitOfPower.MEGA_WATT && Math.abs(numberValue) < 1) { 55 | return `${(numberValue * 1000).toFixed(decimal)} kW`; 56 | }; 57 | 58 | for (const rule of rules) { 59 | if (Math.abs(numberValue) >= rule.threshold) { 60 | const convertedValue = (numberValue / rule.divisor).toFixed(rule.decimal || decimal); 61 | return `${convertedValue} ${rule.targetUnit}`; 62 | } 63 | }; 64 | 65 | return `${numberValue.toFixed(decimal)} ${unit}`; 66 | } 67 | 68 | private static isPopupOpen = false; 69 | 70 | static handlePopup(event, entityId) { 71 | if (!entityId) { 72 | return; 73 | } 74 | event.preventDefault(); 75 | this._handleClick(event, { action: 'more-info' }, entityId); 76 | } 77 | 78 | static handleNavigation(event, navigationPath,) { 79 | if (!navigationPath) { 80 | return; 81 | } 82 | event.preventDefault(); 83 | this._handleClick(event, { action: 'navigate', navigation_path: navigationPath }, null); 84 | } 85 | 86 | private static _handleClick(event, actionConfig, entityId) { 87 | if (!event || (!entityId && !actionConfig.navigation_path)) { 88 | return; 89 | } 90 | 91 | event.stopPropagation(); 92 | 93 | // Handle different actions based on actionConfig 94 | switch (actionConfig.action) { 95 | case 'more-info': 96 | this._dispatchMoreInfoEvent(event, entityId); 97 | break; 98 | 99 | case 'navigate': 100 | this._handleNavigationEvent(event, actionConfig.navigation_path); 101 | break; 102 | 103 | default: 104 | console.warn(`Action '${actionConfig.action}' is not supported.`); 105 | } 106 | } 107 | 108 | private static _dispatchMoreInfoEvent(event, entityId) { 109 | if (Utils.isPopupOpen) { 110 | return; 111 | } 112 | 113 | Utils.isPopupOpen = true; 114 | 115 | const moreInfoEvent = new CustomEvent('hass-more-info', { 116 | composed: true, 117 | detail: { entityId }, 118 | }); 119 | 120 | history.pushState({ popupOpen: true }, '', window.location.href); 121 | 122 | event.target.dispatchEvent(moreInfoEvent); 123 | 124 | const closePopup = () => { 125 | if (Utils.isPopupOpen) { 126 | Utils.isPopupOpen = false; 127 | window.removeEventListener('popstate', closePopup); 128 | //history.back(); // Optionally close the popup with history.back() if needed 129 | } 130 | }; 131 | 132 | window.addEventListener('popstate', closePopup, { once: true }); 133 | } 134 | 135 | static toHexColor(color: string): string { 136 | if (!color) { 137 | return 'grey' 138 | } 139 | if (/^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/.test(color)) { 140 | return color.toUpperCase(); 141 | } 142 | 143 | const match = color.match(/^rgb\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$/); 144 | if (match) { 145 | const [r, g, b] = match.slice(1, 4).map(Number); 146 | return `#${((1 << 24) | (r << 16) | (g << 8) | b).toString(16).slice(1).toUpperCase()}`; 147 | } 148 | // probs a color name 149 | return color 150 | } 151 | 152 | private static _handleNavigationEvent(event, navigationPath) { 153 | // Perform the navigation action 154 | if (navigationPath) { 155 | navigate(event.target, navigationPath); // Assuming 'navigate' is a function available in your environment 156 | } else { 157 | console.warn("Navigation path is not provided."); 158 | } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/inverters/brands/azzurro.ts: -------------------------------------------------------------------------------- 1 | import {InverterSettingsDto} from '../dto/inverter-settings.dto'; 2 | import {InverterModel} from '../../types'; 3 | import {localize} from '../../localize/localize'; 4 | 5 | export class Azzurro extends InverterSettingsDto { 6 | brand = InverterModel.Azzurro; 7 | statusGroups = { 8 | standby: {states: ['0', 'standby', 'stand-by'], color: 'blue', message: localize('common.standby')}, 9 | selftest: {states: ['1', 'selftest', 'self-checking'], color: 'yellow', message: localize('common.selftest')}, 10 | normal: {states: ['2', 'normal', 'ok'], color: 'green', message: localize('common.normal')}, 11 | alarm: {states: ['3', 'alarm'], color: 'orange', message: localize('common.alarm')}, 12 | fault: {states: ['4', 'fault'], color: 'red', message: localize('common.fault')}, 13 | }; 14 | image = 'data:image/png;base64,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' 15 | } 16 | -------------------------------------------------------------------------------- /src/inverters/brands/deye.ts: -------------------------------------------------------------------------------- 1 | import {InverterSettingsDto} from '../dto/inverter-settings.dto'; 2 | import {InverterModel} from '../../types'; 3 | import {localize} from '../../localize/localize'; 4 | 5 | export class Deye extends InverterSettingsDto { 6 | brand = InverterModel.Deye; 7 | statusGroups = { 8 | standby: {states: ['0', 'standby', 'stand-by'], color: 'blue', message: localize('common.standby')}, 9 | selftest: {states: ['1', 'selftest', 'self-checking'], color: 'yellow', message: localize('common.selftest')}, 10 | normal: {states: ['2', 'normal', 'ok'], color: 'green', message: localize('common.normal')}, 11 | alarm: {states: ['3', 'alarm'], color: 'orange', message: localize('common.alarm')}, 12 | fault: {states: ['4', 'fault'], color: 'red', message: localize('common.fault')}, 13 | }; 14 | image = 'data:image/png;base64,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' 15 | } 16 | -------------------------------------------------------------------------------- /src/inverters/brands/e3dc.ts: -------------------------------------------------------------------------------- 1 | import {InverterSettingsDto} from '../dto/inverter-settings.dto'; 2 | import {InverterModel} from '../../types'; 3 | 4 | export class E3dc extends InverterSettingsDto { 5 | brand = InverterModel.E3dc; 6 | image = 'data:image/png;base64,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' 7 | } 8 | -------------------------------------------------------------------------------- /src/inverters/brands/easun.ts: -------------------------------------------------------------------------------- 1 | import {InverterSettingsDto} from '../dto/inverter-settings.dto'; 2 | import {InverterModel} from '../../types'; 3 | import {localize} from '../../localize/localize'; 4 | 5 | export class Easun extends InverterSettingsDto { 6 | brand = InverterModel.Easun; 7 | statusGroups = { 8 | standby: {states: ['0', 'standby', 'stand-by'], color: 'blue', message: localize('common.standby')}, 9 | selftest: {states: ['1', 'selftest', 'self-checking'], color: 'yellow', message: localize('common.selftest')}, 10 | normal: {states: ['2', 'normal', 'ok'], color: 'green', message: localize('common.normal')}, 11 | alarm: {states: ['3', 'alarm'], color: 'orange', message: localize('common.alarm')}, 12 | fault: {states: ['4', 'fault'], color: 'red', message: localize('common.fault')}, 13 | }; 14 | image = 'data:image/png;base64,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' 15 | } 16 | -------------------------------------------------------------------------------- /src/inverters/brands/ferroamp.ts: -------------------------------------------------------------------------------- 1 | import {InverterSettingsDto} from '../dto/inverter-settings.dto'; 2 | import {InverterModel} from '../../types'; 3 | import {localize} from '../../localize/localize'; 4 | 5 | export class Ferroamp extends InverterSettingsDto { 6 | brand = InverterModel.Ferroamp; 7 | statusGroups = { 8 | standby: {states: ['0', 'standby', 'stand-by'], color: 'blue', message: localize('common.standby')}, 9 | selftest: {states: ['1', 'selftest', 'self-checking'], color: 'yellow', message: localize('common.selftest')}, 10 | normal: {states: ['2', 'normal', 'ok'], color: 'green', message: localize('common.normal')}, 11 | alarm: {states: ['3', 'alarm'], color: 'orange', message: localize('common.alarm')}, 12 | fault: {states: ['4', 'fault'], color: 'red', message: localize('common.fault')}, 13 | }; 14 | image = 'data:image/png;base64,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' 15 | } 16 | -------------------------------------------------------------------------------- /src/inverters/brands/fox-ess.ts: -------------------------------------------------------------------------------- 1 | import {InverterSettingsDto, InverterStatus} from '../dto/inverter-settings.dto'; 2 | import {InverterModel} from '../../types'; 3 | import {localize} from '../../localize/localize'; 4 | 5 | export class FoxESS extends InverterSettingsDto { 6 | brand = InverterModel.FoxESS; 7 | image = 'data:image/png;base64,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'; 8 | statusGroups: InverterStatus = { 9 | standby: {states: ['waiting'], color: 'blue', message: localize('common.standby')}, 10 | selftest: {states: ['self test'], color: 'yellow', message: localize('common.selftest')}, 11 | ongrid: {states: ['on grid'], color: 'green', message: localize('common.ongrid')}, 12 | offgrid: {states: ['off grid / eps'], color: 'green', message: localize('common.offgrid')}, 13 | fault: {states: ['recoverable fault', 'unrecoverable fault'], color: 'red', message: localize('common.fault')}, 14 | check: {states: ['checking'], color: 'orange', message: localize('common.check')}, 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/inverters/brands/growatt.ts: -------------------------------------------------------------------------------- 1 | import {InverterSettingsDto, InverterStatus} from '../dto/inverter-settings.dto'; 2 | import {InverterModel} from '../../types'; 3 | import {localize} from '../../localize/localize'; 4 | 5 | export class Growatt extends InverterSettingsDto { 6 | brand = InverterModel.Growatt; 7 | statusGroups: InverterStatus = { 8 | standby: {states: ['0', 'standby', 'stand-by'], color: 'blue', message: localize('common.standby')}, 9 | selftest: {states: ['selftest', 'self-checking'], color: 'yellow', message: localize('common.selftest')}, 10 | normal: {states: ['1', 'normal', 'ok'], color: 'green', message: localize('common.normal')}, 11 | alarm: {states: ['alarm'], color: 'orange', message: localize('common.alarm')}, 12 | fault: {states: ['3', 'fault'], color: 'red', message: localize('common.fault')}, 13 | } 14 | image = 'data:image/png;base64,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'; 15 | } 16 | -------------------------------------------------------------------------------- /src/inverters/brands/huawei.ts: -------------------------------------------------------------------------------- 1 | import {InverterSettingsDto, InverterStatus} from '../dto/inverter-settings.dto'; 2 | import {InverterModel} from '../../types'; 3 | import {localize} from '../../localize/localize'; 4 | 5 | export class Huawei extends InverterSettingsDto { 6 | brand = InverterModel.Huawei; 7 | batteryStatusGroups: InverterStatus = { 8 | offline: {states: ['0', 'offline'], color: 'yellow', message: localize('common.offline')}, 9 | standby: {states: ['1', 'standby'], color: 'blue', message: localize('common.standby')}, 10 | running: {states: ['2', 'running'], color: 'green', message: localize('common.running')}, 11 | fault: {states: ['3', 'fault'], color: 'red', message: localize('common.fault')}, 12 | sleepmode: {states: ['4', 'sleep_mode'], color: 'yellow', message: localize('common.sleepmode')}, 13 | } 14 | statusGroups: InverterStatus = { 15 | standby: {states: ['standby'], color: 'blue', message: localize('common.standby')}, 16 | selftest: {states: ['spot check'], color: 'yellow', message: localize('common.selftest')}, 17 | normal: { 18 | states: ['grid-connected, grid-connected normally', 'grid-connected, grid connection with derating due to power rationing'], 19 | color: 'green', 20 | message: localize('common.normal') 21 | }, 22 | shutdown: {states: ['shutdown'], color: 'red', message: localize('common.shutdown')}, 23 | normalstop: {states: ['normal stop'], color: 'yellow', message: localize('common.normalstop')}, 24 | alarm: { 25 | states: ['grid-connected, grid connection with derating due to internal causes of the solar inverter'], 26 | color: 'orange', message: localize('common.alarm') 27 | }, 28 | fault: { 29 | states: ['stop due to faults', 'stop due to power rationing'], 30 | color: 'red', 31 | message: localize('common.fault') 32 | }, 33 | }; 34 | image = 'data:image/png;base64,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'; 35 | } 36 | -------------------------------------------------------------------------------- /src/inverters/brands/linky.ts: -------------------------------------------------------------------------------- 1 | import {InverterSettingsDto} from '../dto/inverter-settings.dto'; 2 | import {InverterModel} from '../../types'; 3 | import {localize} from '../../localize/localize'; 4 | 5 | export class Linky extends InverterSettingsDto { 6 | brand = InverterModel.Linky; 7 | statusGroups = { 8 | standby: {states: ['0', 'standby', 'stand-by'], color: 'blue', message: localize('common.standby')}, 9 | selftest: {states: ['1', 'selftest', 'self-checking'], color: 'yellow', message: localize('common.selftest')}, 10 | normal: {states: ['2', 'normal', 'ok'], color: 'green', message: localize('common.normal')}, 11 | alarm: {states: ['3', 'alarm'], color: 'orange', message: localize('common.alarm')}, 12 | fault: {states: ['4', 'fault'], color: 'red', message: localize('common.fault')}, 13 | }; 14 | image = 'data:image/png;base64,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' 15 | } 16 | -------------------------------------------------------------------------------- /src/inverters/brands/makeskyblue.ts: -------------------------------------------------------------------------------- 1 | import {InverterSettingsDto, InverterStatus} from '../dto/inverter-settings.dto'; 2 | import {InverterModel} from '../../types'; 3 | import {localize} from '../../localize/localize'; 4 | 5 | export class MakeSkyBlue extends InverterSettingsDto { 6 | brand = InverterModel.MakeSkyBlue; 7 | statusGroups: InverterStatus = { 8 | standby: {states: ['0', 'standby', 'stand-by'], color: 'blue', message: localize('common.standby')}, 9 | selftest: {states: ['selftest', 'self-checking'], color: 'yellow', message: localize('common.selftest')}, 10 | normal: {states: ['1', 'normal', 'ok'], color: 'green', message: localize('common.normal')}, 11 | alarm: {states: ['alarm'], color: 'orange', message: localize('common.alarm')}, 12 | fault: {states: ['3', 'fault'], color: 'red', message: localize('common.fault')}, 13 | } 14 | image = 'data:image/png;base64,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'; 15 | } -------------------------------------------------------------------------------- /src/inverters/brands/powmr.ts: -------------------------------------------------------------------------------- 1 | import {InverterSettingsDto} from '../dto/inverter-settings.dto'; 2 | import {InverterModel} from '../../types'; 3 | import {localize} from '../../localize/localize'; 4 | 5 | export class PowMr extends InverterSettingsDto { 6 | brand = InverterModel.PowMr; 7 | statusGroups = { 8 | standby: {states: ['0', 'standby', 'stand-by'], color: 'blue', message: localize('common.standby')}, 9 | selftest: {states: ['1', 'selftest', 'self-checking'], color: 'yellow', message: localize('common.selftest')}, 10 | normal: {states: ['2', 'normal', 'ok'], color: 'green', message: localize('common.normal')}, 11 | alarm: {states: ['3', 'alarm'], color: 'orange', message: localize('common.alarm')}, 12 | fault: {states: ['4', 'fault'], color: 'red', message: localize('common.fault')}, 13 | }; 14 | image = 'data:image/png;base64,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' 15 | } 16 | -------------------------------------------------------------------------------- /src/inverters/brands/sma-solar.ts: -------------------------------------------------------------------------------- 1 | import {InverterSettingsDto} from '../dto/inverter-settings.dto'; 2 | import {InverterModel} from '../../types'; 3 | import {localize} from '../../localize/localize'; 4 | 5 | export class SMASolar extends InverterSettingsDto { 6 | brand = InverterModel.SMASolar; 7 | statusGroups = { 8 | standby: {states: ['0', 'standby', 'stand-by'], color: 'blue', message: localize('common.standby')}, 9 | selftest: {states: ['1', 'selftest', 'self-checking'], color: 'yellow', message: localize('common.selftest')}, 10 | normal: {states: ['307: Ok (Ok)'], color: 'green', message: localize('common.normal')}, 11 | alarm: {states: ['455: Warning (Wrn)'], color: 'orange', message: localize('common.alarm')}, 12 | fault: {states: ['35: Fault (Alm)'], color: 'red', message: localize('common.fault')}, 13 | }; 14 | image = 'data:image/png;base64,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' 15 | } 16 | -------------------------------------------------------------------------------- /src/inverters/brands/sofar.ts: -------------------------------------------------------------------------------- 1 | import {InverterSettingsDto} from '../dto/inverter-settings.dto'; 2 | import {InverterModel} from '../../types'; 3 | import {localize} from '../../localize/localize'; 4 | 5 | export class Sofar extends InverterSettingsDto { 6 | brand = InverterModel.Sofar; 7 | statusGroups = { 8 | standby: {states: ['0', 'standby', 'stand-by'], color: 'blue', message: localize('common.standby')}, 9 | selftest: {states: ['1', 'selftest', 'self-checking'], color: 'yellow', message: localize('common.selftest')}, 10 | normal: {states: ['2', 'normal', 'ok'], color: 'green', message: localize('common.normal')}, 11 | alarm: {states: ['3', 'alarm'], color: 'orange', message: localize('common.alarm')}, 12 | fault: {states: ['4', 'fault'], color: 'red', message: localize('common.fault')} 13 | } 14 | image = 'data:image/png;base64,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'; 15 | } 16 | -------------------------------------------------------------------------------- /src/inverters/brands/solax.ts: -------------------------------------------------------------------------------- 1 | import {InverterSettingsDto, InverterStatus} from '../dto/inverter-settings.dto'; 2 | import {InverterModel} from '../../types'; 3 | import {localize} from '../../localize/localize'; 4 | 5 | export class Solax extends InverterSettingsDto { 6 | brand = InverterModel.Solax; 7 | statusGroups: InverterStatus = { 8 | normal: { 9 | states: ['2', 'normal', 'normalmode', 'normal mode'], 10 | color: 'green', 11 | message: localize('common.normal')}, 12 | standby: { 13 | states: ['0', 'waiting', 'waitmode', '9', 'idle', 'idlemode', 'idle mode', '10', 'standby', 'standbymode'], 14 | color: 'blue', 15 | message: localize('common.standby') 16 | }, 17 | selftest: { 18 | states: ['8', 'self testing', 'selftest', 'self test'], 19 | color: 'yellow', 20 | message: localize('common.selftest')}, 21 | offgrid: { 22 | states: ['6', 'off-grid waiting', 'epscheckmode', '7', 'off-grid', 'epsmode', 'eps mode'], 23 | color: 'green', 24 | message: localize('common.offgrid') 25 | }, 26 | fault: { 27 | states: ['4', 'permanent fault', 'permanentfaultmode', 'permanent fault mode', '3', 'fault', 'faultmode'], 28 | color: 'red', 29 | message: localize('common.fault')}, 30 | check: { 31 | states: ['1', 'checking', 'checkmode', '5', 'update', 'updatemode', 'update mode', 'eps check mode'], 32 | color: 'orange', 33 | message: localize('common.check') 34 | }, 35 | }; 36 | image = 'data:image/png;base64,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'; 37 | } 38 | -------------------------------------------------------------------------------- /src/inverters/brands/solis.ts: -------------------------------------------------------------------------------- 1 | import {InverterSettingsDto} from '../dto/inverter-settings.dto'; 2 | import {InverterModel} from '../../types'; 3 | import {localize} from '../../localize/localize'; 4 | 5 | /* Solis Status Codes 6 | * Page 22-23: https://www.scss.tcd.ie/Brian.Coghlan/Elios4you/RS485_MODBUS-Hybrid-BACoghlan-201811228-1854.pdf 7 | * 8 | */ 9 | 10 | export class Solis extends InverterSettingsDto { 11 | brand = InverterModel.Solis; 12 | statusGroups = { 13 | normal: { 14 | states: ['0', '3'], 15 | color: 'green', 16 | message: localize('common.normal'), 17 | }, 18 | standby: { 19 | states: ['1', '2'], 20 | color: 'blue', 21 | message: localize('common.standby'), 22 | }, 23 | alarm: { 24 | states: ['4140', '4100', '4112', '4113', '4114', '4115', '4116', '4120', '4122', '4123', '4124', '4125', '4127', '4128', '4129', '4130', '4132', '4133', '4134', '4135', '4136', '4137', '4138', '4144', '4145', '4146', '4147', '4148', '4150', '4151', '4152', '8123'], 25 | color: 'red', 26 | message: localize('common.alarm'), 27 | }, 28 | fault: { 29 | states: ['4117', '4118', '4119', '4121', '4131', '4134', '4135', '4164', '4167', '4144'], 30 | color: 'red', 31 | message: localize('common.fault'), 32 | }, 33 | selftest: { 34 | states: ['4139'], 35 | color: 'purple', 36 | message: localize('common.selftest'), 37 | }, 38 | }; 39 | image = 'data:image/png;base64,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' 40 | } 41 | -------------------------------------------------------------------------------- /src/inverters/brands/sunsynk.ts: -------------------------------------------------------------------------------- 1 | import {InverterSettingsDto} from '../dto/inverter-settings.dto'; 2 | import {InverterModel} from '../../types'; 3 | import {localize} from '../../localize/localize'; 4 | 5 | export class Sunsynk extends InverterSettingsDto { 6 | brand = InverterModel.Sunsynk; 7 | statusGroups = { 8 | standby: {states: ['0', 'standby', 'stand-by'], color: 'blue', message: localize('common.standby')}, 9 | selftest: {states: ['1', 'selftest', 'self-checking'], color: 'yellow', message: localize('common.selftest')}, 10 | normal: {states: ['2', 'normal', 'ok'], color: 'green', message: localize('common.normal')}, 11 | alarm: {states: ['3', 'alarm'], color: 'orange', message: localize('common.alarm')}, 12 | fault: {states: ['4', 'fault'], color: 'red', message: localize('common.fault')}, 13 | }; 14 | image = 'data:image/png;base64,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' 15 | } 16 | -------------------------------------------------------------------------------- /src/inverters/dto/custom-entity.ts: -------------------------------------------------------------------------------- 1 | import {HassEntity} from 'home-assistant-js-websocket/dist/types'; 2 | import {Utils} from '../../helpers/utils'; 3 | import {Percentage, UnitOfElectricalCurrent, UnitOfEnergy, UnitOfPower} from '../../const'; 4 | 5 | /** 6 | * CustomEntity interface represents a custom entity in Home Assistant. 7 | * - this entity aids in reducing common boiler plate code. the end goal is that we can just use the state object instead of multiple vars 8 | */ 9 | export interface CustomEntity extends HassEntity { 10 | state: string, 11 | decimals: number, 12 | measurement: UnitOfPower | UnitOfEnergy | UnitOfElectricalCurrent | Percentage | 'NA' 13 | 14 | /** 15 | * Extension of Utils.toNum, returns the state in a number 16 | * @param decimals 17 | * @param invert 18 | */ 19 | toNum(decimals?: number, invert?: boolean): number; 20 | 21 | /** 22 | * Desired display output, extrapolated from decimals and measurement 23 | */ 24 | toDisplay(): string 25 | 26 | toString(): string; 27 | 28 | /** 29 | * Checks that the state is not null, undefined or unknown 30 | */ 31 | isValid(): boolean; 32 | 33 | /** 34 | * Checks that the state is not equal to '' 35 | */ 36 | notEmpty(): boolean; 37 | 38 | isNaN(): boolean; 39 | 40 | /** 41 | * Auto converts the state to watts/kilowatts 42 | * @param invert 43 | */ 44 | toPower(invert?: boolean): number; 45 | 46 | /** 47 | * Auto converts the state to watts/kilowatts, with the suffix 48 | * @param invert 49 | * @param decimals 50 | * @param scale 51 | */ 52 | toPowerString(scale?: boolean, decimals?: number, invert?: boolean): string; 53 | 54 | getUOM(): UnitOfPower | UnitOfEnergy | UnitOfElectricalCurrent 55 | } 56 | 57 | // Function to convert HassEntity to CustomEntity 58 | export function convertToCustomEntity(entity: any, measurement: UnitOfPower | UnitOfEnergy | UnitOfElectricalCurrent | Percentage | 'NA' = 'NA', decimals: number = -1): CustomEntity { 59 | 60 | return { 61 | ...entity, 62 | measurement: measurement, 63 | decimals: decimals, 64 | toNum: (decimals?: number, invert?: boolean) => Utils.toNum(entity?.state, decimals, invert), 65 | isValid: () => entity?.state !== null && entity.state !== undefined && entity.state !== 'unknown' || false, 66 | notEmpty: () => (entity?.state !== '' && entity?.state !== null && entity?.state !== 'unknown' && entity.state !== undefined) || false, 67 | isNaN: () => entity?.state === null || Number.isNaN(entity?.state), 68 | toPower: (invert?: boolean) => { 69 | const unit = (entity.attributes?.unit_of_measurement || '').toLowerCase(); 70 | if (unit === 'kw' || unit === 'kwh') { 71 | return Utils.toNum(((entity?.state || '0') * 1000), 0, invert); 72 | } else if (unit === 'mw' || unit === 'mwh') { 73 | return Utils.toNum(((entity?.state || '0') * 1000000), 0, invert); 74 | } else { 75 | return Utils.toNum((entity?.state || '0'), 0, invert) || 0; 76 | } 77 | }, 78 | toPowerString: (scale?: boolean, decimals?: number, invert?: boolean) => 79 | scale ? 80 | Utils.convertValueNew(entity?.state, entity?.attributes?.unit_of_measurement, decimals || 0) : 81 | `${Utils.toNum(entity?.state, 0, invert)} ${entity?.attributes?.unit_of_measurement || ''}`, 82 | toString: () => entity?.state?.toString() || '', 83 | getUOM: () => entity?.attributes?.unit_of_measurement || '', 84 | toDisplay: () => toDisplayFunction(entity.state, measurement, decimals), 85 | } 86 | } 87 | 88 | function toDisplayFunction(state: string, measurement: UnitOfPower | UnitOfEnergy | UnitOfElectricalCurrent | Percentage | 'NA', decimals?: number): string { 89 | //console.log(state, measurement, decimals); 90 | if(state == null) 91 | return state; 92 | if(Number.isNaN(state)) 93 | return `${state}${measurement}`; 94 | const stateDec = decimals != null && decimals >= 0 ? parseFloat(state).toFixed(decimals) : state; 95 | const suffix = measurement != 'NA' && measurement ? measurement : ''; 96 | return `${stateDec}${suffix}`; 97 | } -------------------------------------------------------------------------------- /src/inverters/dto/inverter-settings.dto.ts: -------------------------------------------------------------------------------- 1 | import {InverterModel} from '../../types'; 2 | 3 | export class InverterSettingsDto { 4 | brand!: InverterModel; 5 | model?: string; // not currently used, but could be used to support multiple models per brand, where simple rules changes. 6 | statusGroups!: InverterStatus; 7 | batteryStatusGroups?: InverterStatus; 8 | image!: string 9 | 10 | constructor() { 11 | } 12 | 13 | getBatteryCapacity(batteryPower: number, gridStatus: string, shutdown: number, inverterProg, stateBatterySOC, maxsoc: number, invertBatFlow: boolean) { 14 | let batteryCapacity = 0; 15 | if (invertBatFlow === true ? batteryPower < 0 : batteryPower > 0 ) { 16 | if (gridStatus === 'off' || gridStatus === '0' || gridStatus.toLowerCase() === 'off-grid' || !inverterProg.show || parseInt(stateBatterySOC.state) <= inverterProg.capacity) { 17 | batteryCapacity = shutdown; 18 | } else { 19 | batteryCapacity = inverterProg.capacity; 20 | } 21 | } else if (invertBatFlow === true ? batteryPower > 0 : batteryPower < 0) { 22 | if (gridStatus === 'off' || gridStatus === '0' || gridStatus.toLowerCase() === 'off-grid' || !inverterProg.show || parseInt(stateBatterySOC.state) >= inverterProg.capacity) { 23 | batteryCapacity = maxsoc; 24 | } else if (parseInt(stateBatterySOC.state) < inverterProg.capacity) { 25 | batteryCapacity = inverterProg.capacity; 26 | } 27 | } 28 | return batteryCapacity; 29 | } 30 | } 31 | 32 | export type InverterStatus = { 33 | [key in InverterStatuses]?: InverterStatusConfig 34 | } 35 | 36 | export enum InverterStatuses { 37 | Standby = 'standby', 38 | SelfTest = 'selftest', 39 | Normal = 'normal', 40 | Alarm = 'alarm', 41 | Fault = 'fault', 42 | Idle = 'idle', 43 | NormalStop = 'normalstop', 44 | Shutdown = 'shutdown', 45 | OnGrid = 'ongrid', 46 | OffGrid = 'offgrid', 47 | Check = 'check', 48 | NoBattery = 'noBattery', 49 | Discharging = 'discharging', 50 | Charging = 'charging', 51 | Waiting = 'waiting', 52 | Exporting = 'exporting', 53 | Importing = 'importing', 54 | Flash = 'flash', 55 | Offline = 'offline', 56 | Running = 'running', 57 | SleepMode = 'sleepmode', 58 | Off = 'off', 59 | LowPower = 'lowpower', 60 | Bulk = 'bulk', 61 | Absorption = 'absorption', 62 | Float = 'float', 63 | Storage = 'storage', 64 | Equalize = 'equalize', 65 | Passthru = 'passthru', 66 | Inverting = 'inverting', 67 | PowerAssist = 'powerassist', 68 | PowerSupply = 'powersupply', 69 | Sustain = 'sustain', 70 | ExternalControl = 'externalcontrol' 71 | } 72 | 73 | export interface InverterStatusConfig { 74 | states: string[], 75 | color: string, 76 | message: string 77 | } 78 | -------------------------------------------------------------------------------- /src/inverters/inverter-factory.ts: -------------------------------------------------------------------------------- 1 | import {InverterModel} from '../types'; 2 | import {Solis} from './brands/solis'; 3 | import {InverterSettingsDto} from './dto/inverter-settings.dto'; 4 | import {Lux} from './brands/lux'; 5 | import {Goodwe} from './brands/goodwe'; 6 | import {GoodweGrid} from './brands/goodwe-grid'; 7 | import {FoxESS} from './brands/fox-ess'; 8 | import {Huawei} from './brands/huawei'; 9 | import {Fronius} from './brands/fronius'; 10 | import {PowMr} from './brands/powmr'; 11 | import {Victron} from './brands/victron'; 12 | import {Solax} from './brands/solax'; 13 | import {Growatt} from './brands/growatt'; 14 | import {Sofar} from './brands/sofar'; 15 | import {Sunsynk} from './brands/sunsynk'; 16 | import {CesBatteryBox} from './brands/ces-battery-box'; 17 | import {SolarEdge} from './brands/solar-edge'; 18 | import {Deye} from './brands/deye'; 19 | import {Azzurro} from './brands/azzurro'; 20 | import {MakeSkyBlue} from './brands/makeskyblue'; 21 | import {MPPSolar} from './brands/mpp-solar'; 22 | import {SMASolar} from './brands/sma-solar'; 23 | import {E3dc} from './brands/e3dc'; 24 | import {Sungrow} from './brands/sungrow'; 25 | import {Sigenergy} from './brands/sigenergy'; 26 | import {Linky} from './brands/linky'; 27 | import {Ferroamp} from './brands/ferroamp'; 28 | import {Easun} from './brands/easun'; 29 | 30 | 31 | export class InverterFactory { 32 | static instance: InverterSettingsDto; 33 | 34 | public static getInstance(brand: InverterModel): InverterSettingsDto { 35 | if (!this.instance || this.instance.brand !== brand) { 36 | this.instance = this.createInstance(brand); 37 | } 38 | return this.instance; 39 | } 40 | 41 | private static createInstance(brand: InverterModel): InverterSettingsDto { 42 | switch (brand) { 43 | case InverterModel.Sigenergy: 44 | return new Sigenergy(); 45 | case InverterModel.Linky: 46 | return new Linky(); 47 | case InverterModel.Ferroamp: 48 | return new Ferroamp(); 49 | case InverterModel.Easun: 50 | return new Easun(); 51 | case InverterModel.Azzurro: 52 | return new Azzurro(); 53 | case InverterModel.Solis: 54 | return new Solis(); 55 | case InverterModel.Lux: 56 | return new Lux(); 57 | case InverterModel.Goodwe: 58 | return new Goodwe() 59 | case InverterModel.GoodweGridMode: 60 | return new GoodweGrid() 61 | case InverterModel.E3dc: 62 | return new E3dc() 63 | case InverterModel.FoxESS: 64 | return new FoxESS() 65 | case InverterModel.Huawei: 66 | return new Huawei(); 67 | case InverterModel.Fronius: 68 | return new Fronius(); 69 | case InverterModel.Victron: 70 | return new Victron() 71 | case InverterModel.Solax: 72 | return new Solax() 73 | case InverterModel.Growatt: 74 | return new Growatt() 75 | case InverterModel.Sofar: 76 | return new Sofar() 77 | case InverterModel.CESBatteryBox: 78 | return new CesBatteryBox(); 79 | case InverterModel.SolarEdge: 80 | return new SolarEdge(); 81 | case InverterModel.Deye: 82 | return new Deye(); 83 | case InverterModel.PowMr: 84 | return new PowMr(); 85 | case InverterModel.MPPSolar: 86 | return new MPPSolar(); 87 | case InverterModel.SMASolar: 88 | return new SMASolar(); 89 | case InverterModel.Sungrow: 90 | return new Sungrow(); 91 | case InverterModel.MakeSkyBlue: 92 | return new MakeSkyBlue(); 93 | case InverterModel.Sunsynk: 94 | default: 95 | return new Sunsynk() 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/localize/localize.ts: -------------------------------------------------------------------------------- 1 | import * as da from './languages/da.json'; 2 | import * as de from './languages/de.json'; 3 | import * as en from './languages/en.json'; 4 | import * as es from './languages/es.json'; 5 | import * as et from './languages/et.json'; 6 | import * as fr from './languages/fr.json'; 7 | import * as nl from './languages/nl.json'; 8 | import * as ru from './languages/ru.json'; 9 | import * as cs from './languages/cs.json'; 10 | import * as it from './languages/it.json'; 11 | import * as ca from './languages/ca.json'; 12 | import * as sk from './languages/sk.json'; 13 | import * as pt_br from './languages/pt-br.json'; 14 | import * as sv from './languages/sv.json'; 15 | import * as uk from './languages/uk.json'; 16 | import * as sl from './languages/sl.json'; 17 | import {globalData} from '../helpers/globals'; 18 | 19 | const languages: any = { 20 | da: da, 21 | de: de, 22 | en: en, 23 | es: es, 24 | et: et, 25 | fr: fr, 26 | nl: nl, 27 | ru: ru, 28 | cs: cs, 29 | it: it, 30 | ca: ca, 31 | sk: sk, 32 | pt_BR: pt_br, 33 | sv: sv, 34 | uk: uk, 35 | sl: sl, 36 | }; 37 | 38 | export function localize(string: string, search = '', replace = '') { 39 | const langFromLocalStorage = (localStorage.getItem('selectedLanguage') || 'en') 40 | .replace(/['"]+/g, '') 41 | .replace('-', '_'); 42 | 43 | const lang = `${globalData.hass?.selectedLanguage || globalData.hass?.locale?.language || globalData.hass?.language || langFromLocalStorage}`; 44 | 45 | let translated: string; 46 | 47 | try { 48 | translated = string.split('.').reduce((o, i) => o[i], languages[lang]); 49 | } catch (e) { 50 | translated = string.split('.').reduce((o, i) => o[i], languages['en']); 51 | } 52 | 53 | if (translated === undefined) { 54 | translated = string.split('.').reduce((o, i) => o[i], languages['en']); 55 | } 56 | 57 | if (search !== '' && replace !== '') { 58 | translated = translated.replace(search, replace); 59 | } 60 | return translated; 61 | } 62 | -------------------------------------------------------------------------------- /src/style.ts: -------------------------------------------------------------------------------- 1 | import { CSSResultGroup, css, html } from "lit"; 2 | 3 | export const styles: CSSResultGroup = css` 4 | .container { 5 | display: flex; 6 | flex-direction: column; 7 | align-items: center; 8 | justify-content: center; 9 | height: 100%; 10 | width: 100%; 11 | padding: 5px; 12 | } 13 | 14 | .card { 15 | border-radius: var(--ha-card-border-radius, 10px); 16 | box-shadow: var(--ha-card-box-shadow, 0px 0px 0px 1px rgba(0, 0, 0, 0.12), 0px 0px 0px 0px rgba(0, 0, 0, 0.12), 0px 0px 0px 0px rgba(0, 0, 0, 0.12)); 17 | background: var(--ha-card-background, var(--card-background-color, white)); 18 | border-width: var(--ha-card-border-width); 19 | padding: 0px; 20 | } 21 | 22 | text { text-anchor: middle; dominant-baseline: middle; } 23 | 24 | .left-align {text-anchor: start;} 25 | .right-align {text-anchor: end;} 26 | .st1{fill:#ff9b30;} 27 | .st2{fill:#f3b3ca;} 28 | .st3{font-size:9px;} 29 | .st4{font-size:14px;} 30 | .st5{fill:#969696;} 31 | .st6{fill:#5fb6ad;} 32 | .st7{fill:#5490c2;} 33 | .st8{font-weight:500} 34 | .st9{fill:#959595;} 35 | .st10{font-size:16px;} 36 | .st11{fill:transparent;} 37 | .st12{display:none;} 38 | .st13{font-size:22px;} 39 | .st14{font-size:12px;} 40 | .remaining-energy{font-size:9px;} 41 | `; 42 | 43 | export const getDynamicStyles = (data) => html` 44 | 140 | `; 141 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2021", 4 | "module": "ESNext", 5 | "moduleResolution": "node", 6 | "lib": ["ES2021", "DOM", "DOM.Iterable", "WebWorker"], 7 | "noEmit": true, 8 | "noUnusedParameters": true, 9 | "noImplicitReturns": false, 10 | "noFallthroughCasesInSwitch": true, 11 | "strict": true, 12 | "noImplicitAny": false, 13 | "skipLibCheck": true, 14 | "resolveJsonModule": true, 15 | "experimentalDecorators": true, 16 | "allowSyntheticDefaultImports": true, 17 | "esModuleInterop": true, 18 | "isolatedModules": true 19 | }, 20 | "include": [ 21 | "src/*", 22 | "src/**/*" 23 | ] 24 | } 25 | --------------------------------------------------------------------------------