diff --git a/.envrc b/.envrc index cffc922b00..7fa66fa28e 100644 --- a/.envrc +++ b/.envrc @@ -1 +1 @@ -use flake . --impure +use flake . --override-input devenv-root "file+file://"<(printf %s "$PWD") diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index c976ea645b..d6eebc2a00 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -118,6 +118,25 @@ body: attributes: label: Version description: What version of TeslaMate are you running? - placeholder: v1.24.1 + placeholder: v1.33.0 validations: required: true + + - type: input + id: postgresql_version + attributes: + label: PostgreSQL version + description: What version of PostgreSQL are you running? (see Database information dashboard -> PostgreSQL Version (upper right on desktop) or database -> image section in your `docker-compose.yml`) + placeholder: v17 + + - type: checkboxes + attributes: + label: Are you running latest major supported PostgreSQL version? + description: | + Please ensure you are running the latest major supported PostgreSQL version by comparing your installation with the [installation docs](https://docs.teslamate.org/docs/installation/docker/). + + If not, please first do a backup and follow [Upgrading PostgreSQL to a new major version](https://docs.teslamate.org/docs/maintenance/upgrading_postgres) + + options: + - label: I run the latest major supported PostgreSQL version + required: true diff --git a/.github/actions/build/action.yaml b/.github/actions/build/action.yaml index 5c75574580..21fdcd4689 100644 --- a/.github/actions/build/action.yaml +++ b/.github/actions/build/action.yaml @@ -4,7 +4,7 @@ inputs: is_dockerhub_pushed: description: "Need docker hub login?" required: true - default: '' + default: "" docker_password: description: "Docker password" required: true @@ -30,37 +30,46 @@ inputs: runs: using: "composite" steps: + - name: normalize version name to a valid string + # convert backslashes to dashes, so for example dependabot/x/y/z-1.2.3 works + id: normalize_version + shell: bash + run: | + VERSION="${{ inputs.version }}" + NORMALIZED_VERSION="${VERSION//\//-}" + echo "normalized_version=${NORMALIZED_VERSION}" >> $GITHUB_OUTPUT + - name: Docker meta id: meta - uses: docker/metadata-action@v5 + uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81 # v5.5.1 with: images: ${{ env.REGISTRY_IMAGE }} labels: | {{ inputs.labels }} - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@4fd812986e6c8c2a69e18311145f9371337f27d4 # v3.4.0 - name: Login to Docker Hub if: inputs.is_dockerhub_pushed != '' - uses: docker/login-action@v3.1.0 + uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0 with: username: teslamate password: ${{ inputs.docker_password }} - name: Login to GitHub Container Registry - uses: docker/login-action@v3.1.0 + uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0 with: registry: ghcr.io username: ${{ inputs.repository_owner }} password: ${{ inputs.github_token }} - name: Build and push by digest id: build - uses: docker/build-push-action@v5.0.0 + uses: docker/build-push-action@1a162644f9a7e87d8f4b053101d1d9a712edc18c # v6.3.0 with: context: . platforms: ${{ matrix.platform }} labels: ${{ steps.meta.outputs.labels }} tags: ${{ steps.docker_meta.outputs.tags }} - cache-from: type=registry,ref=ghcr.io/${{ inputs.repository }}:buildcache-${{ matrix.cache_id }}-${{ inputs.version }} - cache-to: type=registry,ref=ghcr.io/${{ inputs.repository }}:buildcache-${{ matrix.cache_id }}-${{ inputs.version }},mode=max + cache-from: type=registry,ref=ghcr.io/${{ inputs.repository }}:buildcache-${{ matrix.cache_id }}-${{ steps.normalize_version.outputs.normalized_version }} + cache-to: type=registry,ref=ghcr.io/${{ inputs.repository }}:buildcache-${{ matrix.cache_id }}-${{ steps.normalize_version.outputs.normalized_version }},mode=max outputs: type=image,name=${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true - name: Export digest shell: bash @@ -70,7 +79,7 @@ runs: touch "/tmp/digests/${digest#sha256:}" ls -l /tmp/digests/ - name: Upload digest - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4 with: name: single-digest-${{ matrix.cache_id }} path: /tmp/digests/* diff --git a/.github/actions/grafana/action.yml b/.github/actions/grafana/action.yml index 151503c1e5..b1bcd93484 100644 --- a/.github/actions/grafana/action.yml +++ b/.github/actions/grafana/action.yml @@ -17,17 +17,17 @@ runs: steps: - name: Docker meta id: docker_meta - uses: docker/metadata-action@v5 + uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81 # v5.5.1 with: images: ${{ inputs.image }} tags: ${{ inputs.tags }} labels: ${{ inputs.labels }} - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@5927c834f5b4fdf503fca6f4c7eccda82949e1ee # v3.1.0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@4fd812986e6c8c2a69e18311145f9371337f27d4 # v3.4.0 - name: Build and push - uses: docker/build-push-action@v5.0.0 + uses: docker/build-push-action@1a162644f9a7e87d8f4b053101d1d9a712edc18c # v6.3.0 with: context: grafana push: true diff --git a/.github/actions/merge/action.yml b/.github/actions/merge/action.yml index 540f8c3647..ff5b42472a 100644 --- a/.github/actions/merge/action.yml +++ b/.github/actions/merge/action.yml @@ -13,23 +13,23 @@ runs: using: "composite" steps: - name: Merge digests and reupload - uses: actions/upload-artifact/merge@v4 + uses: actions/upload-artifact/merge@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4 with: name: digests pattern: single-digest-* - name: Download merged digests - uses: actions/download-artifact@v4 + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 with: name: digests path: /tmp/digests - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@4fd812986e6c8c2a69e18311145f9371337f27d4 # v3.4.0 - name: Docker meta id: meta - uses: docker/metadata-action@v5 + uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81 # v5.5.1 with: images: ${{ inputs.image }} tags: ${{ inputs.tags }} diff --git a/.github/actions/setup-elixir-and-cache-deps/action.yml b/.github/actions/setup-elixir-and-cache-deps/action.yml new file mode 100644 index 0000000000..270ce0a992 --- /dev/null +++ b/.github/actions/setup-elixir-and-cache-deps/action.yml @@ -0,0 +1,75 @@ +name: "Setup Elixir and Cache Dependencies" +description: "Setup Elixir, OTP and cache dependencies" +inputs: + elixir-version: + description: "Elixir version" + required: false + default: "1.17.3" + otp-version: + description: "OTP version" + required: false + default: "26" + cache-name-deps: + description: "Cache name for dependencies" + required: true + cache-name-compiled: + description: "Cache name for compiled build" + required: true + mix-env: + description: "Mix environment" + required: false + default: "dev" + ELIXIR_ASSERT_TIMEOUT: + description: "Elixir assert timeout" + required: false + default: "1000" +outputs: + elixir-version: + description: "The Elixir version used in the setup" + value: ${{ steps.beam.outputs.elixir-version }} + otp-version: + description: "The OTP version used in the setup" + value: ${{ steps.beam.outputs.otp-version }} +runs: + using: "composite" + steps: + - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 + + - name: Setup Elixir and OTP + id: beam + uses: erlef/setup-beam@b9c58b0450cd832ccdb3c17cc156a47065d2114f # v1.18.1 + with: + elixir-version: ${{ inputs.elixir-version }} + otp-version: ${{ inputs.otp-version }} + + - name: Cache deps + id: cache-deps + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 + with: + path: deps + key: ${{ runner.os }}-mix-${{ inputs.cache-name-deps }}-${{ hashFiles('**/mix.lock') }} + restore-keys: | + ${{ runner.os }}-mix-${{ inputs.cache-name-deps }}- + + - name: Cache compiled build + id: cache-build + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 + with: + path: | + _build + priv/cldr/locales + key: ${{ runner.os }}-mix-${{ inputs.cache-name-compiled }}-${{ hashFiles('**/mix.lock') }} + restore-keys: | + ${{ runner.os }}-mix-${{ inputs.cache-name-compiled }}- + ${{ runner.os }}-mix- + + - name: Clean to rule out incremental build as a source of flakiness + if: github.run_attempt > 3 + run: | + mix deps.clean --all + mix clean + shell: sh + + - name: Install dependencies + run: mix deps.get + shell: sh diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 98ac7d2b83..a76d035b1e 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,19 +1,24 @@ version: 2 updates: - # - package-ecosystem: "mix" - # directory: "/" - # schedule: - # interval: "monthly" + - package-ecosystem: "mix" + directory: "/" + schedule: + interval: "monthly" - # - package-ecosystem: "npm" - # directory: "/assets" - # schedule: - # interval: "monthly" + - package-ecosystem: "npm" + directory: "/assets" + schedule: + interval: "monthly" + ignore: + # ignore the path based dependencies to prevent "Dependabot couldn't fetch all your path-based dependencies" + - dependency-name: phoenix + - dependency-name: phoenix_html + - dependency-name: phoenix_live_view - # - package-ecosystem: "npm" - # directory: "/website" - # schedule: - # interval: "monthly" + - package-ecosystem: "npm" + directory: "/website" + schedule: + interval: "monthly" - package-ecosystem: "docker" directory: "/" diff --git a/.github/workflows/buildx.yml b/.github/workflows/buildx.yml index 4a4c00cdd0..5fcc2b6c34 100644 --- a/.github/workflows/buildx.yml +++ b/.github/workflows/buildx.yml @@ -2,6 +2,7 @@ name: Publish Docker images on: workflow_dispatch: + workflow_call: schedule: - cron: "0 3 * * *" push: @@ -10,7 +11,8 @@ on: paths: - "**/*" - "!.github/**" # Important: Exclude PRs related to .github from auto-run - - "!.github/workflows/**" # Important: Exclude PRs related to .github from auto-run + - "!.github/workflows/**" # Important: Exclude PRs related to .github/workflows from auto-run + - "!.github/actions/**" # Important: Exclude PRs related to .github/actions from auto-run env: REGISTRY_IMAGE: teslamate/teslamate @@ -20,28 +22,17 @@ permissions: jobs: check_paths: - runs-on: ubuntu-latest - outputs: - githubfolder: ${{ steps.filter.outputs.githubfolder }} - steps: - - uses: actions/checkout@v4 - - - uses: dorny/paths-filter@v3.0.2 - id: filter - with: - filters: | - githubfolder: - - '.github/**' + uses: ./.github/workflows/check_paths.yml teslamate_build: needs: check_paths - if: needs.check_paths.outputs.githubfolder == 'false' || github.event_name == 'schedule' + if: needs.check_paths.outputs.githubfolder != 'true' || github.event_name == 'schedule' strategy: fail-fast: false matrix: include: - platform: "linux/amd64" - runs_on: "ubuntu-latest" + runs_on: "ubuntu-24.04" cache_id: amd64 - platform: "linux/arm/v7" runs_on: "buildjet-2vcpu-ubuntu-2204-arm" @@ -55,7 +46,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Buildx uses: ./.github/actions/build with: @@ -64,20 +55,20 @@ jobs: repository_owner: ${{ github.repository_owner }} repository: ${{ github.repository }} github_token: ${{ secrets.GITHUB_TOKEN }} - version: ${{ github.ref_name }} + version: ${{ github.head_ref || github.ref_name }} teslamate_merge: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - check_paths - teslamate_build - if: needs.check_paths.outputs.githubfolder == 'false' || github.event_name == 'schedule' + if: needs.check_paths.outputs.githubfolder != 'true' || github.event_name == 'schedule' steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Login to Docker Hub - uses: docker/login-action@v3.2.0 + uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 with: username: teslamate password: ${{ secrets.DOCKER_PASSWORD }} @@ -92,15 +83,15 @@ jobs: type=edge grafana: needs: check_paths - if: needs.check_paths.outputs.githubfolder == 'false' || github.event_name == 'schedule' - runs-on: ubuntu-latest + if: needs.check_paths.outputs.githubfolder != 'true' || github.event_name == 'schedule' + runs-on: ubuntu-24.04 timeout-minutes: 10 steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Login to Docker Hub - uses: docker/login-action@v3.2.0 + uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 with: username: teslamate password: ${{ secrets.DOCKER_PASSWORD }} diff --git a/.github/workflows/check_paths.yml b/.github/workflows/check_paths.yml new file mode 100644 index 0000000000..b9f0b46dd5 --- /dev/null +++ b/.github/workflows/check_paths.yml @@ -0,0 +1,28 @@ +name: Check paths + +on: + workflow_call: + # Map the workflow outputs to job outputs + outputs: + githubfolder: + description: "changes to .github folder" + value: ${{ jobs.check_paths.githubfolder }} + +permissions: + contents: read + +jobs: + check_paths: + runs-on: ubuntu-24.04 + outputs: + githubfolder: ${{ steps.filter.outputs.githubfolder }} + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 + id: filter + with: + base: "master" # needed to set as a called workflow does not have direct access to repository.default_branch + filters: | + githubfolder: + - '.github/**' diff --git a/.github/workflows/cleanup_caches.yml b/.github/workflows/cleanup_caches.yml new file mode 100644 index 0000000000..44f549565e --- /dev/null +++ b/.github/workflows/cleanup_caches.yml @@ -0,0 +1,45 @@ +name: Cleanup caches by a branch + +on: + pull_request: + types: + - closed + paths: + - "**/*" + - "!.github/**" # Important: Exclude PRs related to .github from auto-run + - "!.github/workflows/**" # Important: Exclude PRs related to .github/workflows from auto-run + - "!.github/actions/**" # Important: Exclude PRs related to .github/actions from auto-run + +permissions: + contents: read + packages: read + actions: write + +jobs: + check_paths: + uses: ./.github/workflows/check_paths.yml + + cleanup: + name: Delete caches when PR is closed + needs: check_paths + if: needs.check_paths.outputs.githubfolder != 'true' + runs-on: ubuntu-24.04 + + steps: + - name: Cleanup + run: | + echo "Fetching list of cache key" + cacheKeysForPR=$(gh cache list --repo $REPO --ref $BRANCH --limit 100 | cut -f 1 ) + + ## Setting this to not fail the workflow while deleting cache keys. + set +e + echo "Deleting caches..." + for cacheKey in $cacheKeysForPR + do + gh cache delete $cacheKey --repo $REPO + done + echo "Done" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPO: ${{ github.repository }} + BRANCH: refs/pull/${{ github.event.pull_request.number }}/merge diff --git a/.github/workflows/cleanup_largest_caches.yml b/.github/workflows/cleanup_largest_caches.yml new file mode 100644 index 0000000000..9f5e4c0069 --- /dev/null +++ b/.github/workflows/cleanup_largest_caches.yml @@ -0,0 +1,32 @@ +name: Cleanup lages 100 caches + +on: + workflow_dispatch: + +permissions: + contents: read + packages: read + actions: write + +jobs: + cleanup: + name: Delete caches when PR is closed + runs-on: ubuntu-24.04 + + steps: + - name: Cleanup largest 100 caches + run: | + echo "Fetching list of cache key" + cacheKeysLargest=$(gh cache list --repo $REPO --limit 100 --sort size_in_bytes --order desc | cut -f 1 ) + + ## Setting this to not fail the workflow while deleting cache keys. + set +e + echo "Deleting caches..." + for cacheKey in $cacheKeysLargest + do + gh cache delete $cacheKey --repo $REPO + done + echo "Done" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPO: ${{ github.repository }} diff --git a/.github/workflows/devops.yml b/.github/workflows/devops.yml new file mode 100644 index 0000000000..9e93532914 --- /dev/null +++ b/.github/workflows/devops.yml @@ -0,0 +1,51 @@ +name: DevOps + +on: + workflow_dispatch: + push: + paths: + - "**/*" + - "!.github/**" # Important: Exclude PRs related to .github from auto-run + - "!.github/workflows/**" # Important: Exclude PRs related to .github/workflows from auto-run + - "!.github/actions/**" # Important: Exclude PRs related to .github/actions from auto-run + pull_request: + branches: ["master"] + paths: + - "**/*" + - "!.github/**" # Important: Exclude PRs related to .github from auto-run + - "!.github/workflows/**" # Important: Exclude PRs related to .github/workflows from auto-run + - "!.github/actions/**" # Important: Exclude PRs related to .github/actions from auto-run + +permissions: + contents: read + packages: write + +jobs: + check_paths: + uses: ./.github/workflows/check_paths.yml + + spell_check: + needs: check_paths + if: needs.check_paths.outputs.githubfolder != 'true' || github.event_name == 'schedule' + uses: ./.github/workflows/spell_check.yml + + ensure_linting: + needs: + - check_paths + - spell_check + if: needs.check_paths.outputs.githubfolder != 'true' || github.event_name == 'schedule' + uses: ./.github/workflows/ensure_linting.yml + + elixir_dep_verification_and_static_analysis: + needs: + - check_paths + - ensure_linting + if: needs.check_paths.outputs.githubfolder != 'true' || github.event_name == 'schedule' + uses: ./.github/workflows/elixir_dep_verification_and_static_analysis.yml + + elixir_test: + needs: + - check_paths + - ensure_linting + if: needs.check_paths.outputs.githubfolder != 'true' || github.event_name == 'schedule' + uses: ./.github/workflows/elixir_test.yml diff --git a/.github/workflows/elixir.yml b/.github/workflows/elixir.yml deleted file mode 100644 index 0a4b82d034..0000000000 --- a/.github/workflows/elixir.yml +++ /dev/null @@ -1,198 +0,0 @@ -name: Elixir CI - -on: - push: - paths: - - "**/*" - - "!.github/**" # Important: Exclude PRs related to .github from auto-run - pull_request: - branches: ["master"] - paths: - - "**/*" - - "!.github/**" # Important: Exclude PRs related to .github from auto-run - -jobs: - lint: - name: Lint (Elixir ${{ matrix.elixir }} / OTP ${{ matrix.otp }}) - runs-on: ubuntu-20.04 - - permissions: - contents: read - - strategy: - matrix: - include: - - elixir: "1.16.2" - otp: "26" - lint: true - - steps: - - uses: actions/checkout@v4 - - - uses: erlef/setup-beam@v1 - id: beam - with: - otp-version: ${{ matrix.otp }} - elixir-version: ${{ matrix.elixir }} - - - name: Cache deps - id: cache-deps - uses: actions/cache@v4 - env: - cache-name: cache-elixir-deps - with: - path: deps - key: ${{ runner.os }}-mix-${{ env.cache-name }}-${{ hashFiles('**/mix.lock') }} - restore-keys: | - ${{ runner.os }}-mix-${{ env.cache-name }}- - - - name: Cache compiled build - id: cache-build - uses: actions/cache@v4 - env: - cache-name: cache-compiled-dev-build - with: - path: | - _build - priv/cldr/locales - key: ${{ runner.os }}-mix-${{ env.cache-name }}-${{ hashFiles('**/mix.lock') }} - restore-keys: | - ${{ runner.os }}-mix-${{ env.cache-name }}- - ${{ runner.os }}-mix- - - - name: Clean to rule out incremental build as a source of flakiness - if: github.run_attempt > 3 - run: | - mix deps.clean --all - mix clean - shell: sh - - - name: Restore PLT cache - id: plt_cache - uses: actions/cache/restore@v4 - with: - key: | - ${{ runner.os }}-${{ steps.beam.outputs.elixir-version }}-${{ steps.beam.outputs.otp-version }}-plt - restore-keys: | - ${{ runner.os }}-${{ steps.beam.outputs.elixir-version }}-${{ steps.beam.outputs.otp-version }}-plt - path: | - priv/plts - - - name: Install dependencies - run: mix deps.get - - - name: Compile without warnings - run: mix compile --warnings-as-errors - - - name: Verify that POT files are up to date - run: mix gettext.extract --check-up-to-date - - - name: Spell check - uses: crate-ci/typos@v1.22.9 - - - name: Check formatting - run: mix format --check-formatted - - - name: Check unused dependencies - run: mix deps.unlock --check-unused - - - name: Create PLTs - if: steps.plt_cache.outputs.cache-hit != 'true' - run: mix dialyzer --plt - - - name: Save PLT cache - id: plt_cache_save - uses: actions/cache/save@v4 - if: steps.plt_cache.outputs.cache-hit != 'true' - with: - key: | - ${{ runner.os }}-${{ steps.beam.outputs.elixir-version }}-${{ steps.beam.outputs.otp-version }}-plt - path: | - priv/plts - - - name: Run dialyzer - run: mix dialyzer --format github - - test: - name: Test (Elixir ${{ matrix.elixir }} / OTP ${{ matrix.otp }}) - runs-on: ubuntu-20.04 - - permissions: - contents: read - - strategy: - matrix: - include: - - elixir: "1.16.2" - otp: "26" - report_coverage: true - - services: - db: - image: postgres:16 - ports: ["5432:5432"] - env: - POSTGRES_PASSWORD: postgres - options: >- - --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 - - env: - MIX_ENV: test - ELIXIR_ASSERT_TIMEOUT: 1000 - - steps: - - uses: actions/checkout@v4 - - - uses: erlef/setup-beam@v1 - id: beam - with: - otp-version: ${{ matrix.otp }} - elixir-version: ${{ matrix.elixir }} - - - name: Cache deps - id: cache-deps - uses: actions/cache@v4 - env: - cache-name: cache-elixir-deps - with: - path: deps - key: ${{ runner.os }}-mix-${{ env.cache-name }}-${{ hashFiles('**/mix.lock') }} - restore-keys: | - ${{ runner.os }}-mix-${{ env.cache-name }}- - - - name: Cache compiled build - id: cache-build - uses: actions/cache@v4 - env: - cache-name: cache-compiled-test-build - with: - path: | - _build - priv/cldr/locales - key: ${{ runner.os }}-mix-${{ env.cache-name }}-${{ hashFiles('**/mix.lock') }} - restore-keys: | - ${{ runner.os }}-mix-${{ env.cache-name }}- - ${{ runner.os }}-mix- - - - name: Clean to rule out incremental build as a source of flakiness - if: github.run_attempt > 3 - run: | - mix deps.clean --all - mix clean - shell: sh - - - name: Install dependencies - run: mix deps.get - - - name: Compile - run: mix compile - - - name: Run tests - run: mix test --warnings-as-errors - - - name: Check Coverage - if: github.ref == 'refs/heads/master' && matrix.report_coverage - run: mix coveralls.github - continue-on-error: true - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/elixir_dep_verification_and_static_analysis.yml b/.github/workflows/elixir_dep_verification_and_static_analysis.yml new file mode 100644 index 0000000000..d8c64cc74c --- /dev/null +++ b/.github/workflows/elixir_dep_verification_and_static_analysis.yml @@ -0,0 +1,70 @@ +name: Elixir Dependency Verification and Static Analysis + +on: + workflow_call: + +env: + CACHE_NAME_DEPS: cache-elixir-deps + CACHE_NAME_COMPILED_DEV: cache-compiled-dev-build + CACHE_NAME_COMPILED_TEST: cache-compiled-test-build + ELIXIR_ASSERT_TIMEOUT: 1000 + +permissions: + contents: read + +jobs: + verify_dependencies_and_static_analysis: + name: Verify dependencies, POT files, unused dependencies, static analysis + runs-on: ubuntu-24.04 + + permissions: + contents: read + + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Setup Elixir and Cache Dependencies + id: setup-elixir-and-cache-deps + uses: ./.github/actions/setup-elixir-and-cache-deps + with: + cache-name-deps: ${{ env.CACHE_NAME_DEPS }} + cache-name-compiled: ${{ env.CACHE_NAME_COMPILED_DEV }} + mix-env: dev + + - name: Compile without warnings + run: mix compile --warnings-as-errors + shell: sh + + - name: Verify that POT files are up to date + run: mix gettext.extract --check-up-to-date + + - name: Check unused dependencies + run: mix deps.unlock --check-unused + + - name: Restore PLT cache + id: plt_cache + uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + with: + key: | + ${{ runner.os }}-${{ steps.setup-elixir-and-cache-deps.outputs.elixir-version }}-${{ steps.setup-elixir-and-cache-deps.outputs.otp-version }}-plt + restore-keys: | + ${{ runner.os }}-${{ steps.setup-elixir-and-cache-deps.outputs.elixir-version }}-${{ steps.setup-elixir-and-cache-deps.outputs.otp-version }}-plt + path: | + priv/plts + + - name: Create Persistent Lookup Tables (PLTs) for Dialyzer + if: steps.plt_cache.outputs.cache-hit != 'true' + run: mix dialyzer --plt + + - name: Save PLT cache + id: plt_cache_save + uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + if: steps.plt_cache.outputs.cache-hit != 'true' + with: + key: | + ${{ runner.os }}-${{ steps.setup-elixir-and-cache-deps.outputs.elixir-version }}-${{ steps.setup-elixir-and-cache-deps.outputs.otp-version }}-plt + path: | + priv/plts + + - name: Run dialyzer for static analysis + run: mix dialyzer --format github diff --git a/.github/workflows/elixir_test.yml b/.github/workflows/elixir_test.yml new file mode 100644 index 0000000000..458f6f2d11 --- /dev/null +++ b/.github/workflows/elixir_test.yml @@ -0,0 +1,55 @@ +name: Elixir Test and report coverage + +on: + workflow_call: + +env: + CACHE_NAME_DEPS: cache-elixir-deps + CACHE_NAME_COMPILED_TEST: cache-compiled-test-build + ELIXIR_ASSERT_TIMEOUT: 1000 + +permissions: + contents: read + +jobs: + test: + name: Test + runs-on: ubuntu-24.04 + + permissions: + contents: read + + services: + db: + image: postgres:17 + ports: ["5432:5432"] + env: + POSTGRES_PASSWORD: postgres + options: >- + --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 + + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Setup Elixir and Cache Dependencies + id: setup-elixir-and-cache-deps + uses: ./.github/actions/setup-elixir-and-cache-deps + with: + cache-name-deps: ${{ env.CACHE_NAME_DEPS }} + cache-name-compiled: ${{ env.CACHE_NAME_COMPILED_TEST }} + mix-env: test + ELIXIR_ASSERT_TIMEOUT: ${{ env.ELIXIR_ASSERT_TIMEOUT }} + + - name: Compile without warnings + run: mix compile --warnings-as-errors + shell: sh + + - name: Run tests + run: mix test --warnings-as-errors + + - name: Check Coverage + if: github.ref == 'refs/heads/master' + run: mix coveralls.github + continue-on-error: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/ensure_linting.yml b/.github/workflows/ensure_linting.yml new file mode 100644 index 0000000000..1570168a10 --- /dev/null +++ b/.github/workflows/ensure_linting.yml @@ -0,0 +1,41 @@ +name: Ensure Linting + +on: + workflow_call: + +permissions: + contents: read + +jobs: + check_linting: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Install Nix + uses: nixbuild/nix-quick-install-action@5bb6a3b3abe66fd09bbf250dce8ada94f856a703 # v30 + + - name: Nix binary cache + uses: nix-community/cache-nix-action@c448f065ba14308da81de769632ca67a3ce67cf5 # v6 + with: + # restore and save a cache using this key + primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }} + # if there's no cache hit, restore a cache by this prefix + restore-prefixes-first-match: nix-${{ runner.os }}- + # collect garbage until Nix store size (in bytes) is at most this number + # before trying to save a new cache + # 1G = 1073741824 + gc-max-store-size-linux: 1073741824 + # do purge caches + purge: true + # purge all versions of the cache + purge-prefixes: nix-${{ runner.os }}- + # created more than this number of seconds ago + # relative to the start of the `Post Restore and save Nix store` phase + purge-created: 0 + # except any version with the key that is the same as the `primary-key` + purge-primary-key: never + + - name: Run treefmt in CI mode + run: nix develop --override-input devenv-root "file+file://"<(printf %s "$PWD") . --command treefmt --ci + # or use: https://github.com/cachix/git-hooks.nix?tab=readme-ov-file diff --git a/.github/workflows/ghcr_build.yml b/.github/workflows/ghcr_build.yml index c53893c729..dadd6dde97 100644 --- a/.github/workflows/ghcr_build.yml +++ b/.github/workflows/ghcr_build.yml @@ -1,19 +1,13 @@ name: Build GHCR images on: - workflow_dispatch: - push: + pull_request: + branches: ["master"] paths: - "**/*" - "!.github/**" # Important: Exclude PRs related to .github from auto-run - - "!.github/workflows/**" # Important: Exclude PRs related to .github from auto-run - branches: ["ci"] - pull_request_target: - branches: ["master", "ci"] - paths: - - "**/*" - - "!.github/**" # Important: Exclude PRs related to .github from auto-run - - "!.github/workflows/**" # Important: Exclude PRs related to .github from auto-run + - "!.github/workflows/**" # Important: Exclude PRs related to .github/workflows from auto-run + - "!.github/actions/**" # Important: Exclude PRs related to .github/actions from auto-run env: REGISTRY_IMAGE: ghcr.io/${{ github.repository }} @@ -24,29 +18,43 @@ permissions: jobs: check_paths: - runs-on: ubuntu-latest + uses: ./.github/workflows/check_paths.yml + + check_if_pr_from_outside_repo: + name: Check if PR from outside repo + # as we currently only push ghcr images for PRs from our own repo + needs: check_paths + if: needs.check_paths.outputs.githubfolder != 'true' outputs: - githubfolder: ${{ steps.filter.outputs.githubfolder }} + is_pr_from_outside_repo: ${{ steps.check_if_pr_from_outside_repo.outputs.is_pr_from_outside_repo }} + runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v4 - - - uses: dorny/paths-filter@v3.0.2 - id: filter - with: - filters: | - githubfolder: - - '.github/**' + - name: check if PR from outside repo + id: check_if_pr_from_outside_repo + shell: bash + run: | + echo "::group::Check if PR from outside repo" + if [[ "${{ github.event.pull_request.head.repo.full_name }}" != "${{ github.repository }}" ]]; then + echo "This PR is from outside the repo" + echo "is_pr_from_outside_repo=true" >> $GITHUB_OUTPUT + else + echo "This PR is from our own repo" + echo "is_pr_from_outside_repo=false" >> $GITHUB_OUTPUT + fi + echo "::endgroup::" teslamate_build: name: Build images - needs: check_paths - if: needs.check_paths.outputs.githubfolder == 'false' + needs: + - check_paths + - check_if_pr_from_outside_repo + if: needs.check_paths.outputs.githubfolder != 'true' && needs.check_if_pr_from_outside_repo.outputs.is_pr_from_outside_repo == 'false' strategy: fail-fast: false matrix: include: - platform: "linux/amd64" - runs_on: "ubuntu-latest" + runs_on: "ubuntu-24.04" cache_id: amd64 - platform: "linux/arm/v7" runs_on: "buildjet-2vcpu-ubuntu-2204-arm" @@ -58,13 +66,7 @@ jobs: runs-on: ${{ matrix.runs_on }} timeout-minutes: 10 steps: - - uses: actions/checkout@v4 - if: ${{ github.event_name != 'pull_request_target' }} - - uses: actions/checkout@v4 - if: ${{ github.event_name == 'pull_request_target' }} - with: - ref: ${{github.event.pull_request.head.ref}} - repository: ${{github.event.pull_request.head.repo.full_name}} + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Buildx uses: ./.github/actions/build @@ -73,7 +75,7 @@ jobs: repository_owner: ${{ github.repository_owner }} repository: ${{ github.repository }} github_token: ${{ secrets.GITHUB_TOKEN }} - version: ${{ github.ref_name }} + version: ${{ github.head_ref || github.ref_name }} labels: | org.opencontainers.image.version=${{ github.ref || github.ref_name }} @@ -82,14 +84,14 @@ jobs: needs: - check_paths - teslamate_build - if: needs.check_paths.outputs.githubfolder == 'false' - runs-on: ubuntu-latest + if: needs.check_paths.outputs.githubfolder != 'true' + runs-on: ubuntu-24.04 timeout-minutes: 10 steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Login to GitHub Container Registry - uses: docker/login-action@v3.2.0 + uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 with: registry: ghcr.io username: ${{ github.repository_owner }} @@ -101,20 +103,16 @@ jobs: image: ${{ env.REGISTRY_IMAGE }} grafana: - needs: check_paths - if: needs.check_paths.outputs.githubfolder == 'false' - runs-on: ubuntu-latest + needs: + - check_paths + - check_if_pr_from_outside_repo + if: needs.check_paths.outputs.githubfolder != 'true' && needs.check_if_pr_from_outside_repo.outputs.is_pr_from_outside_repo == 'false' + runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v4 - if: ${{ github.event_name != 'pull_request_target' }} - - uses: actions/checkout@v4 - if: ${{ github.event_name == 'pull_request_target' }} - with: - ref: ${{github.event.pull_request.head.ref}} - repository: ${{github.event.pull_request.head.repo.full_name}} + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Login to GitHub Container Registry - uses: docker/login-action@v3.2.0 + uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 with: registry: ghcr.io username: ${{ github.repository_owner }} diff --git a/.github/workflows/ghcr_purge.yml b/.github/workflows/ghcr_purge.yml index 260c1a1155..79660cb1c8 100644 --- a/.github/workflows/ghcr_purge.yml +++ b/.github/workflows/ghcr_purge.yml @@ -1,14 +1,15 @@ name: Purge PR images on: - pull_request_target: + workflow_call: + pull_request: types: - closed paths: - "**/*" - "!.github/**" # Important: Exclude PRs related to .github from auto-run - - "!.github/workflows/**" # Important: Exclude PRs related to .github from auto-run - + - "!.github/workflows/**" # Important: Exclude PRs related to .github/workflows from auto-run + - "!.github/actions/**" # Important: Exclude PRs related to .github/actions from auto-run permissions: contents: read @@ -16,27 +17,16 @@ permissions: jobs: check_paths: - runs-on: ubuntu-latest - outputs: - githubfolder: ${{ steps.filter.outputs.githubfolder }} - steps: - - uses: actions/checkout@v4 - - - uses: dorny/paths-filter@v3.0.2 - id: filter - with: - filters: | - githubfolder: - - '.github/**' + uses: ./.github/workflows/check_paths.yml purge-pr-package: name: Delete images from ghcr.io when PR is closed needs: check_paths - if: needs.check_paths.outputs.githubfolder == 'false' - runs-on: ubuntu-latest + if: needs.check_paths.outputs.githubfolder != 'true' + runs-on: ubuntu-24.04 steps: - - uses: chipkent/action-cleanup-package@v1.0.3 + - uses: chipkent/action-cleanup-package@1316a66015b82d745b57acbb6c570f2bb1d108f9 # v1.0.3 id: cleanup continue-on-error: true with: @@ -44,7 +34,7 @@ jobs: tag: pr-${{ github.event.pull_request.number }} github-token: ${{ secrets.GITHUB_TOKEN }} - - uses: actions/delete-package-versions@v5 + - uses: actions/delete-package-versions@e5bc658cc4c965c472efe991f8beea3981499c55 # v5.0.0 if: job.steps.cleanup.status == success() name: Delete untagged images from ghcr.io continue-on-error: true diff --git a/.github/workflows/spell_check.yml b/.github/workflows/spell_check.yml new file mode 100644 index 0000000000..2ab77d9158 --- /dev/null +++ b/.github/workflows/spell_check.yml @@ -0,0 +1,18 @@ +name: Spell check + +on: + workflow_call: + +permissions: + contents: read + +jobs: + spell_check: + name: Spell check + runs-on: ubuntu-24.04 + + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Spell check + uses: crate-ci/typos@b1a1ef3893ff35ade0cfa71523852a49bfd05d19 # v1.31.1 diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index b9a42a3bef..ea971ec456 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -1,4 +1,4 @@ -name: 'Close Stale Issues and Pull Requests' +name: "Close Stale Issues and Pull Requests" on: schedule: @@ -10,9 +10,9 @@ permissions: jobs: stale: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - - uses: actions/stale@v9 + - uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0 with: stale-issue-message: > This issue has been automatically marked as stale because @@ -26,5 +26,5 @@ jobs: days-before-close: 7 days-before-pr-close: -1 exempt-all-pr-milestones: true - exempt-issue-labels: 'area:grafana, area:tesla api, enhancement, kind:bug, kind:documentation, kind:idea, note:needs investigation, security, pinned' - exempt-pr-labels: 'awaiting-approval, area:grafana, area:tesla api, enhancement, kind:bug, kind:documentation, kind:idea, note:needs investigation, security, pinned' + exempt-issue-labels: "area:grafana, area:tesla api, enhancement, kind:bug, kind:documentation, kind:idea, note:needs investigation, security, pinned" + exempt-pr-labels: "awaiting-approval, area:grafana, area:tesla api, enhancement, kind:bug, kind:documentation, kind:idea, note:needs investigation, security, pinned" diff --git a/.github/workflows/update-flake-lock.yml b/.github/workflows/update-flake-lock.yml new file mode 100644 index 0000000000..1eeca3689c --- /dev/null +++ b/.github/workflows/update-flake-lock.yml @@ -0,0 +1,54 @@ +--- +name: Update flake.lock +on: + workflow_dispatch: + schedule: + # runs weekly on Saturday at 00:00 UTC time + - cron: "0 0 * * 6" + +permissions: + contents: write + pull-requests: write + +jobs: + lockfile: + runs-on: ubuntu-24.04 + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Install Nix + uses: nixbuild/nix-quick-install-action@5bb6a3b3abe66fd09bbf250dce8ada94f856a703 # v30 + + - name: Nix binary cache + uses: nix-community/cache-nix-action@c448f065ba14308da81de769632ca67a3ce67cf5 # v6 + with: + # restore and save a cache using this key + primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }} + # if there's no cache hit, restore a cache by this prefix + restore-prefixes-first-match: nix-${{ runner.os }}- + # collect garbage until Nix store size (in bytes) is at most this number + # before trying to save a new cache + # 1G = 1073741824 + gc-max-store-size-linux: 1073741824 + # do purge caches + purge: true + # purge all versions of the cache + purge-prefixes: nix-${{ runner.os }}- + # created more than this number of seconds ago + # relative to the start of the `Post Restore and save Nix store` phase + purge-created: 0 + # except any version with the key that is the same as the `primary-key` + purge-primary-key: never + + - name: Update flake.lock + id: update + uses: DeterminateSystems/update-flake-lock@a2bbe0274e3a0c4194390a1e445f734c597ebc37 #v24 + with: + pr-title: "build(deps): update flake.lock" + pr-labels: | + dependencies + automated + + - name: Print PR number + run: echo Pull request number is ${{ steps.update.outputs.pull-request-number }}. diff --git a/.markdownlint.yaml b/.markdownlint.yaml index d877b77a70..30bffe0bc5 100644 --- a/.markdownlint.yaml +++ b/.markdownlint.yaml @@ -3,4 +3,6 @@ default: true MD013: line_length: 500 MD024: - siblings_only: true # heading duplication is allowed for non-sibling headings (common in changelogs) + siblings_only: true # heading duplication is allowed for non-sibling headings (common in changelogs) +MD033: + allowed_elements: ["details", "summary", "TabItem", "pre"] diff --git a/.typos.toml b/.typos.toml index 01e613e0bd..deae7da83a 100644 --- a/.typos.toml +++ b/.typos.toml @@ -11,6 +11,7 @@ ro = "ro" hd = "hd" dur = "dur" pn = "pn" +Derivated = "Derivated" [type.po] extend-glob = ["*.po"] diff --git a/CHANGELOG.md b/CHANGELOG.md index 912f4e35a2..01948b3256 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,13 +2,369 @@ ## [unreleased] +**This is a breaking change release:** TeslaMate uses PostgreSQL as database, this is an external dependency and needs to be updated by yourself. We now require PostgreSQL 16.7 or 17.3 or higher as we are upgrading the bundled earthdistance extension to v1.2. TeslaMate will now fail to start if you are using an older version. Ensure to upgrade your database before upgrading TeslaMate. To upgrade PostgreSQL, you need to follow these instructions: + +- [Backup your data](https://docs.teslamate.org/docs/maintenance/backup_restore#backup) +- [Upgrade PostgreSQL to postgres:17](https://docs.teslamate.org/docs/maintenance/upgrading_postgres) (Yes, you will have to erase your data, which is why you need your backup in the first place.) +- [Upgrade TeslaMate to this version](https://docs.teslamate.org/docs/upgrading) +- [Backup your data after the upgrade](https://docs.teslamate.org/docs/maintenance/backup_restore#backup) + +**Note for user which revoked permissions:** If the SUPERUSER privilege has been revoked after the initial (manual) installation, it must be temporarily granted for pending earthdistance migrations to succeed. The privilege can then be safely revoked. + +As always, there are also many improvements. The webview now shows the TPMS values in the low pressure tooltip. We use the latest Grafana 11.6.1 and have improved the battery health dashboard and aligned the range calculation through the dashboards. Additionally time zone handling has been improved and the date formats are now based on the browser locale. + +Enjoy it. + +### Breaking Changes + +- feat: check Postgres version on startup, require 16.7 / 17.3, update earthdistance extension (#4648 - @swiffer) + +### New features + +- feat: show tpms value to the low pressure tooltip in webview (#4654 - @NirKli) + +### Improvements and bug fixes + +- fix(nix): non-recursive provider for ../grafana/dashboards (#4680 - @swiffer) +- feat: use Grafana 11.6.1 (#4662 - @swiffer) + +#### Build, CI, internal + +- build(deps): bump image-size from 1.2.0 to 1.2.1 in /website (#4622) +- ci: switch to cache-nix-action as Magic Nix Cache is deprecated (#4626 - @JakobLichterfeld) +- build(deps): update flake.lock (#4603) +- build(deps): bump crate-ci/typos from 1.30.0 to 1.31.1 (#4611) +- build(deps): bump docker/login-action from 3.3.0 to 3.4.0 (#4612) +- build(deps): bump actions/cache from 4.2.2 to 4.2.3 (#4613) +- build(deps): bump tesla from 1.13.2 to 1.14.1 (#4616) +- ci(sec): remove pull_request_target workflow triggers to improve sec even further, the downside is that test images are now only created for repo's own PRs (#4637 - @JakobLichterfeld / Thanks to @Firebasky for responsibly disclosing the vulnerability) +- ci: fix ghcr build ([..ddf85e6](https://github.com/teslamate-org/teslamate/commit/ba35f417014e6be742ee2b0713cfa7876ddf85e6) - @JakobLichterfeld) +- ci: ensure ghcr images build correctly even if branch contains backslash (#4655 - @JakobLichterfeld) +- ci: skip ghcr build for PRs from outside repo (#4660 and [462b568](https://github.com/teslamate-org/teslamate/commit/462b5680abbfbdfd26f028d88f7a62f4ae4183cd) - @JakobLichterfeld) +- build(deps): bump estree-util-value-to-estree in /website (#4641) +- build(deps): update flake.lock (#4653) +- fix(nix): update mix dependency hash in nix builds ([3d08431](https://github.com/teslamate-org/teslamate/commit/3d08431ee3de0eaf3d3045aa0018c687627c4dac) - @JakobLichterfeld) +- ci(dependabot): add ignore rules for path-based dependencies (#4666 - @JakobLichterfeld) +- sec: upgrade esbuild to 0.25.2 and esbuild-sass-plugin to 3.3.1 to avoid GHSA-67mh-4wv8-2f99 (#4669 - @JakobLichterfeld) +- build(deps): bump http-proxy-middleware from 2.0.7 to 2.0.9 in /website (#4670) +- build(deps): bump phoenix_html from 4.2.0 to 4.2.1 (#4667) +- build(deps): bump ex_cldr from 2.40.2 to 2.42.0 (#4615) +- build(deps): bump react from 18.3.1 to 19.1.0 and docusaurus/core from 3.4.0 to 3.7.0 in /website (#4618 - @JakobLichterfeld) +- build(deps): bump phoenix_ecto from 4.6.2 to 4.6.3 (#4333) +- build(deps): update flake.lock (#4674) + +#### Dashboards + +- fix: improve calc for usable (now) in battery health dashboard (#4644 - @swiffer) +- feat: make use of car filter, add timefilter in locations dashboard (#4647 - @swiffer) +- fix: use same rated range calculation in updates dashboard as in battery health dashboard (#4682 - @swiffer) +- fix: explicitly set height of home dashboard background image based on current layout & grafana css (#4681 -@swiffer) +- fix: set $\_\_timezone explicitly in dashboards to ensure truncation is done with respect to the Grafana timezone (#4684 - @swiffer) +- fix: issues when using browser locale for date formats (#4662 - @swiffer) +- fix: widens Date Columns to fully show date strings formatted in US locale (#4662 - @swiffer) + +#### Translations + +#### Documentation + +- chore(issue-template): add PostgreSQL version input and checkbox for latest version check in bug report template (#4643 - @JakobLichterfeld) +- docs: allow to add energy added to the Home Assistant's Energy tab to measure how much energy each session uses (#4659 - @alexsapran) +- docs: update changelog with breaking changes description (#4691 - @JakobLichterfeld) + +## [1.33.0] - 2025-03-28 + +As always, there are many improvements. + +We now use Grafana 11.6.0 which was release the last days, improved the logging and state transitions, added a new dashboard for database information and improved other dashboards. We also added a new section to the documentation about the Entity Relationship Model (ERM) of TeslaMate. This is a great help for developers who want to understand the data model of TeslaMate and how to extend it. + +Enjoy it. + +### New features + +### Improvements and bug fixes + +- fix(nix): wait for mosquitto to start before starting teslamate (#4419 - @brianmay) +- feat: use Grafana 11.4.0 (#4299 - @swiffer) +- feat: improve logging messages (#4467 - @micves and @brianmay) +- feat: optimize state transitions (#4473 - @micves and @brianmay) + - don't try to sleep if power > 0 + - cancel an ongoing suspended state/trying to sleep and go back to online + - add conditions to enter charging +- feat: support accessing PostgreSQL on unix domain sockets (#4456 - @j-baker) +- fix(nix): temporarily disable browser locale in date formats for nix deployment as well (#4480 - @swiffer) +- feat: Grafana 11.5.0 (#4509 - @swiffer) +- feat: Grafana 11.5.2 (#4551 - @swiffer) +- fix(nix): update mix dependency hash in nix builds. (#4577 - @weiren2) +- feat: Grafana 11.6.0 (#4570 - @swiffer) + +#### Build, CI, internal + +- build(deps): bump castore from 1.0.9 to 1.0.10 (#4414) +- build(deps): bump @docusaurus/preset-classic from 3.5.2 to 3.6.3 in /website (#4412) +- build(deps): bump path-to-regexp from 1.8.0 to 1.9.0 in /website (#4424) +- build(deps): bump crate-ci/typos from 1.27.0 to 1.28.1 (#4411) +- build(deps): bump tesla from 1.13.0 to 1.13.2 (#4416) +- build(deps): bump postgrex from 0.19.1 to 0.19.3 (#4415) +- build(nix): switch to nixos-24.11 (#4420 - @brianmay) +- build(deps): update flake.lock (#4427) +- fix: update mix deps hash to fix build error on recent NixOS 24.11 update (#4428) +- build(deps): bump path-to-regexp and express in /website (#4433) +- build(deps): update flake.lock (#4440) +- build(deps): bump actions/cache from 4.1.2 to 4.2.0 (#4465) +- build(deps): bump phoenix from 1.7.14 to 1.7.18 (#4462) +- build(deps-dev): bump dialyxir from 1.4.4 to 1.4.5 (#4460) +- build(deps): bump ex_cldr from 2.40.1 to 2.40.2 (#4461) +- build(deps): bump crate-ci/typos from 1.28.1 to 1.29.0 (#4464) +- ci: update actions/cache to v4.2.0 ([79107d5](https://github.com/teslamate-org/teslamate/commit/79107d53b7712934587bbe40c503e63d5dd9f122) - @JakobLichterfeld) +- build(deps): bump DeterminateSystems/magic-nix-cache-action from 8 to 9 (#4515) +- build(deps): bump actions/stale from 9.0.0 to 9.1.0 (#4516) +- build(deps): bump crate-ci/typos from 1.29.0 to 1.29.5 (#4514) +- build(deps-dev): bump excoveralls from 0.18.3 to 0.18.5 (#4524) +- build(deps-dev): bump credo from 1.7.8 to 1.7.11 (#4523) +- build(deps): bump @docusaurus/preset-classic from 3.6.3 to 3.7.0 in /website (#4518) +- build(deps): bump serialize-javascript from 6.0.1 to 6.0.2 in /website (#4548) +- build(deps): update flake.lock (#4455) +- style(markdownlint): allow 'details', 'summary', and 'TabItem' elements ([d5b1a55](https://github.com/teslamate-org/teslamate/commit/d5b1a55007eefedd5d852ecd50d67b8c4d36faa5) - @JakobLichterfeld) +- style(environment_variables): remove multiple whitespaces ([603ff82](https://github.com/teslamate-org/teslamate/commit/603ff824b052b4465fcce9fe77e5e40ad586c07a) - @JakobLichterfeld) +- style(docs): fix line length fenced-code-style, no bare url links, multiple whitespaces, alt text, header style ([1972584](https://github.com/teslamate-org/teslamate/commit/1972584d8f9d11c2f640de046a8e9fd47b43c4fb) - @JakobLichterfeld) +- build(deps): bump actions/cache from 4.2.0 to 4.2.2 (#4564) +- build(deps): bump crate-ci/typos from 1.29.5 to 1.30.0 (#4563) +- build(deps): bump castore from 1.0.11 to 1.0.12 (#4565) +- build(deps): bump plug_cowboy from 2.7.2 to 2.7.3 (#4566) +- build(deps): bump prismjs from 1.29.0 to 1.30.0 in /website (#4582) +- build(deps): bump @babel/runtime from 7.26.0 to 7.26.10 in /website (#4589) +- build(deps): bump @babel/helpers from 7.26.7 to 7.26.10 in /website (#4588) +- build(deps): bump @babel/runtime-corejs3 in /website (#4587) +- build(deps): update flake.lock (#4562) + +#### Dashboards + +- fix: for battery health dashboard erroring out if no charge data has been collected so far (#4448 - @swiffer) +- fix: url for releases in home dashboard (#4452 -@FLX3009) +- feat: add 0 as lower bound for gauge to ensure proper scaling (#4498 - @swiffer) +- feat(dashboards): improve elevation scale in drive stats (#4546 - @swiffer) +- feat: add Database Information Dashboard (#4578 - @jheredianet) + +#### Translations + +- feat: Translate remaining Spanish sentences (#4529 - @jheredianet) + +#### Documentation + +- doc: bump elixir based on availability (#4431 - @swiffer) +- doc: align node req with what is used in CI (#4430 - @swiffer) +- doc: added missing topic "charging_state" in mqtt doc (#4466 - @Beme99) +- docs: Grafana 11.4 for manual install on FreeBSD (#4474 - @swiffer) +- doc: Fixing typo for sensor psi calculation (#4470 - @Phazz) +- doc: Simplify Home Assistant sensors, add device_class to allow changing measurement units (#4472 - @longzheng) +- docs: add reindexing instructions for database maintenance to improve performance in case of index bloat due to frequent updates or deletions (#4558 and #4574 - @jheredianet) +- docs: Update projects using TeslaMate (#4573 - @jheredianet) +- docs: fix and rearrange screenshot links (alphabetical) (#4580 - @swiffer) +- docs: enhance TeslaFi import documentation with updated Python script for bulk data export (#4575 - @TheLinuxGuy and @JakobLichterfeld) +- docs: add Entity Relationship Model section to development documentation (#4586 - @DrMichael and @JakobLichterfeld) + +## [1.32.0] - 2024-11-23 + +As always, there are many improvements. The focus has been on quality of life improvements and standardization across all dashboards. Enjoy it. + +### New features + +### Improvements and bug fixes + +- feat: use Grafana 11.2.3 (#4338 - @swiffer) +- feat: Update marketing name to recognize Model S LR+ (#4370 - @cwanja) +- fix(nix): bump hash for dependencies (#4371 - @brianmay) + +#### Build, CI, internal + +- ci: remove unknown flag --ref for gh cache delete in cleanup_caches workflow ([3a515df](https://github.com/teslamate-org/teslamate/commit/3a515df5aa400139acf8ef638e5ae37339c553cf) - @JakobLichterfeld) +- build(deps): bump actions/checkout from 4.2.1 to 4.2.2 (#4340) +- build(deps): bump actions/cache from 4.0.2 to 4.1.2 (#4341) +- build(deps): bump cachix/install-nix-action from 27 to 30 (#4342) +- build(deps): bump tesla from 1.12.1 to 1.13.0 (#4335) +- build(deps): bump floki from 0.36.2 to 0.36.3 (#4336) +- feat: add CONTRIBUTING file to exclusion lists for treefmt (#4359 - @JakobLichterfeld) +- ci: create PR to update flake.lock every saturday (#4372 - @brianmay) +- ci(fix): correct permissions for flake.lock updates ([c673ef3](https://github.com/teslamate-org/teslamate/commit/c673ef363ba73ad076680d71ef54bd549582d41f)- @JakobLichterfeld) +- ci: update flake.lock workflow with appropriate labels for created pr's ([54c41c1](https://github.com/teslamate-org/teslamate/commit/54c41c1fe66664b62d817502d1b2bdb244b70dc2) - @JakobLichterfeld) +- build(deps): bump cross-spawn from 7.0.3 to 7.0.6 in /website (#4391) +- build(deps): bump crate-ci/typos from 1.26.0 to 1.27.0 (#4344) +- build(deps): update flake.lock (#4381) + +#### Dashboards + +- fix: allow editing of dashboards - [changes will be overwritten on update](https://grafana.com/docs/grafana/latest/administration/provisioning/#making-changes-to-a-provisioned-dashboard) (#4338 - @swiffer) +- fix: ensure max speed panels are converted according to length unit setting in drive stats dashboard (#4338 - @swiffer) +- perf: speed up queries used to calculate max speed in drive stats dashboard (#4338 - @swiffer) +- feat: add a welcome dashboard (#4338 - @swiffer) +- fix: Charges Dashboard -> Range added renamed to Ø Charge rate (#4349 - @swiffer) +- fix: Axis Labels for XY Chart in Battery Health and reduces Query count in Visited (#4364 - @swiffer) +- feat: Dashboard refinements and standardization (#4367 - @swiffer) +- feat: add Detailed Energy Use to drive-details (#4386 - @jameskitt616) + +#### Translations + +#### Documentation + +docs: add contributing guidelines link for GitHub (#4345 - @JakobLichterfeld) +docs: update Home Assistant integration documentation with configuration URL and model name hints (#4359 - @JakobLichterfeld) +docs: Remove availability from Home Assistant MQTT sensors, as it can be misleading and prevent sensors from receiving updated values (#4362 - @longzheng) +docs: Introducing TeslaMate Guru on Gurubase.io (#4390 - @kursataktas) + +## [1.31.1] - 2024-10-29 + +This release primarily prevents beam.smp from overloading the CPU on ARM hosts. It also includes a number of other bug fixes and performance improvements. Enjoy it. +Please also note: [v1.31.0 Release Notes](https://github.com/teslamate-org/teslamate/releases/tag/v1.31.0) + ### New features ### Improvements and bug fixes +- fix: use elixir-1.17.3-otp-26 to avoid beam.smp chewing CPU on arm (#4319 - @brianmay, @swiffer and @JakobLichterfeld) + +#### Build, CI, internal + +- ci(fix): update cleanup_caches.yml to use new cache management commands and fix permissions ([d6793ce](https://github.com/teslamate-org/teslamate/commit/d6793ce5717687b9e984067bf4c208415e15fdac), [b0b694f](https://github.com/teslamate-org/teslamate/commit/b0b694fc8c3c45036aafda45200f3b0d068a2f50), [16bb503](https://github.com/teslamate-org/teslamate/commit/16bb5032c7d81cb86e76cc19662e3332456291a0) - @JakobLichterfeld) +- ci: Add workflow to manually cleanup largest 100 caches ([dad7e3d](https://github.com/teslamate-org/teslamate/commit/dad7e3dea0ae1d799398bf1b31a0d598eff784bf), [523419d](https://github.com/teslamate-org/teslamate/commit/523419d35a610c7b06bbf7e9c2edd105e7d089aa) - @JakobLichterfeld) + +#### Dashboards + +- perf: add ideal_battery_range_km as query condition (#4305 - @swiffer) +- fix: re-add missing changes from pr 4124 (#4310 - @swiffer) +- feat: add max speed & speed histogram to drive stats (#4253 - @js94x) +- fix: remove convert_km from kwh calculations in timeline dashboard (#4318 - @swiffer) +- fix: ensure dutch-tax dashboard is not repeating multiple times per car (row and table) (#4317 - @swiffer) + +#### Translations + +- Update default.po for thai (#4312 - @tomzt) +- Spanish translation of missing items (#4320 -@ferminmg) + +#### Documentation + +- docs: fix ghcr image path in contributing guide (#4309 - @swiffer) + +## [1.31.0] - 2024-10-27 + +As always, lots of improvements. The focus has been on performance improvements, especially on slow HW like Raspberry Pi 3B+. We achieved 240x speed improvements in several dashboards :rocket: And we welcomed @swiffer to the TeslaMate-Org team :wave: And much, much more. Enjoy it. + +**Regarding PostgreSQL 17:** TeslaMate uses PostgreSQL as database, this is an external dependency and needs to be updated by yourself. Although TeslaMate currently runs fine with PostgreSQL 14+ we strongly recommend upgrading to the latest supported version. We recommend that you do this as follows: + +- [Backup your data](https://docs.teslamate.org/docs/maintenance/backup_restore#backup) +- [Upgrade TeslaMate to this version](https://docs.teslamate.org/docs/upgrading) +- [Backup your data after the upgrade](https://docs.teslamate.org/docs/maintenance/backup_restore#backup) +- [Upgrade PostgreSQL to postgres:17](https://docs.teslamate.org/docs/maintenance/upgrading_postgres) (Yes, you will have to erase your data, which is why you need your backup in the first place.) + +**Additional info:** In some very rare cases with very old installations of TeslaMate (from 2019) we have observed performance issues due to missing indexes. These should normally be added with our automatic migrations. If you think your installation may be missing some indexes, see #4201 for the corrective SQL command. + +### New features + +### Improvements and bug fixes + +- fix: 401 on direct Fleet API calls (#4095 - @jlestel) +- feat: add support for PostgreSQL 17 (#4231 - @swiffer) +- fix: add nix module option to specify postgres package (#4227 - @brianmay) +- perf: limit positions to set elevation for to last 10 days (#4228 - @swiffer) +- feat: add treefmt-nix to nix flake (#4219 - @JakobLichterfeld) +- feat: use Grafana 11.0.6-security-01 (#4279 - @swiffer) + +#### Build, CI, internal + +- ci: pin GitHub action dependencies to protect against supply chain attacks (#4076 - @JakobLichterfeld) +- chore: correct comment for pinned Docker login-action to version 3.2.0 (#4120 - @JakobLichterfeld) +- build(deps): bump erlef/setup-beam from 1.18.0 to 1.18.1 (#4116) +- build(deps): bump docker/login-action from 3.2.0 to 3.3.0 (#4115) +- chore: update PostgreSQL to version 16 in flake.nix (#4135- @JakobLichterfeld) +- build(deps): bump webpack from 5.92.1 to 5.94.0 in /website (#4171) +- build(deps): bump micromatch from 4.0.5 to 4.0.8 in /website (#4174) +- chore: Update tzdata to version 1.1.2 and mimerl to version 1.3.0 (#4205 - @JakobLichterfeld) +- build(deps): bump send and express in /website (#4203) +- ci: enable dependabot for mix and npm (#4207 - @JakobLichterfeld) +- build(deps): bump @docusaurus/preset-classic from 3.4.0 to 3.5.2 in /website (#4210) +- build(deps): bump phoenix_ecto from 4.4.3 to 4.6.2 (#4213) +- build(deps): bump jason from 1.4.1 to 1.4.4 (#4216) +- build(deps): bump classnames from 2.3.2 to 2.5.1 in /website (#4211) +- ci: add treefmt as code formatting multiplexer (#4219 - @JakobLichterfeld) +- ci(refactor): use composite action to avoid duplication in elixir workflow (#4219 - @JakobLichterfeld) +- ci: prevent workflow runs for certain conditions and allow scheduled runs (#4219 - @JakobLichterfeld) +- ci(refactor): use reusable workflow to check paths (#4219 - @JakobLichterfeld) +- ci(refactor): use reusable workflows for streamlined DevOps pipeline (#4219 - @JakobLichterfeld) +- ci(refactor): allow ghcr_build parallel to elixir test (#4219 - @JakobLichterfeld) +- ci: ensure proper linting via treefmt (#4219 - @JakobLichterfeld) +- doc: update CI badge URL for devops workflow (#4219 - @JakobLichterfeld) +- ci(fix): handle empty path filter output (#4219 - @JakobLichterfeld) +- fix: avoid the need for impure for devenv (#4245 - @brianmay) +- ci(fix): run ghcr build workflow only for specific conditions (#4219 - @JakobLichterfeld) +- ci: remove branch restriction for check_paths workflow to increase sec (#4219 - @JakobLichterfeld) +- build(deps): bump actions/checkout from 4.1.7 to 4.2.1 (#4262) +- ci(fix): only run ghcr build in DevOps workflow on own repo ([022b173](https://github.com/teslamate-org/teslamate/commit/022b173430221d385479f4ec9d91d8ccffbfe7b9) - @JakobLichterfeld) +- ci: pin ubuntu-24.04 as runner OS ([40dab3e](https://github.com/teslamate-org/teslamate/commit/40dab3e2a978b8a867f1159626d4c157ccab6c56) - @JakobLichterfeld) +- ci: cleanup caches when pr is closed ([75cfc7c](https://github.com/teslamate-org/teslamate/commit/75cfc7cdd4b8f83f247211dc7fc5c5cd433bf746) - @JakobLichterfeld) +- ci(fix): run ghcr build in DevOps workflow for forks ([688147e](https://github.com/teslamate-org/teslamate/commit/688147e2cf3fb5b55e702185a97a4a4ebb14d7ca) - @JakobLichterfeld) +- ci(fix): correct syntax in ghcr_build workflow for workflow_call ([9e6a275](https://github.com/teslamate-org/teslamate/commit/9e6a2758d5ff21604976184ad69befc1c546e600) - @JakobLichterfeld) +- ci(fix): run ghcr build as separate workflow to fix permission issues with forks ([0410593](https://github.com/teslamate-org/teslamate/commit/0410593850cde00e8f201a9b7d6009f0581ed43c) - @JakobLichterfeld) +- build(deps-dev): bump credo from 1.7.1 to 1.7.8 (#4238) +- build(deps): bump crate-ci/typos from 1.22.9 to 1.26.0 (#4261) +- refactor: Cleanup nix code (#4265 - @scottbot95) +- build(deps): bump elixir from 1.16.2-otp-26 to 1.17.2-otp-27 (#4296 - @JakobLichterfeld) +- build(deps): bump http-proxy-middleware from 2.0.6 to 2.0.7 in /website (#4303) +- feat: update to Phoenix HTML 4.1, bump dependencies (#4277 - sdwalker and @JakobLichterfeld) + +#### Dashboards + +- Improve Battery Health dashboard estimations on rated range (#4074 - @jheredianet) +- Update charges.json: range added per hour (#4089 - @DrMichael) +- small visual distinguish between AC & DC charging in charges dashboard and unification of the DC coloring in all dashboards (#4124 - @stauffenberg2020) +- Improve drive stats (#4148 - @jheredianet) +- Improve drives dashboard (#4146 - @jheredianet) +- Odometer in charges (#4144 - @jheredianet) +- Update charging-stats for handling suc cost mixed with AC charge on TWC (#4137 - @cyberden) +- Fix the issue of failing to restore efficiency dashboard (#4153 - @ghostiee) +- Improve rounding to month / weeks / days in Updates "Since Previous Update" column (#4164 - @swiffer) +- feat: Improve cost filter on Charges dashboard to show charges with negative cost as well (#4179 - @jheredianet) +- feat: display vehicle VIN as a fallback for vehicle name on grafana dashboards (#4198 - @arcastro) +- feat: Add Moving Average / Percentiles to Charge Level dashboard & bucket data to support longer periods (#4200 - @swiffer) +- increase max battery charge gauge threshold to 101 in case of LFP (#4191 - @neothematrix) +- multiple cars, same name, add VIN next to name (#4230 - @swiffer) +- json_build_object instead of concat in battery-health (#4229 - @swiffer) +- perf: fix skipping streaming data in charging stats (#4252 - @swiffer) +- perf: improvements drive stats (#4258 - @swiffer) +- fix: for drives not showing if duration < 1 minute (#4284 - @swiffer) +- feat: add max speed in drives dashboard (#4284 / #4267 - @js94x) +- perf: exclude streaming data when getting battery level (#4286 - @swiffer) +- perf: exclude streaming data in visited dashboard (#4287 - @swiffer) +- fix: weighted average calculation for consumption in drives dashboard (#4289 - @swiffer) +- perf: improvement in charge level (#4301 - @swiffer) +- perf: improvement and deprecated syntax removal (#4304 - @swiffer) + +#### Translations + +- Adding missing Swedish translation (#4097 - @tobiasehlert) + +#### Documentation + +- doc: Add initial author and list of contributors to README.md (#4084 - @JakobLichterfeld) +- doc: add steps to the guide regarding how to switch to Fleet API (#4103 - @yangiak) +- doc: align TPMS Pressure naming in sensor config to match UI config for home assistant (#4104 - @helmo) +- doc: Update screenshots and rearrange links (#4151 - @jheredianet) +- doc: fix markdownlint warnings in fleet API documentation (#4173 - @JakobLichterfeld) +- doc: clarify using fleet API has lots of drawbacks (#4173 - @JakobLichterfeld) +- docs: fix Home Assistant MQTT sensor JSON templates warnings (#4257 - @longzheng) +- docs: add recommended RAM size (#4278 - @JakobLichterfeld) +- docs: add best practice section to contribution guide (#4288 - @swiffer) + +## [1.30.1] - 2024-07-10 + +This is a hotfix release to work around the map fit problem in the new Upstream Grafana. As soon as a new Grafana version is available, we will update again. + +### Improvements and bug fixes + +- downgrade grafana until maps issue in upstream grafana is resolved (#4071 - @swiffer) + #### Build, CI, internal - ci: fix coverage report env variable (#4066 - @JakobLichterfeld) +- feat: extend version with more build info (#4069 - @JakobLichterfeld) #### Dashboards @@ -17,7 +373,8 @@ #### Translations -#### Documentation +- update zh_hans localized string (#4073 - @mrgaolei) +- Update default.po for thai (#4072 - @tomzt) ## [1.30.0] - 2024-07-07 @@ -290,7 +647,7 @@ same as 1.29.0 but reverted: "Dynamic endpoints and token to use official Tesla #### Translations -- fix: translation Update default.po for simplified chinese (#3600 - @ycjcl868) +- fix: translation Update default.po for simplified Chinese (#3600 - @ycjcl868) - Improvements for Spanish translations (#3610 - @jheredianet) #### Documentation @@ -490,7 +847,7 @@ Note: TeslaMate moved to the new @teslamate-org organization. To ensure that the Tesla API tokens are stored securely, **an encryption key must be provided via the `ENCRYPTION_KEY` environment variable**. -If you use a `docker-compose.yml` file to run TeslaMate, add a line with the `ENCRYPTION_KEY` to the `environment` section or check out the updated installation guiddes on [docs.teslamate.org](https://docs.teslamate.org): +If you use a `docker-compose.yml` file to run TeslaMate, add a line with the `ENCRYPTION_KEY` to the `environment` section or check out the updated installation guides on [docs.teslamate.org](https://docs.teslamate.org): ```yaml services: @@ -536,7 +893,7 @@ If no `ENCRYPTION_KEY` environment variable is provided when running the databas #### Translations -- Update Chinse translation (#2479 - @AemonCao) +- Update Chinese translation (#2479 - @AemonCao) - Add missing Swedish translation (#2731 - @tobiasehlert) #### Documentation @@ -2051,7 +2408,12 @@ New users need to sign in via the web interface. ## [1.0.0] - 2019-07-25 -[unreleased]: https://github.com/teslamate-org/teslamate/compare/v1.30.0...HEAD +[unreleased]: https://github.com/teslamate-org/teslamate/compare/v1.33.0...HEAD +[1.33.0]: https://github.com/teslamate-org/teslamate/compare/v1.32.0...v1.33.0 +[1.32.0]: https://github.com/teslamate-org/teslamate/compare/v1.31.1...v1.32.0 +[1.31.1]: https://github.com/teslamate-org/teslamate/compare/v1.31.0...v1.31.1 +[1.31.0]: https://github.com/teslamate-org/teslamate/compare/v1.30.1...v1.31.0 +[1.30.1]: https://github.com/teslamate-org/teslamate/compare/v1.30.0...v1.30.1 [1.30.0]: https://github.com/teslamate-org/teslamate/compare/v1.29.2...v1.30.0 [1.29.2]: https://github.com/teslamate-org/teslamate/compare/v1.29.1...v1.29.2 [1.29.1]: https://github.com/teslamate-org/teslamate/compare/v1.29.0...v1.29.1 diff --git a/CONTRIBUTING b/CONTRIBUTING new file mode 100644 index 0000000000..592e63e8b3 --- /dev/null +++ b/CONTRIBUTING @@ -0,0 +1,3 @@ +# Contributing Guidelines + +see [Development and Contributing](https://docs.teslamate.org/docs/development/) diff --git a/Dockerfile b/Dockerfile index 857c7e1899..56ff22b77a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM elixir:1.16.2-otp-26 AS builder +FROM elixir:1.17.3-otp-26 AS builder SHELL ["/bin/bash", "-o", "pipefail", "-c"] @@ -12,6 +12,7 @@ RUN apt-get update \ | tee /etc/apt/sources.list.d/nodesource.list \ && apt-get update \ && apt-get install nodejs -y \ + && apt-get install -y git \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* diff --git a/README.md b/README.md index 41ebb133eb..4d4bb6ba27 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ # TeslaMate -[](https://github.com/teslamate-org/teslamate/actions/workflows/elixir.yml) +[](https://github.com/teslamate-org/teslamate/actions/workflows/devops.yml) [](https://github.com/teslamate-org/teslamate/actions/workflows/buildx.yml) -[](https://coveralls.io/github/teslamate-org/teslamate?branch=master) -[](https://hub.docker.com/r/teslamate/teslamate) -[](https://hub.docker.com/r/teslamate/teslamate) -[](https://hub.docker.com/r/teslamate/teslamate) +[](https://coveralls.io/github/teslamate-org/teslamate?branch=master) +[](https://hub.docker.com/r/teslamate/teslamate) +[](https://hub.docker.com/r/teslamate/teslamate) +[](https://hub.docker.com/r/teslamate/teslamate) A powerful, self-hosted data logger for your Tesla. @@ -20,23 +20,7 @@ The documentation is available at [https://docs.teslamate.org](https://docs.tesl ## Features -**Dashboards** - -- [Drive and charging reports](https://docs.teslamate.org/docs/screenshots#charging-details) -- [Driving efficiency report](https://docs.teslamate.org/docs/screenshots#efficiency) -- [Consumption (net / gross)](https://docs.teslamate.org/docs/screenshots#efficiency) -- [Charge energy added vs energy used](https://docs.teslamate.org/docs/screenshots#charges) -- [Vampire drain](https://docs.teslamate.org/docs/screenshots#vampire-drain) -- [Projected 100% range (battery degradation)](https://docs.teslamate.org/docs/screenshots#projected-range) -- [Charging Stats](https://docs.teslamate.org/docs/screenshots#charging-stats) -- [Drive Stats](https://docs.teslamate.org/docs/screenshots#drive-stats) -- [History of installed updates](https://docs.teslamate.org/docs/screenshots#updates) -- [See when your car was online or asleep](https://docs.teslamate.org/docs/screenshots#states) -- [Lifetime driving map](https://docs.teslamate.org/docs/screenshots/#lifetime-driving-map) -- [Visited addresses](https://docs.teslamate.org/docs/screenshots/#visited-addresses) -- [Battery Health](https://docs.teslamate.org/docs/screenshots/#battery-health) - -**General** +### General - High precision drive data recording - No additional vampire drain: the car will fall asleep as soon as possible @@ -48,19 +32,45 @@ The documentation is available at [https://docs.teslamate.org](https://docs.tesl - Charge cost tracking - Import from TeslaFi and tesla-apiscraper +### Dashboards + +Sample screenshots of bundled dashboards can be seen by clicking the links below. + +- [Battery Health](https://docs.teslamate.org/docs/screenshots/#battery-health) +- [Charge Level](https://docs.teslamate.org/docs/screenshots/#charge-level) +- [Charges (Energy added / used)](https://docs.teslamate.org/docs/screenshots#charges) +- [Charge Details](https://docs.teslamate.org/docs/screenshots#charge-details) +- [Charging Stats](https://docs.teslamate.org/docs/screenshots#charging-stats) +- [Database Information](https://docs.teslamate.org/docs/screenshots/#database-information) +- [Drive Stats](https://docs.teslamate.org/docs/screenshots#drive-stats) +- [Drives (Distance / Energy consumed (net))](https://docs.teslamate.org/docs/screenshots/#drives) +- [Drive Details](https://docs.teslamate.org/docs/screenshots/#drive-details) +- [Efficiency (Consumption (net / gross))](https://docs.teslamate.org/docs/screenshots#efficiency) +- [Locations (addresses)](https://docs.teslamate.org/docs/screenshots/#location-addresses) +- [Mileage](https://docs.teslamate.org/docs/screenshots/#mileage) +- [Overview](https://docs.teslamate.org/docs/screenshots/#overview) +- [Projected Range (battery degradation)](https://docs.teslamate.org/docs/screenshots#projected-range) +- [States (see when your car was online or asleep)](https://docs.teslamate.org/docs/screenshots#states) +- [Statistics](https://docs.teslamate.org/docs/screenshots/#statistics) +- [Timeline](https://docs.teslamate.org/docs/screenshots/#timeline) +- [Trip](https://docs.teslamate.org/docs/screenshots/#trip) +- [Updates (History of installed updates)](https://docs.teslamate.org/docs/screenshots#updates) +- [Vampire Drain](https://docs.teslamate.org/docs/screenshots#vampire-drain) +- [Visited (Lifetime driving map)](https://docs.teslamate.org/docs/screenshots/#visited-lifetime-driving-map) + ## Screenshots +Sneak peak into TeslaMate interface and bundled dashboards. See [the docs](https://docs.teslamate.org/docs/screenshots) for additional screenshots. +    -
- MORE SCREENSHOTS -
- ## Credits -- Authors: Adrian Kumpf – [List of contributors](https://github.com/teslamate-org/teslamate/graphs/contributors) +- Initial Author: Adrian Kumpf +- List of Contributors: +- [](https://github.com/teslamate-org/teslamate/graphs/contributors) - Distributed under MIT License diff --git a/VERSION b/VERSION index 1e49de39f7..232e60eab5 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.30.1-dev \ No newline at end of file +1.33.1-dev \ No newline at end of file diff --git a/assets/js/hooks.js b/assets/js/hooks.js index 0eeca83f79..5cdae1d0c5 100644 --- a/assets/js/hooks.js +++ b/assets/js/hooks.js @@ -53,7 +53,11 @@ export const LocalTimeRange = { const time = [this.el.dataset.startDate, this.el.dataset.endDate] .map((date) => - toLocalTime(date, { hour: "2-digit", minute: "2-digit", hour12: false }) + toLocalTime(date, { + hour: "2-digit", + minute: "2-digit", + hour12: false, + }), ) .join(" – "); @@ -125,7 +129,7 @@ const DirectionArrow = CircleMarker.extend({ this.getElement().setAttributeNS( null, "transform", - `translate(${x},${y}) rotate(${this._heading})` + `translate(${x},${y}) rotate(${this._heading})`, ); const path = this._empty() ? "" : `M0,${3} L-4,${5} L0,${-5} L4,${5} z}`; @@ -137,15 +141,14 @@ const DirectionArrow = CircleMarker.extend({ function createMap(opts) { const map = new M(opts.elId != null ? `map_${opts.elId}` : "map", opts); - const osm = new TileLayer( - "https://tile.openstreetmap.org/{z}/{x}/{y}.png", - { maxZoom: 19 } - ); + const osm = new TileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", { + maxZoom: 19, + }); if (opts.enableHybridLayer) { const hybrid = new TileLayer( "http://{s}.google.com/vt/lyrs=s,h&x={x}&y={y}&z={z}", - { maxZoom: 20, subdomains: ["mt0", "mt1", "mt2", "mt3"] } + { maxZoom: 20, subdomains: ["mt0", "mt1", "mt2", "mt3"] }, ); new Control.Layers({ OSM: osm, Hybrid: hybrid }).addTo(map); @@ -184,8 +187,12 @@ export const SimpleMap = { map.removeControl(map.zoomControl); - map.on('mouseover', function(e) { map.addControl( map.zoomControl ); }); - map.on('mouseout', function(e) { map.removeControl( map.zoomControl ); }); + map.on("mouseover", function (e) { + map.addControl(map.zoomControl); + }); + map.on("mouseout", function (e) { + map.removeControl(map.zoomControl); + }); if (isArrow) { const setView = () => { diff --git a/assets/js/main.js b/assets/js/main.js index 744f966118..95af7d442a 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -5,7 +5,7 @@ document.querySelector(".navbar-burger").addEventListener("click", function () { }); for (const navDropdown of document.querySelectorAll( - ".navbar-item.has-dropdown" + ".navbar-item.has-dropdown", )) { navDropdown.addEventListener("click", function () { if (document.querySelector(".navbar-menu.is-active")) { diff --git a/assets/package-lock.json b/assets/package-lock.json index 83e098262c..ba604b00e8 100644 --- a/assets/package-lock.json +++ b/assets/package-lock.json @@ -20,21 +20,33 @@ }, "devDependencies": { "bulma": "^0.9.4", - "esbuild": "^0.19.4", - "esbuild-sass-plugin": "^2.9.0", + "esbuild": "^0.25.2", + "esbuild-sass-plugin": "^3.3.1", "sass": "^1.63.3" } }, "../deps/phoenix": { - "version": "1.6.16", + "version": "1.7.18", "license": "MIT" }, "../deps/phoenix_html": { - "version": "3.3.3" + "version": "4.2.0" }, "../deps/phoenix_live_view": { - "version": "0.17.14", - "license": "MIT" + "version": "0.20.17", + "license": "MIT", + "devDependencies": { + "@playwright/test": "^1.43.1", + "monocart-reporter": "^2.3.1" + } + }, + "node_modules/@bufbuild/protobuf": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.2.5.tgz", + "integrity": "sha512-/g5EzJifw5GF8aren8wZ/G5oMuPoGeS6MQD3ca8ddcvdXR5UELUfdTZITCGNhNXynY/AYl3Z4plmxdj/tRl/hQ==", + "dev": true, + "license": "(Apache-2.0 AND BSD-3-Clause)", + "peer": true }, "node_modules/@creativebulma/bulma-divider": { "version": "1.1.0", @@ -46,356 +58,429 @@ "resolved": "https://registry.npmjs.org/@creativebulma/bulma-tooltip/-/bulma-tooltip-1.2.0.tgz", "integrity": "sha512-ooImbeXEBxf77cttbzA7X5rC5aAWm9UsXIGViFOnsqB+6M944GkB28S5R4UWRqjFd2iW4zGEkEifAU+q43pt2w==" }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz", + "integrity": "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/android-arm": { - "version": "0.19.7", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.7.tgz", - "integrity": "sha512-YGSPnndkcLo4PmVl2tKatEn+0mlVMr3yEpOOT0BeMria87PhvoJb5dg5f5Ft9fbCVgtAz4pWMzZVgSEGpDAlww==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.2.tgz", + "integrity": "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm64": { - "version": "0.19.7", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.7.tgz", - "integrity": "sha512-YEDcw5IT7hW3sFKZBkCAQaOCJQLONVcD4bOyTXMZz5fr66pTHnAet46XAtbXAkJRfIn2YVhdC6R9g4xa27jQ1w==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.2.tgz", + "integrity": "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-x64": { - "version": "0.19.7", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.7.tgz", - "integrity": "sha512-jhINx8DEjz68cChFvM72YzrqfwJuFbfvSxZAk4bebpngGfNNRm+zRl4rtT9oAX6N9b6gBcFaJHFew5Blf6CvUw==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.2.tgz", + "integrity": "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.19.7", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.7.tgz", - "integrity": "sha512-dr81gbmWN//3ZnBIm6YNCl4p3pjnabg1/ZVOgz2fJoUO1a3mq9WQ/1iuEluMs7mCL+Zwv7AY5e3g1hjXqQZ9Iw==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.2.tgz", + "integrity": "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.19.7", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.7.tgz", - "integrity": "sha512-Lc0q5HouGlzQEwLkgEKnWcSazqr9l9OdV2HhVasWJzLKeOt0PLhHaUHuzb8s/UIya38DJDoUm74GToZ6Wc7NGQ==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.2.tgz", + "integrity": "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.19.7", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.7.tgz", - "integrity": "sha512-+y2YsUr0CxDFF7GWiegWjGtTUF6gac2zFasfFkRJPkMAuMy9O7+2EH550VlqVdpEEchWMynkdhC9ZjtnMiHImQ==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.2.tgz", + "integrity": "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.19.7", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.7.tgz", - "integrity": "sha512-CdXOxIbIzPJmJhrpmJTLx+o35NoiKBIgOvmvT+jeSadYiWJn0vFKsl+0bSG/5lwjNHoIDEyMYc/GAPR9jxusTA==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.2.tgz", + "integrity": "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm": { - "version": "0.19.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.7.tgz", - "integrity": "sha512-Y+SCmWxsJOdQtjcBxoacn/pGW9HDZpwsoof0ttL+2vGcHokFlfqV666JpfLCSP2xLxFpF1lj7T3Ox3sr95YXww==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.2.tgz", + "integrity": "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.19.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.7.tgz", - "integrity": "sha512-inHqdOVCkUhHNvuQPT1oCB7cWz9qQ/Cz46xmVe0b7UXcuIJU3166aqSunsqkgSGMtUCWOZw3+KMwI6otINuC9g==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.2.tgz", + "integrity": "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.19.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.7.tgz", - "integrity": "sha512-2BbiL7nLS5ZO96bxTQkdO0euGZIUQEUXMTrqLxKUmk/Y5pmrWU84f+CMJpM8+EHaBPfFSPnomEaQiG/+Gmh61g==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.2.tgz", + "integrity": "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.19.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.7.tgz", - "integrity": "sha512-BVFQla72KXv3yyTFCQXF7MORvpTo4uTA8FVFgmwVrqbB/4DsBFWilUm1i2Oq6zN36DOZKSVUTb16jbjedhfSHw==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.2.tgz", + "integrity": "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==", "cpu": [ "loong64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.19.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.7.tgz", - "integrity": "sha512-DzAYckIaK+pS31Q/rGpvUKu7M+5/t+jI+cdleDgUwbU7KdG2eC3SUbZHlo6Q4P1CfVKZ1lUERRFP8+q0ob9i2w==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.2.tgz", + "integrity": "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==", "cpu": [ "mips64el" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.19.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.7.tgz", - "integrity": "sha512-JQ1p0SmUteNdUaaiRtyS59GkkfTW0Edo+e0O2sihnY4FoZLz5glpWUQEKMSzMhA430ctkylkS7+vn8ziuhUugQ==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.2.tgz", + "integrity": "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==", "cpu": [ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.19.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.7.tgz", - "integrity": "sha512-xGwVJ7eGhkprY/nB7L7MXysHduqjpzUl40+XoYDGC4UPLbnG+gsyS1wQPJ9lFPcxYAaDXbdRXd1ACs9AE9lxuw==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.2.tgz", + "integrity": "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==", "cpu": [ "riscv64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.19.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.7.tgz", - "integrity": "sha512-U8Rhki5PVU0L0nvk+E8FjkV8r4Lh4hVEb9duR6Zl21eIEYEwXz8RScj4LZWA2i3V70V4UHVgiqMpszXvG0Yqhg==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.2.tgz", + "integrity": "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==", "cpu": [ "s390x" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-x64": { - "version": "0.19.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.7.tgz", - "integrity": "sha512-ZYZopyLhm4mcoZXjFt25itRlocKlcazDVkB4AhioiL9hOWhDldU9n38g62fhOI4Pth6vp+Mrd5rFKxD0/S+7aQ==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.2.tgz", + "integrity": "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.2.tgz", + "integrity": "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.19.7", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.7.tgz", - "integrity": "sha512-/yfjlsYmT1O3cum3J6cmGG16Fd5tqKMcg5D+sBYLaOQExheAJhqr8xOAEIuLo8JYkevmjM5zFD9rVs3VBcsjtQ==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.2.tgz", + "integrity": "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "netbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.2.tgz", + "integrity": "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.19.7", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.7.tgz", - "integrity": "sha512-MYDFyV0EW1cTP46IgUJ38OnEY5TaXxjoDmwiTXPjezahQgZd+j3T55Ht8/Q9YXBM0+T9HJygrSRGV5QNF/YVDQ==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.2.tgz", + "integrity": "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "openbsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.19.7", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.7.tgz", - "integrity": "sha512-JcPvgzf2NN/y6X3UUSqP6jSS06V0DZAV/8q0PjsZyGSXsIGcG110XsdmuWiHM+pno7/mJF6fjH5/vhUz/vA9fw==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.2.tgz", + "integrity": "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "sunos" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.19.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.7.tgz", - "integrity": "sha512-ZA0KSYti5w5toax5FpmfcAgu3ZNJxYSRm0AW/Dao5up0YV1hDVof1NvwLomjEN+3/GMtaWDI+CIyJOMTRSTdMw==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.2.tgz", + "integrity": "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.19.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.7.tgz", - "integrity": "sha512-CTOnijBKc5Jpk6/W9hQMMvJnsSYRYgveN6O75DTACCY18RA2nqka8dTZR+x/JqXCRiKk84+5+bRKXUSbbwsS0A==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.2.tgz", + "integrity": "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-x64": { - "version": "0.19.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.7.tgz", - "integrity": "sha512-gRaP2sk6hc98N734luX4VpF318l3w+ofrtTu9j5L8EQXF+FzQKV6alCOHMVoJJHvVK/mGbwBXfOL1HETQu9IGQ==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.2.tgz", + "integrity": "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@geoman-io/leaflet-geoman-free": { @@ -419,6 +504,316 @@ "resolved": "https://registry.npmjs.org/@mdi/font/-/font-7.3.67.tgz", "integrity": "sha512-SWxvzRbUQRfewlIV+OF4/YF4DkeTjMWoT8Hh9yeU/5UBVdJZj9Uf4a9+cXjknSIhIaMxZ/4N1O/s7ojApOOGjg==" }, + "node_modules/@parcel/watcher": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", + "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.1", + "@parcel/watcher-darwin-arm64": "2.5.1", + "@parcel/watcher-darwin-x64": "2.5.1", + "@parcel/watcher-freebsd-x64": "2.5.1", + "@parcel/watcher-linux-arm-glibc": "2.5.1", + "@parcel/watcher-linux-arm-musl": "2.5.1", + "@parcel/watcher-linux-arm64-glibc": "2.5.1", + "@parcel/watcher-linux-arm64-musl": "2.5.1", + "@parcel/watcher-linux-x64-glibc": "2.5.1", + "@parcel/watcher-linux-x64-musl": "2.5.1", + "@parcel/watcher-win32-arm64": "2.5.1", + "@parcel/watcher-win32-ia32": "2.5.1", + "@parcel/watcher-win32-x64": "2.5.1" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", + "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", + "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", + "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", + "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", + "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", + "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", + "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", + "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", + "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", + "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", + "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", + "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", + "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/@turf/bbox": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/@turf/bbox/-/bbox-6.5.0.tgz", @@ -641,39 +1036,27 @@ "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.8.tgz", "integrity": "sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA==" }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, + "license": "MIT", + "optional": true, "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" + "fill-range": "^7.1.1" }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, "engines": { "node": ">=8" } }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "node_modules/buffer-builder": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/buffer-builder/-/buffer-builder-0.2.0.tgz", + "integrity": "sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg==", "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } + "license": "MIT/X11", + "peer": true }, "node_modules/bulma": { "version": "0.9.4", @@ -687,87 +1070,107 @@ "integrity": "sha512-kMu4H0Pr0VjvfsnT6viRDCgptUq0Rvy7y7PX6q+IHg1xUynsjszPjhAdal5ysAlCG5HNO+5YXxeiu92qYGQolw==" }, "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], + "license": "MIT", "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" + "readdirp": "^4.0.1" }, "engines": { - "node": ">= 8.10.0" + "node": ">= 14.16.0" }, - "optionalDependencies": { - "fsevents": "~2.3.2" + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/colorjs.io": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/colorjs.io/-/colorjs.io-0.5.2.tgz", + "integrity": "sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" } }, "node_modules/esbuild": { - "version": "0.19.7", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.7.tgz", - "integrity": "sha512-6brbTZVqxhqgbpqBR5MzErImcpA0SQdoKOkcWK/U30HtQxnokIpG3TX2r0IJqbFUzqLjhU/zC1S5ndgakObVCQ==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.2.tgz", + "integrity": "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==", "dev": true, "hasInstallScript": true, + "license": "MIT", "bin": { "esbuild": "bin/esbuild" }, "engines": { - "node": ">=12" + "node": ">=18" }, "optionalDependencies": { - "@esbuild/android-arm": "0.19.7", - "@esbuild/android-arm64": "0.19.7", - "@esbuild/android-x64": "0.19.7", - "@esbuild/darwin-arm64": "0.19.7", - "@esbuild/darwin-x64": "0.19.7", - "@esbuild/freebsd-arm64": "0.19.7", - "@esbuild/freebsd-x64": "0.19.7", - "@esbuild/linux-arm": "0.19.7", - "@esbuild/linux-arm64": "0.19.7", - "@esbuild/linux-ia32": "0.19.7", - "@esbuild/linux-loong64": "0.19.7", - "@esbuild/linux-mips64el": "0.19.7", - "@esbuild/linux-ppc64": "0.19.7", - "@esbuild/linux-riscv64": "0.19.7", - "@esbuild/linux-s390x": "0.19.7", - "@esbuild/linux-x64": "0.19.7", - "@esbuild/netbsd-x64": "0.19.7", - "@esbuild/openbsd-x64": "0.19.7", - "@esbuild/sunos-x64": "0.19.7", - "@esbuild/win32-arm64": "0.19.7", - "@esbuild/win32-ia32": "0.19.7", - "@esbuild/win32-x64": "0.19.7" + "@esbuild/aix-ppc64": "0.25.2", + "@esbuild/android-arm": "0.25.2", + "@esbuild/android-arm64": "0.25.2", + "@esbuild/android-x64": "0.25.2", + "@esbuild/darwin-arm64": "0.25.2", + "@esbuild/darwin-x64": "0.25.2", + "@esbuild/freebsd-arm64": "0.25.2", + "@esbuild/freebsd-x64": "0.25.2", + "@esbuild/linux-arm": "0.25.2", + "@esbuild/linux-arm64": "0.25.2", + "@esbuild/linux-ia32": "0.25.2", + "@esbuild/linux-loong64": "0.25.2", + "@esbuild/linux-mips64el": "0.25.2", + "@esbuild/linux-ppc64": "0.25.2", + "@esbuild/linux-riscv64": "0.25.2", + "@esbuild/linux-s390x": "0.25.2", + "@esbuild/linux-x64": "0.25.2", + "@esbuild/netbsd-arm64": "0.25.2", + "@esbuild/netbsd-x64": "0.25.2", + "@esbuild/openbsd-arm64": "0.25.2", + "@esbuild/openbsd-x64": "0.25.2", + "@esbuild/sunos-x64": "0.25.2", + "@esbuild/win32-arm64": "0.25.2", + "@esbuild/win32-ia32": "0.25.2", + "@esbuild/win32-x64": "0.25.2" } }, "node_modules/esbuild-sass-plugin": { - "version": "2.16.0", - "resolved": "https://registry.npmjs.org/esbuild-sass-plugin/-/esbuild-sass-plugin-2.16.0.tgz", - "integrity": "sha512-mGCe9MxNYvZ+j77Q/QFO+rwUGA36mojDXkOhtVmoyz1zwYbMaNrtVrmXwwYDleS/UMKTNU3kXuiTtPiAD3K+Pw==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/esbuild-sass-plugin/-/esbuild-sass-plugin-3.3.1.tgz", + "integrity": "sha512-SnO1ls+d52n6j8gRRpjexXI8MsHEaumS0IdDHaYM29Y6gakzZYMls6i9ql9+AWMSQk/eryndmUpXEgT34QrX1A==", "dev": true, + "license": "MIT", "dependencies": { - "resolve": "^1.22.6", - "sass": "^1.7.3" + "resolve": "^1.22.8", + "safe-identifier": "^0.4.2", + "sass": "^1.71.1" }, "peerDependencies": { - "esbuild": "^0.19.4" + "esbuild": ">=0.20.1", + "sass-embedded": "^1.71.1" } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, + "license": "MIT", + "optional": true, "dependencies": { "to-regex-range": "^5.0.1" }, @@ -775,20 +1178,6 @@ "node": ">=8" } }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -810,16 +1199,15 @@ "rbush": "^3.0.1" } }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, + "license": "MIT", + "peer": true, "engines": { - "node": ">= 6" + "node": ">=8" } }, "node_modules/hasown": { @@ -835,22 +1223,11 @@ } }, "node_modules/immutable": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.4.tgz", - "integrity": "sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==", - "dev": true - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.1.tgz", + "integrity": "sha512-3jatXi9ObIsPGr3N5hGw/vWWcTkq6hUYhpQz4k0wLC+owqWi/LiugIw9x0EdNZ2yGedKN/HzePiBvaJRXa0Ujg==", "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } + "license": "MIT" }, "node_modules/is-core-module": { "version": "2.13.1", @@ -869,6 +1246,8 @@ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, + "license": "MIT", + "optional": true, "engines": { "node": ">=0.10.0" } @@ -878,6 +1257,8 @@ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, + "license": "MIT", + "optional": true, "dependencies": { "is-extglob": "^2.1.1" }, @@ -890,6 +1271,8 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, + "license": "MIT", + "optional": true, "engines": { "node": ">=0.12.0" } @@ -915,15 +1298,29 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, "engines": { - "node": ">=0.10.0" + "node": ">=8.6" } }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/open-location-code": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/open-location-code/-/open-location-code-1.0.3.tgz", @@ -953,6 +1350,8 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, + "license": "MIT", + "optional": true, "engines": { "node": ">=8.6" }, @@ -982,15 +1381,17 @@ } }, "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, + "license": "MIT", "engines": { - "node": ">=8.10.0" + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, "node_modules/resolve": { @@ -1010,19 +1411,447 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-identifier": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/safe-identifier/-/safe-identifier-0.4.2.tgz", + "integrity": "sha512-6pNbSMW6OhAi9j+N8V+U715yBQsaWJ7eyEUaOrawX+isg5ZxhUlV1NipNtgaKHmFGiABwt+ZF04Ii+3Xjkg+8w==", + "dev": true, + "license": "ISC" + }, "node_modules/sass": { - "version": "1.69.5", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.69.5.tgz", - "integrity": "sha512-qg2+UCJibLr2LCVOt3OlPhr/dqVHWOa9XtZf2OjbLs/T4VPSJ00udtgJxH3neXZm+QqX8B+3cU7RaLqp1iVfcQ==", + "version": "1.86.3", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.86.3.tgz", + "integrity": "sha512-iGtg8kus4GrsGLRDLRBRHY9dNVA78ZaS7xr01cWnS7PEMQyFtTqBiyCrfpTYTZXRWM94akzckYjh8oADfFNTzw==", "dev": true, + "license": "MIT", "dependencies": { - "chokidar": ">=3.0.0 <4.0.0", - "immutable": "^4.0.0", + "chokidar": "^4.0.0", + "immutable": "^5.0.2", "source-map-js": ">=0.6.2 <2.0.0" }, "bin": { "sass": "sass.js" }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, + "node_modules/sass-embedded": { + "version": "1.86.3", + "resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.86.3.tgz", + "integrity": "sha512-3pZSp24ibO1hdopj+W9DuiWsZOb2YY6AFRo/jjutKLBkqJGM1nJjXzhAYfzRV+Xn5BX1eTI4bBTE09P0XNHOZg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@bufbuild/protobuf": "^2.0.0", + "buffer-builder": "^0.2.0", + "colorjs.io": "^0.5.0", + "immutable": "^5.0.2", + "rxjs": "^7.4.0", + "supports-color": "^8.1.1", + "sync-child-process": "^1.0.2", + "varint": "^6.0.0" + }, + "bin": { + "sass": "dist/bin/sass.js" + }, + "engines": { + "node": ">=16.0.0" + }, + "optionalDependencies": { + "sass-embedded-android-arm": "1.86.3", + "sass-embedded-android-arm64": "1.86.3", + "sass-embedded-android-ia32": "1.86.3", + "sass-embedded-android-riscv64": "1.86.3", + "sass-embedded-android-x64": "1.86.3", + "sass-embedded-darwin-arm64": "1.86.3", + "sass-embedded-darwin-x64": "1.86.3", + "sass-embedded-linux-arm": "1.86.3", + "sass-embedded-linux-arm64": "1.86.3", + "sass-embedded-linux-ia32": "1.86.3", + "sass-embedded-linux-musl-arm": "1.86.3", + "sass-embedded-linux-musl-arm64": "1.86.3", + "sass-embedded-linux-musl-ia32": "1.86.3", + "sass-embedded-linux-musl-riscv64": "1.86.3", + "sass-embedded-linux-musl-x64": "1.86.3", + "sass-embedded-linux-riscv64": "1.86.3", + "sass-embedded-linux-x64": "1.86.3", + "sass-embedded-win32-arm64": "1.86.3", + "sass-embedded-win32-ia32": "1.86.3", + "sass-embedded-win32-x64": "1.86.3" + } + }, + "node_modules/sass-embedded-android-arm": { + "version": "1.86.3", + "resolved": "https://registry.npmjs.org/sass-embedded-android-arm/-/sass-embedded-android-arm-1.86.3.tgz", + "integrity": "sha512-UyeXrFzZSvrGbvrWUBcspbsbivGgAgebLGJdSqJulgSyGbA6no3DWQ5Qpdd6+OAUC39BlpPu74Wx9s4RrVuaFw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-android-arm64": { + "version": "1.86.3", + "resolved": "https://registry.npmjs.org/sass-embedded-android-arm64/-/sass-embedded-android-arm64-1.86.3.tgz", + "integrity": "sha512-q+XwFp6WgAv+UgnQhsB8KQ95kppvWAB7DSoJp+8Vino8b9ND+1ai3cUUZPE5u4SnLZrgo5NtrbPvN5KLc4Pfyg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-android-ia32": { + "version": "1.86.3", + "resolved": "https://registry.npmjs.org/sass-embedded-android-ia32/-/sass-embedded-android-ia32-1.86.3.tgz", + "integrity": "sha512-gTJjVh2cRzvGujXj5ApPk/owUTL5SiO7rDtNLrzYAzi1N5HRuLYXqk3h1IQY3+eCOBjGl7mQ9XyySbJs/3hDvg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-android-riscv64": { + "version": "1.86.3", + "resolved": "https://registry.npmjs.org/sass-embedded-android-riscv64/-/sass-embedded-android-riscv64-1.86.3.tgz", + "integrity": "sha512-Po3JnyiCS16kd6REo1IMUbFGYtvL9O0rmKaXx5vOuBaJD1LPy2LiSSp7TU7wkJ9IxsTDGzFaSeP1I9qb6D8VVg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-android-x64": { + "version": "1.86.3", + "resolved": "https://registry.npmjs.org/sass-embedded-android-x64/-/sass-embedded-android-x64-1.86.3.tgz", + "integrity": "sha512-+7h3jdDv/0kUFx0BvxYlq2fa7CcHiDPlta6k5OxO5K6jyqJwo9hc0Z052BoYEauWTqZ+vK6bB5rv2BIzq4U9nA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-darwin-arm64": { + "version": "1.86.3", + "resolved": "https://registry.npmjs.org/sass-embedded-darwin-arm64/-/sass-embedded-darwin-arm64-1.86.3.tgz", + "integrity": "sha512-EgLwV4ORm5Hr0DmIXo0Xw/vlzwLnfAiqD2jDXIglkBsc5czJmo4/IBdGXOP65TRnsgJEqvbU3aQhuawX5++x9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-darwin-x64": { + "version": "1.86.3", + "resolved": "https://registry.npmjs.org/sass-embedded-darwin-x64/-/sass-embedded-darwin-x64-1.86.3.tgz", + "integrity": "sha512-dfKhfrGPRNLWLC82vy/vQGmNKmAiKWpdFuWiePRtg/E95pqw+sCu6080Y6oQLfFu37Iq3MpnXiSpDuSo7UnPWA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-arm": { + "version": "1.86.3", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm/-/sass-embedded-linux-arm-1.86.3.tgz", + "integrity": "sha512-+fVCIH+OR0SMHn2NEhb/VfbpHuUxcPtqMS34OCV3Ka99LYZUJZqth4M3lT/ppGl52mwIVLNYzR4iLe6mdZ6mYA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-arm64": { + "version": "1.86.3", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm64/-/sass-embedded-linux-arm64-1.86.3.tgz", + "integrity": "sha512-tYq5rywR53Qtc+0KI6pPipOvW7a47ETY69VxfqI9BR2RKw2hBbaz0bIw6OaOgEBv2/XNwcWb7a4sr7TqgkqKAA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-ia32": { + "version": "1.86.3", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-ia32/-/sass-embedded-linux-ia32-1.86.3.tgz", + "integrity": "sha512-CmQ5OkqnaeLdaF+bMqlYGooBuenqm3LvEN9H8BLhjkpWiFW8hnYMetiqMcJjhrXLvDw601KGqA5sr/Rsg5s45g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-arm": { + "version": "1.86.3", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm/-/sass-embedded-linux-musl-arm-1.86.3.tgz", + "integrity": "sha512-SEm65SQknI4pl+mH5Xf231hOkHJyrlgh5nj4qDbiBG6gFeutaNkNIeRgKEg3cflXchCr8iV/q/SyPgjhhzQb7w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-arm64": { + "version": "1.86.3", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm64/-/sass-embedded-linux-musl-arm64-1.86.3.tgz", + "integrity": "sha512-4zOr2C/eW89rxb4ozTfn7lBzyyM5ZigA1ZSRTcAR26Qbg/t2UksLdGnVX9/yxga0d6aOi0IvO/7iM2DPPRRotg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-ia32": { + "version": "1.86.3", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-ia32/-/sass-embedded-linux-musl-ia32-1.86.3.tgz", + "integrity": "sha512-84Tcld32LB1loiqUvczWyVBQRCChm0wNLlkT59qF29nxh8njFIVf9yaPgXcSyyjpPoD9Tu0wnq3dvVzoMCh9AQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-riscv64": { + "version": "1.86.3", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-riscv64/-/sass-embedded-linux-musl-riscv64-1.86.3.tgz", + "integrity": "sha512-IxEqoiD7vdNpiOwccybbV93NljBy64wSTkUOknGy21SyV43C8uqESOwTwW9ywa3KufImKm8L3uQAW/B0KhJMWg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-x64": { + "version": "1.86.3", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-x64/-/sass-embedded-linux-musl-x64-1.86.3.tgz", + "integrity": "sha512-ePeTPXUxPK6JgHcUfnrkIyDtyt+zlAvF22mVZv6y1g/PZFm1lSfX+Za7TYHg9KaYqaaXDiw6zICX4i44HhR8rA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-riscv64": { + "version": "1.86.3", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-riscv64/-/sass-embedded-linux-riscv64-1.86.3.tgz", + "integrity": "sha512-NuXQ72dwfNLe35E+RaXJ4Noq4EkFwM65eWwCwxEWyJO9qxOx1EXiCAJii6x8kkOh5daWuMU0VAI1B9RsJaqqQQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-x64": { + "version": "1.86.3", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-x64/-/sass-embedded-linux-x64-1.86.3.tgz", + "integrity": "sha512-t8be9zJ5B82+og9bQmIQ83yMGYZMTMrlGA+uGWtYacmwg6w3093dk91Fx0YzNSZBp3Tk60qVYjCZnEIwy60x0g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-win32-arm64": { + "version": "1.86.3", + "resolved": "https://registry.npmjs.org/sass-embedded-win32-arm64/-/sass-embedded-win32-arm64-1.86.3.tgz", + "integrity": "sha512-4ghuAzjX4q8Nksm0aifRz8hgXMMxS0SuymrFfkfJlrSx68pIgvAge6AOw0edoZoe0Tf5ZbsWUWamhkNyNxkTvw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-win32-ia32": { + "version": "1.86.3", + "resolved": "https://registry.npmjs.org/sass-embedded-win32-ia32/-/sass-embedded-win32-ia32-1.86.3.tgz", + "integrity": "sha512-tCaK4zIRq9mLRPxLzBAdYlfCuS/xLNpmjunYxeWkIwlJo+k53h1udyXH/FInnQ2GgEz0xMXyvH3buuPgzwWYsw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-win32-x64": { + "version": "1.86.3", + "resolved": "https://registry.npmjs.org/sass-embedded-win32-x64/-/sass-embedded-win32-x64-1.86.3.tgz", + "integrity": "sha512-zS+YNKfTF4SnOfpC77VTb0qNZyTXrxnAezSoRV0xnw6HlY+1WawMSSB6PbWtmbvyfXNgpmJUttoTtsvJjRCucg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, "engines": { "node": ">=14.0.0" } @@ -1041,6 +1870,23 @@ "resolved": "https://registry.npmjs.org/splaytree/-/splaytree-3.1.2.tgz", "integrity": "sha512-4OM2BJgC5UzrhVnnJA4BkHKGtjXNzzUfpQjCO8I05xYPsfS/VuQDwjCGGMi8rYQilHEV4j8NBqTFbls/PZEE7A==" }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -1053,17 +1899,60 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/sync-child-process": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/sync-child-process/-/sync-child-process-1.0.2.tgz", + "integrity": "sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "sync-message-port": "^1.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/sync-message-port": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sync-message-port/-/sync-message-port-1.1.3.tgz", + "integrity": "sha512-GTt8rSKje5FilG+wEdfCkOcLL7LWqpMlr2c3LRuKt/YXxcJ52aGSbGBAdI4L3aaqfrBt6y711El53ItyH1NWzg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, + "license": "MIT", + "optional": true, "dependencies": { "is-number": "^7.0.0" }, "engines": { "node": ">=8.0" } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "peer": true + }, + "node_modules/varint": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz", + "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==", + "dev": true, + "license": "MIT", + "peer": true } } } diff --git a/assets/package.json b/assets/package.json index bba27d24d3..af369ea4ce 100644 --- a/assets/package.json +++ b/assets/package.json @@ -19,8 +19,8 @@ }, "devDependencies": { "bulma": "^0.9.4", - "esbuild": "^0.19.4", - "esbuild-sass-plugin": "^2.9.0", + "esbuild": "^0.25.2", + "esbuild-sass-plugin": "^3.3.1", "sass": "^1.63.3" }, "name": "assets" diff --git a/assets/scripts/build.js b/assets/scripts/build.js index 86a42678b4..41a40d64f8 100644 --- a/assets/scripts/build.js +++ b/assets/scripts/build.js @@ -17,7 +17,8 @@ const buildLogger = { let count = 0; build.onEnd(({ errors, warnings }) => { if (errors.length > 0) console.error("[-] Esbuild failed:", errors); - else if (warnings.length > 0) console.warn("[-] Esbuild finished with warnings:", warnings); + else if (warnings.length > 0) + console.warn("[-] Esbuild finished with warnings:", warnings); else console.log(`[+] Esbuild succeeded`); }); }, diff --git a/config/runtime.exs b/config/runtime.exs index 97334c303b..db8e1c0abc 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -101,14 +101,22 @@ end config :teslamate, default_geofence: System.get_env("DEFAULT_GEOFENCE") +case System.get_env("DATABASE_SOCKET_DIR") do + nil -> + config :teslamate, TeslaMate.Repo, + username: Util.fetch_env!("DATABASE_USER", all: "postgres"), + password: Util.fetch_env!("DATABASE_PASS", all: "postgres"), + hostname: Util.fetch_env!("DATABASE_HOST", all: "localhost"), + port: System.get_env("DATABASE_PORT", "5432") + + socket_dir -> + config :teslamate, TeslaMate.Repo, socket_dir: socket_dir +end + config :teslamate, TeslaMate.Repo, - username: Util.fetch_env!("DATABASE_USER", all: "postgres"), - password: Util.fetch_env!("DATABASE_PASS", all: "postgres"), - database: Util.fetch_env!("DATABASE_NAME", dev: "teslamate_dev", test: "teslamate_test"), - hostname: Util.fetch_env!("DATABASE_HOST", all: "localhost"), - port: System.get_env("DATABASE_PORT", "5432"), pool_size: System.get_env("DATABASE_POOL_SIZE", "10") |> String.to_integer(), - timeout: System.get_env("DATABASE_TIMEOUT", "60000") |> String.to_integer() + timeout: System.get_env("DATABASE_TIMEOUT", "60000") |> String.to_integer(), + database: Util.fetch_env!("DATABASE_NAME", dev: "teslamate_dev", test: "teslamate_test") case System.get_env("DATABASE_SSL") do "true" -> diff --git a/coveralls.json b/coveralls.json index ce5213a486..ba07580c59 100644 --- a/coveralls.json +++ b/coveralls.json @@ -17,4 +17,3 @@ "lib/teslamate_web.ex" ] } - diff --git a/entrypoint.sh b/entrypoint.sh index cf8b4934be..8efe6dec63 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -5,9 +5,9 @@ set -e : "${DATABASE_PORT:=5432}" # wait until Postgres is ready -while ! nc -z ${DATABASE_HOST} ${DATABASE_PORT} 2>/dev/null; do - echo waiting for postgres at ${DATABASE_HOST}:${DATABASE_PORT} - sleep 1s +while ! nc -z "${DATABASE_HOST}" "${DATABASE_PORT}" 2>/dev/null; do + echo waiting for postgres at "${DATABASE_HOST}":"${DATABASE_PORT}" + sleep 1s done # apply migrations diff --git a/flake.lock b/flake.lock index a54be949d8..103309c195 100644 --- a/flake.lock +++ b/flake.lock @@ -2,24 +2,28 @@ "nodes": { "cachix": { "inputs": { - "devenv": "devenv_2", - "flake-compat": "flake-compat_2", - "nixpkgs": [ - "devenv", - "nixpkgs" + "devenv": [ + "devenv" + ], + "flake-compat": [ + "devenv" ], - "pre-commit-hooks": "pre-commit-hooks" + "git-hooks": [ + "devenv" + ], + "nixpkgs": "nixpkgs" }, "locked": { - "lastModified": 1710475558, - "narHash": "sha256-egKrPCKjy/cE+NqCj4hg2fNX/NwLCf0bRDInraYXDgs=", + "lastModified": 1742042642, + "narHash": "sha256-D0gP8srrX0qj+wNYNPdtVJsQuFzIng3q43thnHXQ/es=", "owner": "cachix", "repo": "cachix", - "rev": "661bbb7f8b55722a0406456b15267b5426a3bda6", + "rev": "a624d3eaf4b1d225f918de8543ed739f2f574203", "type": "github" }, "original": { "owner": "cachix", + "ref": "latest", "repo": "cachix", "type": "github" } @@ -27,128 +31,45 @@ "devenv": { "inputs": { "cachix": "cachix", - "flake-compat": "flake-compat_4", - "nix": "nix_2", - "nixpkgs": "nixpkgs_2", - "pre-commit-hooks": "pre-commit-hooks_2" - }, - "locked": { - "lastModified": 1711019207, - "narHash": "sha256-9LnGe0KWqXj18IV+A1panzXQuTamrH/QcasaqnuqiE0=", - "owner": "cachix", - "repo": "devenv", - "rev": "a7ba6766c0cc351b8656c63560c6b19c3788455f", - "type": "github" - }, - "original": { - "owner": "cachix", - "repo": "devenv", - "type": "github" - } - }, - "devenv_2": { - "inputs": { - "flake-compat": [ - "devenv", - "cachix", - "flake-compat" - ], + "flake-compat": "flake-compat", + "git-hooks": "git-hooks", "nix": "nix", - "nixpkgs": "nixpkgs", - "poetry2nix": "poetry2nix", - "pre-commit-hooks": [ - "devenv", - "cachix", - "pre-commit-hooks" - ] + "nixpkgs": "nixpkgs_3" }, "locked": { - "lastModified": 1708704632, - "narHash": "sha256-w+dOIW60FKMaHI1q5714CSibk99JfYxm0CzTinYWr+Q=", + "lastModified": 1745020394, + "narHash": "sha256-045veG11u/fdRjYI+9dc19/u/14j7UFitwKWisE11Iw=", "owner": "cachix", "repo": "devenv", - "rev": "2ee4450b0f4b95a1b90f2eb5ffea98b90e48c196", + "rev": "6599a8c6d02c1d37fe5b7804f70a39c262298729", "type": "github" }, "original": { "owner": "cachix", - "ref": "python-rewrite", "repo": "devenv", "type": "github" } }, - "flake-compat": { - "flake": false, - "locked": { - "lastModified": 1673956053, - "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", - "owner": "edolstra", - "repo": "flake-compat", - "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", - "type": "github" - }, - "original": { - "owner": "edolstra", - "repo": "flake-compat", - "type": "github" - } - }, - "flake-compat_2": { - "flake": false, - "locked": { - "lastModified": 1696426674, - "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", - "owner": "edolstra", - "repo": "flake-compat", - "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", - "type": "github" - }, - "original": { - "owner": "edolstra", - "repo": "flake-compat", - "type": "github" - } - }, - "flake-compat_3": { - "flake": false, - "locked": { - "lastModified": 1696426674, - "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", - "owner": "edolstra", - "repo": "flake-compat", - "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", - "type": "github" - }, - "original": { - "owner": "edolstra", - "repo": "flake-compat", - "type": "github" - } - }, - "flake-compat_4": { + "devenv-root": { "flake": false, "locked": { - "lastModified": 1673956053, - "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", - "owner": "edolstra", - "repo": "flake-compat", - "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", - "type": "github" + "narHash": "sha256-d6xi4mKdjkX2JFicDIv5niSzpyI0m/Hnm8GGAIU04kY=", + "type": "file", + "url": "file:///dev/null" }, "original": { - "owner": "edolstra", - "repo": "flake-compat", - "type": "github" + "type": "file", + "url": "file:///dev/null" } }, - "flake-compat_5": { + "flake-compat": { "flake": false, "locked": { - "lastModified": 1696426674, - "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", + "lastModified": 1733328505, + "narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=", "owner": "edolstra", "repo": "flake-compat", - "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec", "type": "github" }, "original": { @@ -157,112 +78,87 @@ "type": "github" } }, - "flake-utils": { + "flake-parts": { "inputs": { - "systems": "systems" - }, - "locked": { - "lastModified": 1689068808, - "narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, - "flake-utils_2": { - "inputs": { - "systems": "systems_2" - }, - "locked": { - "lastModified": 1701680307, - "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", - "type": "github" + "nixpkgs-lib": [ + "devenv", + "nix", + "nixpkgs" + ] }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, - "flake-utils_3": { "locked": { - "lastModified": 1667395993, - "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f", + "lastModified": 1712014858, + "narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "9126214d0a59633752a136528f5f3b9aa8565b7d", "type": "github" }, "original": { - "owner": "numtide", - "repo": "flake-utils", + "owner": "hercules-ci", + "repo": "flake-parts", "type": "github" } }, - "flake-utils_4": { + "flake-parts_2": { "inputs": { - "systems": "systems_3" + "nixpkgs-lib": [ + "nixpkgs" + ] }, "locked": { - "lastModified": 1705309234, - "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26", + "lastModified": 1743550720, + "narHash": "sha256-hIshGgKZCgWh6AYJpJmRgFdR3WUbkY04o82X05xqQiY=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "c621e8422220273271f52058f618c94e405bb0f5", "type": "github" }, "original": { - "owner": "numtide", - "repo": "flake-utils", + "owner": "hercules-ci", + "repo": "flake-parts", "type": "github" } }, - "gitignore": { + "git-hooks": { "inputs": { + "flake-compat": [ + "devenv" + ], + "gitignore": "gitignore", "nixpkgs": [ "devenv", - "cachix", - "pre-commit-hooks", "nixpkgs" ] }, "locked": { - "lastModified": 1703887061, - "narHash": "sha256-gGPa9qWNc6eCXT/+Z5/zMkyYOuRZqeFZBDbopNZQkuY=", - "owner": "hercules-ci", - "repo": "gitignore.nix", - "rev": "43e1aa1308018f37118e34d3a9cb4f5e75dc11d5", + "lastModified": 1742649964, + "narHash": "sha256-DwOTp7nvfi8mRfuL1escHDXabVXFGT1VlPD1JHrtrco=", + "owner": "cachix", + "repo": "git-hooks.nix", + "rev": "dcf5072734cb576d2b0c59b2ac44f5050b5eac82", "type": "github" }, "original": { - "owner": "hercules-ci", - "repo": "gitignore.nix", + "owner": "cachix", + "repo": "git-hooks.nix", "type": "github" } }, - "gitignore_2": { + "gitignore": { "inputs": { "nixpkgs": [ "devenv", - "pre-commit-hooks", + "git-hooks", "nixpkgs" ] }, "locked": { - "lastModified": 1660459072, - "narHash": "sha256-8DFJjXG8zqoONA1vXtgeKXy68KdJL5UaXR8NtVMUbx8=", + "lastModified": 1709087332, + "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", "owner": "hercules-ci", "repo": "gitignore.nix", - "rev": "a20de23b925fd8264fd7fad6454652e142fd7f73", + "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", "type": "github" }, "original": { @@ -271,167 +167,94 @@ "type": "github" } }, - "nix": { - "inputs": { - "flake-compat": "flake-compat", - "nixpkgs": [ - "devenv", - "cachix", - "devenv", - "nixpkgs" - ], - "nixpkgs-regression": "nixpkgs-regression" - }, - "locked": { - "lastModified": 1708577783, - "narHash": "sha256-92xq7eXlxIT5zFNccLpjiP7sdQqQI30Gyui2p/PfKZM=", - "owner": "domenkozar", - "repo": "nix", - "rev": "ecd0af0c1f56de32cbad14daa1d82a132bf298f8", - "type": "github" - }, - "original": { - "owner": "domenkozar", - "ref": "devenv-2.21", - "repo": "nix", - "type": "github" - } - }, - "nix-github-actions": { - "inputs": { - "nixpkgs": [ - "devenv", - "cachix", - "devenv", - "poetry2nix", - "nixpkgs" - ] - }, + "libgit2": { + "flake": false, "locked": { - "lastModified": 1688870561, - "narHash": "sha256-4UYkifnPEw1nAzqqPOTL2MvWtm3sNGw1UTYTalkTcGY=", - "owner": "nix-community", - "repo": "nix-github-actions", - "rev": "165b1650b753316aa7f1787f3005a8d2da0f5301", + "lastModified": 1697646580, + "narHash": "sha256-oX4Z3S9WtJlwvj0uH9HlYcWv+x1hqp8mhXl7HsLu2f0=", + "owner": "libgit2", + "repo": "libgit2", + "rev": "45fd9ed7ae1a9b74b957ef4f337bc3c8b3df01b5", "type": "github" }, "original": { - "owner": "nix-community", - "repo": "nix-github-actions", + "owner": "libgit2", + "repo": "libgit2", "type": "github" } }, - "nix_2": { + "nix": { "inputs": { - "flake-compat": "flake-compat_5", - "nixpkgs": [ - "devenv", - "nixpkgs" + "flake-compat": [ + "devenv" + ], + "flake-parts": "flake-parts", + "libgit2": "libgit2", + "nixpkgs": "nixpkgs_2", + "nixpkgs-23-11": [ + "devenv" ], - "nixpkgs-regression": "nixpkgs-regression_2" + "nixpkgs-regression": [ + "devenv" + ], + "pre-commit-hooks": [ + "devenv" + ] }, "locked": { - "lastModified": 1710500156, - "narHash": "sha256-zvCqeUO2GLOm7jnU23G4EzTZR7eylcJN+HJ5svjmubI=", + "lastModified": 1741798497, + "narHash": "sha256-E3j+3MoY8Y96mG1dUIiLFm2tZmNbRvSiyN7CrSKuAVg=", "owner": "domenkozar", "repo": "nix", - "rev": "c5bbf14ecbd692eeabf4184cc8d50f79c2446549", + "rev": "f3f44b2baaf6c4c6e179de8cbb1cc6db031083cd", "type": "github" }, "original": { "owner": "domenkozar", - "ref": "devenv-2.21", + "ref": "devenv-2.24", "repo": "nix", "type": "github" } }, "nixpkgs": { "locked": { - "lastModified": 1692808169, - "narHash": "sha256-x9Opq06rIiwdwGeK2Ykj69dNc2IvUH1fY55Wm7atwrE=", + "lastModified": 1733212471, + "narHash": "sha256-M1+uCoV5igihRfcUKrr1riygbe73/dzNnzPsmaLCmpo=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "9201b5ff357e781bf014d0330d18555695df7ba8", + "rev": "55d15ad12a74eb7d4646254e13638ad0c4128776", "type": "github" }, "original": { "owner": "NixOS", - "ref": "nixpkgs-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "nixpkgs-regression": { - "locked": { - "lastModified": 1643052045, - "narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", - "type": "github" - }, - "original": { - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", - "type": "github" - } - }, - "nixpkgs-regression_2": { - "locked": { - "lastModified": 1643052045, - "narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", - "type": "github" - }, - "original": { - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", - "type": "github" - } - }, - "nixpkgs-stable": { - "locked": { - "lastModified": 1704874635, - "narHash": "sha256-YWuCrtsty5vVZvu+7BchAxmcYzTMfolSPP5io8+WYCg=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "3dc440faeee9e889fe2d1b4d25ad0f430d449356", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixos-23.11", + "ref": "nixos-unstable", "repo": "nixpkgs", "type": "github" } }, - "nixpkgs-stable_2": { + "nixpkgs_2": { "locked": { - "lastModified": 1678872516, - "narHash": "sha256-/E1YwtMtFAu2KUQKV/1+KFuReYPANM2Rzehk84VxVoc=", + "lastModified": 1717432640, + "narHash": "sha256-+f9c4/ZX5MWDOuB1rKoWj+lBNm0z0rs4CK47HBLxy1o=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "9b8e5abb18324c7fe9f07cb100c3cd4a29cda8b8", + "rev": "88269ab3044128b7c2f4c7d68448b2fb50456870", "type": "github" }, "original": { "owner": "NixOS", - "ref": "nixos-22.11", + "ref": "release-24.05", "repo": "nixpkgs", "type": "github" } }, - "nixpkgs_2": { + "nixpkgs_3": { "locked": { - "lastModified": 1710796454, - "narHash": "sha256-lQlICw60RhH8sHTDD/tJiiJrlAfNn8FDI9c+7G2F0SE=", + "lastModified": 1733477122, + "narHash": "sha256-qamMCz5mNpQmgBwc8SB5tVMlD5sbwVIToVZtSxMph9s=", "owner": "cachix", "repo": "devenv-nixpkgs", - "rev": "06fb0f1c643aee3ae6838dda3b37ef0abc3c763b", + "rev": "7bd9e84d0452f6d2e63b6e6da29fe73fac951857", "type": "github" }, "original": { @@ -441,150 +264,48 @@ "type": "github" } }, - "nixpkgs_3": { + "nixpkgs_4": { "locked": { - "lastModified": 1707092692, - "narHash": "sha256-ZbHsm+mGk/izkWtT4xwwqz38fdlwu7nUUKXTOmm4SyE=", + "lastModified": 1744440957, + "narHash": "sha256-FHlSkNqFmPxPJvy+6fNLaNeWnF1lZSgqVCl/eWaJRc4=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "faf912b086576fd1a15fca610166c98d47bc667e", + "rev": "26d499fc9f1d567283d5d56fcf367edd815dba1d", "type": "github" }, "original": { "owner": "NixOS", - "ref": "nixos-unstable", + "ref": "nixos-24.11", "repo": "nixpkgs", "type": "github" } }, - "poetry2nix": { - "inputs": { - "flake-utils": "flake-utils", - "nix-github-actions": "nix-github-actions", - "nixpkgs": [ - "devenv", - "cachix", - "devenv", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1692876271, - "narHash": "sha256-IXfZEkI0Mal5y1jr6IRWMqK8GW2/f28xJenZIPQqkY0=", - "owner": "nix-community", - "repo": "poetry2nix", - "rev": "d5006be9c2c2417dafb2e2e5034d83fabd207ee3", - "type": "github" - }, - "original": { - "owner": "nix-community", - "repo": "poetry2nix", - "type": "github" - } - }, - "pre-commit-hooks": { + "root": { "inputs": { - "flake-compat": "flake-compat_3", - "flake-utils": "flake-utils_2", - "gitignore": "gitignore", - "nixpkgs": [ - "devenv", - "cachix", - "nixpkgs" - ], - "nixpkgs-stable": "nixpkgs-stable" - }, - "locked": { - "lastModified": 1708018599, - "narHash": "sha256-M+Ng6+SePmA8g06CmUZWi1AjG2tFBX9WCXElBHEKnyM=", - "owner": "cachix", - "repo": "pre-commit-hooks.nix", - "rev": "5df5a70ad7575f6601d91f0efec95dd9bc619431", - "type": "github" - }, - "original": { - "owner": "cachix", - "repo": "pre-commit-hooks.nix", - "type": "github" + "devenv": "devenv", + "devenv-root": "devenv-root", + "flake-parts": "flake-parts_2", + "nixpkgs": "nixpkgs_4", + "treefmt-nix": "treefmt-nix" } }, - "pre-commit-hooks_2": { + "treefmt-nix": { "inputs": { - "flake-compat": [ - "devenv", - "flake-compat" - ], - "flake-utils": "flake-utils_3", - "gitignore": "gitignore_2", "nixpkgs": [ - "devenv", "nixpkgs" - ], - "nixpkgs-stable": "nixpkgs-stable_2" - }, - "locked": { - "lastModified": 1686050334, - "narHash": "sha256-R0mczWjDzBpIvM3XXhO908X5e2CQqjyh/gFbwZk/7/Q=", - "owner": "cachix", - "repo": "pre-commit-hooks.nix", - "rev": "6881eb2ae5d8a3516e34714e7a90d9d95914c4dc", - "type": "github" - }, - "original": { - "owner": "cachix", - "repo": "pre-commit-hooks.nix", - "type": "github" - } - }, - "root": { - "inputs": { - "devenv": "devenv", - "flake-utils": "flake-utils_4", - "nixpkgs": "nixpkgs_3" - } - }, - "systems": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } - }, - "systems_2": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" + ] }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } - }, - "systems_3": { "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "lastModified": 1744961264, + "narHash": "sha256-aRmUh0AMwcbdjJHnytg1e5h5ECcaWtIFQa6d9gI85AI=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "8d404a69efe76146368885110f29a2ca3700bee6", "type": "github" }, "original": { - "owner": "nix-systems", - "repo": "default", + "owner": "numtide", + "repo": "treefmt-nix", "type": "github" } } diff --git a/flake.nix b/flake.nix index d5ec66d7d7..cca8d4e787 100644 --- a/flake.nix +++ b/flake.nix @@ -2,193 +2,33 @@ description = "TeslaMate Logger"; inputs = { - nixpkgs = { url = "github:NixOS/nixpkgs/nixos-unstable"; }; - flake-utils = { url = "github:numtide/flake-utils"; }; + nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11"; + flake-parts.url = "github:hercules-ci/flake-parts"; + flake-parts.inputs.nixpkgs-lib.follows = "nixpkgs"; + devenv-root.url = "file+file:///dev/null"; + devenv-root.flake = false; devenv.url = "github:cachix/devenv"; + treefmt-nix.url = "github:numtide/treefmt-nix"; + treefmt-nix.inputs.nixpkgs.follows = "nixpkgs"; }; - outputs = inputs@{ self, nixpkgs, flake-utils, devenv }: - (flake-utils.lib.eachDefaultSystem (system: - let - inherit (pkgs.lib) optional optionals; - pkgs = nixpkgs.legacyPackages.${system}; - - elixir = pkgs.beam.packages.erlang.elixir_1_16; - beamPackages = pkgs.beam.packagesWith pkgs.beam.interpreters.erlang; - - src = ./.; - version = builtins.readFile ./VERSION; - pname = "teslamate"; - - mixFodDeps = beamPackages.fetchMixDeps { - TOP_SRC = src; - pname = "${pname}-mix-deps"; - inherit src version; - hash = "sha256-CeGtWKLjZJsoHk+uSAIz8s46XyGJWa3ECm5rSRfqlrc="; - # hash = pkgs.lib.fakeHash; - }; - - nodejs = pkgs.nodejs; - nodePackages = pkgs.buildNpmPackage { - name = "teslamate"; - src = ./assets; - npmDepsHash = "sha256-05AKPyms4WP8MHBqWMup8VXR3a1tv/f/7jT8c6EpWBw="; - # npmDepsHash = pkgs.lib.fakeHash; - dontNpmBuild = true; - inherit nodejs; - - installPhase = '' - mkdir $out - cp -r node_modules $out - ln -s $out/node_modules/.bin $out/bin - - rm $out/node_modules/phoenix - ln -s ${mixFodDeps}/phoenix $out/node_modules - - rm $out/node_modules/phoenix_html - ln -s ${mixFodDeps}/phoenix_html $out/node_modules - - rm $out/node_modules/phoenix_live_view - ln -s ${mixFodDeps}/phoenix_live_view $out/node_modules - ''; - }; - - cldr = pkgs.fetchFromGitHub { - owner = "elixir-cldr"; - repo = "cldr"; - rev = "v2.37.5"; - sha256 = "sha256-T5Qvuo+xPwpgBsqHNZYnTCA4loToeBn1LKTMsDcCdYs="; - # sha256 = pkgs.lib.fakeHash; - }; - - pkg = beamPackages.mixRelease { - TOP_SRC = src; - inherit pname version elixir src mixFodDeps; - - LOCALES = "${cldr}/priv/cldr"; - - postBuild = '' - ln -sf ${mixFodDeps}/deps deps - ln -sf ${nodePackages}/node_modules assets/node_modules - export PATH="${pkgs.nodejs}/bin:${nodePackages}/bin:$PATH" - ${nodejs}/bin/npm run deploy --prefix ./assets - - # for external task you need a workaround for the no deps check flag - # https://github.com/phoenixframework/phoenix/issues/2690 - mix do deps.loadpaths --no-deps-check, phx.digest - mix phx.digest --no-deps-check - ''; - - meta = { - mainProgram = "teslamate"; - }; - }; - - postgres_port = 7000; - mosquitto_port = 7001; - process_compose_port = 7002; - - psql = pkgs.writeShellScriptBin "teslamate_psql" '' - exec "${pkgs.postgresql}/bin/psql" --host "$DATABASE_HOST" --user "$DATABASE_USER" --port "$DATABASE_PORT" "$DATABASE_NAME" "$@" - ''; - mosquitto_sub = pkgs.writeShellScriptBin "teslamate_sub" '' - exec "${pkgs.mosquitto}/bin/mosquitto_sub" -h "$MQTT_HOST" -p "$MQTT_PORT" -u "$MQTT_USERNAME" -P "$MQTT_PASSWORD" "$@" - ''; - - devShell = devenv.lib.mkShell { - inherit inputs pkgs; - modules = with pkgs; [{ - packages = [ - elixir - elixir_ls - glibcLocales - node2nix - nodejs - prefetch-npm-deps - # for dashboard scripts - jq - psql - mosquitto - mosquitto_sub - ] ++ optional stdenv.isLinux inotify-tools - ++ optional stdenv.isDarwin terminal-notifier - ++ optionals stdenv.isDarwin (with darwin.apple_sdk.frameworks; [ - CoreFoundation - CoreServices - ]); - enterShell = '' - export LOCALES="${cldr}/priv/cldr"; - export PORT="4000" - export ENCRYPTION_KEY="your_secure_encryption_key_here" - export DATABASE_USER="teslamate" - export DATABASE_PASS="your_secure_password_here" - export DATABASE_NAME="teslamate" - export DATABASE_HOST="127.0.0.1" - export DATABASE_PORT="${toString postgres_port}" - export MQTT_HOST="127.0.0.1" - export MQTT_PORT="${toString mosquitto_port}" - export RELEASE_COOKIE="1234567890123456789" - export TZDATA_DIR="$PWD/tzdata" - ''; - enterTest = '' - mix test - ''; - processes.mqtt = { - exec = - "${pkgs.mosquitto}/bin/mosquitto -p ${toString mosquitto_port}"; - }; - process.process-compose = { - port = process_compose_port; - tui = true; - }; - services.postgres = { - enable = true; - package = pkgs.postgresql_15; - listen_addresses = "127.0.0.1"; - port = postgres_port; - initialDatabases = [{ name = "teslamate"; }]; - initialScript = '' - CREATE USER teslamate with encrypted password 'your_secure_password_here'; - GRANT ALL PRIVILEGES ON DATABASE teslamate TO teslamate; - ALTER USER teslamate WITH SUPERUSER; - ''; - }; - }]; - - }; - - moduleTest = (nixpkgs.lib.nixos.runTest { - hostPkgs = pkgs; - defaults.documentation.enable = false; - imports = [{ - name = "teslamate"; - nodes.server = { - imports = [ self.nixosModules.default ]; - services.teslamate = { - enable = true; - secretsFile = builtins.toFile "teslamate.env" '' - ENCRYPTION_KEY=123456789 - DATABASE_PASS=123456789 - RELEASE_COOKIE=123456789 - ''; - postgres.enable = true; - grafana.enable = true; - }; - }; - - testScript = '' - server.wait_for_open_port(4000) - ''; - }]; - }).config.result; - in { - packages = { - devenv-up = devShell.config.procfileScript; - default = pkg; - }; - devShells.default = devShell; - checks.default = moduleTest; - })) // { - nixosModules.default = import ./module.nix { inherit self; }; - }; + outputs = inputs@{ self, flake-parts, ... }: + flake-parts.lib.mkFlake { inherit inputs; } { + flake.nixosModules.default = import ./nix/module.nix { inherit self; }; + + systems = [ + "x86_64-linux" + "aarch64-linux" + "x86_64-darwin" + "aarch64-darwin" + ]; + + # See ./nix/flake-modules/*.nix for the modules that are imported here. + imports = [ + ./nix/flake-modules/checks.nix + ./nix/flake-modules/devenv.nix + ./nix/flake-modules/formatter.nix + ./nix/flake-modules/package.nix + ]; + }; } diff --git a/grafana/Dockerfile b/grafana/Dockerfile index 379b25b4c3..4d649f6a45 100644 --- a/grafana/Dockerfile +++ b/grafana/Dockerfile @@ -1,6 +1,6 @@ # Ensure selecting a tag that is available for arm/v7, arm64, and amd64 # https://hub.docker.com/r/grafana/grafana/tags -FROM grafana/grafana:11.1.0 +FROM grafana/grafana:11.6.1 ENV GF_ANALYTICS_REPORTING_ENABLED=false \ GF_AUTH_ANONYMOUS_ENABLED=false \ @@ -9,8 +9,10 @@ ENV GF_ANALYTICS_REPORTING_ENABLED=false \ GF_SECURITY_ADMIN_USER=admin \ GF_SECURITY_ALLOW_EMBEDDING=true \ GF_SECURITY_DISABLE_GRAVATAR=true \ + GF_DATABASE_HIGH_AVAILABILITY=false \ GF_USERS_ALLOW_SIGN_UP=false \ GF_USERS_DEFAULT_LANGUAGE=detect \ + GF_DASHBOARDS_DEFAULT_HOME_DASHBOARD_PATH=/dashboards_internal/home.json \ GF_DATE_FORMATS_USE_BROWSER_LOCALE=true \ DATABASE_PORT=5432 \ DATABASE_SSL_MODE=disable diff --git a/grafana/dashboards.sh b/grafana/dashboards.sh index d0ab11a4a2..0f07977fdc 100755 --- a/grafana/dashboards.sh +++ b/grafana/dashboards.sh @@ -18,108 +18,104 @@ readonly URL=${URL:-"http://localhost:3000"} readonly LOGIN=${LOGIN:-"admin:admin"} readonly DASHBOARDS_DIRECTORY=${DASHBOARDS_DIRECTORY:-"./grafana/dashboards"} - main() { - local task=$1 + local task=$1 - echo " + echo " URL: $URL LOGIN: $LOGIN DASHBOARDS_DIRECTORY: $DASHBOARDS_DIRECTORY " - case $task in - backup) backup;; - restore) restore;; - *) exit 1;; - esac + case $task in + backup) backup ;; + restore) restore ;; + *) exit 1 ;; + esac } - backup() { - local dashboard_json + local dashboard_json - for dashboard in $(list_dashboards); do - dashboard_json=$(get_dashboard "$dashboard") + for dashboard in $(list_dashboards); do + dashboard_json=$(get_dashboard "$dashboard") - if [[ -z "$dashboard_json" ]]; then - echo "ERROR: + if [[ -z $dashboard_json ]]; then + echo "ERROR: Couldn't retrieve dashboard $dashboard. " - exit 1 - fi + exit 1 + fi - echo "$dashboard_json" > "$DASHBOARDS_DIRECTORY/$dashboard".json + echo "$dashboard_json" >"$DASHBOARDS_DIRECTORY/$dashboard".json - echo "BACKED UP $(basename "$dashboard").json" - done + echo "BACKED UP $(basename "$dashboard").json" + done } - restore() { - find "$DASHBOARDS_DIRECTORY" -type f -name \*.json -print0 | - while IFS= read -r -d '' dashboard_path; do - folder_id=$(get_folder_id "$(basename "$dashboard_path" .json)") - curl \ - --silent --show-error --output /dev/null \ - --user "$LOGIN" \ - -X POST -H "Content-Type: application/json" \ - -d "{\"dashboard\":$(cat "$dashboard_path"), \ + find "$DASHBOARDS_DIRECTORY" -type f -name \*.json -print0 | + while IFS= read -r -d '' dashboard_path; do + folder_id=$(get_folder_id "$(basename "$dashboard_path" .json)") + curl \ + --silent --fail --show-error --output /dev/null \ + --user "$LOGIN" \ + -X POST -H "Content-Type: application/json" \ + -d "{\"dashboard\":$(cat "$dashboard_path"), \ \"overwrite\":true, \ \"folderId\":$folder_id, \ \"inputs\":[{\"name\":\"DS_CLOUDWATCH\", \ \"type\":\"datasource\", \ \"pluginId\":\"cloudwatch\", \ \"value\":\"TeslaMate\"}]}" \ - "$URL/api/dashboards/import" + "$URL/api/dashboards/import" - echo "RESTORED $(basename "$dashboard_path")" - done + echo "RESTORED $(basename "$dashboard_path")" + done } get_dashboard() { - local dashboard=$1 + local dashboard=$1 - if [[ -z "$dashboard" ]]; then - echo "ERROR: + if [[ -z $dashboard ]]; then + echo "ERROR: A dashboard must be specified. " - exit 1 - fi - - curl \ - --silent \ - --user "$LOGIN" \ - "$URL/api/dashboards/db/$dashboard" | - jq '.dashboard | .id = null' + exit 1 + fi + + curl \ + --silent \ + --user "$LOGIN" \ + "$URL/api/dashboards/db/$dashboard" | + jq '.dashboard | .id = null' } get_folder_id() { - local dashboard=$1 + local dashboard=$1 - if [[ -z "$dashboard" ]]; then - echo "ERROR: + if [[ -z $dashboard ]]; then + echo "ERROR: A dashboard must be specified. " - exit 1 - fi - - curl \ - --silent \ - --user "$LOGIN" \ - "$URL/api/dashboards/db/$dashboard" | - jq '.meta | .folderId' + exit 1 + fi + + curl \ + --silent \ + --user "$LOGIN" \ + "$URL/api/dashboards/db/$dashboard" | + jq '.meta | .folderId' } list_dashboards() { - curl \ - --silent \ - --user "$LOGIN" \ - "$URL/api/search" | - jq -r '.[] | select(.type == "dash-db") | .uri' | - cut -d '/' -f2 + curl \ + --silent \ + --user "$LOGIN" \ + "$URL/api/search" | + jq -r '.[] | select(.type == "dash-db") | .uri' | + cut -d '/' -f2 } - main "$@" diff --git a/grafana/dashboards.yml b/grafana/dashboards.yml index 528fceb457..6c9e3f7bf8 100644 --- a/grafana/dashboards.yml +++ b/grafana/dashboards.yml @@ -1,33 +1,33 @@ apiVersion: 1 providers: -- name: 'teslamate' - orgId: 1 - folder: TeslaMate - folderUid: Nr4ofiDZk - type: file - disableDeletion: false - editable: true - updateIntervalSeconds: 86400 - options: - path: /dashboards -- name: 'teslamate_internal' - orgId: 1 - folder: Internal - folderUid: Nr5ofiDZk - type: file - disableDeletion: false - editable: true - updateIntervalSeconds: 86400 - options: - path: /dashboards_internal -- name: 'teslamate_reports' - orgId: 1 - folder: Reports - folderUid: Nr6ofiDZk - type: file - disableDeletion: false - editable: true - updateIntervalSeconds: 86400 - options: - path: /dashboards_reports \ No newline at end of file + - name: "teslamate" + orgId: 1 + folder: TeslaMate + folderUid: Nr4ofiDZk + type: file + disableDeletion: false + allowUiUpdates: true + updateIntervalSeconds: 86400 + options: + path: /dashboards + - name: "teslamate_internal" + orgId: 1 + folder: Internal + folderUid: Nr5ofiDZk + type: file + disableDeletion: false + allowUiUpdates: true + updateIntervalSeconds: 86400 + options: + path: /dashboards_internal + - name: "teslamate_reports" + orgId: 1 + folder: Reports + folderUid: Nr6ofiDZk + type: file + disableDeletion: false + allowUiUpdates: true + updateIntervalSeconds: 86400 + options: + path: /dashboards_reports diff --git a/grafana/dashboards/battery-health.json b/grafana/dashboards/battery-health.json index 30fe85d8c8..39ef2f01b1 100644 --- a/grafana/dashboards/battery-health.json +++ b/grafana/dashboards/battery-health.json @@ -1,1986 +1,1740 @@ -{ - "__elements": {}, - "__requires": [ - { - "type": "panel", - "id": "bargauge", - "name": "Bar gauge", - "version": "" - }, - { - "type": "panel", - "id": "gauge", - "name": "Gauge", - "version": "" - }, - { - "type": "grafana", - "id": "grafana", - "name": "Grafana", - "version": "11.1.0" - }, - { - "type": "datasource", - "id": "grafana-postgresql-datasource", - "name": "PostgreSQL", - "version": "1.0.0" - }, - { - "type": "panel", - "id": "piechart", - "name": "Pie chart", - "version": "" - }, - { - "type": "panel", - "id": "stat", - "name": "Stat", - "version": "" - }, - { - "type": "panel", - "id": "xychart", - "name": "XY Chart", - "version": "" - } - ], - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": { - "type": "grafana", - "uid": "-- Grafana --" - }, - "definition": "TeslaMate|U2FsdGVkX1/cEWK+8cz7pjEKXtzJnDN7b21ZDXt1MGneFGPWTLqOPtxKmu02mJPLzi/f29I+NBHd3vi0FB8R4Xn0+GtobWDgk6VAVSBTdSNniOKO8i2WPlhRaOsl2+hG7gnZ7wrf1Th2nxR7f1uYCrbwOek0IzkfLzrkjh7gkr6inT6bbDuJqrmogZajLxmAMrQ6V+/vHxBRGiwjJhgiEeq3hM1q2h04JKkNiZ8RHbsF5Cd/xd8Q9u0JVrZzIrtnhM/SFlaApU7RtRMu8CSj1llTX7WEOj6VDZAMSf+XUAanWdk725kEPN9MNu89o2zEq5P3E3cju8IbbBdPzXLV3oVuzD6/tMnxFToIIV1E/BrpF7s2RtNa8+KJJ1PF8xgs6m+/KTD2hy+WsP0636AgObRAmYg7+qotGrgNvpNPdE0EgrB7WHYlV7R/1q66bcq6tCe51X1Un70k+zo+K6AK0o4B1H6IyMlEVuRH/Fz8QVl9aYu2ztd08RbuKJlYVKpkH+pxVETAO9MclYQ90tzE6TfwDZrQZzsAlMenr4s1ZB1OlFXjLjVjnddnUilzO76cqv4yI2THQEuyQ47nuVQ4gUbx02K59vMQhns3C01JOAYokOaSXe66Y7QYdMlk09Lf|aes-256-cbc", - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "target": { - "limit": 100, - "matchAny": false, - "tags": [], - "type": "dashboard" - }, - "type": "dashboard" - } - ] - }, - "editable": true, - "fiscalYearStartMonth": 0, - "graphTooltip": 1, - "id": null, - "links": [ - { - "icon": "dashboard", - "tags": [], - "title": "TeslaMate", - "tooltip": "", - "type": "link", - "url": "[[base_url:raw]]" - }, - { - "asDropdown": true, - "icon": "external link", - "tags": [ - "tesla" - ], - "title": "Dashboards", - "type": "dashboards" - } - ], - "liveNow": false, - "panels": [ - { - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" - }, - "description": "**Usable (now)** is the estimated current battery capacity. It is average of the estimated capacity reported by the last 10 charging sessions to have a better estimation.\n\nIf you see just '1.0 kWh' here, it means that you need at least a long charge session.\n\n**Usable (new)** is the estimated Battery Capacity since you begun to use Teslamate. That's why, the more data you have logged from your brand new car the better. For those who have not used Teslamate since they got their new car, or for those who have bought it second hand, it's possible to set the max range to 100% and the battery capacity of the car battery when it was new in order to get a better and accurate estimation.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "decimals": 1, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "super-light-blue", - "value": null - }, - { - "color": "dark-red", - "value": 1 - }, - { - "color": "super-light-blue", - "value": 2 - } - ] - }, - "unit": "kwatth" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 6, - "x": 0, - "y": 0 - }, - "id": 13, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "horizontal", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "value_and_name", - "wideLayout": true - }, - "pluginVersion": "11.1.0", - "targets": [ - { - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" - }, - "format": "table", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "SELECT \n CASE WHEN $custom_kwh_new > 0 THEN $custom_kwh_new ELSE ('$aux'::json -> 'MaxCapacity')::text::float END as \"Usable (new)\", \n ('$aux'::json -> 'CurrentCapacity')::text::float as \"Usable (now)\",\n ('$aux'::json -> 'CurrentCapacity')::text::float - CASE WHEN $custom_kwh_new > 0 THEN $custom_kwh_new ELSE ('$aux'::json -> 'MaxCapacity')::text::float END as \"Difference\"\n \n \n \n \n \n \n ", - "refId": "A", - "select": [ - [ - { - "params": [ - "start_km" - ], - "type": "column" - } - ] - ], - "table": "drives", - "timeColumn": "start_date", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "Battery Capacity", - "type": "stat" - }, - { - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" - }, - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "decimals": 1, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "super-light-blue", - "value": null - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byRegexp", - "options": "/.*_km/" - }, - "properties": [ - { - "id": "unit", - "value": "lengthkm" - } - ] - }, - { - "matcher": { - "id": "byRegexp", - "options": "/.*_mi/" - }, - "properties": [ - { - "id": "unit", - "value": "lengthmi" - } - ] - }, - { - "matcher": { - "id": "byRegexp", - "options": "/maxrange_.*/" - }, - "properties": [ - { - "id": "displayName", - "value": "Max range (new)" - } - ] - }, - { - "matcher": { - "id": "byRegexp", - "options": "/currentrange_.*/" - }, - "properties": [ - { - "id": "displayName", - "value": "Max range (now)" - } - ] - }, - { - "matcher": { - "id": "byRegexp", - "options": "/range_lost.*/" - }, - "properties": [ - { - "id": "displayName", - "value": "Range lost" - } - ] - } - ] - }, - "gridPos": { - "h": 6, - "w": 6, - "x": 6, - "y": 0 - }, - "id": 14, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "horizontal", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "value_and_name", - "wideLayout": true - }, - "pluginVersion": "11.1.0", - "targets": [ - { - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" - }, - "format": "table", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "SELECT \n CASE WHEN $custom_max_range > 0 THEN $custom_max_range ELSE ('$aux'::json -> 'MaxRange')::text::float END as \"maxrange_$length_unit\",\n ('$aux'::json -> 'CurrentRange')::text::float as \"currentrange_$length_unit\",\n CASE WHEN $custom_max_range > 0 THEN $custom_max_range ELSE ('$aux'::json -> 'MaxRange')::text::float END - ('$aux'::json -> 'CurrentRange')::text::float as \"range_lost_$length_unit\"", - "refId": "A", - "select": [ - [ - { - "params": [ - "start_km" - ], - "type": "column" - } - ] - ], - "table": "drives", - "timeColumn": "start_date", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "Ranges", - "type": "stat" - }, - { - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" - }, - "description": "\"Logged\" is the distance traveled that is saved on Teslamate database.\n\n\"Mileage\" is the distance the car has traveled since using Teslamate.\n\nSo, if there is a difference between both values, it is the distance that for some reason a drive hasn't been fully recorded, for example due to a bug or an unexpected restart and that Teslamate has not been able to record, either due to lack of connection, areas without signal, or that it has been out of service.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "super-light-blue", - "value": null - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 6, - "x": 12, - "y": 0 - }, - "id": 37, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "horizontal", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "/.*/", - "values": false - }, - "showPercentChange": false, - "textMode": "value_and_name", - "wideLayout": true - }, - "pluginVersion": "11.1.0", - "targets": [ - { - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" - }, - "editorMode": "code", - "format": "table", - "hide": false, - "rawQuery": true, - "rawSql": "select ROUND(convert_km(sum(distance)::numeric, '$length_unit'),0)|| ' $length_unit' as \"Logged\"\r\nfrom drives \r\nwhere car_id = $car_id;\r\n", - "refId": "Looged", - "sql": { - "columns": [ - { - "parameters": [], - "type": "function" - } - ], - "groupBy": [ - { - "property": { - "type": "string" - }, - "type": "groupBy" - } - ], - "limit": 50 - } - }, - { - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" - }, - "editorMode": "code", - "format": "table", - "group": [], - "hide": false, - "metricColumn": "none", - "rawQuery": true, - "rawSql": "SELECT ROUND(convert_km((max(end_km) - min(start_km))::numeric, '$length_unit'),0)|| ' $length_unit' as \"Mileage\"\nFROM drives WHERE car_id = $car_id;", - "refId": "Mileage", - "select": [ - [ - { - "params": [ - "efficiency" - ], - "type": "column" - } - ] - ], - "sql": { - "columns": [ - { - "parameters": [], - "type": "function" - } - ], - "groupBy": [ - { - "property": { - "type": "string" - }, - "type": "groupBy" - } - ], - "limit": 50 - }, - "table": "cars", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - }, - { - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" - }, - "editorMode": "code", - "format": "table", - "group": [], - "hide": false, - "metricColumn": "none", - "rawQuery": true, - "rawSql": "SELECT ROUND(convert_km(max(end_km)::numeric, '$length_unit'),0) || ' $length_unit' as \"Odometer\"\nFROM drives WHERE car_id = $car_id;", - "refId": "Odometer", - "select": [ - [ - { - "params": [ - "efficiency" - ], - "type": "column" - } - ] - ], - "sql": { - "columns": [ - { - "parameters": [], - "type": "function" - } - ], - "groupBy": [ - { - "property": { - "type": "string" - }, - "type": "groupBy" - } - ], - "limit": 50 - }, - "table": "cars", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - }, - { - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" - }, - "editorMode": "code", - "format": "table", - "hide": false, - "rawQuery": true, - "rawSql": "SELECT (\r\n (SELECT ROUND(convert_km((max(end_km) - min(start_km))::numeric, '$length_unit'),0) FROM drives WHERE car_id = $car_id) - \r\n (SELECT ROUND(convert_km(sum(distance)::numeric, '$length_unit'),0) from drives where car_id = $car_id) || ' $length_unit'\r\n)\r\nAS \"Data lost (not logged)\"", - "refId": "Data Lost", - "sql": { - "columns": [ - { - "parameters": [], - "type": "function" - } - ], - "groupBy": [ - { - "property": { - "type": "string" - }, - "type": "groupBy" - } - ], - "limit": 50 - } - } - ], - "title": "Trips", - "type": "stat" - }, - { - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - } - }, - "decimals": 2, - "mappings": [], - "unit": "kwatth" - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "AC" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "semi-dark-green", - "mode": "fixed" - } - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "DC" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "light-orange", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 13, - "w": 6, - "x": 18, - "y": 0 - }, - "id": 34, - "maxDataPoints": 3, - "options": { - "displayLabels": [ - "name", - "percent", - "value" - ], - "legend": { - "displayMode": "list", - "placement": "right", - "showLegend": false, - "values": [ - "value" - ] - }, - "pieType": "pie", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "tooltip": { - "maxHeight": 600, - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "8.5.6", - "targets": [ - { - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" - }, - "editorMode": "code", - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "WITH data AS (\n SELECT\n\t\tcp.id,\n\t\tcp.charge_energy_added,\n\t\tCASE WHEN NULLIF(mode() within group (order by charger_phases),0) is null THEN 'DC'\n\t\t\t\t ELSE 'AC'\n\t\tEND AS current,\n\t\tcp.charge_energy_used\n\tFROM charging_processes cp\n RIGHT JOIN charges ON cp.id = charges.charging_process_id\n WHERE\n\t cp.car_id = $car_id\n\t AND cp.charge_energy_added > 0.01\n GROUP BY 1,2\n)\nSELECT\n\tnow() AS time,\n\tSUM(GREATEST(charge_energy_added, charge_energy_used)) AS value,\n\tcurrent AS metric\nFROM data\nGROUP BY 3\nORDER BY metric DESC;", - "refId": "A", - "select": [ - [ - { - "params": [ - "latitude" - ], - "type": "column" - } - ] - ], - "sql": { - "columns": [ - { - "parameters": [], - "type": "function" - } - ], - "groupBy": [ - { - "property": { - "type": "string" - }, - "type": "groupBy" - } - ], - "limit": 50 - }, - "table": "addresses", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "Summary AC/DC Energy Used", - "type": "piechart" - }, - { - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" - }, - "description": "This dashboard is meant to have a look of the Battery health based on the data logged in Teslamate. So, the more data you have logged from your brand new car the better.\n\n**Degradation** is just an estimated value to have a reference, measured on **usable battery level** of every charging session with enough kWh added (in order to avoid dirty data from the sample), calculated according to the rated efficiency of the car.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "decimals": 1, - "mappings": [], - "max": 100, - "min": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "#EAB839", - "value": 10 - }, - { - "color": "red", - "value": 20 - }, - { - "color": "dark-red", - "value": 30 - } - ] - }, - "unit": "%" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 0, - "y": 6 - }, - "id": 17, - "options": { - "minVizHeight": 75, - "minVizWidth": 75, - "orientation": "auto", - "reduceOptions": { - "calcs": [], - "fields": "/^greatest$/", - "values": false - }, - "showThresholdLabels": false, - "showThresholdMarkers": true, - "sizing": "auto" - }, - "pluginVersion": "11.1.0", - "targets": [ - { - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" - }, - "format": "table", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "SELECT GREATEST(0, 100.0 - (('$aux'::json -> 'CurrentCapacity')::text::float * 100.0 / CASE WHEN $custom_kwh_new > 0 THEN $custom_kwh_new ELSE ('$aux'::json -> 'MaxCapacity')::text::float END))\n\n", - "refId": "A", - "select": [ - [ - { - "params": [ - "start_km" - ], - "type": "column" - } - ] - ], - "table": "drives", - "timeColumn": "start_date", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "Estimated Degradation", - "type": "gauge" - }, - { - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" - }, - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "decimals": 1, - "mappings": [], - "max": 100, - "min": 1, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "light-red", - "value": null - }, - { - "color": "#EAB839", - "value": 80 - }, - { - "color": "light-green", - "value": 90 - } - ] - }, - "unit": "%" - }, - "overrides": [] - }, - "gridPos": { - "h": 3, - "w": 6, - "x": 6, - "y": 6 - }, - "id": 12, - "options": { - "displayMode": "gradient", - "maxVizHeight": 300, - "minVizHeight": 10, - "minVizWidth": 0, - "namePlacement": "auto", - "orientation": "horizontal", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showUnfilled": true, - "sizing": "auto", - "valueMode": "color" - }, - "pluginVersion": "11.1.0", - "targets": [ - { - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" - }, - "format": "table", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "SELECT \n LEAST(100, (100 - GREATEST(0, 100.0 - (('$aux'::json -> 'CurrentCapacity')::text::float * 100.0 / CASE WHEN $custom_kwh_new > 0 THEN $custom_kwh_new ELSE ('$aux'::json -> 'MaxCapacity')::text::float END)))) as \"Battery Health (%)\"\n \n", - "refId": "A", - "select": [ - [ - { - "params": [ - "start_km" - ], - "type": "column" - } - ] - ], - "table": "drives", - "timeColumn": "start_date", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "Battery Health", - "type": "bargauge" - }, - { - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" - }, - "description": "\"Charging cycles\" are estimations based on the whole energy added to the battery.\n\n\"Charge Efficiency\" is estimated on the difference between energy used from the charger and energy added to the battery.", - "fieldConfig": { - "defaults": { - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "light-yellow", - "value": null - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Total energy added" - }, - "properties": [ - { - "id": "unit", - "value": "kwatth" - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "Total energy used" - }, - "properties": [ - { - "id": "unit", - "value": "kwatth" - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "Charge Efficiency" - }, - "properties": [ - { - "id": "unit", - "value": "percent" - } - ] - } - ] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 12, - "y": 6 - }, - "id": 36, - "maxDataPoints": 100, - "options": { - "colorMode": "value", - "fieldOptions": { - "calcs": [ - "mean" - ] - }, - "graphMode": "none", - "justifyMode": "auto", - "orientation": "horizontal", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "mean" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "value_and_name", - "wideLayout": true - }, - "pluginVersion": "11.1.0", - "targets": [ - { - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" - }, - "editorMode": "code", - "format": "table", - "hide": false, - "rawQuery": true, - "rawSql": "SELECT\r\n\tCOUNT(*) AS \"Total charges\"\r\nFROM\r\n\tcharging_processes\r\nWHERE\r\n\tcar_id = $car_id AND charge_energy_added > 0.01\r\n\t", - "refId": "Total charges", - "sql": { - "columns": [ - { - "parameters": [], - "type": "function" - } - ], - "groupBy": [ - { - "property": { - "type": "string" - }, - "type": "groupBy" - } - ], - "limit": 50 - } - }, - { - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" - }, - "editorMode": "code", - "format": "table", - "group": [], - "hide": false, - "metricColumn": "none", - "rawQuery": true, - "rawSql": "SELECT\n\tfloor(sum(charge_energy_added) / CASE WHEN $custom_kwh_new > 0 THEN $custom_kwh_new ELSE ('$aux'::json -> 'MaxCapacity')::text::float END) AS \"Charging cycles\"\nFROM charging_processes WHERE car_id = $car_id AND charge_energy_added > 0.01", - "refId": "Charging cycles", - "select": [ - [ - { - "params": [ - "start_km" - ], - "type": "column" - } - ] - ], - "sql": { - "columns": [ - { - "parameters": [], - "type": "function" - } - ], - "groupBy": [ - { - "property": { - "type": "string" - }, - "type": "groupBy" - } - ], - "limit": 50 - }, - "table": "drives", - "timeColumn": "start_date", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - }, - { - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" - }, - "editorMode": "code", - "format": "table", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "SELECT\n\tsum(charge_energy_added) as \"Total energy added\"\nFROM\n\tcharging_processes\nWHERE\n\tcar_id = $car_id AND charge_energy_added > 0.01", - "refId": "Total energy added", - "select": [ - [ - { - "params": [ - "efficiency" - ], - "type": "column" - } - ] - ], - "sql": { - "columns": [ - { - "parameters": [], - "type": "function" - } - ], - "groupBy": [ - { - "property": { - "type": "string" - }, - "type": "groupBy" - } - ], - "limit": 50 - }, - "table": "cars", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - }, - { - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" - }, - "editorMode": "code", - "format": "table", - "hide": false, - "rawQuery": true, - "rawSql": "SELECT\r\n\tSUM(charge_energy_used) AS \"Total energy used\"\r\nFROM\r\n\tcharging_processes\r\nWHERE\r\n\tcar_id = $car_id AND charge_energy_added > 0.01\r\n", - "refId": "Total energy used", - "sql": { - "columns": [ - { - "parameters": [], - "type": "function" - } - ], - "groupBy": [ - { - "property": { - "type": "string" - }, - "type": "groupBy" - } - ], - "limit": 50 - } - }, - { - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" - }, - "editorMode": "code", - "format": "table", - "hide": false, - "rawQuery": true, - "rawSql": "SELECT\r\n\tSUM(charge_energy_added) * 100 / SUM(charge_energy_used) AS \"Charge Efficiency\"\r\nFROM\r\n\tcharging_processes\r\nWHERE\r\n\tcar_id = $car_id AND charge_energy_added > 0.01\r\n", - "refId": "Charge Efficiency", - "sql": { - "columns": [ - { - "parameters": [], - "type": "function" - } - ], - "groupBy": [ - { - "property": { - "type": "string" - }, - "type": "groupBy" - } - ], - "limit": 50 - } - } - ], - "title": "Battery Stats", - "type": "stat" - }, - { - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "decimals": 1, - "mappings": [], - "max": 100, - "min": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "transparent", - "value": null - } - ] - }, - "unit": "%" - }, - "overrides": [] - }, - "gridPos": { - "h": 2, - "w": 4, - "x": 6, - "y": 9 - }, - "id": 25, - "options": { - "displayMode": "lcd", - "maxVizHeight": 300, - "minVizHeight": 10, - "minVizWidth": 0, - "namePlacement": "auto", - "orientation": "horizontal", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showUnfilled": true, - "sizing": "auto", - "text": {}, - "valueMode": "color" - }, - "pluginVersion": "11.1.0", - "targets": [ - { - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" - }, - "format": "table", - "group": [], - "groupBy": [ - { - "params": [ - "$__interval" - ], - "type": "time" - }, - { - "params": [ - "null" - ], - "type": "fill" - } - ], - "measurement": "%", - "metricColumn": "none", - "orderByTime": "ASC", - "policy": "default", - "rawQuery": true, - "rawSql": "SELECT * FROM ((SELECT usable_battery_level, date\r\nFROM positions\r\nWHERE car_id = $car_id AND usable_battery_level IS NOT NULL\r\nORDER BY date DESC\r\nLIMIT 1)\r\nUNION\r\n(SELECT usable_battery_level, date\r\nFROM charges c\r\nJOIN charging_processes p ON p.id = c.charging_process_id\r\nWHERE p.car_id = $car_id AND usable_battery_level IS NOT NULL\r\nORDER BY date DESC\r\nLIMIT 1)) AS last_usable_battery_level LIMIT 1", - "refId": "SOC", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "start_km" - ], - "type": "column" - } - ] - ], - "table": "drives", - "timeColumn": "start_date", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - }, - { - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" - }, - "editorMode": "code", - "format": "table", - "hide": false, - "rawQuery": true, - "rawSql": "SELECT\r\n 0 as lowest,\r\n 20 as lower,\r\n CASE WHEN lfp_battery THEN 100 ELSE 81 END as upper\r\nfrom cars inner join car_settings on cars.settings_id = car_settings.id\r\nwhere cars.id = $car_id", - "refId": "A", - "sql": { - "columns": [ - { - "parameters": [], - "type": "function" - } - ], - "groupBy": [ - { - "property": { - "type": "string" - }, - "type": "groupBy" - } - ], - "limit": 50 - } - } - ], - "title": "Current SOC", - "transformations": [ - { - "id": "configFromData", - "options": { - "applyTo": { - "id": "byFrameRefID", - "options": "SOC" - }, - "configRefId": "A", - "mappings": [ - { - "fieldName": "lower", - "handlerArguments": { - "threshold": { - "color": "green" - } - }, - "handlerKey": "threshold1" - }, - { - "fieldName": "upper", - "handlerArguments": { - "threshold": { - "color": "orange" - } - }, - "handlerKey": "threshold1" - }, - { - "fieldName": "lowest", - "handlerKey": "threshold1" - } - ] - } - } - ], - "type": "bargauge" - }, - { - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" - }, - "description": "This is the Derived Rated Efficiency that TeslaMate calculates based on battery charges. \nThis information can be seen in more detail on the \"Efficiency\" dashboard.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "decimals": 0, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "super-light-blue", - "value": null - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byRegexp", - "options": "/.*_km/" - }, - "properties": [ - { - "id": "unit", - "value": "Wh/km" - } - ] - }, - { - "matcher": { - "id": "byRegexp", - "options": "/.*_mi/" - }, - "properties": [ - { - "id": "unit", - "value": "Wh/mi" - } - ] - } - ] - }, - "gridPos": { - "h": 4, - "w": 2, - "x": 10, - "y": 9 - }, - "id": 32, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "horizontal", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "/.*/", - "values": false - }, - "showPercentChange": false, - "textMode": "value", - "wideLayout": true - }, - "pluginVersion": "11.1.0", - "targets": [ - { - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" - }, - "editorMode": "code", - "format": "table", - "hide": false, - "rawQuery": true, - "rawSql": "SELECT ('$aux'::json -> 'RatedEfficiency')::text::float * \r\n CASE \r\n WHEN '$length_unit' = 'km' THEN 10\r\n WHEN '$length_unit' = 'mi' THEN 16.0934 \r\n END AS efficiency_$length_unit", - "refId": "Looged", - "sql": { - "columns": [ - { - "parameters": [], - "type": "function" - } - ], - "groupBy": [ - { - "property": { - "type": "string" - }, - "type": "groupBy" - } - ], - "limit": 50 - } - } - ], - "title": "Efficiency", - "type": "stat" - }, - { - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" - }, - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "decimals": 1, - "mappings": [], - "min": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "dark-red", - "value": null - }, - { - "color": "dark-green", - "value": 7.84 - }, - { - "color": "semi-dark-orange", - "value": 31.36 - }, - { - "color": "light-blue", - "value": 35.28 - } - ] - }, - "unit": "kwatth" - }, - "overrides": [] - }, - "gridPos": { - "h": 2, - "w": 4, - "x": 6, - "y": 11 - }, - "id": 27, - "options": { - "displayMode": "gradient", - "maxVizHeight": 300, - "minVizHeight": 10, - "minVizWidth": 0, - "namePlacement": "auto", - "orientation": "horizontal", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "/^kwh$/", - "values": false - }, - "showUnfilled": true, - "sizing": "auto", - "text": {}, - "valueMode": "color" - }, - "pluginVersion": "11.1.0", - "targets": [ - { - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" - }, - "editorMode": "code", - "format": "table", - "group": [], - "groupBy": [ - { - "params": [ - "$__interval" - ], - "type": "time" - }, - { - "params": [ - "null" - ], - "type": "fill" - } - ], - "measurement": "%", - "metricColumn": "none", - "orderByTime": "ASC", - "policy": "default", - "rawQuery": true, - "rawSql": "SELECT * FROM ((SELECT usable_battery_level * ('$aux'::json -> 'CurrentCapacity')::text::float / 100 as kWh, date, ('$aux'::json -> 'CurrentCapacity')::text::float as Total\nFROM positions\nWHERE car_id = $car_id AND usable_battery_level IS NOT NULL\nORDER BY date DESC\nLIMIT 1)\nUNION\n(SELECT battery_level * ('$aux'::json -> 'CurrentCapacity')::text::float / 100 as kWh, date, ('$aux'::json -> 'CurrentCapacity')::text::float as Total\nFROM charges c\nJOIN charging_processes p ON p.id = c.charging_process_id\nWHERE p.car_id = $car_id AND usable_battery_level IS NOT NULL\nORDER BY date DESC\nLIMIT 1)) AS last_usable_battery_level LIMIT 1", - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "start_km" - ], - "type": "column" - } - ] - ], - "sql": { - "columns": [ - { - "parameters": [], - "type": "function" - } - ], - "groupBy": [ - { - "property": { - "type": "string" - }, - "type": "groupBy" - } - ], - "limit": 50 - }, - "table": "drives", - "timeColumn": "start_date", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "Current Stored Energy", - "type": "bargauge" - }, - { - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "continuous-RdYlGr" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "pointSize": { - "fixed": 6 - }, - "scaleDistribution": { - "type": "linear" - }, - "show": "points" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byFrameRefID", - "options": "Median" - }, - "properties": [ - { - "id": "custom.lineWidth", - "value": 2 - }, - { - "id": "custom.show", - "value": "lines" - } - ] - } - ] - }, - "gridPos": { - "h": 8, - "w": 24, - "x": 0, - "y": 13 - }, - "id": 28, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": false - }, - "series": [ - { - "name": "Odometer", - "pointColor": { - "field": "kWh" - }, - "pointSize": { - "fixed": 5, - "max": 100, - "min": 1 - }, - "x": "odometer", - "y": "kWh" - }, - { - "name": "Median Odometer", - "pointColor": { - "fixed": "dark-red" - }, - "x": "M-Odometer", - "y": "M-kWh" - } - ], - "seriesMapping": "manual", - "tooltip": { - "maxHeight": 600, - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "8.5.6", - "targets": [ - { - "alias": "", - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" - }, - "editorMode": "code", - "format": "table", - "group": [], - "hide": false, - "metricColumn": "none", - "rawQuery": true, - "rawSql": "SELECT convert_km(AVG(p.odometer)::numeric,'$length_unit') AS odometer, \r\n\tAVG(c.rated_battery_range_km * ('$aux'::json -> 'RatedEfficiency')::text::float / c.usable_battery_level) AS \"kWh\",\r\n\tMAX(cp.id) AS id,\r\n\tto_char(cp.end_date, 'YYYY-MM-dd') AS Title\r\n\tFROM charging_processes cp\r\n\t\tJOIN (SELECT charging_process_id, MAX(date) as date\tFROM charges WHERE usable_battery_level > 0 GROUP BY charging_process_id) AS last_charges\tON cp.id = last_charges.charging_process_id\r\n\t\tINNER JOIN charges c\r\n\t\tON c.charging_process_id = cp.id AND c.date = last_charges.date\r\n\t\tINNER JOIN positions p ON p.id = cp.position_id\r\n\tWHERE cp.car_id = $car_id\r\n\t\tAND cp.end_date IS NOT NULL\r\n\t\tAND cp.charge_energy_added >= ('$aux'::json -> 'RatedEfficiency')::text::float\r\n\tGROUP BY 4", - "refId": "Projected Range", - "select": [ - [ - { - "params": [ - "value" - ], - "type": "column" - } - ] - ], - "sql": { - "columns": [ - { - "parameters": [], - "type": "function" - } - ], - "groupBy": [ - { - "property": { - "type": "string" - }, - "type": "groupBy" - } - ], - "limit": 50 - }, - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - }, - { - "alias": "", - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" - }, - "editorMode": "code", - "format": "table", - "group": [], - "hide": false, - "metricColumn": "none", - "rawQuery": true, - "rawSql": "SELECT \n ROUND(MIN(convert_km(p.odometer::numeric,'$length_unit')),0) AS \"M-Odometer\",\n\tROUND(PERCENTILE_CONT(0.5) WITHIN GROUP(ORDER BY c.rated_battery_range_km * ('$aux'::json -> 'RatedEfficiency')::text::float / c.usable_battery_level)::numeric,1) AS \"M-kWh\",\n\tto_char(cp.end_date, 'YYYYMM') || CASE WHEN to_char(cp.end_date, 'DD')::int <= 15 THEN '1' ELSE '2' END AS Title\n\tFROM charging_processes cp\n\t\tJOIN (SELECT charging_process_id, MAX(date) as date\tFROM charges WHERE usable_battery_level > 0 GROUP BY charging_process_id) AS last_charges\tON cp.id = last_charges.charging_process_id\n\t\tINNER JOIN charges c\n\t\tON c.charging_process_id = cp.id AND c.date = last_charges.date\n\t\tINNER JOIN positions p ON p.id = cp.position_id\n\tWHERE cp.car_id = $car_id\n\t\tAND cp.end_date IS NOT NULL\n\t\tAND cp.charge_energy_added >= ('$aux'::json -> 'RatedEfficiency')::text::float\n\tGROUP BY 3", - "refId": "Median", - "select": [ - [ - { - "params": [ - "start_km" - ], - "type": "column" - } - ] - ], - "sql": { - "columns": [ - { - "parameters": [], - "type": "function" - } - ], - "groupBy": [ - { - "property": { - "type": "string" - }, - "type": "groupBy" - } - ], - "limit": 50 - }, - "table": "drives", - "timeColumn": "start_date", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "Battery Capacity by Mileage", - "type": "xychart" - } - ], - "refresh": "", - "schemaVersion": 39, - "tags": [ - "tesla" - ], - "templating": { - "list": [ - { - "current": {}, - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" - }, - "definition": "SELECT name AS __text, id AS __value FROM cars ORDER BY display_priority ASC, name ASC", - "hide": 0, - "includeAll": false, - "label": "Car", - "multi": false, - "name": "car_id", - "options": [], - "query": "SELECT name AS __text, id AS __value FROM cars ORDER BY display_priority ASC, name ASC", - "refresh": 1, - "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false - }, - { - "current": {}, - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" - }, - "definition": "SELECT unit_of_length FROM settings LIMIT 1", - "hide": 2, - "includeAll": false, - "label": "", - "multi": false, - "name": "length_unit", - "options": [], - "query": "SELECT unit_of_length FROM settings LIMIT 1", - "refresh": 1, - "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false - }, - { - "current": {}, - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" - }, - "definition": "SELECT preferred_range FROM settings LIMIT 1", - "hide": 2, - "includeAll": false, - "multi": false, - "name": "preferred_range", - "options": [], - "query": "SELECT preferred_range FROM settings LIMIT 1", - "refresh": 1, - "regex": "", - "skipUrlSync": false, - "sort": 0, - "type": "query" - }, - { - "current": {}, - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" - }, - "definition": "SELECT base_url FROM settings LIMIT 1", - "hide": 2, - "includeAll": false, - "label": "", - "multi": false, - "name": "base_url", - "options": [], - "query": "SELECT base_url FROM settings LIMIT 1", - "refresh": 1, - "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false - }, - { - "current": {}, - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" - }, - "definition": "-- CONCATENATED JOIN QUERIES TO IMPROVE PERFORMANCE", - "hide": 2, - "includeAll": false, - "multi": false, - "name": "aux", - "options": [], - "query": "WITH Aux AS\n(\n\t\tSELECT \n car_id,\n\t\tCOALESCE(efficiency, \n\t\t(SELECT efficiency\n\t\t\tFROM cars WHERE id = $car_id) * 100) AS efficiency\n\tFROM (\n\t\tSELECT ROUND((charge_energy_added / NULLIF(end_rated_range_km - start_rated_range_km, 0))::numeric, 3) * 100 as efficiency,\n\t\t\tCOUNT(*) as count, $car_id AS car_id \n\t\tFROM charging_processes\n\t\tWHERE car_id = $car_id\n\t\t\tAND duration_min > 10\n\t\t\tAND end_battery_level <= 95\n\t\t\tAND start_rated_range_km IS NOT NULL\n\t\t\tAND end_rated_range_km IS NOT NULL\n\t\t\tAND charge_energy_added > 0\n\t\tGROUP BY 1\n\t\tORDER BY 2 DESC\n\t\tLIMIT 1\n\t) AS DerivatedEfficiency\n),\nCurrentCapacity\t AS\n(\n\tSELECT AVG(Capacity) AS Capacity\nFROM (\nSELECT \n\tc.[[preferred_range]]_battery_range_km * aux.efficiency / c.usable_battery_level AS Capacity\n\tFROM charging_processes cp\n\t\tINNER JOIN charges c\n\t\tON c.charging_process_id = cp.id \n INNER JOIN aux ON cp.car_id = aux.car_id\n\tWHERE cp.car_id = $car_id\n\t\tAND cp.end_date IS NOT NULL\n\t\tAND cp.charge_energy_added >= aux.efficiency\n\t\tAND c.usable_battery_level > 0\n\t ORDER BY cp.end_date DESC LIMIT 10) AS lastCharges\n), \nMaxCapacity AS\n(\n\tSELECT \n\t\tMAX(c.[[preferred_range]]_battery_range_km * aux.efficiency / c.usable_battery_level) AS Capacity\n\tFROM charging_processes cp\n\t\tINNER JOIN (\n\t\t\tSELECT charging_process_id, MAX(date) as date FROM charges WHERE usable_battery_level > 0 GROUP BY charging_process_id) AS gcharges\t\n\t\t\tON cp.id = gcharges.charging_process_id\n\t\tINNER JOIN charges c\n\t\tON c.charging_process_id = cp.id AND c.date = gcharges.date\n\t\tINNER JOIN aux ON cp.car_id = aux.car_id\n\tWHERE cp.car_id = $car_id\n\t\tAND cp.end_date IS NOT NULL\n\t\tAND cp.charge_energy_added >= aux.efficiency\n), \nCurrentRange AS\n(\n SELECT (range * 100.0 / usable_battery_level) AS range\n\tFROM (\n\t\t(SELECT date, [[preferred_range]]_battery_range_km AS range, usable_battery_level AS usable_battery_level\n\t\t\tFROM positions\tWHERE car_id = $car_id AND [[preferred_range]]_battery_range_km IS NOT NULL AND usable_battery_level > 0 ORDER BY date DESC LIMIT 1)\n\t\tUNION ALL\n\t\t(SELECT date, [[preferred_range]]_battery_range_km AS range, usable_battery_level as usable_battery_level\n\t\t\tFROM charges c JOIN charging_processes p ON p.id = c.charging_process_id\n\t\t\tWHERE p.car_id = $car_id AND usable_battery_level > 0 ORDER BY date DESC LIMIT 1)\n\t) AS data\n\tORDER BY date DESC\n\tLIMIT 1\n), \nMaxRange AS\n(\n SELECT\n\t\tfloor(extract(epoch from date)/86400)*86400 AS time,\n\t CASE WHEN sum(usable_battery_level) = 0 THEN sum([[preferred_range]]_battery_range_km) * 100\n\t\t ELSE sum([[preferred_range]]_battery_range_km) / sum(usable_battery_level) * 100\n\tEND AS range\n FROM (\n\t\tSELECT battery_level, usable_battery_level, date, [[preferred_range]]_battery_range_km from charges c \n\t\tJOIN charging_processes p ON p.id = c.charging_process_id \n\t\tWHERE p.car_id = $car_id AND usable_battery_level IS NOT NULL) AS data\n\tGROUP BY 1\n\tORDER BY 2 DESC\n\tLIMIT 1\n) \nSELECT CONCAT('{\"MaxRange\": ', convert_km(MaxRange.range,'$length_unit'),\n ', \"CurrentRange\": ',convert_km(CurrentRange.range,'$length_unit'),\n ', \"MaxCapacity\": ', MaxCapacity.Capacity,\n ', \"CurrentCapacity\": ', CASE WHEN CurrentCapacity.Capacity IS NULL THEN 1 ELSE CurrentCapacity.Capacity END,\n ', \"RatedEfficiency\": ', aux.efficiency, '}') \nFROM MaxRange, CurrentRange, Aux, MaxCapacity, CurrentCapacity", - "refresh": 1, - "regex": "", - "skipUrlSync": false, - "sort": 0, - "type": "query" - }, - { - "current": { - "selected": false, - "text": "0", - "value": "0" - }, - "description": "Set the capacity of your car battery when it was new, in case you started using Teslamate after a while of having it. If not, leave it at 0, it will be calculated with the data that is logged in Teslamate", - "hide": 0, - "label": "Custom Battery Capacity (kWh) when new", - "name": "custom_kwh_new", - "options": [ - { - "selected": true, - "text": "0", - "value": "0" - } - ], - "query": "0", - "skipUrlSync": false, - "type": "textbox" - }, - { - "current": { - "selected": false, - "text": "0", - "value": "0" - }, - "description": "Set the max range to 100% of your car when it was new, in case you started using Teslamate after a while of having it. If not, leave it at 0, the degradation will be calculated with the data that is logged in Teslamate", - "hide": 0, - "label": "Custom Max Range when new", - "name": "custom_max_range", - "options": [ - { - "selected": true, - "text": "0", - "value": "0" - } - ], - "query": "0", - "skipUrlSync": false, - "type": "textbox" - } - ] - }, - "time": { - "from": "now-6h", - "to": "now" - }, - "timepicker": { - "hidden": true - }, - "timezone": "", - "title": "Battery Health", - "uid": "jchmRiqUfXgTM", - "version": 9, - "weekStart": "" +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 1, + "links": [ + { + "icon": "dashboard", + "tags": [], + "title": "TeslaMate", + "tooltip": "", + "type": "link", + "url": "${base_url:raw}" + }, + { + "asDropdown": true, + "icon": "external link", + "tags": [ + "tesla" + ], + "title": "Dashboards", + "type": "dashboards" + } + ], + "panels": [ + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "description": "**Usable (now)** is the estimated current battery capacity. It is average of the estimated capacity reported by the last 10 charging sessions to have a better estimation.\n\nIf you see just '1.0 kWh' here, it means that you need at least a long charge session.\n\n**Usable (new)** is the estimated Battery Capacity since you begun to use TeslaMate. That's why, the more data you have logged from your brand new car the better. For those who have not used TeslaMate since they got their new car, or for those who have bought it second hand, it's possible to set the max range to 100% and the battery capacity of the car battery when it was new in order to get a better and accurate estimation.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 1, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "super-light-blue" + }, + { + "color": "dark-red", + "value": 1 + }, + { + "color": "super-light-blue", + "value": 2 + } + ] + }, + "unit": "kwatth" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 13, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "value_and_name", + "wideLayout": true + }, + "pluginVersion": "11.6.1", + "targets": [ + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT \n CASE WHEN $custom_kwh_new > 0 THEN $custom_kwh_new ELSE ('$aux'::json ->> 'MaxCapacity')::float END as \"Usable (new)\", \n ('$aux'::json ->> 'CurrentCapacity')::float as \"Usable (now)\",\n ('$aux'::json ->> 'CurrentCapacity')::float - CASE WHEN $custom_kwh_new > 0 THEN $custom_kwh_new ELSE ('$aux'::json ->> 'MaxCapacity')::float END as \"Difference\"", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Battery Capacity", + "type": "stat" + }, + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 1, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "super-light-blue" + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byRegexp", + "options": "/.*_km/" + }, + "properties": [ + { + "id": "unit", + "value": "lengthkm" + } + ] + }, + { + "matcher": { + "id": "byRegexp", + "options": "/.*_mi/" + }, + "properties": [ + { + "id": "unit", + "value": "lengthmi" + } + ] + }, + { + "matcher": { + "id": "byRegexp", + "options": "/maxrange_.*/" + }, + "properties": [ + { + "id": "displayName", + "value": "Max range (new)" + } + ] + }, + { + "matcher": { + "id": "byRegexp", + "options": "/currentrange_.*/" + }, + "properties": [ + { + "id": "displayName", + "value": "Max range (now)" + } + ] + }, + { + "matcher": { + "id": "byRegexp", + "options": "/range_lost.*/" + }, + "properties": [ + { + "id": "displayName", + "value": "Range lost" + } + ] + } + ] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 6, + "y": 0 + }, + "id": 14, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "value_and_name", + "wideLayout": true + }, + "pluginVersion": "11.6.1", + "targets": [ + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT \n CASE WHEN $custom_max_range > 0 THEN $custom_max_range ELSE ('$aux'::json ->> 'MaxRange')::float END as \"maxrange_$length_unit\",\n ('$aux'::json ->> 'CurrentRange')::float as \"currentrange_$length_unit\",\n CASE WHEN $custom_max_range > 0 THEN $custom_max_range ELSE ('$aux'::json ->> 'MaxRange')::float END - ('$aux'::json ->> 'CurrentRange')::float as \"range_lost_$length_unit\"", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Ranges [$preferred_range]", + "type": "stat" + }, + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "description": "\"Logged\" is the distance traveled that is saved on TeslaMate database.\n\n\"Mileage\" is the distance the car has traveled since using TeslaMate.\n\nSo, if there is a difference between both values, it is the distance that for some reason a drive hasn't been fully recorded, for example due to a bug or an unexpected restart and that TeslaMate has not been able to record, either due to lack of connection, areas without signal, or that it has been out of service.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "super-light-blue" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 12, + "y": 0 + }, + "id": 37, + "links": [ + { + "targetBlank": true, + "title": "Drive Stats", + "url": "/d/_7WkNSyWk/drive-stats" + } + ], + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/.*/", + "values": false + }, + "showPercentChange": false, + "textMode": "value_and_name", + "wideLayout": true + }, + "pluginVersion": "11.6.1", + "targets": [ + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "select ROUND(convert_km(sum(distance)::numeric, '$length_unit'),0)|| ' $length_unit' as \"Logged\"\r\nfrom drives \r\nwhere car_id = $car_id;\r\n", + "refId": "Logged", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + }, + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT ROUND(convert_km((max(end_km) - min(start_km))::numeric, '$length_unit'),0)|| ' $length_unit' as \"Mileage\"\nFROM drives WHERE car_id = $car_id;", + "refId": "Mileage", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + }, + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT ROUND(convert_km(max(end_km)::numeric, '$length_unit'),0) || ' $length_unit' as \"Odometer\"\nFROM drives WHERE car_id = $car_id;", + "refId": "Odometer", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + }, + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT (\r\n (SELECT ROUND(convert_km((max(end_km) - min(start_km))::numeric, '$length_unit'),0) FROM drives WHERE car_id = $car_id) - \r\n (SELECT ROUND(convert_km(sum(distance)::numeric, '$length_unit'),0) from drives where car_id = $car_id) || ' $length_unit'\r\n)\r\nAS \"Data lost (not logged)\"", + "refId": "Data Lost", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Drive Stats", + "type": "stat" + }, + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "decimals": 2, + "mappings": [], + "unit": "kwatth" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "AC" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "semi-dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "DC" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-orange", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 13, + "w": 6, + "x": 18, + "y": 0 + }, + "id": 34, + "maxDataPoints": 3, + "options": { + "displayLabels": [ + "name", + "percent", + "value" + ], + "legend": { + "displayMode": "list", + "placement": "right", + "showLegend": false, + "values": [ + "value" + ] + }, + "pieType": "pie", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "tooltip": { + "hideZeros": false, + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.6.1", + "targets": [ + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", + "format": "time_series", + "rawQuery": true, + "rawSql": "WITH data AS (\n SELECT\n\t\tcp.id,\n\t\tcp.charge_energy_added,\n\t\tCASE WHEN NULLIF(mode() within group (order by charger_phases),0) is null THEN 'DC'\n\t\t\t\t ELSE 'AC'\n\t\tEND AS current,\n\t\tcp.charge_energy_used\n\tFROM charging_processes cp\n RIGHT JOIN charges ON cp.id = charges.charging_process_id\n WHERE\n\t cp.car_id = $car_id\n\t AND cp.charge_energy_added > 0.01\n GROUP BY 1,2\n)\nSELECT\n\tnow() AS time,\n\tSUM(GREATEST(charge_energy_added, charge_energy_used)) AS value,\n\tcurrent AS metric\nFROM data\nGROUP BY 3\nORDER BY metric DESC;", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "AC/DC - Energy Used", + "type": "piechart" + }, + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "description": "This dashboard is meant to have a look of the Battery health based on the data logged in TeslaMate. So, the more data you have logged from your brand new car the better.\n\n**Degradation** is just an estimated value to have a reference, measured on **usable battery level** of every charging session with enough kWh added (in order to avoid dirty data from the sample), calculated according to the rated efficiency of the car.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 1, + "mappings": [], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "#EAB839", + "value": 10 + }, + { + "color": "red", + "value": 20 + }, + { + "color": "dark-red", + "value": 30 + } + ] + }, + "unit": "%" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 0, + "y": 6 + }, + "id": 17, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": [], + "fields": "/^greatest$/", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "sizing": "auto" + }, + "pluginVersion": "11.6.1", + "targets": [ + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT GREATEST(0, 100.0 - (('$aux'::json ->> 'CurrentCapacity')::float * 100.0 / CASE WHEN $custom_kwh_new > 0 THEN $custom_kwh_new ELSE ('$aux'::json ->> 'MaxCapacity')::float END))\n\n", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Estimated Degradation", + "type": "gauge" + }, + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 1, + "mappings": [], + "max": 100, + "min": 1, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "light-red" + }, + { + "color": "#EAB839", + "value": 80 + }, + { + "color": "light-green", + "value": 90 + } + ] + }, + "unit": "%" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 6, + "y": 6 + }, + "id": 12, + "options": { + "displayMode": "gradient", + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "maxVizHeight": 300, + "minVizHeight": 10, + "minVizWidth": 0, + "namePlacement": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showUnfilled": true, + "sizing": "auto", + "valueMode": "color" + }, + "pluginVersion": "11.6.1", + "targets": [ + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT \n LEAST(100, (100 - GREATEST(0, 100.0 - (('$aux'::json ->> 'CurrentCapacity')::float * 100.0 / CASE WHEN $custom_kwh_new > 0 THEN $custom_kwh_new ELSE ('$aux'::json ->> 'MaxCapacity')::float END)))) as \"Battery Health (%)\"\n \n", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Battery Health", + "type": "bargauge" + }, + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "description": "\"# of Charging cycles\" is estimated by dividing the whole energy added to the battery by the battery capacity when new.\n\n\"Charging Efficiency\" is estimated on the difference between energy used from the charger and energy added to the battery.", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "light-yellow" + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Total Energy added" + }, + "properties": [ + { + "id": "unit", + "value": "kwatth" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Total Energy used" + }, + "properties": [ + { + "id": "unit", + "value": "kwatth" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Charging Efficiency" + }, + "properties": [ + { + "id": "unit", + "value": "percentunit" + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 12, + "y": 6 + }, + "id": 36, + "links": [ + { + "targetBlank": true, + "title": "Charging Stats", + "url": "/d/-pkIkhmRz/charging-stats" + } + ], + "maxDataPoints": 100, + "options": { + "colorMode": "value", + "fieldOptions": { + "calcs": [ + "mean" + ] + }, + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "value_and_name", + "wideLayout": true + }, + "pluginVersion": "11.6.1", + "targets": [ + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT\r\n\tCOUNT(*) AS \"# of Charges\"\r\nFROM\r\n\tcharging_processes\r\nWHERE\r\n\tcar_id = $car_id AND charge_energy_added > 0.01\r\n\t", + "refId": "# of Charges", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + }, + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT\n\tfloor(sum(charge_energy_added) / CASE WHEN $custom_kwh_new > 0 THEN $custom_kwh_new ELSE ('$aux'::json ->> 'MaxCapacity')::float END) AS \"# of Charging cycles\"\nFROM charging_processes WHERE car_id = $car_id AND charge_energy_added > 0.01", + "refId": "# of Charging cycles", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + }, + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT\n\tsum(charge_energy_added) as \"Total Energy added\"\nFROM\n\tcharging_processes\nWHERE\n\tcar_id = $car_id AND charge_energy_added > 0.01", + "refId": "Total Energy added", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + }, + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT\r\n\tSUM(greatest(charge_energy_added, charge_energy_used)) AS \"Total Energy used\"\r\nFROM\r\n\tcharging_processes\r\nWHERE\r\n\tcar_id = $car_id AND charge_energy_added > 0.01\r\n", + "refId": "Total Energy used", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + }, + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT\r\n\tSUM(charge_energy_added) / SUM(greatest(charge_energy_added, charge_energy_used)) AS \"Charging Efficiency\"\r\nFROM\r\n\tcharging_processes\r\nWHERE\r\n\tcar_id = $car_id AND charge_energy_added > 0.01\r\n", + "refId": "Charging Efficiency", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Charging Stats", + "type": "stat" + }, + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 1, + "mappings": [], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "transparent" + } + ] + }, + "unit": "%" + }, + "overrides": [] + }, + "gridPos": { + "h": 2, + "w": 4, + "x": 6, + "y": 9 + }, + "id": 25, + "options": { + "displayMode": "lcd", + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "maxVizHeight": 300, + "minVizHeight": 10, + "minVizWidth": 0, + "namePlacement": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showUnfilled": true, + "sizing": "auto", + "text": {}, + "valueMode": "color" + }, + "pluginVersion": "11.6.1", + "targets": [ + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT * FROM ((SELECT usable_battery_level, date\r\nFROM positions\r\nWHERE car_id = $car_id AND usable_battery_level IS NOT NULL\r\nORDER BY date DESC\r\nLIMIT 1)\r\nUNION\r\n(SELECT usable_battery_level, date\r\nFROM charges c\r\nJOIN charging_processes p ON p.id = c.charging_process_id\r\nWHERE p.car_id = $car_id AND usable_battery_level IS NOT NULL\r\nORDER BY date DESC\r\nLIMIT 1)) AS last_usable_battery_level LIMIT 1", + "refId": "SOC", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + }, + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT\r\n 0 as lowest,\r\n 20 as lower,\r\n CASE WHEN lfp_battery THEN 100 ELSE 81 END as upper\r\nfrom cars inner join car_settings on cars.settings_id = car_settings.id\r\nwhere cars.id = $car_id", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Current SOC", + "transformations": [ + { + "id": "configFromData", + "options": { + "applyTo": { + "id": "byFrameRefID", + "options": "SOC" + }, + "configRefId": "A", + "mappings": [ + { + "fieldName": "lower", + "handlerArguments": { + "threshold": { + "color": "green" + } + }, + "handlerKey": "threshold1" + }, + { + "fieldName": "upper", + "handlerArguments": { + "threshold": { + "color": "orange" + } + }, + "handlerKey": "threshold1" + }, + { + "fieldName": "lowest", + "handlerKey": "threshold1" + } + ] + } + } + ], + "type": "bargauge" + }, + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "description": "This is the Derived Rated Efficiency that TeslaMate calculates based on battery charges. \nThis information can be seen in more detail on the \"Efficiency\" dashboard.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "super-light-blue" + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byRegexp", + "options": "/.*_km/" + }, + "properties": [ + { + "id": "unit", + "value": "Wh/km" + } + ] + }, + { + "matcher": { + "id": "byRegexp", + "options": "/.*_mi/" + }, + "properties": [ + { + "id": "unit", + "value": "Wh/mi" + } + ] + } + ] + }, + "gridPos": { + "h": 4, + "w": 2, + "x": 10, + "y": 9 + }, + "id": 32, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/.*/", + "values": false + }, + "showPercentChange": false, + "textMode": "value", + "wideLayout": true + }, + "pluginVersion": "11.6.1", + "targets": [ + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT ('$aux'::json ->> 'RatedEfficiency')::float * 10 / convert_km(1, '$length_unit') AS efficiency_$length_unit", + "refId": "Logged", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Efficiency", + "type": "stat" + }, + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 1, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "dark-red" + }, + { + "color": "dark-green", + "value": 7.84 + }, + { + "color": "semi-dark-orange", + "value": 31.36 + }, + { + "color": "light-blue", + "value": 35.28 + } + ] + }, + "unit": "kwatth" + }, + "overrides": [] + }, + "gridPos": { + "h": 2, + "w": 4, + "x": 6, + "y": 11 + }, + "id": 27, + "options": { + "displayMode": "gradient", + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "maxVizHeight": 300, + "minVizHeight": 10, + "minVizWidth": 0, + "namePlacement": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/^kwh$/", + "values": false + }, + "showUnfilled": true, + "sizing": "auto", + "text": {}, + "valueMode": "color" + }, + "pluginVersion": "11.6.1", + "targets": [ + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT * FROM ((SELECT usable_battery_level * ('$aux'::json ->> 'CurrentCapacity')::float / 100 as kWh, date, ('$aux'::json ->> 'CurrentCapacity')::float as Total\nFROM positions\nWHERE car_id = $car_id AND usable_battery_level IS NOT NULL\nORDER BY date DESC\nLIMIT 1)\nUNION\n(SELECT battery_level * ('$aux'::json ->> 'CurrentCapacity')::float / 100 as kWh, date, ('$aux'::json ->> 'CurrentCapacity')::float as Total\nFROM charges c\nJOIN charging_processes p ON p.id = c.charging_process_id\nWHERE p.car_id = $car_id AND usable_battery_level IS NOT NULL\nORDER BY date DESC\nLIMIT 1)) AS last_usable_battery_level LIMIT 1", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Current Stored Energy", + "type": "bargauge" + }, + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-RdYlGr", + "seriesBy": "last" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "fillOpacity": 50, + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 2, + "pointShape": "circle", + "pointSize": { + "fixed": 5 + }, + "pointStrokeWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "show": "points" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byFrameRefID", + "options": "Median" + }, + "properties": [ + { + "id": "custom.show", + "value": "lines" + }, + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Odometer" + }, + "properties": [ + { + "id": "displayName", + "value": "Mileage" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "kWh" + }, + "properties": [ + { + "id": "displayName", + "value": "Battery Capacity" + }, + { + "id": "unit", + "value": "kwatth" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 13 + }, + "id": 28, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "mapping": "manual", + "series": [ + { + "color": { + "matcher": { + "id": "byName", + "options": "kWh" + } + }, + "frame": { + "matcher": { + "id": "byIndex", + "options": 0 + } + }, + "x": { + "matcher": { + "id": "byName", + "options": "Odometer" + } + }, + "y": { + "matcher": { + "id": "byName", + "options": "kWh" + } + } + }, + { + "frame": { + "matcher": { + "id": "byIndex", + "options": 1 + } + }, + "x": { + "matcher": { + "id": "byName", + "options": "Odometer" + } + }, + "y": { + "matcher": { + "id": "byName", + "options": "kWh" + } + } + } + ], + "tooltip": { + "hideZeros": false, + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.6.1", + "targets": [ + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT convert_km(AVG(p.odometer)::numeric,'$length_unit') AS \"Odometer\", \r\n\tAVG(c.rated_battery_range_km * ('$aux'::json ->> 'RatedEfficiency')::float / c.usable_battery_level) AS \"kWh\",\r\n\t--MAX(cp.id) AS id,\r\n\tto_char(timezone('$__timezone', timezone('UTC', cp.end_date)), 'YYYY-MM-dd') AS \"Date\"\r\n\tFROM charging_processes cp\r\n\t\tJOIN (SELECT charging_process_id, MAX(date) as date\tFROM charges WHERE usable_battery_level > 0 GROUP BY charging_process_id) AS last_charges\tON cp.id = last_charges.charging_process_id\r\n\t\tINNER JOIN charges c\r\n\t\tON c.charging_process_id = cp.id AND c.date = last_charges.date\r\n\t\tINNER JOIN positions p ON p.id = cp.position_id\r\n\tWHERE cp.car_id = $car_id\r\n\t\tAND cp.end_date IS NOT NULL\r\n\t\tAND cp.charge_energy_added >= ('$aux'::json ->> 'RatedEfficiency')::float\r\n\tGROUP BY 3", + "refId": "Projected Range", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + }, + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT \n ROUND(MIN(convert_km(p.odometer::numeric,'$length_unit')),0) AS \"Odometer\",\n\tROUND(PERCENTILE_CONT(0.5) WITHIN GROUP(ORDER BY c.rated_battery_range_km * ('$aux'::json ->> 'RatedEfficiency')::float / c.usable_battery_level)::numeric,1) AS \"kWh\",\n\tto_char(timezone('$__timezone', timezone('UTC', cp.end_date)), 'YYYYMM') || CASE WHEN to_char(timezone('$__timezone', timezone('UTC', cp.end_date)), 'DD')::int <= 15 THEN '1' ELSE '2' END AS Title\n\tFROM charging_processes cp\n\t\tJOIN (SELECT charging_process_id, MAX(date) as date\tFROM charges WHERE usable_battery_level > 0 GROUP BY charging_process_id) AS last_charges\tON cp.id = last_charges.charging_process_id\n\t\tINNER JOIN charges c\n\t\tON c.charging_process_id = cp.id AND c.date = last_charges.date\n\t\tINNER JOIN positions p ON p.id = cp.position_id\n\tWHERE cp.car_id = $car_id\n\t\tAND cp.end_date IS NOT NULL\n\t\tAND cp.charge_energy_added >= ('$aux'::json ->> 'RatedEfficiency')::float\n\tGROUP BY 3", + "refId": "Median", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Battery Capacity by Mileage", + "type": "xychart" + } + ], + "preload": false, + "refresh": "", + "schemaVersion": 41, + "tags": [ + "tesla" + ], + "templating": { + "list": [ + { + "current": {}, + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "definition": "SELECT\n id as __value,\n CASE WHEN COUNT(id) OVER (PARTITION BY name) > 1 AND name IS NOT NULL THEN CONCAT(name, ' - ', RIGHT(vin, 6)) ELSE COALESCE(name, CONCAT('VIN ', vin)) end as __text \nFROM cars\nORDER BY display_priority ASC, name ASC, vin ASC", + "includeAll": false, + "label": "Car", + "name": "car_id", + "options": [], + "query": "SELECT\n id as __value,\n CASE WHEN COUNT(id) OVER (PARTITION BY name) > 1 AND name IS NOT NULL THEN CONCAT(name, ' - ', RIGHT(vin, 6)) ELSE COALESCE(name, CONCAT('VIN ', vin)) end as __text \nFROM cars\nORDER BY display_priority ASC, name ASC, vin ASC", + "refresh": 1, + "regex": "", + "type": "query" + }, + { + "current": {}, + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "definition": "SELECT unit_of_length FROM settings LIMIT 1", + "hide": 2, + "includeAll": false, + "name": "length_unit", + "options": [], + "query": "SELECT unit_of_length FROM settings LIMIT 1", + "refresh": 1, + "regex": "", + "type": "query" + }, + { + "current": {}, + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "definition": "SELECT preferred_range FROM settings LIMIT 1", + "hide": 2, + "includeAll": false, + "name": "preferred_range", + "options": [], + "query": "SELECT preferred_range FROM settings LIMIT 1", + "refresh": 1, + "regex": "", + "type": "query" + }, + { + "current": {}, + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "definition": "SELECT base_url FROM settings LIMIT 1", + "hide": 2, + "includeAll": false, + "name": "base_url", + "options": [], + "query": "SELECT base_url FROM settings LIMIT 1", + "refresh": 1, + "regex": "", + "type": "query" + }, + { + "current": {}, + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "definition": "WITH Aux as (\n SELECT \n car_id,\n COALESCE(derived_efficiency, car_efficiency) AS efficiency\n FROM (\n SELECT\n ROUND((charge_energy_added / NULLIF(end_rated_range_km - start_rated_range_km, 0))::numeric, 3) * 100 AS derived_efficiency,\n COUNT(*) as count,\n cars.id as car_id,\n cars.efficiency * 100 AS car_efficiency\n FROM cars\n LEFT JOIN charging_processes ON\n cars.id = charging_processes.car_id \n AND duration_min > 10\n AND end_battery_level <= 95\n AND start_rated_range_km IS NOT NULL\n AND end_rated_range_km IS NOT NULL\n AND charge_energy_added > 0\n WHERE cars.id = $car_id\n GROUP BY 1, 3, 4\n ORDER BY 2 DESC\n LIMIT 1\n ) AS Efficiency\n),\n\nCurrentCapacity AS (\n SELECT\n AVG(Capacity) AS Capacity\n FROM (\n SELECT \n c.rated_battery_range_km * aux.efficiency / c.usable_battery_level AS Capacity\n FROM charging_processes cp\n INNER JOIN charges c ON c.charging_process_id = cp.id \n INNER JOIN aux ON cp.car_id = aux.car_id\n WHERE\n cp.car_id = $car_id\n AND cp.end_date IS NOT NULL\n AND cp.charge_energy_added >= aux.efficiency\n AND c.usable_battery_level > 0\n ORDER BY cp.end_date DESC, c.date desc\n LIMIT 100\n ) AS lastCharges\n),\n\nMaxCapacity AS (\n SELECT \n MAX(c.rated_battery_range_km * aux.efficiency / c.usable_battery_level) AS Capacity\n FROM charging_processes cp\n INNER JOIN (\n SELECT\n charging_process_id,\n MAX(date) as date FROM charges WHERE usable_battery_level > 0 GROUP BY charging_process_id\n ) AS gcharges ON\n cp.id = gcharges.charging_process_id\n INNER JOIN charges c ON\n c.charging_process_id = cp.id\n AND c.date = gcharges.date\n INNER JOIN aux ON cp.car_id = aux.car_id\n WHERE\n cp.car_id = $car_id\n AND cp.end_date IS NOT NULL\n AND cp.charge_energy_added >= aux.efficiency\n),\n\nCurrentRange AS (\n SELECT\n (range * 100.0 / usable_battery_level) AS range\n FROM (\n (\n SELECT\n date,\n ${preferred_range}_battery_range_km AS range,\n usable_battery_level AS usable_battery_level\n FROM positions\n WHERE\n car_id = $car_id\n AND ideal_battery_range_km IS NOT NULL\n AND usable_battery_level > 0 \n ORDER BY date DESC\n LIMIT 1\n )\n UNION ALL\n (\n SELECT date,\n ${preferred_range}_battery_range_km AS range,\n usable_battery_level as usable_battery_level\n FROM charges c\n INNER JOIN charging_processes p ON p.id = c.charging_process_id\n WHERE\n p.car_id = $car_id\n AND usable_battery_level > 0\n ORDER BY date DESC\n LIMIT 1\n )\n ) AS data\n ORDER BY date DESC\n LIMIT 1\n),\n\nMaxRange AS (\n SELECT\n floor(extract(epoch from date)/86400)*86400 AS time,\n CASE\n WHEN sum(usable_battery_level) = 0 THEN sum(${preferred_range}_battery_range_km) * 100\n ELSE sum(${preferred_range}_battery_range_km) / sum(usable_battery_level) * 100\n END AS range\n FROM (\n SELECT\n battery_level,\n usable_battery_level,\n date,\n ${preferred_range}_battery_range_km\n FROM charges c \n INNER JOIN charging_processes p ON p.id = c.charging_process_id \n WHERE\n p.car_id = $car_id\n AND usable_battery_level IS NOT NULL\n ) AS data\n GROUP BY 1\n ORDER BY 2 DESC\n LIMIT 1\n),\n\nBase AS (\n SELECT NULL\n)\n\nSELECT\n json_build_object(\n 'MaxRange', convert_km(MaxRange.range,'$length_unit'),\n 'CurrentRange', convert_km(CurrentRange.range,'$length_unit'),\n 'MaxCapacity', MaxCapacity.Capacity,\n 'CurrentCapacity', CASE WHEN CurrentCapacity.Capacity IS NULL THEN 1 ELSE CurrentCapacity.Capacity END,\n 'RatedEfficiency', aux.efficiency\n )\nFROM Base\n LEFT JOIN MaxRange ON true\n LEFT JOIN CurrentRange ON true\n LEFT JOIN Aux ON true\n LEFT JOIN MaxCapacity ON true\n LEFT JOIN CurrentCapacity ON true", + "hide": 2, + "includeAll": false, + "name": "aux", + "options": [], + "query": "WITH Aux as (\n SELECT \n car_id,\n COALESCE(derived_efficiency, car_efficiency) AS efficiency\n FROM (\n SELECT\n ROUND((charge_energy_added / NULLIF(end_rated_range_km - start_rated_range_km, 0))::numeric, 3) * 100 AS derived_efficiency,\n COUNT(*) as count,\n cars.id as car_id,\n cars.efficiency * 100 AS car_efficiency\n FROM cars\n LEFT JOIN charging_processes ON\n cars.id = charging_processes.car_id \n AND duration_min > 10\n AND end_battery_level <= 95\n AND start_rated_range_km IS NOT NULL\n AND end_rated_range_km IS NOT NULL\n AND charge_energy_added > 0\n WHERE cars.id = $car_id\n GROUP BY 1, 3, 4\n ORDER BY 2 DESC\n LIMIT 1\n ) AS Efficiency\n),\n\nCurrentCapacity AS (\n SELECT\n AVG(Capacity) AS Capacity\n FROM (\n SELECT \n c.rated_battery_range_km * aux.efficiency / c.usable_battery_level AS Capacity\n FROM charging_processes cp\n INNER JOIN charges c ON c.charging_process_id = cp.id \n INNER JOIN aux ON cp.car_id = aux.car_id\n WHERE\n cp.car_id = $car_id\n AND cp.end_date IS NOT NULL\n AND cp.charge_energy_added >= aux.efficiency\n AND c.usable_battery_level > 0\n ORDER BY cp.end_date DESC, c.date desc\n LIMIT 100\n ) AS lastCharges\n),\n\nMaxCapacity AS (\n SELECT \n MAX(c.rated_battery_range_km * aux.efficiency / c.usable_battery_level) AS Capacity\n FROM charging_processes cp\n INNER JOIN (\n SELECT\n charging_process_id,\n MAX(date) as date FROM charges WHERE usable_battery_level > 0 GROUP BY charging_process_id\n ) AS gcharges ON\n cp.id = gcharges.charging_process_id\n INNER JOIN charges c ON\n c.charging_process_id = cp.id\n AND c.date = gcharges.date\n INNER JOIN aux ON cp.car_id = aux.car_id\n WHERE\n cp.car_id = $car_id\n AND cp.end_date IS NOT NULL\n AND cp.charge_energy_added >= aux.efficiency\n),\n\nCurrentRange AS (\n SELECT\n (range * 100.0 / usable_battery_level) AS range\n FROM (\n (\n SELECT\n date,\n ${preferred_range}_battery_range_km AS range,\n usable_battery_level AS usable_battery_level\n FROM positions\n WHERE\n car_id = $car_id\n AND ideal_battery_range_km IS NOT NULL\n AND usable_battery_level > 0 \n ORDER BY date DESC\n LIMIT 1\n )\n UNION ALL\n (\n SELECT date,\n ${preferred_range}_battery_range_km AS range,\n usable_battery_level as usable_battery_level\n FROM charges c\n INNER JOIN charging_processes p ON p.id = c.charging_process_id\n WHERE\n p.car_id = $car_id\n AND usable_battery_level > 0\n ORDER BY date DESC\n LIMIT 1\n )\n ) AS data\n ORDER BY date DESC\n LIMIT 1\n),\n\nMaxRange AS (\n SELECT\n floor(extract(epoch from date)/86400)*86400 AS time,\n CASE\n WHEN sum(usable_battery_level) = 0 THEN sum(${preferred_range}_battery_range_km) * 100\n ELSE sum(${preferred_range}_battery_range_km) / sum(usable_battery_level) * 100\n END AS range\n FROM (\n SELECT\n battery_level,\n usable_battery_level,\n date,\n ${preferred_range}_battery_range_km\n FROM charges c \n INNER JOIN charging_processes p ON p.id = c.charging_process_id \n WHERE\n p.car_id = $car_id\n AND usable_battery_level IS NOT NULL\n ) AS data\n GROUP BY 1\n ORDER BY 2 DESC\n LIMIT 1\n),\n\nBase AS (\n SELECT NULL\n)\n\nSELECT\n json_build_object(\n 'MaxRange', convert_km(MaxRange.range,'$length_unit'),\n 'CurrentRange', convert_km(CurrentRange.range,'$length_unit'),\n 'MaxCapacity', MaxCapacity.Capacity,\n 'CurrentCapacity', CASE WHEN CurrentCapacity.Capacity IS NULL THEN 1 ELSE CurrentCapacity.Capacity END,\n 'RatedEfficiency', aux.efficiency\n )\nFROM Base\n LEFT JOIN MaxRange ON true\n LEFT JOIN CurrentRange ON true\n LEFT JOIN Aux ON true\n LEFT JOIN MaxCapacity ON true\n LEFT JOIN CurrentCapacity ON true", + "refresh": 1, + "regex": "", + "type": "query" + }, + { + "current": { + "text": "0", + "value": "0" + }, + "description": "Set the capacity of your car battery when it was new, in case you started using TeslaMate after a while of having it. If not, leave it at 0, it will be calculated with the data that is logged in TeslaMate", + "label": "Custom Battery Capacity (kWh) when new", + "name": "custom_kwh_new", + "options": [ + { + "selected": true, + "text": "0", + "value": "0" + } + ], + "query": "0", + "type": "textbox" + }, + { + "current": { + "text": "0", + "value": "0" + }, + "description": "Set the max range to 100% of your car when it was new, in case you started using TeslaMate after a while of having it. If not, leave it at 0, the degradation will be calculated with the data that is logged in TeslaMate", + "label": "Custom Max Range when new", + "name": "custom_max_range", + "options": [ + { + "selected": true, + "text": "0", + "value": "0" + } + ], + "query": "0", + "type": "textbox" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "hidden": true + }, + "timezone": "browser", + "title": "Battery Health", + "uid": "jchmRiqUfXgTM", + "version": 1 } \ No newline at end of file diff --git a/grafana/dashboards/charge-level.json b/grafana/dashboards/charge-level.json index 2165f24cc1..ebab716e1f 100644 --- a/grafana/dashboards/charge-level.json +++ b/grafana/dashboards/charge-level.json @@ -1,40 +1,16 @@ { - "__elements": {}, - "__requires": [ - { - "type": "grafana", - "id": "grafana", - "name": "Grafana", - "version": "11.1.0" - }, - { - "type": "datasource", - "id": "grafana-postgresql-datasource", - "name": "PostgreSQL", - "version": "1.0.0" - }, - { - "type": "panel", - "id": "timeseries", - "name": "Time series", - "version": "" - } - ], "annotations": { "list": [ { "builtIn": 1, - "datasource": "-- Grafana --", + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, "enable": true, "hide": true, "iconColor": "rgba(0, 211, 255, 1)", "name": "Annotations & Alerts", - "target": { - "limit": 100, - "matchAny": false, - "tags": [], - "type": "dashboard" - }, "type": "dashboard" } ] @@ -42,7 +18,6 @@ "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 0, - "id": null, "links": [ { "icon": "dashboard", @@ -50,7 +25,7 @@ "title": "TeslaMate", "tooltip": "", "type": "link", - "url": "[[base_url:raw]]" + "url": "${base_url:raw}" }, { "asDropdown": true, @@ -62,11 +37,9 @@ "type": "dashboards" } ], - "liveNow": false, "panels": [ { "collapsed": false, - "datasource": "TeslaMate", "gridPos": { "h": 1, "w": 24, @@ -96,6 +69,7 @@ "axisLabel": "Charge Level", "axisPlacement": "auto", "barAlignment": 0, + "barWidthFactor": 0.6, "drawStyle": "line", "fillOpacity": 10, "gradientMode": "opacity", @@ -130,8 +104,7 @@ "mode": "absolute", "steps": [ { - "color": "transparent", - "value": null + "color": "transparent" } ] }, @@ -158,44 +131,41 @@ "showLegend": true }, "tooltip": { + "hideZeros": false, "maxHeight": 600, "mode": "multi", "sort": "none" } }, - "pluginVersion": "8.5.4", + "pluginVersion": "11.6.1", "targets": [ { - "alias": "", "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "time_series", - "group": [], - "hide": false, - "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT\n\t$__time(date),\n\tbattery_level AS \"Battery Level\",\n\tusable_battery_level AS \"Usable Battery Level\"\nfrom positions\n\tWHERE $__timeFilter(date) AND car_id = $car_id\n\tORDER BY Time ASC\n;", + "rawSql": "SELECT\n\tdate_bin('2 minutes'::interval, timezone('UTC', date), to_timestamp(${__from:date:seconds})) as time,\n\tavg(battery_level) AS \"Battery Level\",\n\tavg(usable_battery_level) AS \"Usable Battery Level\"\nfrom positions\n\tWHERE $__timeFilter(date) AND car_id = $car_id and ideal_battery_range_km is not null\n\tgroup by time\n\tORDER BY time ASC\n;", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "value" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } }, { "datasource": { @@ -204,7 +174,6 @@ }, "editorMode": "code", "format": "table", - "hide": false, "rawQuery": true, "rawSql": "SELECT\r\n 20 as lower,\r\n CASE WHEN lfp_battery THEN 100 ELSE 80 END as upper\r\nfrom cars inner join car_settings on cars.settings_id = car_settings.id\r\nwhere cars.id = $car_id", "refId": "B", @@ -225,6 +194,34 @@ ], "limit": 50 } + }, + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "-- To be able to calculate percentiles for unevenly sampled values we are bucketing & gapfilling values before running calculations\r\nwith positions_filtered as (\r\n select\r\n date,\r\n battery_level\r\n from\r\n positions p\r\n where\r\n p.car_id = $car_id\r\n -- p.ideal_battery_range_km condition is added to reduce overall amount of data and avoid data biases while driving (unevenly sampled data)\r\n and p.ideal_battery_range_km is not null\r\n and 1 = $include_average_percentiles\r\n),\r\ngen_date_series as (\r\n select\r\n -- series is used to bucket data and avoid gaps in series used to determine percentiles\r\n generate_series(to_timestamp(${__from:date:seconds} - (86400 * $days_moving_average_percentiles / 2)), to_timestamp(${__to:date:seconds}), concat($bucket_width, ' seconds')::INTERVAL) as series_id\r\n),\r\ndate_series as (\r\n select\r\n timezone('UTC', series_id) as series_id,\r\n -- before joining, get beginning of next series to be able to left join `positions_filtered`\r\n timezone('UTC', lead(series_id) over (order by series_id asc)) as next_series_id\r\n from\r\n gen_date_series\r\n),\r\npositions_bucketed as (\r\n select\r\n series_id,\r\n -- simple average can result in loss of accuracy, see https://www.timescale.com/blog/what-time-weighted-averages-are-and-why-you-should-care/ for details\r\n avg(battery_level) as battery_level,\r\n min(positions_filtered.date) as series_min_date\r\n from\r\n date_series\r\n left join positions_filtered on\r\n positions_filtered.date >= date_series.series_id\r\n and positions_filtered.date < date_series.next_series_id\r\n group by\r\n series_id\r\n),\r\n-- PostgreSQL cannot IGNORE NULLS via Window Functions LAST_VALUE - therefore use natural behavior of COUNT & MAX, see https://www.reddit.com/r/SQL/comments/wb949v/comment/ii5mmmi/ for details\r\npositions_bucketed_gapfilling_locf_intermediate as (\r\n select\r\n series_id,\r\n battery_level,\r\n series_min_date,\r\n count(battery_level) over (order by series_id) as i\r\n from\r\n positions_bucketed\r\n\r\n),\r\npositions_bucketed_gapfilled_locf as (\r\n select\r\n series_id,\r\n series_min_date,\r\n max(battery_level) over (partition by i) as battery_level_locf\r\n from\r\n positions_bucketed_gapfilling_locf_intermediate\r\n),\r\n-- PostgreSQL cannot use PERCENTILE_DISC as Window Function - therefore use ARRAY_AGG and UNNEST, see https://stackoverflow.com/a/72718604 for details\r\npositions_bucketed_gapfilled_locf_percentile_intermediate as (\r\n select\r\n series_id,\r\n series_min_date,\r\n min(series_min_date) over () as min_date,\r\n array_agg(battery_level_locf) over w as arr,\r\n avg(battery_level_locf) over w as battery_level_avg\r\n from\r\n positions_bucketed_gapfilled_locf\r\n window w as (rows between (86400 / $bucket_width) * ($days_moving_average_percentiles / 2) preceding and (86400 / $bucket_width) * ($days_moving_average_percentiles / 2) following)\r\n)\r\n\r\nselect\r\n series_id::timestamptz,\r\n (select percentile_cont(0.075) within group (order by s) from unnest(arr) trick(s)) as \"$days_moving_average_percentiles Day Moving 7.5% Percentile (${bucket_width:text} buckets)\",\r\n battery_level_avg as \"$days_moving_average_percentiles Day Moving Average (${bucket_width:text} buckets)\",\r\n (select percentile_cont(0.5) within group (order by s) from unnest(arr) trick(s)) as \"$days_moving_average_percentiles Day Moving Median (${bucket_width:text} buckets)\",\r\n (select percentile_cont(0.925) within group (order by s) from unnest(arr) trick(s)) as \"$days_moving_average_percentiles Day Moving 92.5% Percentile (${bucket_width:text} buckets)\"\r\nfrom\r\n positions_bucketed_gapfilled_locf_percentile_intermediate where $__timeFilter(series_id) and series_min_date >= min_date", + "refId": "C", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } } ], "title": "Charge Level", @@ -263,8 +260,9 @@ "type": "timeseries" } ], - "refresh": false, - "schemaVersion": 39, + "preload": false, + "refresh": "", + "schemaVersion": 41, "tags": [ "tesla" ], @@ -276,22 +274,16 @@ "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, - "definition": "SELECT name AS __text, id AS __value FROM cars ORDER BY display_priority ASC, name ASC;", + "definition": "SELECT\n id as __value,\n CASE WHEN COUNT(id) OVER (PARTITION BY name) > 1 AND name IS NOT NULL THEN CONCAT(name, ' - ', RIGHT(vin, 6)) ELSE COALESCE(name, CONCAT('VIN ', vin)) end as __text \nFROM cars\nORDER BY display_priority ASC, name ASC, vin ASC;", "hide": 2, "includeAll": true, "label": "Car", - "multi": false, "name": "car_id", "options": [], - "query": "SELECT name AS __text, id AS __value FROM cars ORDER BY display_priority ASC, name ASC;", + "query": "SELECT\n id as __value,\n CASE WHEN COUNT(id) OVER (PARTITION BY name) > 1 AND name IS NOT NULL THEN CONCAT(name, ' - ', RIGHT(vin, 6)) ELSE COALESCE(name, CONCAT('VIN ', vin)) end as __text \nFROM cars\nORDER BY display_priority ASC, name ASC, vin ASC;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -302,54 +294,114 @@ "definition": "select base_url from settings limit 1;", "hide": 2, "includeAll": false, - "label": "", - "multi": false, "name": "base_url", "options": [], "query": "select base_url from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" + }, + { + "current": { + "text": "2h", + "value": "7200" + }, + "description": "Data used to calculate Moving Average / Percentiles is unevenly sampled in TeslaMate. To avoid biases towards more frequently sampled values, the data is bucketed. For buckets without sampled values, the last observed value is carried forward. Bucketing is not time-weighted but is a simple average. Increasing the bucket width results in a loss of accuracy.", + "includeAll": false, + "label": "Bucket Width", + "name": "bucket_width", + "options": [ + { + "selected": false, + "text": "1h", + "value": "3600" + }, + { + "selected": true, + "text": "2h", + "value": "7200" + }, + { + "selected": false, + "text": "4h", + "value": "14400" + } + ], + "query": "1h : 3600, 2h : 7200, 4h : 14400", + "type": "custom" + }, + { + "current": { + "text": "yes", + "value": "1" + }, + "includeAll": false, + "label": "Include Moving Average / Percentiles", + "name": "include_average_percentiles", + "options": [ + { + "selected": false, + "text": "no", + "value": "0" + }, + { + "selected": true, + "text": "yes", + "value": "1" + } + ], + "query": "no : 0, yes : 1", + "type": "custom" + }, + { + "current": { + "text": "1/6 of interval", + "value": "6" + }, + "description": "", + "includeAll": false, + "label": "Moving Average / Percentiles Width", + "name": "intervals_moving_average_percentiles", + "options": [ + { + "selected": true, + "text": "1/6 of interval", + "value": "6" + }, + { + "selected": false, + "text": "1/12 of interval", + "value": "12" + } + ], + "query": "1/6 of interval : 6, 1/12 of interval : 12", + "type": "custom" + }, + { + "current": {}, + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "definition": "select ((${__to:date:seconds} - ${__from:date:seconds}) / 86400 / $intervals_moving_average_percentiles)", + "hide": 2, + "includeAll": false, + "name": "days_moving_average_percentiles", + "options": [], + "query": "select ((${__to:date:seconds} - ${__from:date:seconds}) / 86400 / $intervals_moving_average_percentiles)", + "refresh": 2, + "regex": "", + "type": "query" } ] }, "time": { - "from": "now-7d", + "from": "now-6M", "to": "now" }, - "timepicker": { - "refresh_intervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "time_options": [ - "5m", - "15m", - "1h", - "6h", - "12h", - "24h", - "2d", - "7d", - "30d" - ] - }, + "timepicker": {}, "timezone": "", "title": "Charge Level", "uid": "WopVO_mgz", - "version": 5, - "weekStart": "" + "version": 1 } \ No newline at end of file diff --git a/grafana/dashboards/charges.json b/grafana/dashboards/charges.json index 53d59abfae..a7f9c276e2 100644 --- a/grafana/dashboards/charges.json +++ b/grafana/dashboards/charges.json @@ -1,53 +1,16 @@ { - "__elements": {}, - "__requires": [ - { - "type": "grafana", - "id": "grafana", - "name": "Grafana", - "version": "11.0.0" - }, - { - "type": "datasource", - "id": "grafana-postgresql-datasource", - "name": "PostgreSQL", - "version": "1.0.0" - }, - { - "type": "panel", - "id": "stat", - "name": "Stat", - "version": "" - }, - { - "type": "panel", - "id": "table", - "name": "Table", - "version": "" - }, - { - "type": "panel", - "id": "text", - "name": "Text", - "version": "" - } - ], "annotations": { "list": [ { "builtIn": 1, - "datasource": "-- Grafana --", - "definition": "TeslaMate|U2FsdGVkX1/cEWK+8cz7pjEKXtzJnDN7b21ZDXt1MGneFGPWTLqOPtxKmu02mJPLzi/f29I+NBHd3vi0FB8R4Xn0+GtobWDgk6VAVSBTdSNniOKO8i2WPlhRaOsl2+hG7gnZ7wrf1Th2nxR7f1uYCrbwOek0IzkfLzrkjh7gkr6inT6bbDuJqrmogZajLxmAMrQ6V+/vHxBRGiwjJhgiEeq3hM1q2h04JKkNiZ8RHbsF5Cd/xd8Q9u0JVrZzIrtnhM/SFlaApU7RtRMu8CSj1llTX7WEOj6VDZAMSf+XUAanWdk725kEPN9MNu89o2zEq5P3E3cju8IbbBdPzXLV3oVuzD6/tMnxFToIIV1E/BrpF7s2RtNa8+KJJ1PF8xgs6m+/KTD2hy+WsP0636AgObRAmYg7+qotGrgNvpNPdE0EgrB7WHYlV7R/1q66bcq6tCe51X1Un70k+zo+K6AK0o4B1H6IyMlEVuRH/Fz8QVl9aYu2ztd08RbuKJlYVKpkH+pxVETAO9MclYQ90tzE6TfwDZrQZzsAlMenr4s1ZB1OlFXjLjVjnddnUilzO76cqv4yI2THQEuyQ47nuVQ4gUbx02K59vMQhns3C01JOAYokOaSXe66Y7QYdMlk09Lf|aes-256-cbc", + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, "enable": true, "hide": true, "iconColor": "rgba(0, 211, 255, 1)", "name": "Annotations & Alerts", - "target": { - "limit": 100, - "matchAny": false, - "tags": [], - "type": "dashboard" - }, "type": "dashboard" } ] @@ -55,7 +18,6 @@ "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 0, - "id": null, "links": [ { "icon": "dashboard", @@ -63,7 +25,7 @@ "title": "TeslaMate", "tooltip": "", "type": "link", - "url": "[[base_url:raw]]" + "url": "${base_url:raw}" }, { "asDropdown": true, @@ -75,147 +37,538 @@ "type": "dashboards" } ], - "liveNow": false, "panels": [ + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 16, + "panels": [], + "title": "Summary of this period", + "type": "row" + }, { "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" + "type": "datasource", + "uid": "-- Dashboard --" }, - "description": "Browse your charges by Geofence, Location, Type, Cost and Duration in order to have an accurate Total of kWh added and their respective costs", "fieldConfig": { "defaults": { "color": { "mode": "thresholds" }, - "custom": { - "align": "center", - "cellOptions": { - "type": "auto" - }, - "filterable": false, - "inspect": false, - "minWidth": 150 - }, - "decimals": 2, - "displayName": "", - "mappings": [], + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], "thresholds": { "mode": "absolute", "steps": [ { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 + "color": "text" } ] }, - "unit": "short" + "unit": "kwatth" }, "overrides": [ { "matcher": { "id": "byName", - "options": "start_date" + "options": "charge_energy_added" }, "properties": [ { "id": "displayName", - "value": "Date" - }, - { - "id": "unit", - "value": "dateTimeAsLocal" - }, - { - "id": "links", - "value": [ - { - "targetBlank": false, - "title": "View charge details", - "url": "d/BHhxFeZRz?from=${__data.fields.start_date_ts.numeric}&to=${__data.fields.end_date_ts.numeric}&var-car_id=${__data.fields.car_id.numeric}&var-charging_process_id=${__data.fields.id.numeric}" - } - ] - }, - { - "id": "custom.align" + "value": "Total Energy added:" + } + ] + } + ] + }, + "gridPos": { + "h": 2, + "w": 6, + "x": 0, + "y": 1 + }, + "id": 10, + "maxDataPoints": 100, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "center", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.6.1", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "panelId": 6, + "refId": "A" + } + ], + "title": "", + "transformations": [ + { + "id": "filterFieldsByName", + "options": { + "include": { + "names": [ + "charge_energy_added" + ] + } + } + } + ], + "transparent": true, + "type": "stat" + }, + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ { - "id": "custom.minWidth", - "value": 160 + "color": "text" } ] }, + "unit": "kwatth" + }, + "overrides": [ { "matcher": { "id": "byName", - "options": "charge_energy_added" + "options": "charge_energy_used" }, "properties": [ { "id": "displayName", - "value": "Added" - }, - { - "id": "unit", - "value": "kwatth" - }, - { - "id": "decimals", - "value": 2 + "value": "Total Energy used:" + } + ] + } + ] + }, + "gridPos": { + "h": 2, + "w": 6, + "x": 6, + "y": 1 + }, + "id": 20, + "maxDataPoints": 100, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "center", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.6.1", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "panelId": 6, + "refId": "A" + } + ], + "title": "", + "transformations": [ + { + "id": "filterFieldsByName", + "options": { + "include": { + "names": [ + "charge_energy_used" + ] + } + } + } + ], + "transparent": true, + "type": "stat" + }, + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 2, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ { - "id": "custom.minWidth", - "value": 80 + "color": "text" } ] }, + "unit": "none" + }, + "overrides": [ { "matcher": { "id": "byName", - "options": "start_battery_level" + "options": "cost" }, "properties": [ { "id": "displayName", - "value": "% Start" - }, - { - "id": "unit", - "value": "percent" - }, - { - "id": "custom.align" - }, - { - "id": "decimals", - "value": 0 - }, + "value": "Total Charging Cost:" + } + ] + } + ] + }, + "gridPos": { + "h": 2, + "w": 6, + "x": 12, + "y": 1 + }, + "id": 14, + "maxDataPoints": 100, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "center", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.6.1", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "panelId": 6, + "refId": "A" + } + ], + "title": "", + "transformations": [ + { + "id": "filterFieldsByName", + "options": { + "include": { + "names": [ + "cost" + ] + } + } + } + ], + "transparent": true, + "type": "stat" + }, + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 0, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "text" + } + ] + }, + "unit": "m" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "duration_min" + }, + "properties": [ + { + "id": "displayName", + "value": "Ø Duration:" + } + ] + } + ] + }, + "gridPos": { + "h": 2, + "w": 6, + "x": 18, + "y": 1 + }, + "id": 15, + "maxDataPoints": 100, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "center", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.6.1", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "panelId": 6, + "refId": "A" + } + ], + "title": "", + "transformations": [ + { + "id": "filterFieldsByName", + "options": { + "include": { + "names": [ + "duration_min" + ] + } + } + } + ], + "transparent": true, + "type": "stat" + }, + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "description": "Browse your charges by Geofence, Location, Type, Cost and Duration in order to have an accurate Total of kWh added and their respective costs", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "filterable": false, + "inspect": false, + "minWidth": 150 + }, + "displayName": "", + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "start_date" + }, + "properties": [ + { + "id": "displayName", + "value": "Date" + }, + { + "id": "links", + "value": [ + { + "targetBlank": false, + "title": "View charge details", + "url": "/d/BHhxFeZRz/charge-details?from=${__data.fields.start_date_ts.numeric}&to=${__data.fields.end_date_ts.numeric}&var-car_id=${__data.fields.car_id.numeric}&var-charging_process_id=${__data.fields.id.numeric}" + } + ] + }, { "id": "custom.minWidth", - "value": 62 + "value": 210 + }, + { + "id": "unit", + "value": "dateTimeAsLocal" } ] }, { "matcher": { "id": "byName", - "options": "end_battery_level" + "options": "charge_energy_added" }, "properties": [ { "id": "displayName", - "value": "% End" + "value": "Energy added" + }, + { + "id": "decimals", + "value": 2 + }, + { + "id": "custom.minWidth", + "value": 115 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "start_battery_level" + }, + "properties": [ + { + "id": "displayName", + "value": "% Start" }, { "id": "unit", "value": "percent" }, { - "id": "custom.align" + "id": "decimals", + "value": 0 + }, + { + "id": "custom.minWidth", + "value": 70 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "end_battery_level" + }, + "properties": [ + { + "id": "displayName", + "value": "% End" + }, + { + "id": "unit", + "value": "percent" }, { "id": "decimals", @@ -223,7 +576,7 @@ }, { "id": "custom.minWidth", - "value": 62 + "value": 65 } ] }, @@ -245,12 +598,9 @@ "id": "decimals", "value": 1 }, - { - "id": "custom.align" - }, { "id": "custom.minWidth", - "value": 75 + "value": 80 } ] }, @@ -278,17 +628,13 @@ "type": "color-text" } }, - { - "id": "custom.align" - }, { "id": "thresholds", "value": { "mode": "absolute", "steps": [ { - "color": "#C0D8FF", - "value": null + "color": "#C0D8FF" }, { "color": "#C8F2C2", @@ -317,10 +663,6 @@ "id": "displayName", "value": "Cost" }, - { - "id": "unit", - "value": "none" - }, { "id": "decimals", "value": 2 @@ -331,20 +673,17 @@ { "targetBlank": false, "title": "Set Cost", - "url": "[[base_url:raw]]/charge-cost/${__data.fields.id.numeric}" + "url": "${base_url:raw}/charge-cost/${__data.fields.id.numeric}" } ] }, - { - "id": "custom.align" - }, { "id": "noValue", "value": "-" }, { "id": "custom.minWidth", - "value": 65 + "value": 70 } ] }, @@ -354,17 +693,6 @@ "options": "/.*_ts/" }, "properties": [ - { - "id": "unit", - "value": "short" - }, - { - "id": "decimals", - "value": 2 - }, - { - "id": "custom.align" - }, { "id": "custom.hidden", "value": true @@ -377,17 +705,6 @@ "options": "id" }, "properties": [ - { - "id": "unit", - "value": "short" - }, - { - "id": "decimals", - "value": 2 - }, - { - "id": "custom.align" - }, { "id": "custom.hidden", "value": true @@ -404,49 +721,55 @@ "id": "displayName", "value": "Location" }, - { - "id": "unit", - "value": "short" - }, - { - "id": "decimals", - "value": 2 - }, { "id": "links", "value": [ { "targetBlank": true, "title": "Create or edit geo-fence", - "url": "[[base_url:raw]]/geo-fences/${__data.fields.path}" + "url": "${base_url:raw}/geo-fences/${__data.fields.path}" } ] }, - { - "id": "custom.align" - }, { "id": "custom.minWidth", - "value": 300 + "value": 180 } ] }, { "matcher": { "id": "byName", - "options": "distance_km" + "options": "range_added_km" }, "properties": [ { "id": "displayName", - "value": "Driven" + "value": "Range gained" + }, + { + "id": "decimals", + "value": 0 + }, + { + "id": "custom.minWidth", + "value": 120 }, { "id": "unit", "value": "lengthkm" - }, + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "range_added_mi" + }, + "properties": [ { - "id": "custom.align" + "id": "displayName", + "value": "Range gained" }, { "id": "decimals", @@ -454,7 +777,11 @@ }, { "id": "custom.minWidth", - "value": 90 + "value": 120 + }, + { + "id": "unit", + "value": "lengthmi" } ] }, @@ -466,7 +793,7 @@ "properties": [ { "id": "displayName", - "value": "kW" + "value": "Ø Power" }, { "id": "unit", @@ -482,17 +809,13 @@ "type": "color-text" } }, - { - "id": "custom.align" - }, { "id": "thresholds", "value": { "mode": "absolute", "steps": [ { - "color": "#96D98D", - "value": null + "color": "#96D98D" }, { "color": "#56A64B", @@ -507,7 +830,7 @@ }, { "id": "custom.minWidth", - "value": 70 + "value": 90 } ] }, @@ -519,22 +842,19 @@ "properties": [ { "id": "displayName", - "value": "Range added" + "value": "Ø Charge rate" }, { "id": "unit", "value": "velocitykmh" }, { - "id": "custom.align" + "id": "custom.minWidth", + "value": 120 }, { "id": "decimals", "value": 0 - }, - { - "id": "custom.minWidth", - "value": 120 } ] }, @@ -556,9 +876,6 @@ "id": "decimals", "value": 1 }, - { - "id": "custom.align" - }, { "id": "custom.minWidth", "value": 70 @@ -575,8 +892,7 @@ "mode": "absolute", "steps": [ { - "color": "super-light-blue", - "value": null + "color": "super-light-blue" }, { "color": "super-light-green", @@ -591,33 +907,6 @@ } ] }, - { - "matcher": { - "id": "byName", - "options": "distance_mi" - }, - "properties": [ - { - "id": "displayName", - "value": "Driven" - }, - { - "id": "unit", - "value": "lengthmi" - }, - { - "id": "custom.align" - }, - { - "id": "decimals", - "value": 0 - }, - { - "id": "custom.minWidth", - "value": 90 - } - ] - }, { "matcher": { "id": "byName", @@ -626,22 +915,19 @@ "properties": [ { "id": "displayName", - "value": "Range added" + "value": "Ø Charge rate" }, { "id": "unit", "value": "velocitymph" }, { - "id": "custom.align" + "id": "custom.minWidth", + "value": 120 }, { "id": "decimals", "value": 0 - }, - { - "id": "custom.minWidth", - "value": 120 } ] }, @@ -651,17 +937,6 @@ "options": "path" }, "properties": [ - { - "id": "unit", - "value": "short" - }, - { - "id": "decimals", - "value": 2 - }, - { - "id": "custom.align" - }, { "id": "custom.hidden", "value": true @@ -676,22 +951,15 @@ "properties": [ { "id": "displayName", - "value": "Used" - }, - { - "id": "unit", - "value": "kwatth" + "value": "Energy used" }, { "id": "decimals", "value": 2 }, - { - "id": "custom.align" - }, { "id": "custom.minWidth", - "value": 85 + "value": 105 } ] }, @@ -709,10 +977,6 @@ "id": "unit", "value": "percentunit" }, - { - "id": "custom.align", - "value": "auto" - }, { "id": "decimals", "value": 0 @@ -736,7 +1000,7 @@ }, { "id": "custom.minWidth", - "value": 100 + "value": 120 } ] }, @@ -746,17 +1010,6 @@ "options": "car_id" }, "properties": [ - { - "id": "unit", - "value": "short" - }, - { - "id": "decimals", - "value": 2 - }, - { - "id": "custom.align" - }, { "id": "custom.hidden", "value": true @@ -769,17 +1022,6 @@ "options": "end_date" }, "properties": [ - { - "id": "unit", - "value": "short" - }, - { - "id": "decimals", - "value": 2 - }, - { - "id": "custom.align" - }, { "id": "custom.hidden", "value": true @@ -792,13 +1034,9 @@ "options": "cost_per_kwh" }, "properties": [ - { - "id": "unit", - "value": "none" - }, { "id": "displayName", - "value": "Cost/kWh" + "value": "Cost / kWh" }, { "id": "decimals", @@ -810,502 +1048,157 @@ }, { "id": "custom.minWidth", - "value": 80 - }, - { - "id": "custom.align", - "value": "right" - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "charge_type" - }, - "properties": [ - { - "id": "displayName", - "value": "Type" - }, - { - "id": "custom.cellOptions", - "value": { - "type": "color-text" - } - }, - { - "id": "custom.cellOptions", - "value": { - "type": "color-text" - } - }, - { - "id": "color", - "value": { - "fixedColor": "super-light-blue", - "mode": "fixed" - } - }, - { - "id": "custom.minWidth", - "value": 40 - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "Date" - }, - "properties": [ - { - "id": "custom.width", - "value": 184 - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "Type" - }, - "properties": [ - { - "id": "custom.width", - "value": 62 - } - ] - } - ] - }, - "gridPos": { - "h": 19, - "w": 24, - "x": 0, - "y": 0 - }, - "id": 6, - "options": { - "cellHeight": "sm", - "footer": { - "countRows": false, - "fields": "", - "reducer": [ - "sum" - ], - "show": false - }, - "showHeader": true, - "sortBy": [] - }, - "pluginVersion": "11.0.0", - "targets": [ - { - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" - }, - "format": "table", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "WITH data AS (\n SELECT\n (round(extract(epoch FROM start_date) - 10) * 1000) AS start_date_ts,\n (round(extract(epoch FROM end_date) + 10) * 1000) AS end_date_ts,\n start_date,\n end_date,\n CONCAT_WS(', ', COALESCE(addresses.name, nullif(CONCAT_WS(' ', addresses.road, addresses.house_number), '')), addresses.city) AS address,\n g.name as geofence_name,\n g.id as geofence_id,\n p.latitude,\n p.longitude,\n cp.charge_energy_added,\n cp.charge_energy_used,\n duration_min,\n start_battery_level,\n end_battery_level,\n start_[[preferred_range]]_range_km,\n end_[[preferred_range]]_range_km,\n outside_temp_avg,\n cp.id,\n lag(end_[[preferred_range]]_range_km) OVER (ORDER BY start_date) - start_[[preferred_range]]_range_km AS range_loss,\n p.odometer - lag(p.odometer) OVER (ORDER BY start_date) AS distance,\n cars.efficiency,\n cp.car_id,\n cost,\n max(c.charger_voltage) as max_charger_voltage,\n CASE WHEN NULLIF(mode() within group (order by charger_phases),0) is null THEN 'DC' ELSE 'AC' END AS charge_type\n FROM\n charging_processes cp\n\t LEFT JOIN charges c ON cp.id = c.charging_process_id\n LEFT JOIN positions p ON p.id = cp.position_id\n LEFT JOIN cars ON cars.id = cp.car_id\n LEFT JOIN addresses ON addresses.id = cp.address_id\n LEFT JOIN geofences g ON g.id = geofence_id\nWHERE \n cp.car_id = $car_id AND\n $__timeFilter(start_date) AND\n (cp.charge_energy_added IS NULL OR cp.charge_energy_added > 0) AND\n ('${geofence:pipe}' = '-1' OR geofence_id in ($geofence))\nGROUP BY 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,11,12,13,14,15,16,17,18,21,p.odometer\nORDER BY\n start_date\n)\nSELECT\n start_date_ts,\n end_date_ts,\n CASE WHEN geofence_id IS NULL THEN CONCAT('new?lat=', latitude, '&lng=', longitude)\n WHEN geofence_id IS NOT NULL THEN CONCAT(geofence_id, '/edit')\n END as path,\n car_id,\n id,\n -- Columns\n start_date,\n end_date,\n COALESCE(geofence_name, address) as address, \n charge_type,\n duration_min,\n cost,\n cost / NULLIF(greatest(charge_energy_added, charge_energy_used), 0) as cost_per_kwh,\n charge_energy_added,\n charge_energy_used,\n CASE WHEN charge_energy_used IS NULL THEN NULL ELSE LEAST(charge_energy_added / NULLIF(charge_energy_used, 0), 1.0) END as charging_efficiency,\n convert_celsius(outside_temp_avg, '$temp_unit') AS outside_temp_avg_$temp_unit,\n charge_energy_added * 60 / NULLIF (duration_min, 0) AS charge_energy_added_per_hour,\n convert_km((end_[[preferred_range]]_range_km - start_[[preferred_range]]_range_km) * 60 / NULLIF (duration_min, 0), '$length_unit') AS range_added_per_hour_$length_unit,\n start_battery_level,\n end_battery_level,\n convert_km(distance::numeric, '$length_unit') AS distance_$length_unit\n FROM\n data\nWHERE\n (distance >= 0 OR distance IS NULL)\n AND duration_min >= '$min_duration_min'\n AND CASE WHEN $cost = 0 THEN (cost IS NULL OR cost >= 0) ELSE cost >= $cost END\n AND charge_type = ANY(CASE WHEN array_to_string(ARRAY[$charge_type], ',') = 'DC' THEN ARRAY['DC'] WHEN array_to_string(ARRAY[$charge_type], ',') = 'AC' THEN ARRAY['AC'] ELSE ARRAY['DC', 'AC'] END)\n AND address ILIKE '%$location%'\nORDER BY\n start_date DESC;", - "refId": "A", - "select": [ - [ - { - "params": [ - "value" - ], - "type": "column" - } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "Charges: $charge_type", - "type": "table" - }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 19 - }, - "id": 16, - "panels": [], - "title": "Summary of this period", - "type": "row" - }, - { - "datasource": { - "type": "datasource", - "uid": "-- Dashboard --" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [ - { - "options": { - "match": "null", - "result": { - "text": "N/A" - } - }, - "type": "special" - } - ], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "kwatth" - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 6, - "x": 0, - "y": 20 - }, - "id": 10, - "maxDataPoints": 100, - "options": { - "colorMode": "none", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "horizontal", - "reduceOptions": { - "calcs": [ - "sum" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "11.0.0", - "targets": [ - { - "datasource": { - "type": "datasource", - "uid": "-- Dashboard --" - }, - "panelId": 6, - "refId": "A" - } - ], - "title": "Energy added", - "transformations": [ - { - "id": "filterFieldsByName", - "options": { - "include": { - "names": [ - "charge_energy_added" - ] - } - } - } - ], - "transparent": true, - "type": "stat" - }, - { - "datasource": { - "type": "datasource", - "uid": "-- Dashboard --" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [ - { - "options": { - "match": "null", - "result": { - "text": "N/A" - } - }, - "type": "special" - } - ], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "kwatth" - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 6, - "x": 6, - "y": 20 - }, - "id": 12, - "maxDataPoints": 100, - "options": { - "colorMode": "none", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "horizontal", - "reduceOptions": { - "calcs": [ - "sum" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "11.0.0", - "targets": [ - { - "datasource": { - "type": "datasource", - "uid": "-- Dashboard --" - }, - "panelId": 6, - "refId": "A" - } - ], - "title": "Energy used", - "transformations": [ - { - "id": "filterFieldsByName", - "options": { - "include": { - "names": [ - "charge_energy_used" - ] - } - } - } - ], - "transparent": true, - "type": "stat" - }, - { - "datasource": { - "type": "datasource", - "uid": "-- Dashboard --" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" + "value": 100 + } + ] }, - "decimals": 2, - "mappings": [ - { - "options": { - "match": "null", - "result": { - "text": "N/A" + { + "matcher": { + "id": "byName", + "options": "charge_type" + }, + "properties": [ + { + "id": "displayName", + "value": "Type" + }, + { + "id": "custom.cellOptions", + "value": { + "type": "color-text" } }, - "type": "special" - } - ], - "thresholds": { - "mode": "absolute", - "steps": [ { - "color": "green", - "value": null + "id": "custom.minWidth", + "value": 40 }, { - "color": "red", - "value": 80 + "id": "mappings", + "value": [ + { + "options": { + "AC": { + "color": "green", + "index": 0 + }, + "DC": { + "color": "light-orange", + "index": 1 + } + }, + "type": "value" + } + ] + }, + { + "id": "custom.align", + "value": "center" } ] }, - "unit": "none" - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 6, - "x": 12, - "y": 20 - }, - "id": 14, - "maxDataPoints": 100, - "options": { - "colorMode": "none", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "horizontal", - "reduceOptions": { - "calcs": [ - "sum" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "11.0.0", - "targets": [ - { - "datasource": { - "type": "datasource", - "uid": "-- Dashboard --" - }, - "panelId": 6, - "refId": "A" - } - ], - "title": "Cost", - "transformations": [ - { - "id": "filterFieldsByName", - "options": { - "include": { - "names": [ - "cost" - ] - } - } - } - ], - "transparent": true, - "type": "stat" - }, - { - "datasource": { - "type": "datasource", - "uid": "-- Dashboard --" - }, - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "decimals": 0, - "mappings": [ - { - "options": { - "match": "null", - "result": { - "text": "N/A" - } + { + "matcher": { + "id": "byName", + "options": "odometer_km" + }, + "properties": [ + { + "id": "unit", + "value": "km" }, - "type": "special" - } - ], - "thresholds": { - "mode": "absolute", - "steps": [ { - "color": "green", - "value": null + "id": "displayName", + "value": "Odometer" }, { - "color": "red", - "value": 80 + "id": "custom.minWidth", + "value": 95 + }, + { + "id": "decimals", + "value": 0 } ] }, - "unit": "m" - }, - "overrides": [] + { + "matcher": { + "id": "byName", + "options": "odometer_mi" + }, + "properties": [ + { + "id": "unit", + "value": "mi" + }, + { + "id": "displayName", + "value": "Odometer" + }, + { + "id": "custom.minWidth", + "value": 95 + }, + { + "id": "decimals", + "value": 0 + } + ] + } + ] }, "gridPos": { - "h": 4, - "w": 6, - "x": 18, - "y": 20 + "h": 19, + "w": 24, + "x": 0, + "y": 3 }, - "id": 15, - "maxDataPoints": 100, + "id": 6, "options": { - "colorMode": "none", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "horizontal", - "reduceOptions": { - "calcs": [ - "mean" - ], + "cellHeight": "sm", + "footer": { + "countRows": false, "fields": "", - "values": false + "reducer": [ + "sum" + ], + "show": false }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true + "showHeader": true, + "sortBy": [] }, - "pluginVersion": "11.0.0", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { - "type": "datasource", - "uid": "-- Dashboard --" + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" }, - "panelId": 6, - "refId": "A" - } - ], - "title": "Average Duration", - "transformations": [ - { - "id": "filterFieldsByName", - "options": { - "include": { - "names": [ - "duration_min" - ] - } + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "WITH data AS (\n SELECT\n (round(extract(epoch FROM start_date) - 10) * 1000) AS start_date_ts,\n (round(extract(epoch FROM end_date) + 10) * 1000) AS end_date_ts,\n start_date,\n end_date,\n CONCAT_WS(', ', COALESCE(addresses.name, nullif(CONCAT_WS(' ', addresses.road, addresses.house_number), '')), addresses.city) AS address,\n g.name as geofence_name,\n g.id as geofence_id,\n p.latitude,\n p.longitude,\n cp.charge_energy_added,\n cp.charge_energy_used,\n duration_min,\n start_battery_level,\n end_battery_level,\n end_${preferred_range}_range_km - start_${preferred_range}_range_km as range_added,\n outside_temp_avg,\n cp.id,\n p.odometer - lag(p.odometer) OVER (ORDER BY start_date) AS distance,\n cars.efficiency,\n cp.car_id,\n cost,\n max(c.charger_voltage) as max_charger_voltage,\n CASE WHEN NULLIF(mode() within group (order by charger_phases),0) is null THEN 'DC' ELSE 'AC' END AS charge_type,\n p.odometer as odometer\n FROM\n charging_processes cp\n\tLEFT JOIN charges c ON cp.id = c.charging_process_id\n LEFT JOIN positions p ON p.id = cp.position_id\n LEFT JOIN cars ON cars.id = cp.car_id\n LEFT JOIN addresses ON addresses.id = cp.address_id\n LEFT JOIN geofences g ON g.id = geofence_id\n WHERE \n cp.car_id = $car_id AND\n $__timeFilter(start_date) AND\n (cp.charge_energy_added IS NULL OR cp.charge_energy_added > 0) AND\n ('${geofence:pipe}' = '-1' OR geofence_id in ($geofence))\n GROUP BY 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 19, p.odometer\n ORDER BY\n start_date\n)\nSELECT\n start_date_ts,\n end_date_ts,\n CASE WHEN geofence_id IS NULL THEN CONCAT('new?lat=', latitude, '&lng=', longitude)\n WHEN geofence_id IS NOT NULL THEN CONCAT(geofence_id, '/edit')\n END as path,\n car_id,\n id,\n -- Columns\n start_date,\n end_date,\n COALESCE(geofence_name, address) as address, \n charge_type,\n duration_min,\n cost,\n cost / NULLIF(greatest(charge_energy_added, charge_energy_used), 0) as cost_per_kwh,\n charge_energy_added,\n greatest(charge_energy_used, charge_energy_added) as charge_energy_used,\n charge_energy_added / greatest(charge_energy_used, charge_energy_added) as charging_efficiency,\n convert_celsius(outside_temp_avg, '$temp_unit') AS outside_temp_avg_$temp_unit,\n charge_energy_added * 60 / NULLIF (duration_min, 0) AS charge_energy_added_per_hour,\n convert_km(range_added * 60 / NULLIF (duration_min, 0), '$length_unit') AS range_added_per_hour_$length_unit,\n convert_km(range_added, '$length_unit') AS range_added_$length_unit,\n start_battery_level,\n end_battery_level,\n convert_km(odometer::numeric, '$length_unit') AS odometer_$length_unit\n FROM\n data\nWHERE\n (distance >= 0 OR distance IS NULL)\n AND duration_min >= '$min_duration_min'\n AND \n CASE\n WHEN '$cost' !~ '^[0-9]+$' THEN TRUE \n ELSE cost >= COALESCE(NULLIF('$cost', '')::NUMERIC, 0) \n END\n AND charge_type = ANY(CASE WHEN array_to_string(ARRAY[$charge_type], ',') = 'DC' THEN ARRAY['DC'] WHEN array_to_string(ARRAY[$charge_type], ',') = 'AC' THEN ARRAY['AC'] ELSE ARRAY['DC', 'AC'] END)\n AND address ILIKE '%$location%'\nORDER BY\n start_date DESC;", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 } } ], - "transparent": true, - "type": "stat" + "title": "Charger type: $charge_type", + "type": "table" }, { "collapsed": false, @@ -1313,7 +1206,7 @@ "h": 1, "w": 24, "x": 0, - "y": 24 + "y": 22 }, "id": 18, "panels": [], @@ -1321,15 +1214,15 @@ "type": "row" }, { - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" + "fieldConfig": { + "defaults": {}, + "overrides": [] }, "gridPos": { "h": 2, "w": 24, "x": 0, - "y": 25 + "y": 23 }, "id": 19, "options": { @@ -1341,41 +1234,8 @@ "content": "From here you can check if you have \nincomplete data of **Charges** (charges without ending date)\nIf so, you may follow the official \nguide by Manually fixing data", "mode": "markdown" }, - "pluginVersion": "11.0.0", - "targets": [ - { - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" - }, - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": false, - "rawSql": "SELECT\n start_date AS \"time\",\n start_km\nFROM drives\nWHERE\n $__timeFilter(start_date)\nORDER BY 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "start_km" - ], - "type": "column" - } - ] - ], - "table": "drives", - "timeColumn": "start_date", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], + "pluginVersion": "11.6.1", + "title": "", "type": "text" }, { @@ -1402,8 +1262,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" } ] } @@ -1411,10 +1270,10 @@ "overrides": [] }, "gridPos": { - "h": 14, + "h": 6, "w": 24, "x": 0, - "y": 27 + "y": 25 }, "id": 17, "options": { @@ -1430,47 +1289,44 @@ }, "showHeader": true }, - "pluginVersion": "11.0.0", + "pluginVersion": "11.6.1", "targets": [ { - "alias": "", "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "table", - "group": [], - "hide": false, - "metricColumn": "none", "rawQuery": true, "rawSql": "SELECT id as \"Charging Process ID\", start_date, end_date, charge_energy_added, charge_energy_used, start_battery_level, end_battery_level, duration_min\nFROM charging_processes \nWHERE car_id = $car_id AND end_date is null\nORDER BY start_date DESC\n", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "value" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } } ], "title": "Incomplete Charges 🪫", "type": "table" } ], + "preload": false, "refresh": "", - "schemaVersion": 39, + "schemaVersion": 41, "tags": [ "tesla" ], @@ -1482,22 +1338,15 @@ "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, - "definition": "SELECT name AS __text, id AS __value FROM cars ORDER BY display_priority ASC, name ASC;", - "hide": 0, + "definition": "SELECT\n id as __value,\n CASE WHEN COUNT(id) OVER (PARTITION BY name) > 1 AND name IS NOT NULL THEN CONCAT(name, ' - ', RIGHT(vin, 6)) ELSE COALESCE(name, CONCAT('VIN ', vin)) end as __text \nFROM cars\nORDER BY display_priority ASC, name ASC, vin ASC;", "includeAll": false, "label": "Car", - "multi": false, "name": "car_id", "options": [], - "query": "SELECT name AS __text, id AS __value FROM cars ORDER BY display_priority ASC, name ASC;", + "query": "SELECT\n id as __value,\n CASE WHEN COUNT(id) OVER (PARTITION BY name) > 1 AND name IS NOT NULL THEN CONCAT(name, ' - ', RIGHT(vin, 6)) ELSE COALESCE(name, CONCAT('VIN ', vin)) end as __text \nFROM cars\nORDER BY display_priority ASC, name ASC, vin ASC;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -1508,19 +1357,12 @@ "definition": "select unit_of_length from settings limit 1;", "hide": 2, "includeAll": false, - "label": "", - "multi": false, "name": "length_unit", "options": [], "query": "select unit_of_length from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -1531,19 +1373,12 @@ "definition": "select unit_of_temperature from settings limit 1;", "hide": 2, "includeAll": false, - "label": "", - "multi": false, "name": "temp_unit", "options": [], "query": "select unit_of_temperature from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -1554,18 +1389,12 @@ "definition": "select preferred_range from settings limit 1;", "hide": 2, "includeAll": false, - "multi": false, "name": "preferred_range", "options": [], "query": "select preferred_range from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -1576,18 +1405,12 @@ "definition": "select base_url from settings limit 1;", "hide": 2, "includeAll": false, - "multi": false, "name": "base_url", "options": [], "query": "select base_url from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "allValue": "-1", @@ -1597,7 +1420,6 @@ "uid": "TeslaMate" }, "definition": "SELECT name AS __text, id AS __value FROM geofences ORDER BY name COLLATE \"C\" ASC;", - "hide": 0, "includeAll": true, "label": "Geofence", "multi": true, @@ -1606,21 +1428,14 @@ "query": "SELECT name AS __text, id AS __value FROM geofences ORDER BY name COLLATE \"C\" ASC;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": { - "selected": false, "text": "", "value": "" }, "description": "Type a text contained in Location", - "hide": 0, "label": "Location", "name": "location", "options": [ @@ -1631,31 +1446,15 @@ } ], "query": "", - "skipUrlSync": false, "type": "textbox" }, { - "allValue": "", - "current": { - "selected": true, - "text": [ - "All" - ], - "value": [ - "$__all" - ] - }, - "hide": 0, + "current": {}, "includeAll": true, "label": "Type", "multi": true, "name": "charge_type", "options": [ - { - "selected": true, - "text": "All", - "value": "$__all" - }, { "selected": false, "text": "AC", @@ -1668,37 +1467,30 @@ } ], "query": "AC, DC", - "queryValue": "", - "skipUrlSync": false, "type": "custom" }, { "current": { - "selected": false, - "text": "0", - "value": "0" + "text": "", + "value": "" }, - "hide": 0, "label": "Cost >=", "name": "cost", "options": [ { "selected": true, - "text": "0", - "value": "0" + "text": "", + "value": "" } ], - "query": "0", - "skipUrlSync": false, + "query": "", "type": "textbox" }, { "current": { - "selected": true, "text": "0", "value": "0" }, - "hide": 0, "label": "Duration (minutes) >=", "name": "min_duration_min", "options": [ @@ -1709,7 +1501,6 @@ } ], "query": "0", - "skipUrlSync": false, "type": "textbox" } ] @@ -1718,35 +1509,9 @@ "from": "now-3M", "to": "now" }, - "timeRangeUpdatedDuringEditOrView": false, - "timepicker": { - "refresh_intervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "time_options": [ - "5m", - "15m", - "1h", - "6h", - "12h", - "24h", - "2d", - "7d", - "30d" - ] - }, + "timepicker": {}, "timezone": "", "title": "Charges", "uid": "TSmNYvRRk", - "version": 4, - "weekStart": "" + "version": 1 } \ No newline at end of file diff --git a/grafana/dashboards/charging-stats.json b/grafana/dashboards/charging-stats.json index 420f63c132..9ab86241de 100644 --- a/grafana/dashboards/charging-stats.json +++ b/grafana/dashboards/charging-stats.json @@ -1,77 +1,16 @@ { - "__elements": {}, - "__requires": [ - { - "type": "panel", - "id": "geomap", - "name": "Geomap", - "version": "" - }, - { - "type": "grafana", - "id": "grafana", - "name": "Grafana", - "version": "11.1.0" - }, - { - "type": "datasource", - "id": "grafana-postgresql-datasource", - "name": "PostgreSQL", - "version": "1.0.0" - }, - { - "type": "panel", - "id": "heatmap", - "name": "Heatmap", - "version": "" - }, - { - "type": "panel", - "id": "piechart", - "name": "Pie chart", - "version": "" - }, - { - "type": "panel", - "id": "stat", - "name": "Stat", - "version": "" - }, - { - "type": "panel", - "id": "table", - "name": "Table", - "version": "" - }, - { - "type": "panel", - "id": "timeseries", - "name": "Time series", - "version": "" - }, - { - "type": "panel", - "id": "xychart", - "name": "XY Chart", - "version": "" - } - ], "annotations": { "list": [ { - "$$hashKey": "object:75", "builtIn": 1, - "datasource": "-- Grafana --", + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, "enable": true, "hide": true, "iconColor": "rgba(0, 211, 255, 1)", "name": "Annotations & Alerts", - "target": { - "limit": 100, - "matchAny": false, - "tags": [], - "type": "dashboard" - }, "type": "dashboard" } ] @@ -79,7 +18,6 @@ "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 1, - "id": null, "links": [ { "icon": "dashboard", @@ -87,7 +25,7 @@ "title": "TeslaMate", "tooltip": "", "type": "link", - "url": "[[base_url:raw]]" + "url": "${base_url:raw}" }, { "asDropdown": true, @@ -99,11 +37,9 @@ "type": "dashboards" } ], - "liveNow": false, "panels": [ { "collapsed": false, - "datasource": "TeslaMate", "gridPos": { "h": 1, "w": 24, @@ -128,8 +64,7 @@ "mode": "absolute", "steps": [ { - "color": "#c7d0d9", - "value": null + "color": "#c7d0d9" } ] }, @@ -139,7 +74,7 @@ }, "gridPos": { "h": 3, - "w": 5, + "w": 3, "x": 0, "y": 1 }, @@ -167,42 +102,38 @@ "textMode": "value", "wideLayout": true }, - "pluginVersion": "11.1.0", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, "rawSql": "SELECT\n\tcount(*)\nFROM\n\tcharging_processes\nWHERE\n\t$__timeFilter(end_date)\n\tAND charge_energy_added > 0.01\n\tAND car_id = $car_id;", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "efficiency" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "cars", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } } ], - "title": "Number of Charges", + "title": "# of Charges", "type": "stat" }, { @@ -217,8 +148,7 @@ "mode": "absolute", "steps": [ { - "color": "#c7d0d9", - "value": null + "color": "#c7d0d9" } ] }, @@ -228,8 +158,8 @@ }, "gridPos": { "h": 3, - "w": 5, - "x": 5, + "w": 3, + "x": 3, "y": 1 }, "id": 10, @@ -256,42 +186,38 @@ "textMode": "value", "wideLayout": true }, - "pluginVersion": "11.1.0", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, "rawSql": "SELECT\n\tsum(charge_energy_added)\nFROM\n\tcharging_processes\nWHERE\n\t$__timeFilter(end_date)\n\tAND car_id = $car_id;\n", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "efficiency" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "cars", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } } ], - "title": "Charged in total", + "title": "Total Energy added", "type": "stat" }, { @@ -307,8 +233,7 @@ "mode": "absolute", "steps": [ { - "color": "#c7d0d9", - "value": null + "color": "#c7d0d9" } ] }, @@ -319,7 +244,7 @@ "gridPos": { "h": 3, "w": 3, - "x": 10, + "x": 6, "y": 1 }, "id": 14, @@ -346,42 +271,38 @@ "textMode": "value", "wideLayout": true }, - "pluginVersion": "11.1.0", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT\n\tsum(cp.cost)\nFROM\n\tcharging_processes cp\nLEFT JOIN \n\taddresses addr ON addr.id = address_id\nLEFT JOIN\n geofences geo ON geo.id = geofence_id\nJOIN\n charges char ON char.charging_process_id = cp.id AND char.date = end_date\t\nWHERE\n $__timeFilter(end_date)\n AND (addr.name ILIKE '%supercharger%' OR geo.name ILIKE '%supercharger%' OR char.fast_charger_brand = 'Tesla')\n\tAND cp.cost IS NOT NULL\n\tAND cp.car_id = $car_id;", + "rawSql": "SELECT\n\tCOALESCE(sum(cp.cost),0)\nFROM\n\tcharging_processes cp\nLEFT JOIN \n\taddresses addr ON addr.id = address_id\nLEFT JOIN\n geofences geo ON geo.id = geofence_id\nJOIN\n charges char ON char.charging_process_id = cp.id AND char.date = end_date\t\nWHERE\n $__timeFilter(end_date)\n AND (addr.name ILIKE '%supercharger%' OR geo.name ILIKE '%supercharger%' OR char.fast_charger_brand = 'Tesla')\n\tAND NULLIF(char.charger_phases, 0) IS NULL\n\tAND char.fast_charger_type != 'ACSingleWireCAN'\n\tAND cp.cost IS NOT NULL\n\tAND cp.car_id = $car_id;", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "latitude" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "addresses", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } } ], - "title": "Charging Cost at SuC", + "title": "SuC Charging Cost", "type": "stat" }, { @@ -397,8 +318,7 @@ "mode": "absolute", "steps": [ { - "color": "#c7d0d9", - "value": null + "color": "#c7d0d9" } ] }, @@ -409,7 +329,7 @@ "gridPos": { "h": 3, "w": 3, - "x": 13, + "x": 9, "y": 1 }, "id": 27, @@ -436,39 +356,35 @@ "textMode": "value", "wideLayout": true }, - "pluginVersion": "11.1.0", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, "rawSql": "SELECT\n\tsum(cost)\nFROM\n\tcharging_processes\nWHERE\n\t$__timeFilter(end_date)\n\tAND car_id = $car_id;", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "latitude" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "addresses", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } } ], "title": "Total Charging Cost", @@ -488,8 +404,7 @@ "mode": "absolute", "steps": [ { - "color": "#d8d9da", - "value": null + "color": "#d8d9da" } ] } @@ -498,8 +413,8 @@ }, "gridPos": { "h": 3, - "w": 2, - "x": 16, + "w": 3, + "x": 12, "y": 1 }, "id": 26, @@ -520,42 +435,38 @@ "textMode": "auto", "wideLayout": true }, - "pluginVersion": "11.1.0", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT (\n SELECT sum(cost)\n FROM charging_processes\n WHERE $__timeFilter(end_date) AND car_id = $car_id\n) / (\n\tSELECT convert_km((max(odometer) - min(odometer))::numeric, '$length_unit')\n\tFROM positions\n\tWHERE $__timeFilter(date) AND car_id = $car_id\n) * 100", + "rawSql": "SELECT (\n SELECT sum(cost)\n FROM charging_processes\n WHERE $__timeFilter(end_date) AND car_id = $car_id\n) / (\n\tSELECT convert_km((max(end_km) - min(start_km))::numeric, '$length_unit')\n\tFROM drives\n\tWHERE $__timeFilter(end_date) AND car_id = $car_id\n) * 100", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "efficiency" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "cars", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } } ], - "title": "Cost per 100 $length_unit", + "title": "Ø Cost per 100 $length_unit", "type": "stat" }, { @@ -574,8 +485,7 @@ "mode": "absolute", "steps": [ { - "color": "#d8d9da", - "value": null + "color": "#d8d9da" } ] } @@ -584,8 +494,8 @@ }, "gridPos": { "h": 3, - "w": 2, - "x": 18, + "w": 3, + "x": 15, "y": 1 }, "id": 31, @@ -606,32 +516,38 @@ "textMode": "auto", "wideLayout": true }, - "pluginVersion": "11.1.0", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, "rawSql": "SELECT (\n SELECT sum(cost)\n FROM charging_processes\n WHERE $__timeFilter(end_date) AND car_id = $car_id\n) / (\n SELECT sum(greatest(charge_energy_added, charge_energy_used))\n FROM charging_processes\n WHERE $__timeFilter(end_date) AND car_id = $car_id\n)", "refId": "A", - "table": "drives", - "timeColumn": "start_date", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } } ], - "title": "Average Cost per kWh", + "title": "Ø Cost per kWh", "type": "stat" }, { @@ -650,8 +566,7 @@ "mode": "absolute", "steps": [ { - "color": "#d8d9da", - "value": null + "color": "#d8d9da" } ] } @@ -660,8 +575,8 @@ }, "gridPos": { "h": 3, - "w": 2, - "x": 20, + "w": 3, + "x": 18, "y": 1 }, "id": 32, @@ -682,7 +597,7 @@ "textMode": "auto", "wideLayout": true }, - "pluginVersion": "11.1.0", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { @@ -691,8 +606,6 @@ }, "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, "rawSql": "WITH data AS (\n SELECT\n\t\tcp.id,\n cp.cost,\n cp.end_date,\n cp.car_id,\n cp.charge_energy_added,\n cp.charge_energy_used,\n\t\tCASE WHEN NULLIF(mode() within group (order by charger_phases),0) is null THEN 'DC'\n\t\t\t\t ELSE 'AC'\n\t\tEND AS current\n\tFROM charging_processes cp\n RIGHT JOIN charges ON cp.id = charges.charging_process_id\n WHERE\n\t cp.car_id = $car_id\n\t AND $__timeFilter(end_date)\n GROUP BY 1\n)\nSELECT (\n SELECT \n sum(cost)\n FROM data\n WHERE $__timeFilter(end_date) AND car_id = $car_id AND current = 'DC'\n)/(\n SELECT sum(greatest(charge_energy_added, charge_energy_used))\n FROM data\n WHERE $__timeFilter(end_date) AND car_id = $car_id AND current = 'DC'\n )", "refId": "A", @@ -712,20 +625,10 @@ } ], "limit": 50 - }, - "table": "drives", - "timeColumn": "start_date", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + } } ], - "title": "DC Avg Cost per kWh", + "title": "Ø Cost per kWh DC", "type": "stat" }, { @@ -744,8 +647,7 @@ "mode": "absolute", "steps": [ { - "color": "#d8d9da", - "value": null + "color": "#d8d9da" } ] } @@ -754,8 +656,8 @@ }, "gridPos": { "h": 3, - "w": 2, - "x": 22, + "w": 3, + "x": 21, "y": 1 }, "id": 33, @@ -776,7 +678,7 @@ "textMode": "auto", "wideLayout": true }, - "pluginVersion": "11.1.0", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { @@ -785,8 +687,6 @@ }, "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, "rawSql": "WITH data AS (\n SELECT\n\t\tcp.id,\n cp.cost,\n cp.end_date,\n cp.car_id,\n cp.charge_energy_added,\n cp.charge_energy_used,\n\t\tCASE WHEN NULLIF(mode() within group (order by charger_phases),0) is null THEN 'DC'\n\t\t\t\t ELSE 'AC'\n\t\tEND AS current\n\tFROM charging_processes cp\n RIGHT JOIN charges ON cp.id = charges.charging_process_id\n WHERE\n\t cp.car_id = $car_id\n\t AND $__timeFilter(end_date)\n GROUP BY 1\n)\nSELECT (\n SELECT \n sum(cost)\n FROM data\n WHERE $__timeFilter(end_date) AND car_id = $car_id AND current = 'AC'\n)/(\n SELECT sum(greatest(charge_energy_added, charge_energy_used))\n FROM data\n WHERE $__timeFilter(end_date) AND car_id = $car_id AND current = 'AC'\n )", "refId": "A", @@ -806,33 +706,13 @@ } ], "limit": 50 - }, - "table": "drives", - "timeColumn": "start_date", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + } } ], - "title": "AC Avg Cost per kWh", + "title": "Ø Cost per kWh AC", "type": "stat" }, { - "cards": {}, - "color": { - "cardColor": "#b4ff00", - "colorScale": "linear", - "colorScheme": "interpolateGreens", - "exponent": 0.5, - "min": 0, - "mode": "opacity" - }, - "dataFormat": "timeseries", "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" @@ -858,13 +738,7 @@ "x": 0, "y": 4 }, - "heatmap": {}, - "hideZeroBuckets": false, - "highlightCards": true, "id": 15, - "legend": { - "show": false - }, "options": { "calculate": true, "calculation": { @@ -911,60 +785,40 @@ "unit": "short" } }, - "pluginVersion": "11.1.0", - "reverseYBuckets": false, + "pluginVersion": "11.6.1", "targets": [ { "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "time_series", - "group": [], - "metricColumn": "none", "rawQuery": true, "rawSql": "SELECT\n\t$__time(start_date),\n\tstart_battery_level,\n\tend_battery_level\nFROM\n\tcharging_processes\nWHERE\n\t$__timeFilter(start_date)\n\tAND duration_min > 3\n\tAND car_id = $car_id\nORDER BY\n\tstart_date;", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "charge_energy_added" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "charges", - "timeColumn": "date", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } } ], "timeFrom": "6M", "title": "Charge Heatmap", - "tooltip": { - "show": true, - "showHistogram": false - }, - "type": "heatmap", - "xAxis": { - "show": true - }, - "yAxis": { - "format": "short", - "logBase": 1, - "max": "100", - "show": true - }, - "yBucketBound": "auto", - "yBucketSize": 10.00001 + "type": "heatmap" }, { "datasource": { @@ -983,6 +837,7 @@ "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, + "barWidthFactor": 0.6, "drawStyle": "line", "fillOpacity": 35, "gradientMode": "none", @@ -1015,8 +870,7 @@ "mode": "absolute", "steps": [ { - "color": "transparent", - "value": null + "color": "transparent" } ] }, @@ -1079,44 +933,41 @@ "showLegend": false }, "tooltip": { + "hideZeros": false, "maxHeight": 600, "mode": "multi", "sort": "desc" } }, - "pluginVersion": "8.5.4", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "time_series", - "group": [], - "metricColumn": "none", "rawQuery": true, "rawSql": "WITH charges AS (\n\tSELECT\n\t\tstart_date,\n\t\tstart_battery_level,\n\t\tend_battery_level,\n\t\tp.odometer,\n\t\tCOALESCE(\n\t\t\tLAG(p.odometer) OVER (\n\t\t\t\tORDER BY cp.start_date\n\t\t\t),\n\t\t\tp.odometer\n\t\t) as odometer_prev\n\tFROM\n\t\tcharging_processes cp\n\tJOIN positions p\n\tON p.id = cp.position_id\n\tWHERE\n\t\t$__timeFilter(cp.start_date)\n\t\tAND cp.duration_min > 3\n\t\tAND cp.car_id = $car_id\n)\nSELECT\n\tMIN(start_date) as time,\n\tMIN(start_battery_level) as \"Start SOC\",\n\tMAX(end_battery_level) as \"End SOC\"\nFROM charges\nGROUP BY\n\tCASE WHEN odometer - odometer_prev < 2 THEN odometer_prev ELSE odometer END\nORDER BY\n\ttime;", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "charge_energy_added" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "charges", - "timeColumn": "date", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } }, { "datasource": { @@ -1125,7 +976,6 @@ }, "editorMode": "code", "format": "table", - "hide": false, "rawQuery": true, "rawSql": "SELECT\r\n 20 as lower,\r\n CASE WHEN lfp_battery THEN 100 ELSE 80 END as upper\r\nfrom cars inner join car_settings on cars.settings_id = car_settings.id\r\nwhere cars.id = $car_id", "refId": "B", @@ -1201,7 +1051,7 @@ "viz": false } }, - "decimals": 0, + "decimals": 2, "mappings": [], "unit": "kwatth" }, @@ -1230,7 +1080,7 @@ { "id": "color", "value": { - "fixedColor": "#FADE2A", + "fixedColor": "light-orange", "mode": "fixed" } } @@ -1269,46 +1119,44 @@ "values": false }, "tooltip": { + "hideZeros": false, "maxHeight": 600, "mode": "single", "sort": "none" } }, + "pluginVersion": "11.6.1", "targets": [ { "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "time_series", - "group": [], - "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH data AS (\n SELECT\n\t\tcp.id,\n\t\tcp.charge_energy_added,\n\t\tCASE WHEN NULLIF(mode() within group (order by charger_phases),0) is null THEN 'DC'\n\t\t\t\t ELSE 'AC'\n\t\tEND AS current\n\tFROM charging_processes cp\n RIGHT JOIN charges ON cp.id = charges.charging_process_id\n WHERE\n\t cp.car_id = $car_id\n\t AND cp.charge_energy_added > 0.01\n\t AND $__timeFilter(start_date)\n GROUP BY 1,2\n)\nSELECT\n\tnow() AS time,\n\tsum(charge_energy_added) AS value,\n\tcurrent AS metric\nFROM data\nGROUP BY 3\nORDER BY metric DESC;", + "rawSql": "WITH data AS (\n SELECT\n\t\tcp.id,\n\t\tcp.charge_energy_added,\n\t\tCASE WHEN NULLIF(mode() within group (order by charger_phases),0) is null THEN 'DC'\n\t\t\t\t ELSE 'AC'\n\t\tEND AS current,\n\t\tcp.charge_energy_used\n\tFROM charging_processes cp\n RIGHT JOIN charges ON cp.id = charges.charging_process_id\n WHERE\n\t cp.car_id = $car_id\n\t AND cp.charge_energy_added > 0.01\n\t AND $__timeFilter(start_date)\n GROUP BY 1,2\n)\nSELECT\n\tnow() AS time,\n\tSUM(GREATEST(charge_energy_added, charge_energy_used)) AS value,\n\tcurrent AS metric\nFROM data\nGROUP BY 3\nORDER BY metric DESC;", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "latitude" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "addresses", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } } ], - "title": "AC/DC - kWh", + "title": "AC/DC - Energy Used", "type": "piechart" }, { @@ -1334,8 +1182,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" } ] }, @@ -1454,7 +1301,7 @@ "zoom": 15 } }, - "pluginVersion": "11.1.0", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { @@ -1463,21 +1310,9 @@ }, "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, "rawSql": "WITH charge_data AS (\r\nSELECT COALESCE(geofence.name, CONCAT_WS(', ', COALESCE(address.name, nullif(CONCAT_WS(' ', address.road, address.house_number), '')), address.city)) AS loc_nm\r\n, AVG(position.latitude) AS latitude\r\n, AVG(position.longitude) AS longitude\r\n, sum(charge.charge_energy_added) AS chg_total\r\n, count(*) as charges\r\nFROM charging_processes charge\r\nLEFT JOIN addresses address ON charge.address_id = address.id\r\nLEFT JOIN positions position ON charge.position_id = position.id\r\nLEFT JOIN geofences geofence ON charge.geofence_id = geofence.id\r\nWHERE $__timeFilter(charge.start_date) \r\nAND charge.car_id = $car_id\r\nGROUP BY COALESCE(geofence.name, CONCAT_WS(', ', COALESCE(address.name, nullif(CONCAT_WS(' ', address.road, address.house_number), '')), address.city))\r\n) \r\nSELECT loc_nm\r\n\t,latitude\r\n\t,longitude\r\n\t,chg_total\r\n\t,chg_total * 1.0 / (SELECT sum(chg_total) FROM charge_data) * 100 AS pct\r\n\t,charges\r\nFROM charge_data", "refId": "A", - "select": [ - [ - { - "params": [ - "efficiency" - ], - "type": "column" - } - ] - ], "sql": { "columns": [ { @@ -1494,17 +1329,7 @@ } ], "limit": 50 - }, - "table": "cars", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + } } ], "title": "Charging heat map by kWh", @@ -1556,7 +1381,7 @@ { "id": "color", "value": { - "fixedColor": "#FADE2A", + "fixedColor": "light-orange", "mode": "fixed" } } @@ -1595,43 +1420,41 @@ "values": false }, "tooltip": { + "hideZeros": false, "maxHeight": 600, "mode": "single", "sort": "none" } }, + "pluginVersion": "11.6.1", "targets": [ { "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "time_series", - "group": [], - "metricColumn": "none", "rawQuery": true, "rawSql": "WITH data AS (\n SELECT\n\t\tcp.id,\n\t\tcp.duration_min,\n\t\tCASE WHEN NULLIF(mode() within group (order by charger_phases),0) is null THEN 'DC'\n\t\t\t\t ELSE 'AC'\n\t\tEND AS current\n\tFROM charging_processes cp\n RIGHT JOIN charges ON cp.id = charges.charging_process_id\n WHERE\n\t cp.car_id = $car_id\n\t AND cp.charge_energy_added > 0.01\n\t AND $__timeFilter(start_date)\n GROUP BY 1,2\n)\nSELECT\n\tnow() AS time,\n\tsum(duration_min) * 60 AS value,\n\tcurrent AS metric\nFROM data\nGROUP BY 3\nORDER BY metric DESC;", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "latitude" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "addresses", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } } ], "title": "AC/DC - Duration", @@ -1654,14 +1477,17 @@ "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", + "fillOpacity": 50, "hideFrom": { "legend": false, "tooltip": false, "viz": false }, + "pointShape": "circle", "pointSize": { "fixed": 3 }, + "pointStrokeWidth": 1, "scaleDistribution": { "type": "linear" }, @@ -1673,8 +1499,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -1695,11 +1520,47 @@ "value": [ { "title": "Show charge details", - "url": "d/BHhxFeZRz?from=${__data.fields.start_date.numeric}&to=${__data.fields.end_date.numeric}&var-car_id=${car_id}&var-charging_process_id=${__data.fields.charging_process_id.numeric}" + "url": "/d/BHhxFeZRz/charge-details?from=${__data.fields.start_date.numeric}&to=${__data.fields.end_date.numeric}&var-car_id=${car_id}&var-charging_process_id=${__data.fields.charging_process_id.numeric}" } ] } ] + }, + { + "matcher": { + "id": "byFrameRefID", + "options": "B" + }, + "properties": [ + { + "id": "custom.pointSize.fixed", + "value": 15 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Power" + }, + "properties": [ + { + "id": "unit", + "value": "kwatt" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "SoC" + }, + "properties": [ + { + "id": "unit", + "value": "percent" + } + ] } ] }, @@ -1711,72 +1572,86 @@ }, "id": 29, "options": { - "dims": { - "exclude": [ - "charging_process_id" - ], - "frame": 0 - }, "legend": { "calcs": [], "displayMode": "list", "placement": "bottom", "showLegend": false }, + "mapping": "manual", "series": [ { - "pointColor": { - "field": "Power [kW]" + "color": { + "matcher": { + "id": "byName", + "options": "Power" + } + }, + "frame": { + "matcher": { + "id": "byIndex", + "options": 0 + } }, - "x": "SOC [%]", - "y": "Power [kW]" + "x": { + "matcher": { + "id": "byName", + "options": "SoC" + } + }, + "y": { + "matcher": { + "id": "byName", + "options": "Power" + } + } }, { - "pointColor": { - "field": "B - Avg Power [kW]" + "color": { + "matcher": { + "id": "byName", + "options": "Power" + } }, - "pointSize": { - "fixed": 15, - "max": 100, - "min": 1 + "frame": { + "matcher": { + "id": "byIndex", + "options": 1 + } + }, + "x": { + "matcher": { + "id": "byName", + "options": "SoC" + } }, - "x": "B - SOC [%]", - "y": "B - Avg Power [kW]" + "y": { + "matcher": { + "id": "byName", + "options": "Power" + } + } } ], - "seriesMapping": "manual", "tooltip": { + "hideZeros": false, "maxHeight": 600, "mode": "single", "sort": "none" } }, - "pluginVersion": "7.5.11", + "pluginVersion": "11.6.1", "targets": [ { - "alias": "", "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, "editorMode": "code", "format": "table", - "group": [], - "hide": false, - "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT\r\n c.battery_level as \"SOC [%]\",\r\n round(avg(c.charger_power), 0) as \"Power [kW]\",\r\n c.charging_process_id as \"charging_process_id\",\r\n p.start_date as \"start_date\",\r\n p.end_date as \"end_date\",\r\n COALESCE(g.name, a.name) || ' ' || to_char(c.date, 'YYYY-MM-dd') as \"Charge\"\r\nFROM\r\n charges c\r\nJOIN charging_processes p ON p.id = c.charging_process_id \r\nJOIN addresses a ON a.id = p.address_id\r\nLEFT JOIN geofences g ON g.id = p.geofence_id\r\nWHERE\r\n $__timeFilter(date)\r\n AND p.car_id = $car_id\r\n AND charger_power > 0\r\n AND c.fast_charger_present\r\nGROUP BY c.battery_level, c.charging_process_id, a.name, g.name, p,start_date, p.end_date, to_char(c.date, 'YYYY-MM-dd')", + "rawSql": "SELECT\r\n c.battery_level as \"SoC\",\r\n round(avg(c.charger_power), 0) as \"Power\",\r\n c.charging_process_id as \"charging_process_id\",\r\n p.start_date as \"start_date\",\r\n p.end_date as \"end_date\",\r\n COALESCE(g.name, a.name) || ' ' || to_char(timezone('$__timezone', timezone('UTC', c.date)), 'YYYY-MM-dd') as \"Charge\"\r\nFROM\r\n charges c\r\nJOIN charging_processes p ON p.id = c.charging_process_id \r\nJOIN addresses a ON a.id = p.address_id\r\nLEFT JOIN geofences g ON g.id = p.geofence_id\r\nWHERE\r\n $__timeFilter(date)\r\n AND p.car_id = $car_id\r\n AND charger_power > 0\r\n AND c.fast_charger_present\r\nGROUP BY c.battery_level, c.charging_process_id, a.name, g.name, p,start_date, p.end_date, to_char(timezone('$__timezone', timezone('UTC', c.date)), 'YYYY-MM-dd')", "refId": "A", - "select": [ - [ - { - "params": [ - "value" - ], - "type": "column" - } - ] - ], "sql": { "columns": [ { @@ -1793,48 +1668,35 @@ } ], "limit": 50 - }, - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + } }, { "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "table", - "group": [], - "hide": false, - "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT\n c.battery_level as \"B - SOC [%]\",\n PERCENTILE_CONT(0.5) WITHIN GROUP(ORDER BY charger_power) as \"B - Avg Power [kW]\"\nFROM\n charges c\njoin\n charging_processes p ON p.id = c.charging_process_id \nWHERE\n $__timeFilter(date)\n AND p.car_id = $car_id\n AND charger_power > 0\n AND c.fast_charger_present\nGROUP BY battery_level", + "rawSql": "SELECT\n c.battery_level as \"SoC\",\n PERCENTILE_CONT(0.5) WITHIN GROUP(ORDER BY charger_power) as \"Power\"\nFROM\n charges c\njoin\n charging_processes p ON p.id = c.charging_process_id \nWHERE\n $__timeFilter(date)\n AND p.car_id = $car_id\n AND charger_power > 0\n AND c.fast_charger_present\nGROUP BY battery_level", "refId": "B", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "efficiency" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "cars", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } } ], "title": "DC Charging Curve", @@ -1852,15 +1714,15 @@ "cellOptions": { "type": "auto" }, - "inspect": false + "inspect": false, + "minWidth": 50 }, "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" } ] } @@ -1874,7 +1736,7 @@ "properties": [ { "id": "custom.width", - "value": 70 + "value": 50 }, { "id": "displayName", @@ -1906,7 +1768,8 @@ "id": "custom.cellOptions", "value": { "mode": "gradient", - "type": "gauge" + "type": "gauge", + "valueDisplayMode": "text" } }, { @@ -1915,6 +1778,10 @@ { "id": "min", "value": 0 + }, + { + "id": "custom.align", + "value": "left" } ] } @@ -1940,37 +1807,35 @@ "showHeader": true, "sortBy": [] }, - "pluginVersion": "11.1.0", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, "rawSql": "SELECT\n\tROUND(end_battery_level / 5, 0) * 5 AS SOC,\n\tcount(*) AS n\nFROM\n\tcharging_processes\nWHERE\n\t$__timeFilter(end_date)\n\tAND duration_min > 3\n\tAND car_id = $car_id\nGROUP BY\n\tROUND(end_battery_level / 5, 0) * 5\nORDER BY\n\tSOC DESC", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "value" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } }, { "datasource": { @@ -1979,7 +1844,6 @@ }, "editorMode": "code", "format": "table", - "hide": false, "rawQuery": true, "rawSql": "SELECT\r\n CASE WHEN lfp_battery THEN 100 ELSE 81 END as high,\r\n CASE WHEN lfp_battery THEN 100 ELSE 91 END as highest\r\nfrom cars inner join car_settings on cars.settings_id = car_settings.id\r\nwhere cars.id = $car_id", "refId": "B", @@ -2044,15 +1908,15 @@ "cellOptions": { "type": "auto" }, - "inspect": false + "inspect": false, + "minWidth": 50 }, "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -2082,8 +1946,7 @@ "mode": "absolute", "steps": [ { - "color": "red", - "value": null + "color": "red" }, { "color": "#EAB839", @@ -2104,7 +1967,7 @@ }, { "id": "custom.width", - "value": 70 + "value": 50 } ] }, @@ -2128,6 +1991,10 @@ { "id": "min", "value": 0 + }, + { + "id": "custom.align", + "value": "left" } ] } @@ -2153,37 +2020,35 @@ "showHeader": true, "sortBy": [] }, - "pluginVersion": "11.1.0", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, "rawSql": "SELECT\n\tROUND(start_battery_level / 5, 0) * 5 AS SOC,\n\tcount(*) AS n\nFROM\n\tcharging_processes\nWHERE\n\t$__timeFilter(end_date)\n\tAND duration_min > 3\n\tAND car_id = $car_id\nGROUP BY\n 1\nORDER BY\n\tSOC DESC", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "value" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } } ], "title": "Discharge Stats", @@ -2208,8 +2073,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -2248,6 +2112,14 @@ { "id": "custom.align", "value": "left" + }, + { + "id": "unit", + "value": "kwatth" + }, + { + "id": "decimals", + "value": 2 } ] } @@ -2272,37 +2144,35 @@ }, "showHeader": true }, - "pluginVersion": "11.1.0", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT\n\tCOALESCE(geofence.name, CONCAT_WS(', ', COALESCE(address.name, nullif(CONCAT_WS(' ', address.road, address.house_number), '')), address.city)) AS location,\n\tCASE\n WHEN SUM(charge_energy_added) < 1000 THEN SUM(charge_energy_added)::NUMERIC(4,0)::VARCHAR || ' kWh' \n WHEN SUM(charge_energy_added) < 1000000 THEN (SUM(charge_energy_added) / 1000)::NUMERIC(9, 3)::VARCHAR || ' MWh' \n WHEN SUM(charge_energy_added) >= 1000000 THEN (SUM(charge_energy_added) / 1000000)::NUMERIC(9, 3)::VARCHAR || ' GWh' \n END as charge_energy_added\nFROM\n\tcharging_processes c\nLEFT JOIN addresses address ON c.address_id = address.id\nLEFT JOIN geofences geofence ON geofence_id = geofence.id\nWHERE\n\t$__timeFilter(end_date)\n\tAND car_id = $car_id\nGROUP BY\n\t1\nORDER BY\n\tSUM(charge_energy_added) DESC\nLIMIT 17;", + "rawSql": "SELECT\n\tCOALESCE(geofence.name, CONCAT_WS(', ', COALESCE(address.name, nullif(CONCAT_WS(' ', address.road, address.house_number), '')), address.city)) AS location,\n sum(charge_energy_added) as charge_energy_added\nFROM\n\tcharging_processes c\nLEFT JOIN addresses address ON c.address_id = address.id\nLEFT JOIN geofences geofence ON geofence_id = geofence.id\nWHERE\n\t$__timeFilter(end_date)\n\tAND car_id = $car_id\nGROUP BY\n\t1\nORDER BY\n\tSUM(charge_energy_added) DESC\nLIMIT 17;", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "value" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } } ], "title": "Top Charging Stations (Charged)", @@ -2327,8 +2197,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -2395,47 +2264,44 @@ }, "showHeader": true }, - "pluginVersion": "11.1.0", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, "rawSql": "SELECT\n\tCOALESCE(geofence.name, CONCAT_WS(', ', COALESCE(address.name, CONCAT_WS(' ', address.road, address.house_number)), address.city)) AS location,\n\tsum(cost) as cost\nFROM\n\tcharging_processes c\n\tLEFT JOIN addresses address ON c.address_id = address.id\n\tLEFT JOIN geofences geofence ON geofence_id = geofence.id\nWHERE\n $__timeFilter(end_date) AND\n\tcar_id = $car_id AND\n\tCOST IS NOT NULL\nGROUP BY\n\t1\nORDER BY\n\t2 DESC NULLS LAST\nLIMIT 17;", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "efficiency" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "cars", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } } ], "title": "Top Charging Stations (Cost)", "type": "table" } ], - "refresh": false, - "schemaVersion": 39, + "preload": false, + "refresh": "", + "schemaVersion": 41, "tags": [ "tesla" ], @@ -2447,22 +2313,16 @@ "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, - "definition": "SELECT name AS __text, id AS __value FROM cars ORDER BY display_priority ASC, name ASC;", + "definition": "SELECT\n id as __value,\n CASE WHEN COUNT(id) OVER (PARTITION BY name) > 1 AND name IS NOT NULL THEN CONCAT(name, ' - ', RIGHT(vin, 6)) ELSE COALESCE(name, CONCAT('VIN ', vin)) end as __text \nFROM cars\nORDER BY display_priority ASC, name ASC, vin ASC;", "hide": 2, "includeAll": true, "label": "Car", - "multi": false, "name": "car_id", "options": [], - "query": "SELECT name AS __text, id AS __value FROM cars ORDER BY display_priority ASC, name ASC;", + "query": "SELECT\n id as __value,\n CASE WHEN COUNT(id) OVER (PARTITION BY name) > 1 AND name IS NOT NULL THEN CONCAT(name, ' - ', RIGHT(vin, 6)) ELSE COALESCE(name, CONCAT('VIN ', vin)) end as __text \nFROM cars\nORDER BY display_priority ASC, name ASC, vin ASC;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -2473,19 +2333,12 @@ "definition": "select base_url from settings limit 1;", "hide": 2, "includeAll": false, - "label": "", - "multi": false, "name": "base_url", "options": [], "query": "select base_url from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -2496,18 +2349,12 @@ "definition": "select unit_of_length from settings limit 1;", "hide": 2, "includeAll": false, - "multi": false, "name": "length_unit", "options": [], "query": "select unit_of_length from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" } ] }, @@ -2515,35 +2362,9 @@ "from": "now-10y", "to": "now" }, - "timepicker": { - "hidden": false, - "refresh_intervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "time_options": [ - "5m", - "15m", - "1h", - "6h", - "12h", - "24h", - "2d", - "7d", - "30d" - ] - }, + "timepicker": {}, "timezone": "", "title": "Charging Stats", "uid": "-pkIkhmRz", - "version": 8, - "weekStart": "" + "version": 1 } \ No newline at end of file diff --git a/grafana/dashboards/database-info.json b/grafana/dashboards/database-info.json new file mode 100644 index 0000000000..9fdab0d4be --- /dev/null +++ b/grafana/dashboards/database-info.json @@ -0,0 +1,1695 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 1, + "links": [ + { + "icon": "dashboard", + "tags": [], + "title": "TeslaMate", + "tooltip": "", + "type": "link", + "url": "${base_url:raw}" + }, + { + "asDropdown": true, + "icon": "external link", + "tags": [ + "tesla" + ], + "title": "Dashboards", + "type": "dashboards" + } + ], + "panels": [ + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "super-light-blue" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 32, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/.*/", + "values": false + }, + "showPercentChange": false, + "textMode": "value_and_name", + "wideLayout": true + }, + "pluginVersion": "11.6.1", + "targets": [ + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT\n ROUND(convert_km(sum(distance)::numeric, '$length_unit'), 0) || ' $length_unit' as \"Logged\",\n ROUND(convert_km(max(end_km)::numeric, '$length_unit'), 0) || ' $length_unit' as \"Odometer\"\nfrom drives where car_id = $car_id;", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Mileage", + "type": "stat" + }, + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "super-light-blue" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 4, + "x": 6, + "y": 0 + }, + "id": 36, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/.*/", + "values": false + }, + "showPercentChange": false, + "textMode": "value_and_name", + "wideLayout": true + }, + "pluginVersion": "11.6.1", + "targets": [ + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT COUNT(id) AS \"Charges\" FROM charging_processes WHERE car_id=$car_id \n", + "refId": "Charges", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + }, + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT COUNT(id) AS \"Drives\" FROM drives WHERE car_id=$car_id ", + "refId": "Drives", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Stats", + "type": "stat" + }, + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "super-light-blue" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 10, + "y": 0 + }, + "id": 39, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/.*/", + "values": false + }, + "showPercentChange": false, + "textMode": "value_and_name", + "wideLayout": true + }, + "pluginVersion": "11.6.1", + "targets": [ + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT COUNT(id) as \"Nº Car Updates\"\nFROM Updates \nWHERE car_id = $car_id\n", + "refId": "Car Updates", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + }, + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "select split_part(version, ' ', 1) as \"Current Car Firmware\" \r\nfrom updates \r\nwhere car_id = $car_id \r\norder by start_date desc \r\nlimit 1", + "refId": "Firmware", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Software", + "type": "stat" + }, + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "description": "This means you have some **Drives** or **Charges** not closed.\nIf so, you may follow the official guide: \nManually fixing data", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "super-light-blue" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 4, + "x": 16, + "y": 0 + }, + "id": 42, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/.*/", + "values": false + }, + "showPercentChange": false, + "textMode": "value_and_name", + "wideLayout": true + }, + "pluginVersion": "11.6.1", + "targets": [ + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT COUNT(id) as \"Charges not closed\"\nFROM charging_processes \nWHERE car_id = $car_id AND end_date is null\n", + "refId": "Charges", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + }, + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT COUNT(id) AS \"Drives not closed\"\r\nFROM drives \r\nWHERE car_id = $car_id AND end_date is null", + "refId": "Drives", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Incomplete Data", + "type": "stat" + }, + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "super-light-blue" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 4, + "x": 20, + "y": 0 + }, + "id": 51, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [], + "fields": "/^version$/", + "values": true + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.6.1", + "targets": [ + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT regexp_replace(version(), 'PostgreSQL ([^ ]+) .*', '\\1') AS version;", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "PostgreSQL Version", + "type": "stat" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 4 + }, + "id": 34, + "panels": [], + "title": "Database Information", + "type": "row" + }, + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "right", + "cellOptions": { + "type": "auto" + }, + "inspect": false, + "minWidth": 80 + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Table" + }, + "properties": [ + { + "id": "custom.align", + "value": "auto" + }, + { + "id": "custom.minWidth", + "value": 160 + } + ] + } + ] + }, + "gridPos": { + "h": 16, + "w": 6, + "x": 0, + "y": 5 + }, + "id": 33, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "11.6.1", + "targets": [ + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT \r\n relname AS \"Table\",\r\n pg_relation_size(relid) as \"Data\",\r\n pg_indexes_size(relid) as \"Indexes\",\r\n pg_total_relation_size(relid) As \"Total\"\r\nFROM \r\n pg_catalog.pg_statio_user_tables\r\nORDER BY \r\n pg_total_relation_size(relid) DESC;", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "", + "type": "table" + }, + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "inspect": false, + "minWidth": 160 + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Row Count" + }, + "properties": [ + { + "id": "custom.align", + "value": "right" + }, + { + "id": "custom.minWidth", + "value": 100 + } + ] + } + ] + }, + "gridPos": { + "h": 16, + "w": 4, + "x": 6, + "y": 5 + }, + "id": 38, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "11.6.1", + "targets": [ + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT table_name AS \"Table Name\", \r\n (xpath('/row/cnt/text()', xml_count))[1]::text::int AS \"Row Count\"\r\nFROM (\r\n SELECT table_name, \r\n query_to_xml(format('SELECT count(*) as cnt FROM %I.%I', table_schema, table_name), false, true, '') AS xml_count\r\n FROM information_schema.tables\r\n WHERE table_schema NOT IN ('pg_catalog', 'information_schema') and table_type = 'BASE TABLE'\r\n) AS t\r\nORDER BY 2 DESC;", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "", + "type": "table" + }, + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "description": "These statistics can help you evaluate the efficiency of your indexes.\n\nIf your database experiences a lot of updates or deletions, like importing data from other sources or deleting a loaner car or some other similar situation, you might encounter index bloat, which can degrade performance. In such cases, reindexing could be beneficial.\n\nCheck Reindex Database", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "right", + "cellOptions": { + "type": "auto" + }, + "inspect": false, + "minWidth": 110 + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Index Size" + }, + "properties": [ + { + "id": "unit", + "value": "bytes" + }, + { + "id": "custom.minWidth", + "value": 100 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Table" + }, + "properties": [ + { + "id": "custom.align", + "value": "auto" + }, + { + "id": "custom.minWidth", + "value": 160 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Index" + }, + "properties": [ + { + "id": "custom.align", + "value": "auto" + }, + { + "id": "custom.minWidth", + "value": 350 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Tuples Fetched" + }, + "properties": [ + { + "id": "custom.minWidth", + "value": 130 + } + ] + } + ] + }, + "gridPos": { + "h": 19, + "w": 14, + "x": 10, + "y": 5 + }, + "id": 41, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "enablePagination": true, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "11.6.1", + "targets": [ + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT\r\n relname AS \"Table\",\r\n indexrelname AS \"Index\",\r\n idx_scan AS \"Index Scans\",\r\n idx_tup_read AS \"Tuples Read\",\r\n idx_tup_fetch AS \"Tuples Fetched\",\r\n PG_RELATION_SIZE(indexrelid) as \"Index Size\"\r\nFROM\r\n pg_stat_all_indexes\r\nWHERE\r\n schemaname NOT LIKE 'pg_%' AND\r\n indexrelname IS NOT NULL\r\nORDER BY 3 DESC;", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Indexes", + "type": "table" + }, + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "super-light-blue" + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 21 + }, + "id": 35, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/.*/", + "values": false + }, + "showPercentChange": false, + "textMode": "value", + "wideLayout": true + }, + "pluginVersion": "11.6.1", + "targets": [ + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT \n SUM(pg_total_relation_size(relid)) As \"Size\"\nFROM \n pg_catalog.pg_statio_user_tables;", + "refId": "DistanceLogged", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Database Total Size", + "type": "stat" + }, + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "super-light-blue" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 4, + "x": 6, + "y": 21 + }, + "id": 50, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/.*/", + "values": false + }, + "showPercentChange": false, + "textMode": "value", + "wideLayout": true + }, + "pluginVersion": "11.6.1", + "targets": [ + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "show timezone;", + "refId": "DistanceLogged", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Timezone", + "type": "stat" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 24 + }, + "id": 44, + "panels": [], + "title": "Statistics of SQL planning and execution", + "type": "row" + }, + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "super-light-blue" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 25 + }, + "id": 48, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [], + "fields": "/.*/", + "values": false + }, + "showPercentChange": false, + "textMode": "value", + "wideLayout": true + }, + "pluginVersion": "11.6.1", + "targets": [ + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "${pg_stat_statements_info_last_reset:raw}", + "refId": "Charges", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Time at which all statistics in the pg_stat_statements view were last reset", + "type": "stat" + }, + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "super-light-blue" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 6, + "y": 25 + }, + "id": 49, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [], + "fields": "/.*/", + "values": false + }, + "showPercentChange": false, + "textMode": "value", + "wideLayout": true + }, + "pluginVersion": "11.6.1", + "targets": [ + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "${pg_stat_statements_count:raw}", + "refId": "Charges", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Number of Statements tracked via pg_stat_statements", + "type": "stat" + }, + { + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 12, + "x": 12, + "y": 25 + }, + "id": 47, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "Check the [contribution docs](https://docs.teslamate.org/docs/development#enable-pg_stat_statements-to-collect-query-statistics) on how to enable tracking planning and execution statistics of all SQL statements executed by a server.\n", + "mode": "markdown" + }, + "pluginVersion": "11.6.1", + "title": "About pg_stat_statements (track statistics of SQL planning and execution)", + "type": "text" + }, + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto", + "wrapText": false + }, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ms" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Calls" + }, + "properties": [ + { + "id": "unit", + "value": "none" + }, + { + "id": "custom.width", + "value": 80 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Query" + }, + "properties": [ + { + "id": "custom.inspect", + "value": true + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Mean Exec Time" + }, + "properties": [ + { + "id": "custom.width", + "value": 140 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Total Exec Time" + }, + "properties": [ + { + "id": "custom.width", + "value": 140 + } + ] + } + ] + }, + "gridPos": { + "h": 14, + "w": 12, + "x": 0, + "y": 28 + }, + "id": 45, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "enablePagination": true, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "11.6.1", + "targets": [ + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "${pg_stat_statements_top_20_mean:raw}", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Top 20 Statements (by mean time spent executing the statement)", + "type": "table" + }, + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto", + "wrapText": false + }, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ms" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Calls" + }, + "properties": [ + { + "id": "unit", + "value": "none" + }, + { + "id": "custom.width", + "value": 80 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Query" + }, + "properties": [ + { + "id": "custom.inspect", + "value": true + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Mean Exec Time" + }, + "properties": [ + { + "id": "custom.width", + "value": 140 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Total Exec Time" + }, + "properties": [ + { + "id": "custom.width", + "value": 140 + } + ] + } + ] + }, + "gridPos": { + "h": 14, + "w": 12, + "x": 12, + "y": 28 + }, + "id": 46, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "enablePagination": true, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "11.6.1", + "targets": [ + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "${pg_stat_statements_top_20_total:raw}", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Top 20 Statements (by total time spent executing the statement)", + "type": "table" + } + ], + "preload": false, + "refresh": "", + "schemaVersion": 41, + "tags": [ + "tesla" + ], + "templating": { + "list": [ + { + "current": {}, + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "definition": "select unit_of_length from settings limit 1;", + "hide": 2, + "includeAll": false, + "name": "length_unit", + "options": [], + "query": "select unit_of_length from settings limit 1;", + "refresh": 1, + "regex": "", + "type": "query" + }, + { + "current": {}, + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "definition": "SELECT\n id as __value,\n CASE WHEN COUNT(id) OVER (PARTITION BY name) > 1 AND name IS NOT NULL THEN CONCAT(name, ' - ', RIGHT(vin, 6)) ELSE COALESCE(name, CONCAT('VIN ', vin)) end as __text \nFROM cars\nORDER BY display_priority ASC, name ASC, vin ASC;", + "includeAll": false, + "label": "Car", + "name": "car_id", + "options": [], + "query": "SELECT\n id as __value,\n CASE WHEN COUNT(id) OVER (PARTITION BY name) > 1 AND name IS NOT NULL THEN CONCAT(name, ' - ', RIGHT(vin, 6)) ELSE COALESCE(name, CONCAT('VIN ', vin)) end as __text \nFROM cars\nORDER BY display_priority ASC, name ASC, vin ASC;", + "refresh": 1, + "regex": "", + "type": "query" + }, + { + "current": {}, + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "definition": "SELECT\n CASE WHEN $pg_stat_statements_enabled = 1 THEN 'SELECT stats_reset FROM pg_stat_statements_info;' else 'SELECT NULL;' end", + "hide": 2, + "includeAll": false, + "name": "pg_stat_statements_info_last_reset", + "options": [], + "query": "SELECT\n CASE WHEN $pg_stat_statements_enabled = 1 THEN 'SELECT stats_reset FROM pg_stat_statements_info;' else 'SELECT NULL;' end", + "refresh": 1, + "regex": "", + "type": "query" + }, + { + "current": {}, + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "definition": "SELECT\n CASE WHEN $pg_stat_statements_enabled = 1 THEN 'SELECT count(*) FROM pg_stat_statements;' else 'SELECT NULL;' end", + "hide": 2, + "includeAll": false, + "name": "pg_stat_statements_count", + "options": [], + "query": "SELECT\n CASE WHEN $pg_stat_statements_enabled = 1 THEN 'SELECT count(*) FROM pg_stat_statements;' else 'SELECT NULL;' end", + "refresh": 1, + "regex": "", + "type": "query" + }, + { + "current": {}, + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "definition": "SELECT\n CASE WHEN $pg_stat_statements_enabled = 1 THEN 'select\n calls as \"Calls\",\n mean_exec_time as \"Mean Exec Time\",\n total_exec_time as \"Total Exec Time\",\n query as \"Query\"\nfrom pg_stat_statements\nORDER BY mean_exec_time desc\nlimit 20;' else 'SELECT NULL;' end", + "hide": 2, + "includeAll": false, + "name": "pg_stat_statements_top_20_mean", + "options": [], + "query": "SELECT\n CASE WHEN $pg_stat_statements_enabled = 1 THEN 'select\n calls as \"Calls\",\n mean_exec_time as \"Mean Exec Time\",\n total_exec_time as \"Total Exec Time\",\n query as \"Query\"\nfrom pg_stat_statements\nORDER BY mean_exec_time desc\nlimit 20;' else 'SELECT NULL;' end", + "refresh": 1, + "regex": "", + "type": "query" + }, + { + "current": {}, + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "definition": "SELECT\n CASE WHEN $pg_stat_statements_enabled = 1 THEN 'select\n calls as \"Calls\",\n mean_exec_time as \"Mean Exec Time\",\n total_exec_time as \"Total Exec Time\",\n query as \"Query\"\nfrom pg_stat_statements\nORDER BY total_exec_time desc\nlimit 20;' else 'SELECT NULL;' end", + "hide": 2, + "includeAll": false, + "name": "pg_stat_statements_top_20_total", + "options": [], + "query": "SELECT\n CASE WHEN $pg_stat_statements_enabled = 1 THEN 'select\n calls as \"Calls\",\n mean_exec_time as \"Mean Exec Time\",\n total_exec_time as \"Total Exec Time\",\n query as \"Query\"\nfrom pg_stat_statements\nORDER BY total_exec_time desc\nlimit 20;' else 'SELECT NULL;' end", + "refresh": 1, + "regex": "", + "type": "query" + }, + { + "current": {}, + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "definition": "SELECT EXISTS (\nSELECT 1\nFROM information_schema.tables\nWHERE table_name = 'pg_stat_statements'\n)::int AS table_existence", + "hide": 2, + "includeAll": false, + "name": "pg_stat_statements_enabled", + "options": [], + "query": "SELECT EXISTS (\nSELECT 1\nFROM information_schema.tables\nWHERE table_name = 'pg_stat_statements'\n)::int AS table_existence", + "refresh": 1, + "regex": "", + "type": "query" + }, + { + "current": {}, + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "definition": "select base_url from settings limit 1;", + "hide": 2, + "includeAll": false, + "name": "base_url", + "options": [], + "query": "select base_url from settings limit 1;", + "refresh": 1, + "regex": "", + "type": "query" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "hidden": true, + "refresh_intervals": [] + }, + "timezone": "browser", + "title": "Database Information", + "uid": "jchmDbInfo", + "version": 1 +} \ No newline at end of file diff --git a/grafana/dashboards/drive-stats.json b/grafana/dashboards/drive-stats.json index c19e225b5f..6b555d0614 100644 --- a/grafana/dashboards/drive-stats.json +++ b/grafana/dashboards/drive-stats.json @@ -1,38 +1,11 @@ { - "__elements": {}, - "__requires": [ - { - "type": "panel", - "id": "bargauge", - "name": "Bar gauge", - "version": "" - }, - { - "type": "grafana", - "id": "grafana", - "name": "Grafana", - "version": "11.0.0" - }, - { - "type": "datasource", - "id": "grafana-postgresql-datasource", - "name": "PostgreSQL", - "version": "1.0.0" - }, - { - "type": "panel", - "id": "stat", - "name": "Stat", - "version": "" - } - ], "annotations": { "list": [ { "builtIn": 1, "datasource": { - "type": "datasource", - "uid": "grafana" + "type": "grafana", + "uid": "-- Grafana --" }, "enable": true, "hide": true, @@ -45,7 +18,6 @@ "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 0, - "id": null, "links": [ { "icon": "dashboard", @@ -53,7 +25,7 @@ "title": "TeslaMate", "tooltip": "", "type": "link", - "url": "[[base_url:raw]]" + "url": "${base_url:raw}" }, { "asDropdown": true, @@ -66,34 +38,6 @@ } ], "panels": [ - { - "collapsed": false, - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" - }, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 0 - }, - "id": 12, - "panels": [], - "repeat": "car_id", - "repeatDirection": "h", - "targets": [ - { - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" - }, - "refId": "A" - } - ], - "title": "$car_id", - "type": "row" - }, { "datasource": { "type": "grafana-postgresql-datasource", @@ -106,8 +50,7 @@ "mode": "absolute", "steps": [ { - "color": "semi-dark-blue", - "value": null + "color": "semi-dark-blue" } ] }, @@ -119,7 +62,7 @@ "h": 4, "w": 8, "x": 0, - "y": 1 + "y": 0 }, "id": 20, "maxDataPoints": 100, @@ -133,6 +76,7 @@ "graphMode": "area", "justifyMode": "auto", "orientation": "horizontal", + "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "sum" @@ -144,42 +88,38 @@ "textMode": "value", "wideLayout": true }, - "pluginVersion": "11.0.0", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "time_series", - "group": [], - "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT\n date_trunc('day', start_date) as \"time\",\n count(*)\nFROM drives\nWHERE $__timeFilter(start_date) AND car_id = $car_id\nGROUP BY 1\nORDER BY 1;", + "rawSql": "WITH since as (\n\tSELECT timezone('UTC', min(start_date)) as date FROM drives\n\tWHERE car_id = $car_id\n\tGROUP BY car_id\n),\n\nactual AS (\n\tSELECT\n\t\tdate_trunc('day', timezone('UTC', start_date), '$__timezone') AS date,\n\t\tcount(*) AS number_of_drives\n\tFROM drives\n\tWHERE car_id = $car_id and $__timeFilter(start_date) and end_date is not null\n\tGROUP BY 1\n),\n\nbase_line AS (\n\tSELECT date from generate_series(date_trunc('day', (select date from since), '$__timezone'), date_trunc('day', timestamp with time zone $__timeTo(), '$__timezone'), '1 day'::interval, '$__timezone') date\n)\n\nSELECT\n base_line.date as time,\n\tCOALESCE(actual.number_of_drives, 0) as number_of_drives\nFROM base_line\nLEFT JOIN actual ON actual.date = base_line.date\nWHERE date_trunc('day', timestamp with time zone $__timeFrom(), '$__timezone') <= base_line.date\norder by base_line.date\n", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "latitude" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "addresses", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } } ], - "title": "Number of drives", + "title": "# of Drives", "type": "stat" }, { @@ -194,8 +134,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" } ] }, @@ -232,7 +171,7 @@ "h": 4, "w": 8, "x": 8, - "y": 1 + "y": 0 }, "id": 16, "maxDataPoints": 100, @@ -246,6 +185,7 @@ "graphMode": "area", "justifyMode": "auto", "orientation": "horizontal", + "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "sum" @@ -257,42 +197,38 @@ "textMode": "value", "wideLayout": true }, - "pluginVersion": "11.0.0", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "time_series", - "group": [], - "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH since as (\n\tSELECT date FROM positions\n\tWHERE car_id = $car_id\n\tORDER BY date ASC\n\tLIMIT 1\n),\nactual AS (\n\tSELECT\n\t\tdate_trunc('day', date)::date AS date,\n\t\tmax(odometer) - min(odometer) AS distance\n\tFROM positions\n\tWHERE car_id = $car_id\n\tGROUP BY 1\n),\nbase_line AS (\n\tSELECT date_trunc('day', dd)::date AS date\n FROM generate_series((select date from since) , now(), '1 day'::interval) dd\n)\nSELECT \n $__time(base_line.date), \n convert_km(COALESCE(distance, 0)::numeric, '$length_unit') as \"distance_$length_unit\"\nFROM base_line\nLEFT JOIN actual ON actual.date = base_line.date\nWHERE $__timeFilter(base_line.date)\nORDER BY 1;", + "rawSql": "WITH since as (\n\tSELECT timezone('UTC', min(start_date)) as date FROM drives\n\tWHERE car_id = $car_id\n\tGROUP BY car_id\n),\n\nactual AS (\n\tSELECT\n\t\tdate_trunc('day', timezone('UTC', start_date), '$__timezone') AS date,\n\t\tsum(distance) AS distance\n\tFROM drives\n\tWHERE car_id = $car_id and $__timeFilter(start_date) and end_date is not null\n\tGROUP BY 1\n),\n\nbase_line AS (\n\tSELECT date from generate_series(date_trunc('day', (select date from since), '$__timezone'), date_trunc('day', timestamp with time zone $__timeTo(), '$__timezone'), '1 day'::interval, '$__timezone') date)\n\nSELECT\n base_line.date as time,\n\tconvert_km(COALESCE(actual.distance, 0)::numeric, '$length_unit') as \"distance_$length_unit\"\nFROM base_line\nLEFT JOIN actual ON actual.date = base_line.date\nWHERE date_trunc('day', timestamp with time zone $__timeFrom(), '$__timezone') <= base_line.date\norder by base_line.date\n", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "latitude" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "addresses", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } } ], - "title": "$length_unit driven", + "title": "Total Distance logged", "type": "stat" }, { @@ -307,8 +243,7 @@ "mode": "absolute", "steps": [ { - "color": "semi-dark-yellow", - "value": null + "color": "semi-dark-yellow" } ] }, @@ -320,7 +255,7 @@ "h": 4, "w": 8, "x": 16, - "y": 1 + "y": 0 }, "id": 22, "maxDataPoints": 100, @@ -334,6 +269,7 @@ "graphMode": "area", "justifyMode": "auto", "orientation": "horizontal", + "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "sum" @@ -345,42 +281,38 @@ "textMode": "value", "wideLayout": true }, - "pluginVersion": "11.0.0", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "time_series", - "group": [], - "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT\n $__time(start_date),\n NULLIF(GREATEST(start_[[preferred_range]]_range_km - end_[[preferred_range]]_range_km, 0), 0) * car.efficiency AS energy\nFROM drives\nJOIN cars car ON car.id = car_id\nWHERE $__timeFilter(start_date) AND car_id = $car_id\nORDER BY 1", + "rawSql": "WITH since as (\n\tSELECT timezone('UTC', min(start_date)) as date FROM drives\n\tWHERE car_id = $car_id\n\tGROUP BY car_id\n),\n\nactual AS (\n\tSELECT\n\t\tdate_trunc('day', timezone('UTC', start_date), '$__timezone') AS date,\n\t\tsum(NULLIF(GREATEST(start_${preferred_range}_range_km - end_${preferred_range}_range_km, 0), 0) * cars.efficiency) AS energy\n\tFROM drives\n INNER JOIN cars on drives.car_id = cars.id\n\tWHERE car_id = $car_id and $__timeFilter(start_date) and end_date is not null\n\tGROUP BY 1\n),\n\nbase_line AS (\n\tSELECT date from generate_series(date_trunc('day', (select date from since), '$__timezone'), date_trunc('day', timestamp with time zone $__timeTo(), '$__timezone'), '1 day'::interval, '$__timezone') date)\n\nSELECT\n base_line.date as time,\n\tcoalesce(energy, 0) as energy\nFROM base_line\nLEFT JOIN actual ON actual.date = base_line.date\nWHERE date_trunc('day', timestamp with time zone $__timeFrom(), '$__timezone') <= base_line.date\norder by base_line.date\n", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "latitude" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "addresses", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } } ], - "title": "kWh used", + "title": "Total Energy consumed (net)", "type": "stat" }, { @@ -405,8 +337,7 @@ "mode": "absolute", "steps": [ { - "color": "#c7d0d9", - "value": null + "color": "#c7d0d9" } ] }, @@ -443,7 +374,7 @@ "h": 3, "w": 8, "x": 0, - "y": 5 + "y": 4 }, "id": 26, "maxDataPoints": 100, @@ -457,10 +388,9 @@ "graphMode": "none", "justifyMode": "auto", "orientation": "horizontal", + "percentChangeColorMode": "standard", "reduceOptions": { - "calcs": [ - "mean" - ], + "calcs": [], "fields": "", "values": false }, @@ -468,48 +398,45 @@ "textMode": "value", "wideLayout": true }, - "pluginVersion": "11.0.0", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT convert_km((percentile_disc(0.5) WITHIN GROUP (ORDER BY distance))::numeric, '$length_unit') as \"distance_$length_unit\"\nFROM drives\nWHERE car_id = $car_id AND $__timeFilter(start_date);", + "rawSql": "SELECT convert_km((percentile_cont(0.5) WITHIN GROUP (ORDER BY distance))::numeric, '$length_unit') as \"distance_$length_unit\"\nFROM drives\nWHERE car_id = $car_id AND $__timeFilter(start_date) AND end_date IS NOT NULL;", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "latitude" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "addresses", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } } ], - "title": "Average distance of a drive", + "title": "Median distance of a drive", "type": "stat" }, { "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" + "default": false, + "type": "datasource", + "uid": "-- Dashboard --" }, "fieldConfig": { "defaults": { @@ -518,8 +445,7 @@ "mode": "absolute", "steps": [ { - "color": "#c7d0d9", - "value": null + "color": "#c7d0d9" } ] }, @@ -556,7 +482,7 @@ "h": 3, "w": 8, "x": 8, - "y": 5 + "y": 4 }, "id": 8, "maxDataPoints": 100, @@ -570,6 +496,72 @@ "graphMode": "none", "justifyMode": "auto", "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "value", + "wideLayout": true + }, + "pluginVersion": "11.6.1", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "panelId": 16, + "refId": "A" + } + ], + "title": "Ø Distance driven per day", + "type": "stat" + }, + { + "datasource": { + "default": false, + "type": "datasource", + "uid": "-- Dashboard --" + }, + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "#c7d0d9" + } + ] + }, + "unit": "kwatth" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 8, + "x": 16, + "y": 4 + }, + "id": 14, + "maxDataPoints": 100, + "options": { + "colorMode": "value", + "fieldOptions": { + "calcs": [ + "lastNotNull" + ] + }, + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "mean" @@ -581,42 +573,257 @@ "textMode": "value", "wideLayout": true }, - "pluginVersion": "11.0.0", + "pluginVersion": "11.6.1", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "panelId": 22, + "refId": "A" + } + ], + "title": "Ø Energy consumed (net) per day", + "type": "stat" + }, + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "fieldConfig": { + "defaults": { + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "#c7d0d9" + } + ] + }, + "unit": "none" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "speed_kmh" + }, + "properties": [ + { + "id": "unit", + "value": "velocitykmh" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "speed_mih" + }, + "properties": [ + { + "id": "unit", + "value": "velocitymph" + } + ] + } + ] + }, + "gridPos": { + "h": 3, + "w": 8, + "x": 0, + "y": 7 + }, + "id": 33, + "maxDataPoints": 100, + "options": { + "colorMode": "value", + "fieldOptions": { + "calcs": [ + "mean" + ] + }, + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "value", + "wideLayout": true + }, + "pluginVersion": "11.6.1", "targets": [ { "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH since as (\n\tSELECT date FROM positions\n\tWHERE car_id = $car_id\n\tORDER BY date ASC\n\tLIMIT 1\n),\nactual AS (\n\tSELECT\n\t\tdate_trunc('day', start_date)::date AS date,\n\t\tsum(distance) AS distance\n\tFROM drives\n\tWHERE car_id = $car_id\n\tGROUP BY 1\n),\nbase_line AS (\n\tSELECT date_trunc('day', dd)::date AS date\n FROM generate_series((select date from since), NOW(), '1 day'::interval) dd\n),\ncombined as (\n SELECT base_line.date, COALESCE(actual.distance, 0) as distance\n FROM base_line\n LEFT JOIN actual ON actual.date = base_line.date\n WHERE $__timeFilter(base_line.date)\n)\nSELECT convert_km((percentile_disc(0.5) WITHIN GROUP (ORDER BY distance))::numeric, '$length_unit') AS \"distance_$length_unit\"\nFROM combined;", + "rawSql": "SELECT convert_km(max(speed_max), '$length_unit') AS speed_${length_unit}h\nFROM drives\nWHERE car_id = $car_id and $__timeFilter(start_date) and end_date is not null;", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "efficiency" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "cars", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Max Speed", + "type": "stat" + }, + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "fieldConfig": { + "defaults": { + "mappings": [ { - "name": "$__timeFilter", - "params": [], - "type": "macro" + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "#c7d0d9" + } + ] + }, + "unit": "none" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "speed_kmh" + }, + "properties": [ + { + "id": "unit", + "value": "velocitykmh" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "speed_mih" + }, + "properties": [ + { + "id": "unit", + "value": "velocitymph" + } + ] + } + ] + }, + "gridPos": { + "h": 3, + "w": 8, + "x": 8, + "y": 7 + }, + "id": 35, + "maxDataPoints": 100, + "options": { + "colorMode": "value", + "fieldOptions": { + "calcs": [ + "mean" ] + }, + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "value", + "wideLayout": true + }, + "pluginVersion": "11.6.1", + "targets": [ + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT convert_km(max(speed_max), '$length_unit') AS speed_${length_unit}h\nFROM drives\nWHERE car_id = $car_id and $__timeFilter(start_date) and end_date is not null;", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } } ], - "title": "Average distance driven per day", + "timeFrom": "30d", + "title": "Max Speed", "type": "stat" }, { @@ -626,41 +833,76 @@ }, "fieldConfig": { "defaults": { - "mappings": [], + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], "thresholds": { "mode": "absolute", "steps": [ { - "color": "#c7d0d9", - "value": null + "color": "#c7d0d9" } ] }, - "unit": "kwatth" + "unit": "none" }, - "overrides": [] + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "speed_kmh" + }, + "properties": [ + { + "id": "unit", + "value": "velocitykmh" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "speed_mih" + }, + "properties": [ + { + "id": "unit", + "value": "velocitymph" + } + ] + } + ] }, "gridPos": { "h": 3, "w": 8, "x": 16, - "y": 5 + "y": 7 }, - "id": 14, + "id": 34, "maxDataPoints": 100, "options": { "colorMode": "value", "fieldOptions": { "calcs": [ - "lastNotNull" + "mean" ] }, "graphMode": "none", "justifyMode": "auto", "orientation": "horizontal", + "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ - "mean" + "last" ], "fields": "", "values": false @@ -669,43 +911,187 @@ "textMode": "value", "wideLayout": true }, - "pluginVersion": "11.0.0", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH since as (\n\tSELECT date FROM positions\n\tWHERE car_id = $car_id\n\tORDER BY date ASC\n\tLIMIT 1\n),\nactual AS (\n\tSELECT\n\t\tdate_trunc('day', start_date)::date AS date,\n\t\tsum(NULLIF(GREATEST(start_[[preferred_range]]_range_km - end_[[preferred_range]]_range_km, 0), 0) * car.efficiency) AS energy\n\tFROM drives\n\tJOIN cars car ON car.id = car_id\n\tWHERE car_id = $car_id\n\tGROUP BY 1\n),\nbase_line AS (\n\tSELECT date_trunc('day', dd)::date AS date\n FROM generate_series((select date from since), NOW(), '1 day'::interval) dd\n),\ncombined as (\n SELECT base_line.date, COALESCE(actual.energy, 0) as energy\n FROM base_line\n LEFT JOIN actual ON actual.date = base_line.date\n WHERE $__timeFilter(base_line.date)\n)\nSELECT percentile_disc(0.5) WITHIN GROUP (ORDER BY energy) AS energy\nFROM combined;", + "rawSql": "SELECT convert_km(max(speed_max), '$length_unit') AS speed_${length_unit}h\nFROM drives\nWHERE car_id = $car_id and $__timeFilter(start_date) and end_date is not null;", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "timeFrom": "7d", + "title": "Max Speed", + "type": "stat" + }, + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-GrYlRd" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisGridShow": false, + "axisLabel": "", + "axisPlacement": "auto", + "axisWidth": -10, + "fillOpacity": 90, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "fieldMinMax": true, + "mappings": [], + "noValue": "0", + "thresholds": { + "mode": "absolute", + "steps": [ { - "params": [ - "latitude" - ], - "type": "column" + "color": "super-light-green" } ] - ], - "table": "addresses", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Elapsed" + }, + "properties": [ + { + "id": "unit", + "value": "percent" + }, + { + "id": "decimals", + "value": 1 + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 10 + }, + "id": 36, + "options": { + "barRadius": 0.05, + "barWidth": 0.97, + "colorByField": "Elapsed", + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": {}, + "tooltip": { + "hideZeros": false, + "maxHeight": 600, + "mode": "multi", + "sort": "none" + }, + "xField": "Speed", + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "11.6.1", + "targets": [ + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "WITH drivedata AS (\r\n SELECT\r\n ROUND(convert_km(p.speed::numeric, '$length_unit') / 10, 0) * 10 AS speed_section_${length_unit},\r\n EXTRACT(EPOCH FROM (LEAD(p.\"date\") OVER (PARTITION BY p.drive_id ORDER BY p.\"date\") - p.\"date\")) AS seconds_elapsed\r\n FROM positions p\r\n WHERE p.car_id = $car_id AND $__timeFilter(p.date) AND p.ideal_battery_range_km IS NOT NULL\r\n)\r\n\r\nSELECT \r\n speed_section_${length_unit} AS \"Speed\",\r\n SUM(seconds_elapsed) * 100 / SUM(SUM(seconds_elapsed)) OVER () AS \"Elapsed\", \r\n TO_CHAR((SUM(seconds_elapsed) || ' second')::interval, 'HH24:MI:SS') AS \"Time\"\r\nFROM drivedata\r\nWHERE speed_section_${length_unit} > 0\r\nGROUP BY speed_section_${length_unit}\r\nORDER BY speed_section_${length_unit};\r\n", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Speed Histogram ($speed_unit)", + "transformations": [ + { + "id": "filterFieldsByName", + "options": { + "include": { + "names": [ + "Elapsed", + "Time", + "Speed", + "SpeedUnit" + ] } - ] + } } ], - "title": "Average kWh used per day", - "type": "stat" + "type": "barchart" }, { "datasource": { @@ -719,8 +1105,7 @@ "mode": "absolute", "steps": [ { - "color": "#c7d0d9", - "value": null + "color": "#c7d0d9" } ] }, @@ -757,7 +1142,7 @@ "h": 3, "w": 12, "x": 0, - "y": 8 + "y": 17 }, "id": 32, "maxDataPoints": 100, @@ -771,6 +1156,7 @@ "graphMode": "none", "justifyMode": "auto", "orientation": "horizontal", + "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "mean" @@ -782,39 +1168,35 @@ "textMode": "value", "wideLayout": true }, - "pluginVersion": "11.0.0", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH first_position AS (\n\tSELECT date, odometer\n\tFROM positions\n\tWHERE car_id = $car_id AND $__timeFilter(date)\n\tORDER BY date ASC\n\tLIMIT 1\n),\nlast_position AS (\n\tSELECT date, odometer\n\tFROM positions\n\tWHERE car_id = $car_id AND $__timeFilter(date)\n\tORDER BY date DESC\n\tLIMIT 1\n)\nSELECT\n\tconvert_km((((SELECT odometer FROM last_position) - (SELECT odometer\tFROM first_position)) /\n\tEXTRACT(days FROM (SELECT date FROM last_position) - (SELECT date\tFROM first_position)) * \n\t(365/12))::numeric, '$length_unit') AS \"mileage_$length_unit\";", + "rawSql": "WITH since as (\r\n\tSELECT timezone('UTC', min(start_date)) as date FROM drives\r\n\tWHERE car_id = $car_id\r\n\tGROUP BY car_id\r\n)\r\n\r\nselect\r\n convert_km(((max(end_km) - min(start_km)) / greatest(extract(days from (timestamp with time zone $__timeTo() - greatest(timestamp with time zone $__timeFrom(), (select date from since)))), 1) * (365/12))::numeric, '$length_unit') as \"mileage_$length_unit\"\r\nfrom drives\r\nwhere car_id = $car_id and $__timeFilter(start_date) and end_date is not null\r\ngroup by car_id", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "latitude" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "addresses", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } } ], "title": "Extrapolated monthly mileage", @@ -822,8 +1204,9 @@ }, { "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" + "default": false, + "type": "datasource", + "uid": "-- Dashboard --" }, "fieldConfig": { "defaults": { @@ -832,8 +1215,7 @@ "mode": "absolute", "steps": [ { - "color": "#c7d0d9", - "value": null + "color": "#c7d0d9" } ] }, @@ -843,7 +1225,7 @@ { "matcher": { "id": "byName", - "options": "mileage_km" + "options": "yearly_mileage_km" }, "properties": [ { @@ -855,7 +1237,7 @@ { "matcher": { "id": "byName", - "options": "mileage_mi" + "options": "yearly_mileage_mi" }, "properties": [ { @@ -870,7 +1252,7 @@ "h": 3, "w": 12, "x": 12, - "y": 8 + "y": 17 }, "id": 30, "maxDataPoints": 100, @@ -884,6 +1266,7 @@ "graphMode": "none", "justifyMode": "auto", "orientation": "horizontal", + "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "mean" @@ -895,42 +1278,74 @@ "textMode": "value", "wideLayout": true }, - "pluginVersion": "11.0.0", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" + "type": "datasource", + "uid": "-- Dashboard --" }, - "format": "table", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "WITH first_position AS (\n\tSELECT date, odometer\n\tFROM positions\n\tWHERE car_id = $car_id AND $__timeFilter(date)\n\tORDER BY date ASC\n\tLIMIT 1\n),\nlast_position AS (\n\tSELECT date, odometer\n\tFROM positions\n\tWHERE car_id = $car_id AND $__timeFilter(date)\n\tORDER BY date DESC\n\tLIMIT 1\n)\nSELECT\n\tconvert_km(((lp.odometer - fp.odometer) /\n\tEXTRACT(days FROM lp.date - fp.date) * \n\t365)::numeric, '$length_unit') AS \"mileage_$length_unit\" from first_position as fp, last_position as lp;", - "refId": "A", - "select": [ - [ - { - "params": [ - "latitude" - ], - "type": "column" + "panelId": 32, + "refId": "A" + } + ], + "title": "Extrapolated annual mileage", + "transformations": [ + { + "id": "calculateField", + "options": { + "alias": "yearly_mileage_km", + "binary": { + "left": { + "matcher": { + "id": "byName", + "options": "mileage_km" + } + }, + "operator": "*", + "right": { + "fixed": "12" } - ] - ], - "table": "addresses", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" + }, + "mode": "binary", + "reduce": { + "reducer": "sum" + }, + "replaceFields": false + } + }, + { + "id": "calculateField", + "options": { + "alias": "yearly_mileage_mi", + "binary": { + "left": { + "matcher": { + "id": "byName", + "options": "mileage_mi" + } + }, + "operator": "*", + "right": { + "fixed": "12" + } + }, + "mode": "binary", + "reduce": { + "reducer": "sum" + }, + "replaceFields": false + } + }, + { + "id": "filterFieldsByName", + "options": { + "include": { + "pattern": "yearly.*" } - ] + } } ], - "title": "Extrapolated annual mileage", "type": "stat" }, { @@ -945,14 +1360,12 @@ }, "displayName": "$__cell_0", "mappings": [], - "max": 100, "min": 0, "thresholds": { "mode": "absolute", "steps": [ { - "color": "semi-dark-blue", - "value": null + "color": "semi-dark-blue" }, { "color": "light-red", @@ -968,11 +1381,17 @@ "h": 11, "w": 24, "x": 0, - "y": 11 + "y": 20 }, "id": 24, "options": { "displayMode": "gradient", + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, "maxVizHeight": 300, "minVizHeight": 16, "minVizWidth": 8, @@ -989,47 +1408,44 @@ "sizing": "auto", "valueMode": "color" }, - "pluginVersion": "11.0.0", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT\n\tCOALESCE(g.name, array_to_string(((string_to_array(a.display_name, ', ', ''))[0:3]), ', ')) as name,\n\tcount(*) AS visited\nFROM drives t\nINNER JOIN addresses a ON end_address_id = a.id\nLEFT JOIN geofences g ON end_geofence_id = g.id\nWHERE t.car_id = $car_id AND $__timeFilter(t.start_date)\nGROUP BY 1\nORDER BY visited DESC\nLIMIT 10;", + "rawSql": "SELECT * FROM (\nSELECT\n\tCOALESCE(g.name, COALESCE(a.name, nullif(CONCAT_WS(' ', a.road, a.house_number), ''))) as name,\n\tcount(*) AS visited\nFROM drives t\nINNER JOIN addresses a ON end_address_id = a.id\nLEFT JOIN geofences g ON end_geofence_id = g.id\nWHERE t.car_id = $car_id AND $__timeFilter(t.start_date) and $__timeFilter(t.end_date) \nGROUP BY 1\nORDER BY visited DESC) AS destinations\nWHERE name NOT ILIKE ALL ${exclude_formatted_string:raw}\nLIMIT 10;", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "latitude" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "addresses", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } } ], - "title": "Top Destinations", + "title": "Top 10 Destinations (in this period)", "type": "bargauge" } ], + "preload": false, "refresh": false, - "schemaVersion": 39, + "schemaVersion": 41, "tags": [ "tesla" ], @@ -1041,22 +1457,15 @@ "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, - "definition": "SELECT name AS __text, id AS __value FROM cars ORDER BY display_priority ASC, name ASC;", - "hide": 2, - "includeAll": true, + "definition": "SELECT\n id as __value,\n CASE WHEN COUNT(id) OVER (PARTITION BY name) > 1 AND name IS NOT NULL THEN CONCAT(name, ' - ', RIGHT(vin, 6)) ELSE COALESCE(name, CONCAT('VIN ', vin)) end as __text \nFROM cars\nORDER BY display_priority ASC, name ASC, vin ASC;", + "includeAll": false, "label": "Car", - "multi": false, "name": "car_id", "options": [], - "query": "SELECT name AS __text, id AS __value FROM cars ORDER BY display_priority ASC, name ASC;", + "query": "SELECT\n id as __value,\n CASE WHEN COUNT(id) OVER (PARTITION BY name) > 1 AND name IS NOT NULL THEN CONCAT(name, ' - ', RIGHT(vin, 6)) ELSE COALESCE(name, CONCAT('VIN ', vin)) end as __text \nFROM cars\nORDER BY display_priority ASC, name ASC, vin ASC;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -1067,18 +1476,12 @@ "definition": "select unit_of_length from settings limit 1;", "hide": 2, "includeAll": false, - "multi": false, "name": "length_unit", "options": [], "query": "select unit_of_length from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -1086,21 +1489,15 @@ "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, - "definition": "select unit_of_temperature from settings limit 1;", + "definition": "SELECT CASE WHEN '$length_unit' = 'km' THEN 'km/h' WHEN '$length_unit' = 'mi' THEN 'mph' END", "hide": 2, "includeAll": false, - "multi": false, - "name": "temp_unit", + "name": "speed_unit", "options": [], - "query": "select unit_of_temperature from settings limit 1;", + "query": "SELECT CASE WHEN '$length_unit' = 'km' THEN 'km/h' WHEN '$length_unit' = 'mi' THEN 'mph' END", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -1111,19 +1508,12 @@ "definition": "select preferred_range from settings limit 1;", "hide": 2, "includeAll": false, - "label": "", - "multi": false, "name": "preferred_range", "options": [], "query": "select preferred_range from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -1134,19 +1524,46 @@ "definition": "select base_url from settings limit 1;", "hide": 2, "includeAll": false, - "label": "", - "multi": false, "name": "base_url", "options": [], "query": "select base_url from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" + }, + { + "current": { + "text": "", + "value": "" + }, + "description": "Comma separated list of locations to exclude. Ex: home, work", + "label": "Exclude locations", + "name": "exclude", + "options": [ + { + "selected": true, + "text": "", + "value": "" + } + ], + "query": "", + "type": "textbox" + }, + { + "current": {}, + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "definition": "WITH splits AS (\n SELECT unnest(string_to_array('$exclude', ', ')) AS part\n),\nsplit_strings AS (\n\tSELECT part AS part\n\tFROM (VALUES (NULL)) AS v(dummy)\n\tLEFT JOIN splits ON TRUE\n),\nexclude_string AS (\n\tSELECT array_to_string(array_agg(case when part is null then '''''' else '''%' || part || '%''' end), ', ') AS formatted_string\n\tFROM split_strings\n)\nSELECT '(ARRAY[' || formatted_string || '])' FROM exclude_string", + "hide": 2, + "includeAll": false, + "name": "exclude_formatted_string", + "options": [], + "query": "WITH splits AS (\n SELECT unnest(string_to_array('$exclude', ', ')) AS part\n),\nsplit_strings AS (\n\tSELECT part AS part\n\tFROM (VALUES (NULL)) AS v(dummy)\n\tLEFT JOIN splits ON TRUE\n),\nexclude_string AS (\n\tSELECT array_to_string(array_agg(case when part is null then '''''' else '''%' || part || '%''' end), ', ') AS formatted_string\n\tFROM split_strings\n)\nSELECT '(ARRAY[' || formatted_string || '])' FROM exclude_string", + "refresh": 1, + "regex": "", + "type": "query" } ] }, @@ -1154,36 +1571,9 @@ "from": "now-1y", "to": "now" }, - "timeRangeUpdatedDuringEditOrView": false, - "timepicker": { - "hidden": false, - "refresh_intervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "time_options": [ - "5m", - "15m", - "1h", - "6h", - "12h", - "24h", - "2d", - "7d", - "30d" - ] - }, + "timepicker": {}, "timezone": "", "title": "Drive Stats", "uid": "_7WkNSyWk", - "version": 3, - "weekStart": "" + "version": 1 } \ No newline at end of file diff --git a/grafana/dashboards/drives.json b/grafana/dashboards/drives.json index 018b719fb4..f336947439 100644 --- a/grafana/dashboards/drives.json +++ b/grafana/dashboards/drives.json @@ -1,44 +1,9 @@ { - "__elements": {}, - "__requires": [ - { - "type": "grafana", - "id": "grafana", - "name": "Grafana", - "version": "11.0.0" - }, - { - "type": "datasource", - "id": "grafana-postgresql-datasource", - "name": "PostgreSQL", - "version": "1.0.0" - }, - { - "type": "panel", - "id": "stat", - "name": "Stat", - "version": "" - }, - { - "type": "panel", - "id": "table", - "name": "Table", - "version": "" - }, - { - "type": "panel", - "id": "text", - "name": "Text", - "version": "" - } - ], "annotations": { "list": [ { - "$$hashKey": "object:24", "builtIn": 1, "datasource": "-- Grafana --", - "definition": "TeslaMate|U2FsdGVkX1/cEWK+8cz7pjEKXtzJnDN7b21ZDXt1MGneFGPWTLqOPtxKmu02mJPLzi/f29I+NBHd3vi0FB8R4Xn0+GtobWDgk6VAVSBTdSNniOKO8i2WPlhRaOsl2+hG7gnZ7wrf1Th2nxR7f1uYCrbwOek0IzkfLzrkjh7gkr6inT6bbDuJqrmogZajLxmAMrQ6V+/vHxBRGiwjJhgiEeq3hM1q2h04JKkNiZ8RHbsF5Cd/xd8Q9u0JVrZzIrtnhM/SFlaApU7RtRMu8CSj1llTX7WEOj6VDZAMSf+XUAanWdk725kEPN9MNu89o2zEq5P3E3cju8IbbBdPzXLV3oVuzD6/tMnxFToIIV1E/BrpF7s2RtNa8+KJJ1PF8xgs6m+/KTD2hy+WsP0636AgObRAmYg7+qotGrgNvpNPdE0EgrB7WHYlV7R/1q66bcq6tCe51X1Un70k+zo+K6AK0o4B1H6IyMlEVuRH/Fz8QVl9aYu2ztd08RbuKJlYVKpkH+pxVETAO9MclYQ90tzE6TfwDZrQZzsAlMenr4s1ZB1OlFXjLjVjnddnUilzO76cqv4yI2THQEuyQ47nuVQ4gUbx02K59vMQhns3C01JOAYokOaSXe66Y7QYdMlk09Lf|aes-256-cbc", "enable": true, "hide": true, "iconColor": "rgba(0, 211, 255, 1)", @@ -56,7 +21,6 @@ "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 0, - "id": null, "links": [ { "icon": "dashboard", @@ -64,7 +28,7 @@ "title": "TeslaMate", "tooltip": "", "type": "link", - "url": "[[base_url:raw]]" + "url": "${base_url:raw}" }, { "asDropdown": true, @@ -76,118 +40,264 @@ "type": "dashboards" } ], - "liveNow": false, "panels": [ + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 3, + "panels": [], + "title": "Summary of this period", + "type": "row" + }, { "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" + "type": "datasource", + "uid": "-- Dashboard --" }, "fieldConfig": { "defaults": { "color": { "mode": "thresholds" }, - "custom": { - "align": "center", - "cellOptions": { - "type": "auto" - }, - "filterable": false, - "inspect": false, - "minWidth": 150 - }, - "mappings": [], + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], "thresholds": { "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "text" } ] - } + }, + "unit": "kwatth" }, "overrides": [ { "matcher": { "id": "byName", - "options": "start_date" + "options": "consumption_kWh" }, "properties": [ { "id": "displayName", - "value": "Date" - }, - { - "id": "unit", - "value": "dateTimeAsLocal" - }, - { - "id": "links", - "value": [ - { - "targetBlank": false, - "title": "View drive details", - "url": "d/zm7wN6Zgz?from=${__data.fields.start_date_ts.numeric}&to=${__data.fields.end_date_ts.numeric}&var-car_id=${__data.fields.car_id.numeric}&var-drive_id=${__data.fields.drive_id.numeric}" - } - ] - }, - { - "id": "custom.align", - "value": "auto" + "value": "Total Energy consumed (net):" + } + ] + } + ] + }, + "gridPos": { + "h": 2, + "w": 6, + "x": 0, + "y": 1 + }, + "id": 4, + "maxDataPoints": 100, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "center", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.6.1", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "panelId": 2, + "refId": "A" + } + ], + "title": "", + "transformations": [ + { + "id": "filterFieldsByName", + "options": { + "include": { + "names": [ + "consumption_kWh" + ] + } + } + } + ], + "transparent": true, + "type": "stat" + }, + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 0, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ { - "id": "custom.width", - "value": 180 + "color": "text" } ] }, + "unit": "m" + }, + "overrides": [ { "matcher": { "id": "byName", - "options": "consumption_kwh_km" + "options": "duration_min" }, "properties": [ { "id": "displayName", - "value": "Consumption" - }, - { - "id": "unit", - "value": "Wh/km" - }, - { - "id": "custom.align", - "value": "center" + "value": "Total Duration:" + } + ] + } + ] + }, + "gridPos": { + "h": 2, + "w": 6, + "x": 6, + "y": 1 + }, + "id": 5, + "maxDataPoints": 100, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "center", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.6.1", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "panelId": 2, + "refId": "A" + } + ], + "title": "", + "transformations": [ + { + "id": "filterFieldsByName", + "options": { + "include": { + "names": [ + "duration_min" + ] + } + } + } + ], + "transparent": true, + "type": "stat" + }, + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 0, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ { - "id": "custom.width", - "value": 150 + "color": "text" } ] - }, + } + }, + "overrides": [ { "matcher": { "id": "byName", - "options": "consumption_kwh_mi" + "options": "distance_mi" }, "properties": [ - { - "id": "displayName", - "value": "Consumption" - }, { "id": "unit", - "value": "Wh/mi" - }, - { - "id": "custom.align", - "value": "center" + "value": "mi" }, { - "id": "custom.width", - "value": 150 + "id": "displayName", + "value": "Total Distance logged:" } ] }, @@ -198,137 +308,456 @@ }, "properties": [ { - "id": "displayName", - "value": "Distance" + "id": "unit", + "value": "km" }, { - "id": "unit", - "value": "lengthkm" + "id": "displayName", + "value": "Total Distance logged:" + } + ] + } + ] + }, + "gridPos": { + "h": 2, + "w": 6, + "x": 12, + "y": 1 + }, + "id": 6, + "maxDataPoints": 100, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "center", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.6.1", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "panelId": 2, + "refId": "A" + } + ], + "title": "", + "transformations": [ + { + "id": "filterFieldsByName", + "options": { + "include": { + "names": [ + "distance_mi", + "distance_km" + ] + } + } + } + ], + "transparent": true, + "type": "stat" + }, + { + "datasource": { + "default": false, + "type": "datasource", + "uid": "-- Dashboard --" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 0, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ { - "id": "decimals", - "value": 1 - }, + "color": "text" + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "consumption_kwh_mi" + }, + "properties": [ { - "id": "custom.align" + "id": "unit", + "value": "Wh/mi" }, { - "id": "custom.width", - "value": 90 + "id": "displayName", + "value": "Ø Consumption (net):" } ] }, { "matcher": { "id": "byName", - "options": "consumption_kWh" + "options": "consumption_kwh_km" }, "properties": [ - { - "id": "displayName", - "value": "kWh" - }, { "id": "unit", - "value": "kwatth" - }, - { - "id": "decimals", - "value": 1 - }, - { - "id": "custom.align", - "value": "center" - }, - { - "id": "custom.cellOptions", - "value": { - "type": "color-text" - } - }, - { - "id": "thresholds", - "value": { - "mode": "absolute", - "steps": [ - { - "color": "super-light-green", - "value": null - }, - { - "color": "green", - "value": 20 - }, - { - "color": "dark-green", - "value": 30 - } - ] - } + "value": "Wh/km" }, { - "id": "custom.width", - "value": 80 + "id": "displayName", + "value": "Ø Consumption (net):" } ] + } + ] + }, + "gridPos": { + "h": 2, + "w": 6, + "x": 18, + "y": 1 + }, + "id": 7, + "maxDataPoints": 100, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "center", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.6.1", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "panelId": 2, + "refId": "A" + } + ], + "title": "", + "transformations": [ + { + "id": "filterFieldsByName", + "options": { + "include": { + "pattern": "distance_km|distance_mi|consumption_kWh" + } + } + }, + { + "id": "renameByRegex", + "options": { + "regex": "distance_(mi|km)", + "renamePattern": "distance" + } + }, + { + "id": "reduce", + "options": { + "includeTimeField": false, + "mode": "reduceFields", + "reducers": [ + "sum" + ] + } + }, + { + "id": "calculateField", + "options": { + "binary": { + "left": "consumption_kWh", + "operator": "/", + "right": "distance" + }, + "mode": "binary", + "reduce": { + "reducer": "sum" + }, + "replaceFields": true + } + }, + { + "id": "calculateField", + "options": { + "alias": "consumption_kwh_$length_unit", + "binary": { + "left": "1000", + "operator": "*", + "right": "consumption_kWh / distance" + }, + "mode": "binary", + "reduce": { + "reducer": "sum" + }, + "replaceFields": true + } + } + ], + "transparent": true, + "type": "stat" + }, + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "filterable": false, + "inspect": false, + "minWidth": 150 }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [ { "matcher": { "id": "byName", - "options": "start_address" + "options": "start_date" }, "properties": [ { "id": "displayName", - "value": "Start" + "value": "Date" + }, + { + "id": "unit", + "value": "dateTimeAsLocal" }, { "id": "links", "value": [ { - "targetBlank": true, - "title": "Create or edit geo-fence", - "url": "[[base_url:raw]]/geo-fences/${__data.fields.start_path}" + "targetBlank": false, + "title": "View drive details", + "url": "/d/zm7wN6Zgz/drive-details?from=${__data.fields.start_date_ts.numeric}&to=${__data.fields.end_date_ts.numeric}&var-car_id=${__data.fields.car_id.numeric}&var-drive_id=${__data.fields.drive_id.numeric}" } ] }, { - "id": "custom.align", - "value": "auto" - }, - { - "id": "custom.width", - "value": 250 + "id": "custom.minWidth", + "value": 210 } ] }, { "matcher": { "id": "byName", - "options": "end_address" + "options": "consumption_kwh_km" }, "properties": [ { "id": "displayName", - "value": "Destination" + "value": "Ø Consumption (net)" }, { - "id": "links", - "value": [ - { - "targetBlank": true, - "title": "Create or edit geo-fence", - "url": "[[base_url:raw]]/geo-fences/${__data.fields.end_path}" - } - ] + "id": "unit", + "value": "Wh/km" }, { - "id": "custom.align", - "value": "auto" + "id": "custom.minWidth", + "value": 165 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "consumption_kwh_mi" + }, + "properties": [ + { + "id": "displayName", + "value": "Ø Consumption (net)" + }, + { + "id": "unit", + "value": "Wh/mi" + }, + { + "id": "custom.minWidth", + "value": 165 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "distance_km" + }, + "properties": [ + { + "id": "displayName", + "value": "Distance" + }, + { + "id": "unit", + "value": "lengthkm" + }, + { + "id": "decimals", + "value": 1 + }, + { + "id": "custom.minWidth", + "value": 90 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "consumption_kWh" + }, + "properties": [ + { + "id": "displayName", + "value": "Energy consumed (net)" + }, + { + "id": "unit", + "value": "kwatth" + }, + { + "id": "decimals", + "value": 1 + }, + { + "id": "custom.cellOptions", + "value": { + "type": "color-text" + } + }, + { + "id": "thresholds", + "value": { + "mode": "absolute", + "steps": [ + { + "color": "super-light-green" + }, + { + "color": "green", + "value": 20 + }, + { + "color": "dark-green", + "value": 30 + } + ] + } + }, + { + "id": "custom.minWidth", + "value": 180 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "start_address" + }, + "properties": [ + { + "id": "displayName", + "value": "Start" + }, + { + "id": "links", + "value": [ + { + "targetBlank": true, + "title": "Create or edit geo-fence", + "url": "${base_url:raw}/geo-fences/${__data.fields.start_path}" + } + ] + }, + { + "id": "custom.minWidth", + "value": 200 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "end_address" + }, + "properties": [ + { + "id": "displayName", + "value": "Destination" + }, + { + "id": "links", + "value": [ + { + "targetBlank": true, + "title": "Create or edit geo-fence", + "url": "${base_url:raw}/geo-fences/${__data.fields.end_path}" + } + ] }, { - "id": "custom.width", - "value": 250 + "id": "custom.minWidth", + "value": 200 } ] }, @@ -346,15 +775,12 @@ "id": "unit", "value": "celsius" }, - { - "id": "custom.align" - }, { "id": "decimals", "value": 1 }, { - "id": "custom.width", + "id": "custom.minWidth", "value": 70 }, { @@ -369,8 +795,7 @@ "mode": "absolute", "steps": [ { - "color": "super-light-blue", - "value": null + "color": "super-light-blue" }, { "color": "super-light-green", @@ -400,15 +825,12 @@ "value": "m" }, { - "id": "custom.align" + "id": "decimals", + "value": 1 }, { - "id": "custom.width", + "id": "custom.minWidth", "value": 90 - }, - { - "id": "decimals", - "value": 1 } ] }, @@ -429,27 +851,49 @@ { "id": "custom.cellOptions", "value": { - "mode": "basic", + "mode": "lcd", "type": "gauge" } }, { - "id": "custom.align", - "value": "center" + "id": "max", + "value": 1.25 }, { - "id": "max", - "value": 1 + "id": "min", + "value": 0 }, { "id": "color", "value": { - "mode": "continuous-RdYlGr" + "mode": "thresholds" } }, { - "id": "min", - "value": 0 + "id": "custom.minWidth", + "value": 120 + }, + { + "id": "thresholds", + "value": { + "mode": "absolute", + "steps": [ + { + "color": "super-light-orange" + }, + { + "color": "light-orange", + "value": 0.65 + }, + { + "color": "green", + "value": 0.99 + } + ] + } + }, + { + "id": "decimals" } ] }, @@ -459,17 +903,6 @@ "options": "/.*_ts/" }, "properties": [ - { - "id": "unit", - "value": "short" - }, - { - "id": "decimals", - "value": 2 - }, - { - "id": "custom.align" - }, { "id": "custom.hidden", "value": true @@ -484,21 +917,18 @@ "properties": [ { "id": "displayName", - "value": "Speed" + "value": "Ø Speed" }, { "id": "unit", "value": "velocitykmh" }, - { - "id": "custom.align" - }, { "id": "decimals", "value": 0 }, { - "id": "custom.width", + "id": "custom.minWidth", "value": 90 } ] @@ -509,17 +939,6 @@ "options": "id" }, "properties": [ - { - "id": "unit", - "value": "short" - }, - { - "id": "decimals", - "value": 2 - }, - { - "id": "custom.align" - }, { "id": "custom.hidden", "value": true @@ -545,10 +964,7 @@ "value": 1 }, { - "id": "custom.align" - }, - { - "id": "custom.width", + "id": "custom.minWidth", "value": 90 } ] @@ -567,15 +983,12 @@ "id": "unit", "value": "fahrenheit" }, - { - "id": "custom.align" - }, { "id": "decimals", "value": 1 }, { - "id": "custom.width", + "id": "custom.minWidth", "value": 70 }, { @@ -590,8 +1003,7 @@ "mode": "absolute", "steps": [ { - "color": "super-light-blue", - "value": null + "color": "super-light-blue" }, { "color": "super-light-green", @@ -614,88 +1026,76 @@ "properties": [ { "id": "displayName", - "value": "Speed" + "value": "Ø Speed" }, { "id": "unit", "value": "velocitymph" }, - { - "id": "custom.align" - }, { "id": "decimals", "value": 0 }, { - "id": "custom.width", + "id": "custom.minWidth", "value": 90 } ] }, { "matcher": { - "id": "byRegexp", - "options": "/(start|end)_path/" + "id": "byName", + "options": "speed_max_mi" }, "properties": [ { - "id": "unit", - "value": "short" + "id": "displayName", + "value": "max Speed" }, { - "id": "decimals", - "value": 2 + "id": "unit", + "value": "velocitymph" }, { - "id": "custom.align" + "id": "decimals", + "value": 0 }, { - "id": "custom.hidden", - "value": true + "id": "custom.minWidth", + "value": 95 } ] }, { "matcher": { "id": "byName", - "options": "duration_str" + "options": "speed_max_km" }, "properties": [ { - "id": "unit", - "value": "short" + "id": "displayName", + "value": "max Speed" }, { - "id": "decimals", - "value": 2 + "id": "unit", + "value": "velocitykmh" }, { - "id": "custom.align" + "id": "decimals", + "value": 0 }, { - "id": "custom.hidden", - "value": true + "id": "custom.minWidth", + "value": 95 } ] }, { "matcher": { - "id": "byName", - "options": "car_id" + "id": "byRegexp", + "options": "/(start|end)_path/" }, "properties": [ - { - "id": "unit", - "value": "short" - }, - { - "id": "decimals", - "value": 2 - }, - { - "id": "custom.align" - }, { "id": "custom.hidden", "value": true @@ -705,662 +1105,225 @@ { "matcher": { "id": "byName", - "options": "% Start" + "options": "duration_str" }, "properties": [ { - "id": "unit", - "value": "percent" - }, - { - "id": "custom.align" - }, - { - "id": "decimals", - "value": 0 - }, - { - "id": "custom.width", - "value": 76 + "id": "custom.hidden", + "value": true } ] }, { "matcher": { - "id": "byName", - "options": "% End" - }, - "properties": [ - { - "id": "unit", - "value": "percent" - }, - { - "id": "custom.align" - }, - { - "id": "decimals", - "value": 0 - }, - { - "id": "custom.width", - "value": 65 - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "has_reduced_range" - }, - "properties": [ - { - "id": "displayName", - "value": "❄" - }, - { - "id": "custom.cellOptions", - "value": { - "type": "color-text" - } - }, - { - "id": "custom.align", - "value": "center" - }, - { - "id": "mappings", - "value": [ - { - "options": { - "false": { - "color": "transparent", - "index": 1, - "text": "." - }, - "true": { - "color": "dark-blue", - "index": 0, - "text": "❄" - } - }, - "type": "value" - } - ] - }, - { - "id": "custom.width", - "value": 5 - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "drive_id" - }, - "properties": [ - { - "id": "unit", - "value": "short" - }, - { - "id": "decimals", - "value": 2 - }, - { - "id": "custom.align" - }, - { - "id": "custom.hidden", - "value": true - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "power_max" - }, - "properties": [ - { - "id": "displayName", - "value": "max Power" - }, - { - "id": "unit", - "value": "kwatt" - }, - { - "id": "custom.align" - }, - { - "id": "custom.width", - "value": 90 - } - ] - } - ] - }, - "gridPos": { - "h": 22, - "w": 24, - "x": 0, - "y": 0 - }, - "id": 2, - "options": { - "cellHeight": "sm", - "footer": { - "countRows": false, - "enablePagination": false, - "fields": "", - "reducer": [ - "sum" - ], - "show": false - }, - "showHeader": true, - "sortBy": [] - }, - "pluginVersion": "11.0.0", - "targets": [ - { - "alias": "", - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" - }, - "editorMode": "code", - "format": "table", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "WITH data AS (\n SELECT\n round(extract(epoch FROM start_date)) * 1000 AS start_date_ts,\n round(extract(epoch FROM end_date)) * 1000 AS end_date_ts,\n car.id as car_id,\n CASE WHEN start_geofence.id IS NULL THEN CONCAT('new?lat=', start_position.latitude, '&lng=', start_position.longitude)\n WHEN start_geofence.id IS NOT NULL THEN CONCAT(start_geofence.id, '/edit')\n END as start_path,\n CASE WHEN end_geofence.id IS NULL THEN CONCAT('new?lat=', end_position.latitude, '&lng=', end_position.longitude)\n WHEN end_geofence.id IS NOT NULL THEN CONCAT(end_geofence.id, '/edit')\n END as end_path,\n TO_CHAR((duration_min * INTERVAL '1 minute'), 'HH24:MI') as duration_str,\n drives.id as drive_id,\n -- Columns\n start_date,\n COALESCE(start_geofence.name, CONCAT_WS(', ', COALESCE(start_address.name, nullif(CONCAT_WS(' ', start_address.road, start_address.house_number), '')), start_address.city)) AS start_address,\n COALESCE(end_geofence.name, CONCAT_WS(', ', COALESCE(end_address.name, nullif(CONCAT_WS(' ', end_address.road, end_address.house_number), '')), end_address.city)) AS end_address,\n duration_min,\n distance,\n start_position.usable_battery_level as start_usable_battery_level,\n start_position.battery_level as start_battery_level,\n end_position.usable_battery_level as end_usable_battery_level,\n end_position.battery_level as end_battery_level,\n case when (start_position.battery_level != start_position.usable_battery_level OR end_position.battery_level != end_position.usable_battery_level) = true then true else false end as reduced_range,\n duration_min > 1 AND distance > 1 AND ( \n start_position.usable_battery_level IS NULL OR end_position.usable_battery_level IS NULL\tOR\n (end_position.battery_level - end_position.usable_battery_level) = 0 \n ) as is_sufficiently_precise,\n NULLIF(GREATEST(start_[[preferred_range]]_range_km - end_[[preferred_range]]_range_km, 0), 0) as range_diff,\n car.efficiency as car_efficiency,\n outside_temp_avg,\n distance / NULLIF(duration_min, 0) * 60 AS avg_speed,\n power_max\n FROM drives\n LEFT JOIN addresses start_address ON start_address_id = start_address.id\n LEFT JOIN addresses end_address ON end_address_id = end_address.id\n LEFT JOIN positions start_position ON start_position_id = start_position.id\n LEFT JOIN positions end_position ON end_position_id = end_position.id\n LEFT JOIN geofences start_geofence ON start_geofence_id = start_geofence.id\n LEFT JOIN geofences end_geofence ON end_geofence_id = end_geofence.id\n LEFT JOIN cars car ON car.id = drives.car_id\n WHERE $__timeFilter(start_date) AND drives.car_id = $car_id \n AND convert_km(distance::numeric, '$length_unit') >= $min_dist \n AND convert_km(distance::numeric, '$length_unit') / NULLIF(duration_min, 0) * 60 >= $min_speed \n AND ('${geofence:pipe}' = '-1' OR start_geofence.id in ($geofence) OR end_geofence.id in ($geofence)) \n ORDER BY start_date DESC\n)\nSELECT\n start_date_ts,\n end_date_ts,\n car_id,\n start_path,\n end_path,\n duration_str,\n drive_id,\n -- Columns\n start_date,\n start_address,\n end_address,\n duration_min,\n convert_km(distance::numeric, '$length_unit') AS distance_$length_unit,\n start_battery_level as \"% Start\",\n end_battery_level as \"% End\",\n convert_celsius(outside_temp_avg, '$temp_unit') AS outside_temp_$temp_unit,\n convert_km(avg_speed::numeric, '$length_unit') AS speed_avg_$length_unit,\n power_max,\n reduced_range as has_reduced_range,\n CASE WHEN is_sufficiently_precise THEN distance / range_diff\n ELSE NULL\n END AS efficiency,\n range_diff * car_efficiency as \"consumption_kWh\",\n CASE WHEN is_sufficiently_precise THEN range_diff * car_efficiency / distance * 1000 * CASE WHEN '$length_unit' = 'km' THEN 1\n WHEN '$length_unit' = 'mi' THEN 1.60934\n END\n END AS consumption_kWh_$length_unit\nFROM data\nWHERE\n start_address ILIKE '%$location%' OR end_address ILIKE '%$location%';", - "refId": "A", - "select": [ - [ - { - "params": [ - "value" - ], - "type": "column" - } - ] - ], - "sql": { - "columns": [ - { - "parameters": [], - "type": "function" - } - ], - "groupBy": [ - { - "property": { - "type": "string" - }, - "type": "groupBy" - } - ], - "limit": 50 - }, - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "Drive", - "transformations": [ - { - "id": "merge", - "options": { - "reducers": [] - } - } - ], - "type": "table" - }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 22 - }, - "id": 3, - "panels": [], - "title": "Summary of this period", - "type": "row" - }, - { - "datasource": { - "type": "datasource", - "uid": "-- Dashboard --" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [ - { - "options": { - "match": "null", - "result": { - "text": "N/A" - } - }, - "type": "special" - } - ], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "kwatth" - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 6, - "x": 0, - "y": 23 - }, - "id": 4, - "maxDataPoints": 100, - "options": { - "colorMode": "none", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "horizontal", - "reduceOptions": { - "calcs": [ - "sum" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "11.0.0", - "targets": [ - { - "datasource": { - "type": "datasource", - "uid": "-- Dashboard --" - }, - "panelId": 2, - "refId": "A" - } - ], - "title": "Energy used", - "transformations": [ - { - "id": "filterFieldsByName", - "options": { - "include": { - "names": [ - "consumption_kWh" - ] - } - } - } - ], - "transparent": true, - "type": "stat" - }, - { - "datasource": { - "type": "datasource", - "uid": "-- Dashboard --" - }, - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "decimals": 0, - "mappings": [ - { - "options": { - "match": "null", - "result": { - "text": "N/A" - } - }, - "type": "special" - } - ], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "m" - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 6, - "x": 6, - "y": 23 - }, - "id": 5, - "maxDataPoints": 100, - "options": { - "colorMode": "none", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "horizontal", - "reduceOptions": { - "calcs": [ - "sum" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "11.0.0", - "targets": [ - { - "datasource": { - "type": "datasource", - "uid": "-- Dashboard --" - }, - "panelId": 2, - "refId": "A" - } - ], - "title": "Total Duration", - "transformations": [ - { - "id": "filterFieldsByName", - "options": { - "include": { - "names": [ - "duration_min" - ] - } - } - } - ], - "transparent": true, - "type": "stat" - }, - { - "datasource": { - "type": "datasource", - "uid": "-- Dashboard --" - }, - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "decimals": 0, - "mappings": [ - { - "options": { - "match": "null", - "result": { - "text": "N/A" - } - }, - "type": "special" - } - ], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "distance_mi" - }, - "properties": [ - { - "id": "unit", - "value": "lengthmi" - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "distance_km" - }, - "properties": [ - { - "id": "unit", - "value": "lengthkm" - } - ] - } - ] - }, - "gridPos": { - "h": 4, - "w": 6, - "x": 12, - "y": 23 - }, - "id": 6, - "maxDataPoints": 100, - "options": { - "colorMode": "none", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "horizontal", - "reduceOptions": { - "calcs": [ - "sum" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "11.0.0", - "targets": [ - { - "datasource": { - "type": "datasource", - "uid": "-- Dashboard --" - }, - "panelId": 2, - "refId": "A" - } - ], - "title": "Total Distance", - "transformations": [ - { - "id": "filterFieldsByName", - "options": { - "include": { - "names": [ - "distance_mi", - "distance_km" - ] - } - } - } - ], - "transparent": true, - "type": "stat" - }, - { - "datasource": { - "type": "datasource", - "uid": "-- Dashboard --" - }, - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" + "id": "byName", + "options": "car_id" + }, + "properties": [ + { + "id": "custom.hidden", + "value": true + } + ] }, - "decimals": 0, - "mappings": [ - { - "options": { - "match": "null", - "result": { - "text": "N/A" - } + { + "matcher": { + "id": "byName", + "options": "% Start" + }, + "properties": [ + { + "id": "unit", + "value": "percent" }, - "type": "special" - } - ], - "thresholds": { - "mode": "absolute", - "steps": [ { - "color": "green", - "value": null + "id": "decimals", + "value": 0 }, { - "color": "red", - "value": 80 + "id": "custom.minWidth", + "value": 75 } ] - } - }, - "overrides": [ + }, { "matcher": { "id": "byName", - "options": "consumption_kwh_mi" + "options": "% End" }, "properties": [ { "id": "unit", - "value": "Wh/mi" + "value": "percent" + }, + { + "id": "decimals", + "value": 0 + }, + { + "id": "custom.minWidth", + "value": 65 } ] }, { "matcher": { "id": "byName", - "options": "consumption_kwh_km" + "options": "has_reduced_range" + }, + "properties": [ + { + "id": "displayName", + "value": "❄" + }, + { + "id": "custom.cellOptions", + "value": { + "type": "color-text" + } + }, + { + "id": "custom.align", + "value": "center" + }, + { + "id": "mappings", + "value": [ + { + "options": { + "false": { + "color": "transparent", + "index": 1, + "text": "." + }, + "true": { + "color": "dark-blue", + "index": 0, + "text": "❄" + } + }, + "type": "value" + } + ] + }, + { + "id": "custom.minWidth", + "value": 50 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "drive_id" + }, + "properties": [ + { + "id": "custom.hidden", + "value": true + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "power_max" }, "properties": [ + { + "id": "displayName", + "value": "max Power" + }, { "id": "unit", - "value": "Wh/km" + "value": "kwatt" + }, + { + "id": "custom.minWidth", + "value": 90 } ] } ] }, "gridPos": { - "h": 4, - "w": 6, - "x": 18, - "y": 23 + "h": 19, + "w": 24, + "x": 0, + "y": 3 }, - "id": 7, - "maxDataPoints": 100, + "id": 2, "options": { - "colorMode": "none", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "horizontal", - "reduceOptions": { - "calcs": [ - "mean" - ], + "cellHeight": "sm", + "footer": { + "countRows": false, + "enablePagination": false, "fields": "", - "values": false + "reducer": [ + "sum" + ], + "show": false }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true + "showHeader": true, + "sortBy": [] }, - "pluginVersion": "11.0.0", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { - "type": "datasource", - "uid": "-- Dashboard --" + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" }, - "panelId": 2, - "refId": "A" - } - ], - "title": "Average Consumption", - "transformations": [ - { - "id": "filterFieldsByName", - "options": { - "include": { - "names": [ - "consumption_kwh_mi", - "consumption_kwh_km" - ] - } + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "WITH data AS (\n SELECT\n round(extract(epoch FROM start_date)) * 1000 AS start_date_ts,\n round(extract(epoch FROM end_date)) * 1000 AS end_date_ts,\n car.id as car_id,\n CASE WHEN start_geofence.id IS NULL THEN CONCAT('new?lat=', start_position.latitude, '&lng=', start_position.longitude)\n WHEN start_geofence.id IS NOT NULL THEN CONCAT(start_geofence.id, '/edit')\n END as start_path,\n CASE WHEN end_geofence.id IS NULL THEN CONCAT('new?lat=', end_position.latitude, '&lng=', end_position.longitude)\n WHEN end_geofence.id IS NOT NULL THEN CONCAT(end_geofence.id, '/edit')\n END as end_path,\n TO_CHAR((duration_min * INTERVAL '1 minute'), 'HH24:MI') as duration_str,\n drives.id as drive_id,\n -- Columns\n start_date,\n COALESCE(start_geofence.name, CONCAT_WS(', ', COALESCE(start_address.name, nullif(CONCAT_WS(' ', start_address.road, start_address.house_number), '')), start_address.city)) AS start_address,\n COALESCE(end_geofence.name, CONCAT_WS(', ', COALESCE(end_address.name, nullif(CONCAT_WS(' ', end_address.road, end_address.house_number), '')), end_address.city)) AS end_address,\n duration_min,\n distance,\n start_position.usable_battery_level as start_usable_battery_level,\n start_position.battery_level as start_battery_level,\n end_position.usable_battery_level as end_usable_battery_level,\n end_position.battery_level as end_battery_level,\n case when (start_position.battery_level != start_position.usable_battery_level OR end_position.battery_level != end_position.usable_battery_level) = true then true else false end as reduced_range,\n duration_min > 1 AND distance > 1 AND ( \n start_position.usable_battery_level IS NULL OR end_position.usable_battery_level IS NULL\tOR\n (end_position.battery_level - end_position.usable_battery_level) = 0 \n ) as is_sufficiently_precise,\n NULLIF(GREATEST(start_${preferred_range}_range_km - end_${preferred_range}_range_km, 0), 0) as range_diff,\n car.efficiency as car_efficiency,\n outside_temp_avg,\n distance / coalesce(NULLIF(duration_min, 0) * 60, extract(epoch from end_date - start_date)) * 3600 AS avg_speed,\n\tspeed_max,\n power_max\n FROM drives\n LEFT JOIN addresses start_address ON start_address_id = start_address.id\n LEFT JOIN addresses end_address ON end_address_id = end_address.id\n LEFT JOIN positions start_position ON start_position_id = start_position.id\n LEFT JOIN positions end_position ON end_position_id = end_position.id\n LEFT JOIN geofences start_geofence ON start_geofence_id = start_geofence.id\n LEFT JOIN geofences end_geofence ON end_geofence_id = end_geofence.id\n LEFT JOIN cars car ON car.id = drives.car_id\n WHERE $__timeFilter(start_date) AND drives.car_id = $car_id \n AND convert_km(distance::numeric, '$length_unit') >= $min_dist \n AND convert_km(distance::numeric, '$length_unit') / coalesce(NULLIF(duration_min, 0) * 60, extract(epoch from end_date - start_date)) * 3600 >= $min_speed \n AND ('${geofence:pipe}' = '-1' OR start_geofence.id in ($geofence) OR end_geofence.id in ($geofence)) \n ORDER BY start_date DESC\n)\nSELECT\n start_date_ts,\n end_date_ts,\n car_id,\n start_path,\n end_path,\n duration_str,\n drive_id,\n -- Columns\n start_date,\n start_address,\n end_address,\n duration_min,\n convert_km(distance::numeric, '$length_unit') AS distance_$length_unit,\n start_battery_level as \"% Start\",\n end_battery_level as \"% End\",\n convert_celsius(outside_temp_avg, '$temp_unit') AS outside_temp_$temp_unit,\n convert_km(avg_speed::numeric, '$length_unit') AS speed_avg_$length_unit,\n convert_km(speed_max::numeric, '$length_unit') AS speed_max_$length_unit,\n power_max,\n reduced_range as has_reduced_range,\n CASE WHEN is_sufficiently_precise THEN distance / range_diff\n ELSE NULL\n END AS efficiency,\n range_diff * car_efficiency as \"consumption_kWh\",\n CASE WHEN is_sufficiently_precise THEN range_diff * car_efficiency / convert_km(distance::numeric, '$length_unit') * 1000\n END AS consumption_kWh_$length_unit\nFROM data\nWHERE\n start_address ILIKE '%$location%' OR end_address ILIKE '%$location%';", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 } } ], - "transparent": true, - "type": "stat" + "title": "Drive", + "type": "table" }, { - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 22 + }, + "id": 10, + "panels": [], + "title": "General information (All drives)", + "type": "row" + }, + { + "fieldConfig": { + "defaults": {}, + "overrides": [] }, "gridPos": { "h": 2, "w": 24, "x": 0, - "y": 27 + "y": 23 }, "id": 8, "options": { @@ -1372,41 +1335,8 @@ "content": "From here you can check if you have \nincomplete data of **Drives** (drives without ending date)\nIf so, you may follow the official \nguide by Manually fixing data", "mode": "markdown" }, - "pluginVersion": "11.0.0", - "targets": [ - { - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" - }, - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": false, - "rawSql": "SELECT\n start_date AS \"time\",\n start_km\nFROM drives\nWHERE\n $__timeFilter(start_date)\nORDER BY 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "start_km" - ], - "type": "column" - } - ] - ], - "table": "drives", - "timeColumn": "start_date", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], + "pluginVersion": "11.6.1", + "title": "", "type": "text" }, { @@ -1433,8 +1363,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" } ] } @@ -1445,7 +1374,7 @@ "h": 7, "w": 24, "x": 0, - "y": 29 + "y": 25 }, "id": 9, "options": { @@ -1461,46 +1390,44 @@ }, "showHeader": true }, - "pluginVersion": "11.0.0", + "pluginVersion": "11.6.1", "targets": [ { - "alias": "", "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "table", - "group": [], - "hide": false, - "metricColumn": "none", "rawQuery": true, "rawSql": "SELECT id AS \"Drive ID\", start_date, end_date, distance, duration_min \nFROM drives \nWHERE car_id = $car_id AND end_date is null\nORDER BY start_date DESC", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "value" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } } ], "title": "Incomplete Drives 🛣️", "type": "table" } ], - "schemaVersion": 39, + "preload": false, + "refresh": "", + "schemaVersion": 41, "tags": [ "tesla" ], @@ -1512,22 +1439,15 @@ "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, - "definition": "SELECT name AS __text, id AS __value FROM cars ORDER BY display_priority ASC, name ASC;", - "hide": 0, + "definition": "SELECT\n id as __value,\n CASE WHEN COUNT(id) OVER (PARTITION BY name) > 1 AND name IS NOT NULL THEN CONCAT(name, ' - ', RIGHT(vin, 6)) ELSE COALESCE(name, CONCAT('VIN ', vin)) end as __text \nFROM cars\nORDER BY display_priority ASC, name ASC, vin ASC;", "includeAll": false, "label": "Car", - "multi": false, "name": "car_id", "options": [], - "query": "SELECT name AS __text, id AS __value FROM cars ORDER BY display_priority ASC, name ASC;", + "query": "SELECT\n id as __value,\n CASE WHEN COUNT(id) OVER (PARTITION BY name) > 1 AND name IS NOT NULL THEN CONCAT(name, ' - ', RIGHT(vin, 6)) ELSE COALESCE(name, CONCAT('VIN ', vin)) end as __text \nFROM cars\nORDER BY display_priority ASC, name ASC, vin ASC;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "allValue": "-1", @@ -1538,7 +1458,6 @@ }, "definition": "SELECT name AS __text, id AS __value FROM geofences ORDER BY name COLLATE \"C\" ASC;", "description": "Start or Destination Geofence", - "hide": 0, "includeAll": true, "label": "Geofence", "multi": true, @@ -1547,18 +1466,14 @@ "query": "SELECT name AS __text, id AS __value FROM geofences ORDER BY name COLLATE \"C\" ASC;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, "type": "query" }, { "current": { - "selected": false, "text": "", "value": "" }, "description": "Type a text contained in Start or Destination Location ", - "hide": 0, "label": "Location", "name": "location", "options": [ @@ -1569,7 +1484,6 @@ } ], "query": "", - "skipUrlSync": false, "type": "textbox" }, { @@ -1582,18 +1496,12 @@ "hide": 2, "includeAll": false, "label": "temperature unit", - "multi": false, "name": "temp_unit", "options": [], "query": "select unit_of_temperature from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -1605,18 +1513,12 @@ "hide": 2, "includeAll": false, "label": "length unit", - "multi": false, "name": "length_unit", "options": [], "query": "select unit_of_length from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -1627,19 +1529,12 @@ "definition": "select preferred_range from settings limit 1;", "hide": 2, "includeAll": false, - "label": "", - "multi": false, "name": "preferred_range", "options": [], "query": "select preferred_range from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -1650,48 +1545,45 @@ "definition": "select base_url from settings limit 1;", "hide": 2, "includeAll": false, - "label": "", - "multi": false, "name": "base_url", "options": [], "query": "select base_url from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": { - "selected": false, "text": "0", "value": "0" }, - "hide": 0, - "includeAll": false, "label": "Distance >=", "name": "min_dist", - "options": [], + "options": [ + { + "selected": true, + "text": "0", + "value": "0" + } + ], "query": "0", - "skipUrlSync": false, "type": "textbox" }, { "current": { - "selected": false, "text": "0", "value": "0" }, - "hide": 0, - "includeAll": false, "label": "Speed >=", "name": "min_speed", - "options": [], + "options": [ + { + "selected": true, + "text": "0", + "value": "0" + } + ], "query": "0", - "skipUrlSync": false, "type": "textbox" } ] @@ -1700,35 +1592,9 @@ "from": "now-3M", "to": "now" }, - "timeRangeUpdatedDuringEditOrView": false, - "timepicker": { - "refresh_intervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "time_options": [ - "5m", - "15m", - "1h", - "6h", - "12h", - "24h", - "2d", - "7d", - "30d" - ] - }, + "timepicker": {}, "timezone": "", "title": "Drives", "uid": "Y8upc6ZRk", - "version": 2, - "weekStart": "" + "version": 1 } \ No newline at end of file diff --git a/grafana/dashboards/efficiency.json b/grafana/dashboards/efficiency.json index a91e2fb91a..cbbc633085 100644 --- a/grafana/dashboards/efficiency.json +++ b/grafana/dashboards/efficiency.json @@ -1,48 +1,11 @@ { - "__inputs": [ - { - "name": "DS_TESLAMATE", - "label": "TeslaMate", - "description": "", - "type": "datasource", - "pluginId": "grafana-postgresql-datasource", - "pluginName": "PostgreSQL" - } - ], - "__elements": {}, - "__requires": [ - { - "type": "grafana", - "id": "grafana", - "name": "Grafana", - "version": "11.0.0" - }, - { - "type": "datasource", - "id": "grafana-postgresql-datasource", - "name": "PostgreSQL", - "version": "1.0.0" - }, - { - "type": "panel", - "id": "stat", - "name": "Stat", - "version": "" - }, - { - "type": "panel", - "id": "table", - "name": "Table", - "version": "" - } - ], "annotations": { "list": [ { "builtIn": 1, "datasource": { - "type": "datasource", - "uid": "grafana" + "type": "grafana", + "uid": "-- Grafana --" }, "enable": true, "hide": true, @@ -55,7 +18,6 @@ "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 0, - "id": null, "links": [ { "icon": "dashboard", @@ -63,7 +25,7 @@ "title": "TeslaMate", "tooltip": "", "type": "link", - "url": "[[base_url:raw]]" + "url": "${base_url:raw}" }, { "asDropdown": true, @@ -78,10 +40,6 @@ "panels": [ { "collapsed": false, - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" - }, "gridPos": { "h": 1, "w": 24, @@ -91,15 +49,6 @@ "id": 10, "panels": [], "repeat": "car_id", - "targets": [ - { - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" - }, - "refId": "A" - } - ], "title": "$car_id", "type": "row" }, @@ -108,6 +57,7 @@ "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "description": "(Range lost while driving * Efficiency) / Distance driven", "fieldConfig": { "defaults": { "decimals": 0, @@ -116,8 +66,7 @@ "mode": "absolute", "steps": [ { - "color": "#c7d0d9", - "value": null + "color": "#c7d0d9" } ] }, @@ -168,6 +117,7 @@ "graphMode": "none", "justifyMode": "auto", "orientation": "horizontal", + "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "mean" @@ -179,42 +129,38 @@ "textMode": "value", "wideLayout": true }, - "pluginVersion": "11.0.0", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, - "rawSql": "select \n sum((start_[[preferred_range]]_range_km - end_[[preferred_range]]_range_km) * cars.efficiency) / sum(distance) * 1000 * \n CASE WHEN '$length_unit' = 'km' THEN 1\n WHEN '$length_unit' = 'mi' THEN 1.60934\n END AS \"consumption_$length_unit\"\nfrom drives \ninner join cars on cars.id = car_id\nwhere \n distance is not null and\n start_[[preferred_range]]_range_km - end_[[preferred_range]]_range_km >= 0.1 and\n car_id = $car_id", + "rawSql": "select \n sum((start_${preferred_range}_range_km - end_${preferred_range}_range_km) * cars.efficiency) / convert_km(sum(distance)::numeric, '$length_unit') * 1000 AS \"consumption_$length_unit\"\nfrom drives \ninner join cars on cars.id = car_id\nwhere \n distance is not null and\n start_${preferred_range}_range_km - end_${preferred_range}_range_km >= 0.1 and\n car_id = $car_id", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "latitude" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "addresses", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } } ], - "title": "Consumption (net)", + "title": "Ø Consumption (net)", "type": "stat" }, { @@ -222,6 +168,7 @@ "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "description": "(Range lost between charges * Efficiency) / Distance driven between charges", "fieldConfig": { "defaults": { "decimals": 0, @@ -230,8 +177,7 @@ "mode": "absolute", "steps": [ { - "color": "#c7d0d9", - "value": null + "color": "#c7d0d9" } ] }, @@ -282,6 +228,7 @@ "graphMode": "none", "justifyMode": "auto", "orientation": "horizontal", + "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "mean" @@ -293,42 +240,38 @@ "textMode": "value", "wideLayout": true }, - "pluginVersion": "11.0.0", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH d1 AS (\n\tSELECT\n\t\tc.car_id,\n\t\tlag(end_[[preferred_range]]_range_km) OVER (ORDER BY start_date) - start_[[preferred_range]]_range_km AS range_loss,\n\t\tp.odometer - lag(p.odometer) OVER (ORDER BY start_date) AS distance\n\tFROM\n\t\tcharging_processes c\n\tLEFT JOIN positions p ON p.id = c.position_id \n\tWHERE\n\t end_date IS NOT NULL AND\n\t c.car_id = $car_id\n\tORDER BY\n\t\tstart_date\n),\nd2 AS (\nSELECT\n\tcar_id,\n\tsum(range_loss) AS range_loss,\n\tsum(distance) AS distance\nFROM\n\td1\nWHERE\n\tdistance >= 0 AND range_loss >= 0\nGROUP BY\n\tcar_id\n)\nSELECT\nrange_loss * c.efficiency / distance * 1000 *\n CASE WHEN '$length_unit' = 'km' THEN 1\n WHEN '$length_unit' = 'mi' THEN 1.60934\n END AS \"consumption_$length_unit\"\nFROM\n\td2\n\tLEFT JOIN cars c ON c.id = car_id", + "rawSql": "WITH d1 AS (\n\tSELECT\n\t\tc.car_id,\n\t\tlag(end_${preferred_range}_range_km) OVER (ORDER BY start_date) - start_${preferred_range}_range_km AS range_loss,\n\t\tp.odometer - lag(p.odometer) OVER (ORDER BY start_date) AS distance\n\tFROM\n\t\tcharging_processes c\n\tLEFT JOIN positions p ON p.id = c.position_id \n\tWHERE\n\t end_date IS NOT NULL AND\n\t c.car_id = $car_id\n\tORDER BY\n\t\tstart_date\n),\nd2 AS (\nSELECT\n\tcar_id,\n\tsum(range_loss) AS range_loss,\n\tsum(distance) AS distance\nFROM\n\td1\nWHERE\n\tdistance >= 0 AND range_loss >= 0\nGROUP BY\n\tcar_id\n)\nSELECT\nrange_loss * c.efficiency / convert_km(distance::numeric, '$length_unit') * 1000 AS \"consumption_$length_unit\"\nFROM\n\td2\n\tLEFT JOIN cars c ON c.id = car_id", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "latitude" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "addresses", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } } ], - "title": "Consumption (gross) ", + "title": "Ø Consumption (gross)", "type": "stat" }, { @@ -336,6 +279,7 @@ "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "description": "Distance of all logged drives", "fieldConfig": { "defaults": { "decimals": 0, @@ -344,8 +288,7 @@ "mode": "absolute", "steps": [ { - "color": "#c7d0d9", - "value": null + "color": "#c7d0d9" } ] }, @@ -396,6 +339,7 @@ "graphMode": "none", "justifyMode": "auto", "orientation": "horizontal", + "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "mean" @@ -407,39 +351,35 @@ "textMode": "value", "wideLayout": true }, - "pluginVersion": "11.0.0", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, "rawSql": "select convert_km(sum(distance)::numeric, '$length_unit') as \"distance_$length_unit\" \nfrom drives \nwhere car_id = $car_id;", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "latitude" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "addresses", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } } ], "title": "Logged Distance", @@ -453,6 +393,7 @@ "fieldConfig": { "defaults": { "custom": { + "align": "auto", "cellOptions": { "type": "auto" }, @@ -466,8 +407,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" } ] } @@ -521,7 +461,7 @@ "properties": [ { "id": "displayName", - "value": "Efficiency" + "value": "Driving Efficiency" }, { "id": "unit", @@ -544,8 +484,7 @@ "mode": "absolute", "steps": [ { - "color": "super-light-orange", - "value": null + "color": "super-light-orange" }, { "color": "light-orange", @@ -561,6 +500,10 @@ { "id": "max", "value": 1.15 + }, + { + "id": "min", + "value": 0 } ] }, @@ -572,7 +515,7 @@ "properties": [ { "id": "displayName", - "value": "Consumption" + "value": "Ø Consumption (net)" }, { "id": "unit", @@ -592,7 +535,7 @@ "properties": [ { "id": "displayName", - "value": "Consumption" + "value": "Ø Consumption (net)" }, { "id": "unit", @@ -612,7 +555,7 @@ "properties": [ { "id": "displayName", - "value": "$length_unit" + "value": "Distance" }, { "id": "unit", @@ -632,7 +575,7 @@ "properties": [ { "id": "displayName", - "value": "$length_unit" + "value": "Distance" }, { "id": "unit", @@ -652,7 +595,7 @@ "properties": [ { "id": "displayName", - "value": "Speed" + "value": "Ø Speed" }, { "id": "unit", @@ -672,7 +615,7 @@ "properties": [ { "id": "displayName", - "value": "Speed" + "value": "Ø Speed" }, { "id": "unit", @@ -711,42 +654,38 @@ } ] }, - "pluginVersion": "11.0.0", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH t AS (\n\tSELECT\n\t CASE WHEN '$temp_unit' = 'C' THEN ROUND(cast(outside_temp_avg AS numeric) / 5, 0) * 5 \n\t\t\t WHEN '$temp_unit' = 'F' THEN ROUND(cast(convert_celsius(outside_temp_avg, '$temp_unit') AS numeric) / 10, 0) * 10\n\t\tEND AS outside_temp,\n\t\tsum(start_ideal_range_km - end_ideal_range_km) AS total_ideal_range,\n\t\tsum(start_rated_range_km - end_rated_range_km) AS total_rated_range,\n\t\tsum(distance) AS total_distance,\n\t\tsum(duration_min) as duration,\n\t\tcar_id\n\tFROM\n\t\tdrives\n\tWHERE\n\t\tdistance IS NOT NULL\n\t\tAND car_id = $car_id\n\t\tAND convert_km(distance::numeric, '$length_unit') >= $min_distance \n\t\tAND start_[[preferred_range]]_range_km - end_[[preferred_range]]_range_km > 0.1\n\tGROUP BY\n\t\t1,\n\t\tcar_id\n)\n\nSELECT\n\toutside_temp as outside_temp_$temp_unit,\n total_distance / total_[[preferred_range]]_range AS efficiency,\n\ttotal_[[preferred_range]]_range / total_distance * c.efficiency * 1000 * \n CASE \n WHEN '$length_unit' = 'km' THEN 1 \n WHEN '$length_unit' = 'mi' THEN 1.60934 \n END AS consumption_$length_unit,\n convert_km(total_distance::numeric, '$length_unit') as total_distance_$length_unit,\n\t(total_distance / duration) * 60 / (CASE \n WHEN '$length_unit' = 'km' THEN 1 \n WHEN '$length_unit' = 'mi' THEN 1.60934 \n END) avg_speed_$length_unit\nFROM\n\tt\nJOIN cars c ON t.car_id = c.id\nWHERE outside_temp IS NOT NULL\norder by 1 desc\n", + "rawSql": "WITH t AS (\n\tSELECT\n\t CASE WHEN '$temp_unit' = 'C' THEN ROUND(cast(outside_temp_avg AS numeric) / 5, 0) * 5 \n\t\t\t WHEN '$temp_unit' = 'F' THEN ROUND(cast(convert_celsius(outside_temp_avg, '$temp_unit') AS numeric) / 10, 0) * 10\n\t\tEND AS outside_temp,\n\t\tsum(start_ideal_range_km - end_ideal_range_km) AS total_ideal_range,\n\t\tsum(start_rated_range_km - end_rated_range_km) AS total_rated_range,\n\t\tsum(distance) AS total_distance,\n\t\tsum(duration_min) as duration,\n\t\tcar_id\n\tFROM\n\t\tdrives\n\tWHERE\n\t\tdistance IS NOT NULL\n\t\tAND car_id = $car_id\n\t\tAND convert_km(distance::numeric, '$length_unit') >= $min_distance \n\t\tAND start_${preferred_range}_range_km - end_${preferred_range}_range_km > 0.1\n\tGROUP BY\n\t\t1,\n\t\tcar_id\n)\n\nSELECT\n\toutside_temp as outside_temp_$temp_unit,\n total_distance / total_${preferred_range}_range AS efficiency,\n\ttotal_${preferred_range}_range / convert_km(total_distance::numeric, '$length_unit') * c.efficiency * 1000 AS consumption_$length_unit,\n convert_km(total_distance::numeric, '$length_unit') as total_distance_$length_unit,\n\t(convert_km(total_distance::numeric, '$length_unit') / duration) * 60 as avg_speed_$length_unit\nFROM\n\tt\nJOIN cars c ON t.car_id = c.id\nWHERE outside_temp IS NOT NULL\norder by 1 desc\n", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "latitude" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "addresses", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } } ], - "title": "Temperature – Efficiency", + "title": "Temperature – Driving Efficiency", "type": "table" }, { @@ -762,8 +701,7 @@ "mode": "absolute", "steps": [ { - "color": "#c7d0d9", - "value": null + "color": "#c7d0d9" } ] }, @@ -798,7 +736,7 @@ }, "gridPos": { "h": 5, - "w": 2, + "w": 4, "x": 0, "y": 16 }, @@ -814,6 +752,7 @@ "graphMode": "none", "justifyMode": "auto", "orientation": "horizontal", + "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "mean" @@ -825,39 +764,35 @@ "textMode": "value", "wideLayout": true }, - "pluginVersion": "11.0.0", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT\n\tCASE WHEN '$length_unit' = 'km' THEN efficiency\n\t WHEN '$length_unit' = 'mi' THEN efficiency * 1.60934\n\tEND * 1000 as \"efficiency_$length_unit\"\nFROM\n\tcars\nWHERE\n\tid = $car_id;", + "rawSql": "SELECT\n\tefficiency / convert_km(1, '$length_unit') * 1000 as \"efficiency_$length_unit\"\nFROM\n\tcars\nWHERE\n\tid = $car_id;", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "charge_energy_added" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "charges", - "timeColumn": "date", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } } ], "title": "Current $preferred_range efficiency", @@ -871,6 +806,7 @@ "fieldConfig": { "defaults": { "custom": { + "align": "auto", "cellOptions": { "type": "auto" }, @@ -882,8 +818,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" } ] } @@ -925,8 +860,8 @@ }, "gridPos": { "h": 5, - "w": 11, - "x": 2, + "w": 10, + "x": 4, "y": 16 }, "id": 12, @@ -942,39 +877,35 @@ }, "showHeader": true }, - "pluginVersion": "11.0.0", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT\n round((charge_energy_added / NULLIF(end_ideal_range_km - start_ideal_range_km, 0))::numeric *\n\t CASE WHEN '$length_unit' = 'km' THEN 1.0\n\t WHEN '$length_unit' = 'mi' THEN 1.60934\n\t END, 3) * 1000 as \"efficiency_$length_unit\",\n count(*) as count\nFROM\n charging_processes\nWHERE \n car_id = $car_id\n AND duration_min > 10\n AND end_battery_level <= 95\n AND start_ideal_range_km IS NOT NULL\n AND end_ideal_range_km IS NOT NULL\n AND charge_energy_added > 0\nGROUP BY\n 1\nORDER BY\n 2 DESC\nLIMIT 3", + "rawSql": "SELECT\n round((charge_energy_added / NULLIF(end_ideal_range_km - start_ideal_range_km, 0))::numeric / convert_km(1, '$length_unit'), 3) * 1000 as \"efficiency_$length_unit\",\n count(*) as count\nFROM\n charging_processes\nWHERE \n car_id = $car_id\n AND duration_min > 10\n AND end_battery_level <= 95\n AND start_ideal_range_km IS NOT NULL\n AND end_ideal_range_km IS NOT NULL\n AND charge_energy_added > 0\nGROUP BY\n 1\nORDER BY\n 2 DESC\nLIMIT 3", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "charge_energy_added" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "charges", - "timeColumn": "date", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } } ], "title": "Derived ideal efficiencies", @@ -988,6 +919,7 @@ "fieldConfig": { "defaults": { "custom": { + "align": "auto", "cellOptions": { "type": "auto" }, @@ -999,8 +931,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" } ] } @@ -1042,8 +973,8 @@ }, "gridPos": { "h": 5, - "w": 11, - "x": 13, + "w": 10, + "x": 14, "y": 16 }, "id": 15, @@ -1059,46 +990,44 @@ }, "showHeader": true }, - "pluginVersion": "11.0.0", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT\n round((charge_energy_added / NULLIF(end_rated_range_km - start_rated_range_km, 0))::numeric *\n\t CASE WHEN '$length_unit' = 'km' THEN 1.0\n\t WHEN '$length_unit' = 'mi' THEN 1.60934\n\t END, 3) * 1000 as \"efficiency_$length_unit\",\n\tcount(*) as count\nFROM\n charging_processes\nWHERE \n car_id = $car_id\n AND duration_min > 10\n AND end_battery_level <= 95\n AND start_rated_range_km IS NOT NULL\n AND end_rated_range_km IS NOT NULL\n AND charge_energy_added > 0\nGROUP BY\n 1\nORDER BY\n 2 DESC\nLIMIT 3", + "rawSql": "SELECT\n round((charge_energy_added / NULLIF(end_rated_range_km - start_rated_range_km, 0))::numeric / convert_km(1, '$length_unit'), 3) * 1000 as \"efficiency_$length_unit\",\n\tcount(*) as count\nFROM\n charging_processes\nWHERE \n car_id = $car_id\n AND duration_min > 10\n AND end_battery_level <= 95\n AND start_rated_range_km IS NOT NULL\n AND end_rated_range_km IS NOT NULL\n AND charge_energy_added > 0\nGROUP BY\n 1\nORDER BY\n 2 DESC\nLIMIT 3", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "charge_energy_added" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "charges", - "timeColumn": "date", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } } ], "title": "Derived rated efficiencies", "type": "table" } ], - "schemaVersion": 39, + "preload": false, + "refresh": "", + "schemaVersion": 41, "tags": [ "tesla" ], @@ -1110,22 +1039,16 @@ "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, - "definition": "SELECT name AS __text, id AS __value FROM cars ORDER BY display_priority ASC, name ASC;", + "definition": "SELECT\n id as __value,\n CASE WHEN COUNT(id) OVER (PARTITION BY name) > 1 AND name IS NOT NULL THEN CONCAT(name, ' - ', RIGHT(vin, 6)) ELSE COALESCE(name, CONCAT('VIN ', vin)) end as __text \nFROM cars\nORDER BY display_priority ASC, name ASC, vin ASC;", "hide": 2, "includeAll": true, "label": "Car", - "multi": false, "name": "car_id", "options": [], - "query": "SELECT name AS __text, id AS __value FROM cars ORDER BY display_priority ASC, name ASC;", + "query": "SELECT\n id as __value,\n CASE WHEN COUNT(id) OVER (PARTITION BY name) > 1 AND name IS NOT NULL THEN CONCAT(name, ' - ', RIGHT(vin, 6)) ELSE COALESCE(name, CONCAT('VIN ', vin)) end as __text \nFROM cars\nORDER BY display_priority ASC, name ASC, vin ASC;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -1136,19 +1059,12 @@ "definition": "select unit_of_temperature from settings limit 1;", "hide": 2, "includeAll": false, - "label": "", - "multi": false, "name": "temp_unit", "options": [], "query": "select unit_of_temperature from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -1159,19 +1075,12 @@ "definition": "select unit_of_length from settings limit 1;", "hide": 2, "includeAll": false, - "label": "", - "multi": false, "name": "length_unit", "options": [], "query": "select unit_of_length from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -1182,29 +1091,20 @@ "definition": "select preferred_range from settings limit 1;", "hide": 2, "includeAll": false, - "multi": false, "name": "preferred_range", "options": [], "query": "select preferred_range from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": { - "selected": false, "text": "1", "value": "1" }, - "hide": 0, "includeAll": false, "label": "min. distance per drive", - "multi": false, "name": "min_distance", "options": [ { @@ -1234,8 +1134,6 @@ } ], "query": "1, 5, 10, 25, 50", - "queryValue": "", - "skipUrlSync": false, "type": "custom" }, { @@ -1247,19 +1145,12 @@ "definition": "select base_url from settings limit 1;", "hide": 2, "includeAll": false, - "label": "", - "multi": false, "name": "base_url", "options": [], "query": "select base_url from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" } ] }, @@ -1267,36 +1158,11 @@ "from": "now-6h", "to": "now" }, - "timeRangeUpdatedDuringEditOrView": false, "timepicker": { - "hidden": true, - "refresh_intervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "time_options": [ - "5m", - "15m", - "1h", - "6h", - "12h", - "24h", - "2d", - "7d", - "30d" - ] + "hidden": true }, "timezone": "", "title": "Efficiency", "uid": "fu4SiQgWz", - "version": 2, - "weekStart": "" + "version": 1 } \ No newline at end of file diff --git a/grafana/dashboards/internal/charge-details.json b/grafana/dashboards/internal/charge-details.json index e6efa9aa27..b7b5215639 100644 --- a/grafana/dashboards/internal/charge-details.json +++ b/grafana/dashboards/internal/charge-details.json @@ -1,59 +1,16 @@ { - "__elements": {}, - "__requires": [ - { - "type": "panel", - "id": "geomap", - "name": "Geomap", - "version": "" - }, - { - "type": "grafana", - "id": "grafana", - "name": "Grafana", - "version": "10.4.0" - }, - { - "type": "datasource", - "id": "grafana-postgresql-datasource", - "name": "PostgreSQL", - "version": "1.0.0" - }, - { - "type": "panel", - "id": "stat", - "name": "Stat", - "version": "" - }, - { - "type": "panel", - "id": "timeseries", - "name": "Time series", - "version": "" - }, - { - "type": "panel", - "id": "xychart", - "name": "XY Chart", - "version": "" - } - ], "annotations": { "list": [ { "builtIn": 1, - "datasource": "-- Grafana --", + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, "enable": true, "hide": true, - "definition": "TeslaMate|U2FsdGVkX1/cEWK+8cz7pjEKXtzJnDN7b21ZDXt1MGneFGPWTLqOPtxKmu02mJPLzi/f29I+NBHd3vi0FB8R4Xn0+GtobWDgk6VAVSBTdSNniOKO8i2WPlhRaOsl2+hG7gnZ7wrf1Th2nxR7f1uYCrbwOek0IzkfLzrkjh7gkr6inT6bbDuJqrmogZajLxmAMrQ6V+/vHxBRGiwjJhgiEeq3hM1q2h04JKkNiZ8RHbsF5Cd/xd8Q9u0JVrZzIrtnhM/SFlaApU7RtRMu8CSj1llTX7WEOj6VDZAMSf+XUAanWdk725kEPN9MNu89o2zEq5P3E3cju8IbbBdPzXLV3oVuzD6/tMnxFToIIV1E/BrpF7s2RtNa8+KJJ1PF8xgs6m+/KTD2hy+WsP0636AgObRAmYg7+qotGrgNvpNPdE0EgrB7WHYlV7R/1q66bcq6tCe51X1Un70k+zo+K6AK0o4B1H6IyMlEVuRH/Fz8QVl9aYu2ztd08RbuKJlYVKpkH+pxVETAO9MclYQ90tzE6TfwDZrQZzsAlMenr4s1ZB1OlFXjLjVjnddnUilzO76cqv4yI2THQEuyQ47nuVQ4gUbx02K59vMQhns3C01JOAYokOaSXe66Y7QYdMlk09Lf|aes-256-cbc", "iconColor": "rgba(0, 211, 255, 1)", "name": "Annotations & Alerts", - "target": { - "limit": 100, - "matchAny": false, - "tags": [], - "type": "dashboard" - }, "type": "dashboard" } ] @@ -68,7 +25,7 @@ "title": "TeslaMate", "tooltip": "", "type": "link", - "url": "[[base_url:raw]]" + "url": "${base_url:raw}" }, { "asDropdown": true, @@ -80,7 +37,6 @@ "type": "dashboards" } ], - "liveNow": false, "panels": [ { "datasource": { @@ -101,6 +57,7 @@ "axisSoftMax": 100, "axisSoftMin": 0, "barAlignment": 0, + "barWidthFactor": 0.6, "drawStyle": "line", "fillOpacity": 10, "gradientMode": "opacity", @@ -132,8 +89,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -388,43 +344,40 @@ "showLegend": true }, "tooltip": { + "hideZeros": false, "mode": "multi", "sort": "none" } }, - "pluginVersion": "8.5.4", + "pluginVersion": "11.6.1", "targets": [ { - "alias": "", "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "time_series", - "group": [], - "hide": false, - "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT\n $__time(date),\n battery_level,\n charger_power,\n (case when battery_heater_on then 10 when battery_heater then 10 else 0 end) as battery_heater,\n convert_km([[preferred_range]]_battery_range_km, '$length_unit') as range_$length_unit,\n charger_voltage,\n (case when charger_phases = 2 then 3 when charger_phases = 1 then 1 else 0 end) as charger_phases,\n charger_actual_current,\n charger_pilot_current,\n convert_celsius(outside_temp, '$temp_unit') outside_temp_$temp_unit\nFROM\n charges c\njoin\n charging_processes p ON p.id = c.charging_process_id \nWHERE\n $__timeFilter(date)\n AND p.car_id = $car_id\nORDER BY\n date ASC", + "rawSql": "SELECT\n $__time(date),\n battery_level,\n charger_power,\n (case when battery_heater_on then 10 when battery_heater then 10 else 0 end) as battery_heater,\n convert_km(${preferred_range}_battery_range_km, '$length_unit') as range_$length_unit,\n charger_voltage,\n (case when charger_phases = 2 then 3 when charger_phases = 1 then 1 else 0 end) as charger_phases,\n charger_actual_current,\n charger_pilot_current,\n convert_celsius(outside_temp, '$temp_unit') outside_temp_$temp_unit\nFROM\n charges c\njoin\n charging_processes p ON p.id = c.charging_process_id \nWHERE\n $__timeFilter(date)\n AND p.car_id = $car_id\nORDER BY\n date ASC", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "value" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } } ], "title": "Charge Details", @@ -458,8 +411,7 @@ "mode": "absolute", "steps": [ { - "color": "#c7d0d9", - "value": null + "color": "#c7d0d9" } ] }, @@ -477,7 +429,7 @@ "links": [ { "title": "Set cost", - "url": "[[base_url:raw]]/charge-cost/${charging_process_id}" + "url": "${base_url:raw}/charge-cost/${charging_process_id}" } ], "maxDataPoints": 100, @@ -486,6 +438,7 @@ "graphMode": "none", "justifyMode": "auto", "orientation": "horizontal", + "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "lastNotNull" @@ -497,7 +450,7 @@ "textMode": "value", "wideLayout": true }, - "pluginVersion": "10.4.0", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { @@ -506,22 +459,9 @@ }, "editorMode": "code", "format": "table", - "group": [], - "hide": false, - "metricColumn": "none", "rawQuery": true, "rawSql": "SELECT cost from charging_processes where id = $charging_process_id;", "refId": "A", - "select": [ - [ - { - "params": [ - "latitude" - ], - "type": "column" - } - ] - ], "sql": { "columns": [ { @@ -538,17 +478,7 @@ } ], "limit": 50 - }, - "table": "addresses", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + } } ], "title": "Cost", @@ -579,8 +509,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -605,6 +534,7 @@ "graphMode": "none", "justifyMode": "auto", "orientation": "horizontal", + "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "lastNotNull" @@ -616,41 +546,38 @@ "textMode": "value", "wideLayout": true }, - "pluginVersion": "10.4.0", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, "rawSql": "SELECT\n COALESCE(g.name, CONCAT_WS(', ', COALESCE(addresses.name, CONCAT_WS(' ', addresses.road, addresses.house_number)), addresses.city))\nFROM\n\tcharging_processes c\n\tLEFT JOIN addresses ON addresses.id = c.address_id\n\tLEFT JOIN geofences g ON g.id = geofence_id\nWHERE\n\tc.id = $charging_process_id", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "latitude" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "addresses", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } } ], + "title": "", "type": "stat" }, { @@ -679,8 +606,7 @@ "mode": "absolute", "steps": [ { - "color": "semi-dark-yellow", - "value": null + "color": "semi-dark-yellow" } ] }, @@ -714,6 +640,7 @@ "graphMode": "area", "justifyMode": "center", "orientation": "auto", + "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "lastNotNull" @@ -725,7 +652,7 @@ "textMode": "value_and_name", "wideLayout": true }, - "pluginVersion": "10.4.0", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { @@ -734,21 +661,9 @@ }, "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, "rawSql": "SELECT coalesce(cp_added, c_added) AS \"Added\"\r\nFROM (SELECT cp.charge_energy_added AS cp_added from charging_processes cp where id = $charging_process_id) cp\r\nCROSS JOIN (SELECT max(charge_energy_added) AS c_added FROM charges WHERE charging_process_id=$charging_process_id) c", "refId": "A", - "select": [ - [ - { - "params": [ - "latitude" - ], - "type": "column" - } - ] - ], "sql": { "columns": [ { @@ -765,17 +680,7 @@ } ], "limit": 50 - }, - "table": "addresses", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + } }, { "datasource": { @@ -784,7 +689,6 @@ }, "editorMode": "code", "format": "table", - "hide": false, "rawQuery": true, "rawSql": "SELECT charge_energy_used AS \"Used\"\r\nFROM charging_processes where id = $charging_process_id", "refId": "B", @@ -813,7 +717,6 @@ }, "editorMode": "code", "format": "table", - "hide": false, "rawQuery": true, "rawSql": "SELECT CASE WHEN charge_energy_used IS NULL THEN NULL ELSE LEAST(charge_energy_added / NULLIF(charge_energy_used, 0), 1.0) * 100 END AS \"Efficiency\"\r\nFROM charging_processes where id = $charging_process_id", "refId": "C", @@ -861,8 +764,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -957,31 +859,18 @@ "zoom": 15 } }, - "pluginVersion": "10.4.0", + "pluginVersion": "11.6.1", "targets": [ { - "alias": "", "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, "editorMode": "code", "format": "time_series", - "group": [], - "metricColumn": "none", "rawQuery": true, "rawSql": "SELECT\n\t$__time(date),\n\tunnest(ARRAY[latitude, latitude]) AS latitude,\n\tunnest(ARRAY[longitude, longitude]) AS longitude\nFROM\n\tcharging_processes c\n\tJOIN positions p ON c.position_id = p.id\nWHERE\n\t$__timeFilter(date)\n\tAND c.car_id = $car_id;", "refId": "A", - "select": [ - [ - { - "params": [ - "value" - ], - "type": "column" - } - ] - ], "sql": { "columns": [ { @@ -998,17 +887,10 @@ } ], "limit": 50 - }, - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + } } ], + "title": "", "type": "geomap" }, { @@ -1037,8 +919,7 @@ "mode": "absolute", "steps": [ { - "color": "semi-dark-yellow", - "value": null + "color": "semi-dark-yellow" } ] }, @@ -1059,6 +940,7 @@ "graphMode": "area", "justifyMode": "center", "orientation": "auto", + "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "lastNotNull" @@ -1070,7 +952,7 @@ "textMode": "value_and_name", "wideLayout": true }, - "pluginVersion": "10.4.0", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { @@ -1079,21 +961,9 @@ }, "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, "rawSql": "SELECT start_battery_level AS \"Start\" FROM charging_processes WHERE id=$charging_process_id", "refId": "Start", - "select": [ - [ - { - "params": [ - "latitude" - ], - "type": "column" - } - ] - ], "sql": { "columns": [ { @@ -1110,17 +980,7 @@ } ], "limit": 50 - }, - "table": "addresses", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + } }, { "datasource": { @@ -1129,7 +989,6 @@ }, "editorMode": "code", "format": "table", - "hide": false, "rawQuery": true, "rawSql": "SELECT end_battery_level - start_battery_level AS \"Added\" FROM charging_processes WHERE id=$charging_process_id", "refId": "Added", @@ -1158,7 +1017,6 @@ }, "editorMode": "code", "format": "table", - "hide": false, "rawQuery": true, "rawSql": "SELECT end_battery_level AS \"End\" FROM charging_processes WHERE id=$charging_process_id\r\n", "refId": "End", @@ -1200,8 +1058,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" } ] }, @@ -1221,6 +1078,7 @@ "graphMode": "area", "justifyMode": "auto", "orientation": "auto", + "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "mean" @@ -1233,7 +1091,7 @@ "textMode": "value", "wideLayout": true }, - "pluginVersion": "10.4.0", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { @@ -1242,22 +1100,9 @@ }, "editorMode": "code", "format": "time_series", - "group": [], - "hide": false, - "metricColumn": "none", "rawQuery": true, "rawSql": "SELECT $__timeGroupAlias(date,$__interval),\r\navg(\r\n case when charger_phases >= 1 then (\r\n case when charger_phases = 2 then 3 when charger_phases = 1 then 1 else 0 end)\r\n * charger_actual_current * charger_voltage / 1000.0 else charger_power end) as \"Power\" \r\nFROM charges WHERE charging_process_id = $charging_process_id\r\nGROUP BY 1 ORDER BY 1 ", "refId": "Current", - "select": [ - [ - { - "params": [ - "value" - ], - "type": "column" - } - ] - ], "sql": { "columns": [ { @@ -1274,18 +1119,10 @@ } ], "limit": 50 - }, - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + } } ], - "title": "Average Power", + "title": "Ø Power", "type": "stat" }, { @@ -1304,8 +1141,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" } ] } @@ -1349,6 +1185,7 @@ "graphMode": "area", "justifyMode": "auto", "orientation": "auto", + "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "mean" @@ -1361,7 +1198,7 @@ "textMode": "value", "wideLayout": true }, - "pluginVersion": "10.4.0", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { @@ -1370,22 +1207,9 @@ }, "editorMode": "code", "format": "time_series", - "group": [], - "hide": false, - "metricColumn": "none", "rawQuery": true, "rawSql": "SELECT $__timeGroupAlias(date,$__interval),\r\nconvert_celsius(avg(outside_temp), '$temp_unit') as outside_temp_$temp_unit \r\nFROM charges WHERE charging_process_id = $charging_process_id\r\nGROUP BY 1 ORDER BY 1 ", "refId": "Current", - "select": [ - [ - { - "params": [ - "value" - ], - "type": "column" - } - ] - ], "sql": { "columns": [ { @@ -1402,18 +1226,10 @@ } ], "limit": 50 - }, - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + } } ], - "title": "Average Outdoor Temperature", + "title": "Ø Outdoor Temperature", "type": "stat" }, { @@ -1433,8 +1249,7 @@ "mode": "absolute", "steps": [ { - "color": "orange", - "value": null + "color": "orange" } ] } @@ -1514,6 +1329,7 @@ "graphMode": "area", "justifyMode": "center", "orientation": "vertical", + "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "lastNotNull" @@ -1526,41 +1342,18 @@ "textMode": "value_and_name", "wideLayout": true }, - "pluginVersion": "10.4.0", + "pluginVersion": "11.6.1", "targets": [ { - "alias": "Real", "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, "editorMode": "code", "format": "table", - "group": [], - "groupBy": [], - "hide": false, - "measurement": "km", - "metricColumn": "none", - "orderByTime": "ASC", - "policy": "default", "rawQuery": true, - "rawSql": "SELECT convert_km(start_[[preferred_range]]_range_km, '$length_unit') AS start_$length_unit FROM charging_processes WHERE id=$charging_process_id ", + "rawSql": "SELECT convert_km(start_${preferred_range}_range_km, '$length_unit') AS start_$length_unit FROM charging_processes WHERE id=$charging_process_id ", "refId": "Initial Range", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "value" - ], - "type": "field" - }, - { - "params": [], - "type": "last" - } - ] - ], "sql": { "columns": [ { @@ -1577,49 +1370,18 @@ } ], "limit": 50 - }, - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + } }, { - "alias": "Real", "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, "editorMode": "code", "format": "table", - "group": [], - "groupBy": [], - "hide": false, - "measurement": "km", - "metricColumn": "none", - "orderByTime": "ASC", - "policy": "default", "rawQuery": true, - "rawSql": "SELECT convert_km(end_[[preferred_range]]_range_km - start_[[preferred_range]]_range_km, '$length_unit') AS added_$length_unit FROM charging_processes WHERE id=$charging_process_id ", + "rawSql": "SELECT convert_km(end_${preferred_range}_range_km - start_${preferred_range}_range_km, '$length_unit') AS added_$length_unit FROM charging_processes WHERE id=$charging_process_id ", "refId": "Range Added", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "value" - ], - "type": "field" - }, - { - "params": [], - "type": "last" - } - ] - ], "sql": { "columns": [ { @@ -1636,49 +1398,18 @@ } ], "limit": 50 - }, - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + } }, { - "alias": "Estimated", "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, "editorMode": "code", "format": "table", - "group": [], - "groupBy": [], - "hide": false, - "measurement": "km", - "metricColumn": "none", - "orderByTime": "ASC", - "policy": "default", "rawQuery": true, - "rawSql": "SELECT convert_km(end_[[preferred_range]]_range_km, '$length_unit') AS end_$length_unit FROM charging_processes WHERE id=$charging_process_id", + "rawSql": "SELECT convert_km(end_${preferred_range}_range_km, '$length_unit') AS end_$length_unit FROM charging_processes WHERE id=$charging_process_id", "refId": "End Range", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "value" - ], - "type": "field" - }, - { - "params": [], - "type": "last" - } - ] - ], "sql": { "columns": [ { @@ -1695,15 +1426,7 @@ } ], "limit": 50 - }, - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + } } ], "title": "Ranges ($preferred_range)", @@ -1725,14 +1448,17 @@ "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", + "fillOpacity": 50, "hideFrom": { "legend": false, "tooltip": false, "viz": false }, + "pointShape": "circle", "pointSize": { "fixed": 5 }, + "pointStrokeWidth": 1, "scaleDistribution": { "type": "linear" }, @@ -1743,8 +1469,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -1773,6 +1498,13 @@ { "id": "custom.lineWidth", "value": 1 + }, + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } } ] } @@ -1792,52 +1524,67 @@ "placement": "bottom", "showLegend": false }, + "mapping": "manual", "series": [ { - "pointColor": {}, - "x": "SOC [%]", - "y": "Power [kW]" + "frame": { + "matcher": { + "id": "byIndex", + "options": 0 + } + }, + "x": { + "matcher": { + "id": "byName", + "options": "SOC [%]" + } + }, + "y": { + "matcher": { + "id": "byName", + "options": "Power [kW]" + } + } }, { - "pointColor": { - "fixed": "blue" + "frame": { + "matcher": { + "id": "byIndex", + "options": 1 + } + }, + "x": { + "matcher": { + "id": "byName", + "options": "SOC [%]" + } }, - "x": "avg SOC [%]", - "y": "avg Power [kW]" + "y": { + "matcher": { + "id": "byName", + "options": "Power [kW]" + } + } } ], - "seriesMapping": "manual", "tooltip": { + "hideZeros": false, "mode": "single", "sort": "none" } }, - "pluginVersion": "7.5.11", + "pluginVersion": "11.6.1", "targets": [ { - "alias": "", "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, "editorMode": "code", "format": "table", - "group": [], - "hide": false, - "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT\n battery_level as \"SOC [%]\",\n charger_power as \"Power [kW]\"\nFROM\n charges c\njoin\n charging_processes p ON p.id = c.charging_process_id \nWHERE\n $__timeFilter(date)\n AND p.car_id = $car_id\n AND charger_power > 0\nORDER BY\n date ASC", + "rawSql": "SELECT\n battery_level as \"SOC [%]\",\n charger_power as \"Power [kW]\"\nFROM\n charges c\njoin\n charging_processes p ON p.id = c.charging_process_id \nWHERE\n $__timeFilter(date)\n AND p.car_id = $car_id\n AND charger_power > 0\nORDER BY\n date ASC", "refId": "A", - "select": [ - [ - { - "params": [ - "value" - ], - "type": "column" - } - ] - ], "sql": { "columns": [ { @@ -1854,76 +1601,62 @@ } ], "limit": 50 - }, - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + } }, { "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "table", - "group": [], - "hide": false, - "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT\n battery_level as \"avg SOC [%]\",\n avg(charger_power) as \"avg Power [kW]\"\nFROM\n charges c\njoin\n charging_processes p ON p.id = c.charging_process_id \nWHERE\n $__timeFilter(date)\n AND p.car_id = $car_id\n AND charger_power > 0\nGROUP BY\n battery_level, fast_charger_present\nORDER BY\n battery_level ASC ", + "rawSql": "SELECT\n battery_level as \"SOC [%]\",\n avg(charger_power) as \"Power [kW]\"\nFROM\n charges c\njoin\n charging_processes p ON p.id = c.charging_process_id \nWHERE\n $__timeFilter(date)\n AND p.car_id = $car_id\n AND charger_power > 0\nGROUP BY\n battery_level, fast_charger_present\nORDER BY\n battery_level ASC ", "refId": "B", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "odometer" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "positions", - "timeColumn": "date", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } } ], "title": "Charging curve", "type": "xychart" } ], - "schemaVersion": 39, + "preload": false, + "refresh": "", + "schemaVersion": 41, "tags": [], "templating": { "list": [ { "current": { - "selected": false, "text": "NULL", "value": "NULL" }, "hide": 2, - "label": "", "name": "charging_process_id", "options": [ { - "selected": false, + "selected": true, "text": "NULL", "value": "NULL" } ], "query": "NULL", - "skipUrlSync": false, "type": "textbox" }, { @@ -1935,19 +1668,12 @@ "definition": "select unit_of_temperature from settings limit 1;", "hide": 2, "includeAll": false, - "label": "", - "multi": false, "name": "temp_unit", "options": [], "query": "select unit_of_temperature from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -1955,22 +1681,15 @@ "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, - "definition": "SELECT name AS __text, id AS __value FROM cars ORDER BY display_priority ASC, name ASC;", - "hide": 0, + "definition": "SELECT\n id as __value,\n CASE WHEN COUNT(id) OVER (PARTITION BY name) > 1 AND name IS NOT NULL THEN CONCAT(name, ' - ', RIGHT(vin, 6)) ELSE COALESCE(name, CONCAT('VIN ', vin)) end as __text \nFROM cars\nORDER BY display_priority ASC, name ASC, vin ASC;", "includeAll": false, "label": "Car", - "multi": false, "name": "car_id", "options": [], - "query": "SELECT name AS __text, id AS __value FROM cars ORDER BY display_priority ASC, name ASC;", + "query": "SELECT\n id as __value,\n CASE WHEN COUNT(id) OVER (PARTITION BY name) > 1 AND name IS NOT NULL THEN CONCAT(name, ' - ', RIGHT(vin, 6)) ELSE COALESCE(name, CONCAT('VIN ', vin)) end as __text \nFROM cars\nORDER BY display_priority ASC, name ASC, vin ASC;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -1981,18 +1700,12 @@ "definition": "select unit_of_length from settings limit 1;", "hide": 2, "includeAll": false, - "multi": false, "name": "length_unit", "options": [], "query": "select unit_of_length from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -2003,18 +1716,12 @@ "definition": "select preferred_range from settings limit 1;", "hide": 2, "includeAll": false, - "multi": false, "name": "preferred_range", "options": [], "query": "select preferred_range from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -2025,18 +1732,12 @@ "definition": "select base_url from settings limit 1;", "hide": 2, "includeAll": false, - "multi": false, "name": "base_url", "options": [], "query": "select base_url from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" } ] }, @@ -2044,34 +1745,9 @@ "from": "now-12h", "to": "now" }, - "timepicker": { - "refresh_intervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "time_options": [ - "5m", - "15m", - "1h", - "6h", - "12h", - "24h", - "2d", - "7d", - "30d" - ] - }, + "timepicker": {}, "timezone": "", "title": "Charge Details", "uid": "BHhxFeZRz", - "version": 3, - "weekStart": "" + "version": 1 } \ No newline at end of file diff --git a/grafana/dashboards/internal/drive-details.json b/grafana/dashboards/internal/drive-details.json index bc284154f1..e50c6aca91 100644 --- a/grafana/dashboards/internal/drive-details.json +++ b/grafana/dashboards/internal/drive-details.json @@ -1,60 +1,16 @@ { - "__elements": {}, - "__requires": [ - { - "type": "panel", - "id": "barchart", - "name": "Bar chart", - "version": "" - }, - { - "type": "panel", - "id": "geomap", - "name": "Geomap", - "version": "" - }, - { - "type": "grafana", - "id": "grafana", - "name": "Grafana", - "version": "11.0.0" - }, - { - "type": "datasource", - "id": "grafana-postgresql-datasource", - "name": "PostgreSQL", - "version": "1.0.0" - }, - { - "type": "panel", - "id": "stat", - "name": "Stat", - "version": "" - }, - { - "type": "panel", - "id": "timeseries", - "name": "Time series", - "version": "" - } - ], "annotations": { "list": [ { - "$$hashKey": "object:31", "builtIn": 1, - "datasource": "-- Grafana --", - "definition": "TeslaMate|U2FsdGVkX1/cEWK+8cz7pjEKXtzJnDN7b21ZDXt1MGneFGPWTLqOPtxKmu02mJPLzi/f29I+NBHd3vi0FB8R4Xn0+GtobWDgk6VAVSBTdSNniOKO8i2WPlhRaOsl2+hG7gnZ7wrf1Th2nxR7f1uYCrbwOek0IzkfLzrkjh7gkr6inT6bbDuJqrmogZajLxmAMrQ6V+/vHxBRGiwjJhgiEeq3hM1q2h04JKkNiZ8RHbsF5Cd/xd8Q9u0JVrZzIrtnhM/SFlaApU7RtRMu8CSj1llTX7WEOj6VDZAMSf+XUAanWdk725kEPN9MNu89o2zEq5P3E3cju8IbbBdPzXLV3oVuzD6/tMnxFToIIV1E/BrpF7s2RtNa8+KJJ1PF8xgs6m+/KTD2hy+WsP0636AgObRAmYg7+qotGrgNvpNPdE0EgrB7WHYlV7R/1q66bcq6tCe51X1Un70k+zo+K6AK0o4B1H6IyMlEVuRH/Fz8QVl9aYu2ztd08RbuKJlYVKpkH+pxVETAO9MclYQ90tzE6TfwDZrQZzsAlMenr4s1ZB1OlFXjLjVjnddnUilzO76cqv4yI2THQEuyQ47nuVQ4gUbx02K59vMQhns3C01JOAYokOaSXe66Y7QYdMlk09Lf|aes-256-cbc", + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, "enable": true, "hide": true, "iconColor": "rgba(0, 211, 255, 1)", "name": "Annotations & Alerts", - "target": { - "limit": 100, - "matchAny": false, - "tags": [], - "type": "dashboard" - }, "type": "dashboard" } ] @@ -62,7 +18,6 @@ "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 1, - "id": null, "links": [ { "icon": "dashboard", @@ -70,7 +25,7 @@ "title": "TeslaMate", "tooltip": "", "type": "link", - "url": "[[base_url:raw]]" + "url": "${base_url:raw}" }, { "icon": "", @@ -78,7 +33,7 @@ "title": "GpxExport", "tooltip": "", "type": "link", - "url": "[[base_url:raw]]/drive/$drive_id/gpx" + "url": "${base_url:raw}/drive/$drive_id/gpx" }, { "asDropdown": true, @@ -90,7 +45,6 @@ "type": "dashboards" } ], - "liveNow": false, "panels": [ { "datasource": { @@ -109,6 +63,7 @@ "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, + "barWidthFactor": 0.6, "drawStyle": "line", "fillOpacity": 5, "gradientMode": "opacity", @@ -140,8 +95,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -501,36 +455,24 @@ "showLegend": true }, "tooltip": { + "hideZeros": false, "maxHeight": 600, "mode": "multi", "sort": "none" } }, + "pluginVersion": "11.6.1", "targets": [ { - "alias": "", "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, "editorMode": "code", "format": "time_series", - "group": [], - "hide": false, - "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT\n\t$__time(date),\n\tconvert_km(speed::numeric, '$length_unit') AS speed_[[length_unit]]h,\n\tpower,\n\tconvert_km([[preferred_range]]_battery_range_km, '$length_unit') AS range_[[preferred_range]]_[[length_unit]],\n\tconvert_km(est_battery_range_km, '$length_unit') AS range_estimated_[[length_unit]],\n\tbattery_level,\n\tusable_battery_level,\n\tbattery_heater::integer\nFROM\n\tpositions\nWHERE\n car_id = $car_id AND\n $__timeFilter(date)\nORDER BY\n\tdate ASC", + "rawSql": "SELECT\n\t$__time(date),\n\tconvert_km(speed::numeric, '$length_unit') AS speed_${length_unit}h,\n\tpower,\n\tconvert_km(${preferred_range}_battery_range_km, '$length_unit') AS range_${preferred_range}_${length_unit},\n\tconvert_km(est_battery_range_km, '$length_unit') AS range_estimated_${length_unit},\n\tbattery_level,\n\tusable_battery_level,\n\tbattery_heater::integer\nFROM\n\tpositions\nWHERE\n car_id = $car_id AND\n $__timeFilter(date)\nORDER BY\n\tdate ASC", "refId": "A", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - } - ] - ], "sql": { "columns": [ { @@ -547,17 +489,7 @@ } ], "limit": 50 - }, - "table": "charging", - "timeColumn": "Datum", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + } } ], "title": "Drive", @@ -586,8 +518,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" } ] } @@ -617,6 +548,49 @@ "showZoom": true }, "layers": [ + { + "config": { + "showLegend": false, + "style": { + "color": { + "fixed": "transparent" + }, + "opacity": 0, + "rotation": { + "fixed": 0, + "max": 360, + "min": -360, + "mode": "mod" + }, + "size": { + "fixed": 3, + "max": 15, + "min": 2 + }, + "symbol": { + "fixed": "img/icons/marker/circle.svg", + "mode": "fixed" + }, + "symbolAlign": { + "horizontal": "center", + "vertical": "center" + }, + "textConfig": { + "fontSize": 12, + "offsetX": 0, + "offsetY": 0, + "textAlign": "center", + "textBaseline": "middle" + } + } + }, + "location": { + "mode": "auto" + }, + "name": "Workaround for Grafana Issue #89777, fixed in 11.6.0 via #101391, extra layer to be removed on broad availability of 11.6.0", + "tooltip": false, + "type": "markers" + }, { "config": { "arrow": 0, @@ -675,72 +649,18 @@ "zoom": 15 } }, - "pluginVersion": "11.0.0", + "pluginVersion": "11.6.1", "targets": [ { - "alias": "", "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, "editorMode": "code", "format": "time_series", - "group": [ - { - "params": [ - "$__interval", - "none" - ], - "type": "time" - } - ], - "hide": false, - "metricColumn": "none", "rawQuery": true, "rawSql": "SELECT\n $__time(date),\n latitude,\n longitude\nFROM positions\nWHERE \n car_id = $car_id AND \n $__timeFilter(date)\nORDER BY \n date ASC", "refId": "A", - "select": [ - [ - { - "params": [ - "lat" - ], - "type": "column" - }, - { - "params": [ - "avg" - ], - "type": "aggregate" - }, - { - "params": [ - "lat" - ], - "type": "alias" - } - ], - [ - { - "params": [ - "lng" - ], - "type": "column" - }, - { - "params": [ - "avg" - ], - "type": "aggregate" - }, - { - "params": [ - "lat" - ], - "type": "alias" - } - ] - ], "sql": { "columns": [ { @@ -755,20 +675,12 @@ }, "type": "groupBy" } - ] - }, - "table": "pos", - "timeColumn": "Datum", - "timeColumnType": "datetime", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "limit": 50 + } } ], + "title": "", "type": "geomap" }, { @@ -788,6 +700,7 @@ "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, + "barWidthFactor": 0.6, "drawStyle": "line", "fillOpacity": 5, "gradientMode": "opacity", @@ -813,15 +726,14 @@ "mode": "off" } }, - "decimals": 0, + "decimals": 1, "links": [], "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -892,43 +804,41 @@ "showLegend": false }, "tooltip": { + "hideZeros": false, "maxHeight": 600, "mode": "multi", "sort": "none" } }, + "pluginVersion": "11.6.1", "targets": [ { "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "time_series", - "group": [], - "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT\n\t$__time(date),\n\tROUND(convert_m(elevation, '$alternative_length_unit')) AS elevation_[[alternative_length_unit]]\nFROM\n\tpositions\nWHERE\n car_id = $car_id AND\n $__timeFilter(date)\nORDER BY\n\tdate ASC", + "rawSql": "SELECT\n\t$__time(date),\n\tROUND(convert_m(elevation, '$alternative_length_unit')) AS elevation_${alternative_length_unit}\nFROM\n\tpositions\nWHERE\n car_id = $car_id AND\n $__timeFilter(date)\nORDER BY\n\tdate ASC", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "charge_energy_added" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "charges", - "timeColumn": "date", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } } ], "title": "Elevation", @@ -951,6 +861,7 @@ "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, + "barWidthFactor": 0.6, "drawStyle": "line", "fillOpacity": 5, "gradientMode": "opacity", @@ -982,8 +893,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -1163,43 +1073,41 @@ "showLegend": true }, "tooltip": { + "hideZeros": false, "maxHeight": 600, "mode": "multi", "sort": "none" } }, + "pluginVersion": "11.6.1", "targets": [ { "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "time_series", - "group": [], - "metricColumn": "none", "rawQuery": true, "rawSql": "SELECT\n\t$__time(date),\n\tconvert_celsius(outside_temp, '$temp_unit') AS outside_temp_$temp_unit,\n\tconvert_celsius(inside_temp, '$temp_unit') AS inside_temp_$temp_unit,\n\tconvert_celsius(driver_temp_setting, '$temp_unit') as driver_temp_$temp_unit,\n\tconvert_celsius(passenger_temp_setting, '$temp_unit') as passenger_temp_$temp_unit,\n is_climate_on::integer,\n\tfan_status\nFROM\n\tpositions\nWHERE\n car_id = $car_id AND\n $__timeFilter(date)\nORDER BY\n\tdate ASC", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "charge_energy_added" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "charges", - "timeColumn": "date", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } } ], "title": "Temperatures", @@ -1222,6 +1130,7 @@ "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, + "barWidthFactor": 0.6, "drawStyle": "line", "fillOpacity": 5, "gradientMode": "opacity", @@ -1256,8 +1165,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -1416,45 +1324,41 @@ "showLegend": true }, "tooltip": { + "hideZeros": false, "maxHeight": 600, "mode": "multi", "sort": "none" } }, + "pluginVersion": "11.6.1", "targets": [ { - "alias": "", "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "time_series", - "group": [], - "hide": false, - "metricColumn": "none", "rawQuery": true, "rawSql": "SELECT\r\n $__time(date),\r\n convert_tire_pressure(tpms_pressure_fl,'$pressure_unit') AS tpms_pressure_front_left_$pressure_unit,\r\n convert_tire_pressure(tpms_pressure_fr,'$pressure_unit') AS tpms_pressure_front_right_$pressure_unit,\r\n convert_tire_pressure(tpms_pressure_rl,'$pressure_unit') AS tpms_pressure_rear_left_$pressure_unit,\r\n convert_tire_pressure(tpms_pressure_rr,'$pressure_unit') AS tpms_pressure_rear_right_$pressure_unit\r\nFROM\r\n positions\r\nWHERE\r\n car_id = $car_id AND\r\n $__timeFilter(date) AND\r\n tpms_pressure_fl is not null\r\nORDER BY\r\n date ASC", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "id" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "pos", - "timeColumn": "date", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } } ], "title": "Tire Pressure", @@ -1497,8 +1401,7 @@ "mode": "absolute", "steps": [ { - "color": "super-light-blue", - "value": null + "color": "super-light-blue" } ] }, @@ -1519,6 +1422,7 @@ "graphMode": "area", "justifyMode": "center", "orientation": "auto", + "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "lastNotNull" @@ -1530,7 +1434,7 @@ "textMode": "auto", "wideLayout": true }, - "pluginVersion": "11.0.0", + "pluginVersion": "11.5.2", "targets": [ { "datasource": { @@ -1539,21 +1443,9 @@ }, "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, "rawSql": "SELECT CONCAT_WS(' - ', round(convert_km(start_km::numeric, '$length_unit')), round(convert_km(end_km::numeric, '$length_unit'))) ||' $length_unit' as \"Odometer\"\r\nFROM drives d\r\nWHERE d.id = $drive_id", "refId": "A", - "select": [ - [ - { - "params": [ - "latitude" - ], - "type": "column" - } - ] - ], "sql": { "columns": [ { @@ -1570,17 +1462,7 @@ } ], "limit": 50 - }, - "table": "addresses", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + } } ], "title": "Odometer (From - To)", @@ -1610,8 +1492,7 @@ "mode": "absolute", "steps": [ { - "color": "text", - "value": null + "color": "text" } ] }, @@ -1632,6 +1513,7 @@ "graphMode": "none", "justifyMode": "auto", "orientation": "horizontal", + "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "lastNotNull" @@ -1643,39 +1525,35 @@ "textMode": "auto", "wideLayout": true }, - "pluginVersion": "11.0.0", + "pluginVersion": "11.5.2", "targets": [ { "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, "rawSql": "SELECT ((DATE_PART('day', end_date - start_date) * 24 + \n DATE_PART('hour', end_date - start_date)) * 60 +\n DATE_PART('minute', end_date - start_date)) * 60 +\n DATE_PART('second', end_date - start_date) as sec_diff\nFROM drives\nWHERE drives.id = $drive_id;", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "latitude" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "addresses", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } } ], "title": "Drive Duration", @@ -1698,8 +1576,7 @@ "mode": "absolute", "steps": [ { - "color": "text", - "value": null + "color": "text" } ] }, @@ -1719,6 +1596,7 @@ "graphMode": "area", "justifyMode": "auto", "orientation": "auto", + "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "lastNotNull" @@ -1730,7 +1608,7 @@ "textMode": "auto", "wideLayout": true }, - "pluginVersion": "11.0.0", + "pluginVersion": "11.5.2", "targets": [ { "datasource": { @@ -1805,8 +1683,7 @@ "mode": "absolute", "steps": [ { - "color": "super-light-green", - "value": null + "color": "super-light-green" } ] } @@ -1854,6 +1731,7 @@ "stacking": "none", "text": {}, "tooltip": { + "hideZeros": false, "maxHeight": 600, "mode": "multi", "sort": "none" @@ -1862,7 +1740,7 @@ "xTickLabelRotation": 0, "xTickLabelSpacing": 0 }, - "pluginVersion": "10.4.0", + "pluginVersion": "11.5.2", "targets": [ { "datasource": { @@ -1871,21 +1749,9 @@ }, "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, "rawSql": "SELECT \r\n speed_section_$length_unit AS \"Speed\",\r\n SUM(seconds_elapsed) * 100 / MAX(duration) as \"Elapsed\",\r\n TO_CHAR((SUM(seconds_elapsed) || ' second')::interval, 'HH24:MI:SS') AS \"Time\"\r\nFROM (\r\n SELECT\r\n ROUND(convert_km(p.speed::numeric, '$length_unit') / 10,0) * 10 AS speed_section_$length_unit,\r\n EXTRACT(EPOCH FROM (LEAD(p.\"date\") OVER (ORDER BY p.\"date\") - p.\"date\")) AS seconds_elapsed,\r\n EXTRACT(EPOCH FROM (end_date - start_date)) AS duration\r\n FROM drives d\r\n INNER JOIN positions p ON p.drive_id = d.id\r\n WHERE d.id = $drive_id\r\n) AS drivedata\r\nWHERE speed_section_$length_unit>0\r\nGROUP BY 1\r\nORDER BY 1", "refId": "A", - "select": [ - [ - { - "params": [ - "value" - ], - "type": "column" - } - ] - ], "sql": { "columns": [ { @@ -1902,15 +1768,7 @@ } ], "limit": 50 - }, - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + } } ], "title": "Speed Histogram ($speed_unit)", @@ -1956,8 +1814,7 @@ "mode": "absolute", "steps": [ { - "color": "semi-dark-green", - "value": null + "color": "semi-dark-green" } ] }, @@ -2003,6 +1860,7 @@ "graphMode": "none", "justifyMode": "auto", "orientation": "auto", + "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "lastNotNull" @@ -2014,42 +1872,38 @@ "textMode": "value", "wideLayout": true }, - "pluginVersion": "11.0.0", + "pluginVersion": "11.5.0", "targets": [ { "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, "rawSql": "select convert_km(distance::numeric, '$length_unit') as \"$length_unit\" from drives where id = $drive_id;", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "latitude" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "addresses", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } } ], - "title": "Distance", + "title": "Distance driven", "type": "stat" }, { @@ -2067,8 +1921,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" } ] } @@ -2087,6 +1940,7 @@ "graphMode": "none", "justifyMode": "auto", "orientation": "vertical", + "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "lastNotNull" @@ -2099,7 +1953,7 @@ "textMode": "value_and_name", "wideLayout": true }, - "pluginVersion": "11.0.0", + "pluginVersion": "11.5.0", "targets": [ { "datasource": { @@ -2108,21 +1962,9 @@ }, "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, "rawSql": "WITH height as (SELECT\n\televation-LAG(elevation,1) over ( order BY\n\tdate ASC ) as diff\nFROM\n\tpositions\nWHERE\n car_id = $car_id AND\n $__timeFilter(date)\nORDER BY\n\tdate ASC\n\t)\nSELECT ROUND(convert_m(sum(diff), '$alternative_length_unit')::numeric,0) || ' $alternative_length_unit' as \"UP\" from height where diff > 0", "refId": "A", - "select": [ - [ - { - "params": [ - "start_km" - ], - "type": "column" - } - ] - ], "sql": { "columns": [ { @@ -2139,17 +1981,7 @@ } ], "limit": 50 - }, - "table": "drives", - "timeColumn": "start_date", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + } }, { "datasource": { @@ -2158,22 +1990,9 @@ }, "editorMode": "code", "format": "table", - "group": [], - "hide": false, - "metricColumn": "none", "rawQuery": true, "rawSql": "WITH height as (SELECT\n\televation-LAG(elevation,1) over ( order BY\n\tdate ASC ) as diff\nFROM\n\tpositions\nWHERE\n car_id = $car_id AND\n $__timeFilter(date)\nORDER BY\n\tdate ASC\n\t)\nSELECT ROUND(convert_m(sum(diff), '$alternative_length_unit')::numeric,0) || ' $alternative_length_unit' as \"DOWN\" from height where diff < 0", "refId": "B", - "select": [ - [ - { - "params": [ - "start_km" - ], - "type": "column" - } - ] - ], "sql": { "columns": [ { @@ -2190,17 +2009,7 @@ } ], "limit": 50 - }, - "table": "drives", - "timeColumn": "start_date", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + } } ], "title": "Elevation Summary", @@ -2229,8 +2038,7 @@ "mode": "absolute", "steps": [ { - "color": "light-yellow", - "value": null + "color": "light-yellow" } ] }, @@ -2240,7 +2048,7 @@ }, "gridPos": { "h": 3, - "w": 6, + "w": 3, "x": 0, "y": 31 }, @@ -2251,6 +2059,7 @@ "graphMode": "none", "justifyMode": "center", "orientation": "horizontal", + "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "lastNotNull" @@ -2262,42 +2071,129 @@ "textMode": "value", "wideLayout": true }, - "pluginVersion": "11.0.0", + "pluginVersion": "11.5.0", "targets": [ { "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT\n\t(NULLIF(GREATEST(start_[[preferred_range]]_range_km - end_[[preferred_range]]_range_km, 0), 0) * car.efficiency)\nFROM\n\tdrives d\nJOIN cars car ON car.id = car_id\nWHERE\n\td.id = $drive_id;", + "rawSql": "SELECT\n\t(NULLIF(GREATEST(start_${preferred_range}_range_km - end_${preferred_range}_range_km, 0), 0) * car.efficiency)\nFROM\n\tdrives d\nJOIN cars car ON car.id = car_id\nWHERE\n\td.id = $drive_id;", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "latitude" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "addresses", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Energy consumed (net)", + "type": "stat" + }, + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "description": "Shows how much energy has been recovered through regenerative braking.\n\n\"Energy recovered\" is already included in \"Energy consumed (net)\" and is determined by looking for periods with negative power within your drive. \n\nRequires Streaming API to be enabled and available for this drive.", + "fieldConfig": { + "defaults": { + "decimals": 2, + "mappings": [ { - "name": "$__timeFilter", - "params": [], - "type": "macro" + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" } - ] + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "light-yellow" + } + ] + }, + "unit": "kwatth" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 3, + "x": 3, + "y": 31 + }, + "id": 40, + "maxDataPoints": 100, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "center", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "value", + "wideLayout": true + }, + "pluginVersion": "11.5.0", + "targets": [ + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "with data as (\nselect\n\tp.power,\n\textract (second from p.date - lag(p.date) over (order by p.date)) as seconds\nfrom positions p\n\twhere drive_id = ${drive_id} and power < 0\n)\n\nselect sum(power * (seconds / 3600)) * -1 from data where seconds is not null and seconds < 1.5", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } } ], - "title": "Energy Used", + "title": "Energy recovered", "type": "stat" }, { @@ -2316,8 +2212,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "yellow", @@ -2370,6 +2265,7 @@ "graphMode": "area", "justifyMode": "auto", "orientation": "auto", + "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "mean" @@ -2381,7 +2277,7 @@ "textMode": "value", "wideLayout": true }, - "pluginVersion": "11.0.0", + "pluginVersion": "11.5.0", "targets": [ { "datasource": { @@ -2390,21 +2286,9 @@ }, "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, - "rawSql": "select\n\t(NULLIF(GREATEST(start_[[preferred_range]]_range_km - end_[[preferred_range]]_range_km, 0), 0) * car.efficiency) *1000 /\n\t convert_km(distance::numeric, '$length_unit') as \"$length_unit\"\nfrom drives d\nJOIN cars car ON car.id = car_id\nwhere d.id = $drive_id;", + "rawSql": "select\n\t(NULLIF(GREATEST(start_${preferred_range}_range_km - end_${preferred_range}_range_km, 0), 0) * car.efficiency) *1000 /\n\t convert_km(distance::numeric, '$length_unit') as \"$length_unit\"\nfrom drives d\nJOIN cars car ON car.id = car_id\nwhere d.id = $drive_id;", "refId": "A", - "select": [ - [ - { - "params": [ - "start_km" - ], - "type": "column" - } - ] - ], "sql": { "columns": [ { @@ -2421,20 +2305,10 @@ } ], "limit": 50 - }, - "table": "drives", - "timeColumn": "start_date", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + } } ], - "title": "Consumption", + "title": "Consumption (net)", "type": "stat" }, { @@ -2455,8 +2329,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" } ] } @@ -2500,6 +2373,7 @@ "graphMode": "area", "justifyMode": "center", "orientation": "auto", + "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "mean" @@ -2511,7 +2385,7 @@ "textMode": "value", "wideLayout": false }, - "pluginVersion": "11.0.0", + "pluginVersion": "11.5.0", "targets": [ { "datasource": { @@ -2520,21 +2394,9 @@ }, "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT\n\tconvert_km(avg(speed)::numeric, '$length_unit') AS speed_[[length_unit]]h\nFROM positions\nWHERE car_id = $car_id AND $__timeFilter(date)", + "rawSql": "SELECT\n\tconvert_km(avg(speed)::numeric, '$length_unit') AS speed_${length_unit}h\nFROM positions\nWHERE car_id = $car_id AND $__timeFilter(date)", "refId": "A", - "select": [ - [ - { - "params": [ - "start_km" - ], - "type": "column" - } - ] - ], "sql": { "columns": [ { @@ -2551,40 +2413,33 @@ } ], "limit": 50 - }, - "table": "drives", - "timeColumn": "start_date", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + } } ], - "title": "Avg. Speed", + "title": "Ø Speed", "type": "stat" } ], - "schemaVersion": 39, + "preload": false, + "schemaVersion": 41, "tags": [], "templating": { "list": [ { - "current": {}, + "current": { + "text": "1514", + "value": "1514" + }, "hide": 2, "name": "drive_id", "options": [ { - "selected": false, - "text": "NULL", - "value": "NULL" + "selected": true, + "text": "1514", + "value": "1514" } ], - "query": "", - "skipUrlSync": false, + "query": "1514", "type": "textbox" }, { @@ -2596,19 +2451,12 @@ "definition": "select unit_of_temperature from settings limit 1;", "hide": 2, "includeAll": false, - "label": "", - "multi": false, "name": "temp_unit", "options": [], "query": "select unit_of_temperature from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -2619,19 +2467,12 @@ "definition": "select unit_of_length from settings limit 1;", "hide": 2, "includeAll": false, - "label": "", - "multi": false, "name": "length_unit", "options": [], "query": "select unit_of_length from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -2639,22 +2480,15 @@ "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, - "definition": "SELECT name AS __text, id AS __value FROM cars ORDER BY display_priority ASC, name ASC;", - "hide": 0, + "definition": "SELECT\n id as __value,\n CASE WHEN COUNT(id) OVER (PARTITION BY name) > 1 AND name IS NOT NULL THEN CONCAT(name, ' - ', RIGHT(vin, 6)) ELSE COALESCE(name, CONCAT('VIN ', vin)) end as __text \nFROM cars\nORDER BY display_priority ASC, name ASC, vin ASC;", "includeAll": false, "label": "Car", - "multi": false, "name": "car_id", "options": [], - "query": "SELECT name AS __text, id AS __value FROM cars ORDER BY display_priority ASC, name ASC;", + "query": "SELECT\n id as __value,\n CASE WHEN COUNT(id) OVER (PARTITION BY name) > 1 AND name IS NOT NULL THEN CONCAT(name, ' - ', RIGHT(vin, 6)) ELSE COALESCE(name, CONCAT('VIN ', vin)) end as __text \nFROM cars\nORDER BY display_priority ASC, name ASC, vin ASC;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -2665,19 +2499,12 @@ "definition": "select case when unit_of_length = 'km' then 'm' when unit_of_length = 'mi' then 'ft' end from settings limit 1;", "hide": 2, "includeAll": false, - "label": "", - "multi": false, "name": "alternative_length_unit", "options": [], "query": "select case when unit_of_length = 'km' then 'm' when unit_of_length = 'mi' then 'ft' end from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -2688,18 +2515,12 @@ "definition": "select preferred_range from settings limit 1;", "hide": 2, "includeAll": false, - "multi": false, "name": "preferred_range", "options": [], "query": "select preferred_range from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -2710,19 +2531,12 @@ "definition": "select base_url from settings limit 1;", "hide": 2, "includeAll": false, - "label": "", - "multi": false, "name": "base_url", "options": [], "query": "select base_url from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -2733,14 +2547,11 @@ "definition": "select unit_of_pressure from settings limit 1;", "hide": 2, "includeAll": false, - "multi": false, "name": "pressure_unit", "options": [], "query": "select unit_of_pressure from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, "type": "query" }, { @@ -2752,25 +2563,24 @@ "definition": "SELECT CASE WHEN '$length_unit' = 'km' THEN 'km/h' WHEN '$length_unit' = 'mi' THEN 'mph' END", "hide": 2, "includeAll": false, - "multi": false, "name": "speed_unit", "options": [], "query": "SELECT CASE WHEN '$length_unit' = 'km' THEN 'km/h' WHEN '$length_unit' = 'mi' THEN 'mph' END", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, "type": "query" } ] }, + "time": { + "from": "now-12h", + "to": "now" + }, "timepicker": { - "refresh_intervals": [], - "time_options": [] + "refresh_intervals": [] }, "timezone": "", "title": "Drive Details", "uid": "zm7wN6Zgz", - "version": 6, - "weekStart": "" + "version": 1 } \ No newline at end of file diff --git a/grafana/dashboards/internal/home.json b/grafana/dashboards/internal/home.json new file mode 100644 index 0000000000..5aee5206f8 --- /dev/null +++ b/grafana/dashboards/internal/home.json @@ -0,0 +1,115 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "gridPos": { + "h": 20, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 2, + "options": { + "folderUID": "Nr4ofiDZk", + "includeVars": false, + "keepTime": false, + "maxItems": 100, + "query": "", + "showFolderNames": false, + "showHeadings": false, + "showRecentlyViewed": false, + "showSearch": true, + "showStarred": false, + "tags": [] + }, + "pluginVersion": "11.6.1", + "title": "", + "type": "dashlist" + }, + { + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "gridPos": { + "h": 20, + "w": 16, + "x": 6, + "y": 0 + }, + "id": 1, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "", + "mode": "html" + }, + "pluginVersion": "11.6.1", + "title": "", + "type": "text" + }, + { + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "gridPos": { + "h": 20, + "w": 2, + "x": 22, + "y": 0 + }, + "id": 3, + "options": { + "feedUrl": "https://corsproxy.io/?url=https://github.com/teslamate-org/teslamate/tags.atom", + "showImage": false + }, + "pluginVersion": "11.6.1", + "title": "Releases", + "type": "news" + } + ], + "preload": false, + "schemaVersion": 41, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "hidden": true + }, + "timezone": "browser", + "title": "Home", + "uid": "be2m9kga7b8qoc", + "version": 1 +} \ No newline at end of file diff --git a/grafana/dashboards/locations.json b/grafana/dashboards/locations.json index 24288b45c8..3602f91730 100644 --- a/grafana/dashboards/locations.json +++ b/grafana/dashboards/locations.json @@ -1,52 +1,16 @@ { - "__elements": {}, - "__requires": [ - { - "type": "panel", - "id": "bargauge", - "name": "Bar gauge", - "version": "" - }, - { - "type": "grafana", - "id": "grafana", - "name": "Grafana", - "version": "11.0.0" - }, - { - "type": "datasource", - "id": "grafana-postgresql-datasource", - "name": "PostgreSQL", - "version": "1.0.0" - }, - { - "type": "panel", - "id": "stat", - "name": "Stat", - "version": "" - }, - { - "type": "panel", - "id": "table", - "name": "Table", - "version": "" - } - ], "annotations": { "list": [ { "builtIn": 1, - "datasource": "-- Grafana --", + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, "enable": true, "hide": true, "iconColor": "rgba(0, 211, 255, 1)", "name": "Annotations & Alerts", - "target": { - "limit": 100, - "matchAny": false, - "tags": [], - "type": "dashboard" - }, "type": "dashboard" } ] @@ -54,7 +18,6 @@ "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 0, - "id": null, "links": [ { "icon": "dashboard", @@ -62,7 +25,7 @@ "title": "TeslaMate", "tooltip": "", "type": "link", - "url": "[[base_url:raw]]" + "url": "${base_url:raw}" }, { "asDropdown": true, @@ -74,7 +37,6 @@ "type": "dashboards" } ], - "liveNow": false, "panels": [ { "datasource": { @@ -88,8 +50,7 @@ "mode": "absolute", "steps": [ { - "color": "#c7d0d9", - "value": null + "color": "#c7d0d9" } ] }, @@ -99,7 +60,7 @@ }, "gridPos": { "h": 3, - "w": 7, + "w": 6, "x": 0, "y": 0 }, @@ -115,6 +76,7 @@ "graphMode": "none", "justifyMode": "auto", "orientation": "horizontal", + "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "mean" @@ -126,48 +88,59 @@ "textMode": "value", "wideLayout": true }, - "pluginVersion": "11.0.0", + "pluginVersion": "11.6.1", "targets": [ { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, - "rawSql": "select count(*) from addresses;", + "rawSql": "select count(*), count(distinct city) as city_count, count(distinct state) as state_count, count(distinct country) as country_count from addresses where id in (\r\n select start_address_id from drives where car_id in ($car_id) and $__timeFilter(start_date)\r\n union\r\n select end_address_id from drives where car_id in ($car_id) and $__timeFilter(end_date)\r\n union\r\n select address_id from charging_processes where car_id in ($car_id) and ($__timeFilter(start_date) or $__timeFilter(end_date))\r\n);", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "latitude" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "addresses", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ], - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "# of Addresses", + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": { + "city_count": true, + "country_count": true, + "state_count": true + }, + "includeByName": {}, + "indexByName": {}, + "renameByName": {} } } ], - "title": "Total addresses", "type": "stat" }, { "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" + "type": "datasource", + "uid": "-- Dashboard --" }, "fieldConfig": { "defaults": { @@ -176,8 +149,7 @@ "mode": "absolute", "steps": [ { - "color": "#c7d0d9", - "value": null + "color": "#c7d0d9" } ] }, @@ -187,8 +159,8 @@ }, "gridPos": { "h": 3, - "w": 7, - "x": 7, + "w": 6, + "x": 6, "y": 0 }, "id": 20, @@ -203,6 +175,7 @@ "graphMode": "none", "justifyMode": "auto", "orientation": "horizontal", + "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "mean" @@ -214,48 +187,39 @@ "textMode": "value", "wideLayout": true }, - "pluginVersion": "11.0.0", + "pluginVersion": "11.6.1", "targets": [ { - "format": "table", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "SELECT\n\tCOUNT(DISTINCT city)\nFROM\n\taddresses;", - "refId": "A", - "select": [ - [ - { - "params": [ - "latitude" - ], - "type": "column" - } - ] - ], - "table": "addresses", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ], "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" + "type": "datasource", + "uid": "-- Dashboard --" + }, + "panelId": 12, + "refId": "A" + } + ], + "title": "# of Cities", + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": { + "count": true, + "country_count": true, + "state_count": true + }, + "includeByName": {}, + "indexByName": {}, + "renameByName": {} } } ], - "title": "Cities", "type": "stat" }, { "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" + "type": "datasource", + "uid": "-- Dashboard --" }, "fieldConfig": { "defaults": { @@ -264,8 +228,7 @@ "mode": "absolute", "steps": [ { - "color": "#c7d0d9", - "value": null + "color": "#c7d0d9" } ] }, @@ -275,8 +238,8 @@ }, "gridPos": { "h": 3, - "w": 5, - "x": 14, + "w": 6, + "x": 12, "y": 0 }, "id": 18, @@ -291,6 +254,7 @@ "graphMode": "none", "justifyMode": "auto", "orientation": "horizontal", + "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "mean" @@ -302,48 +266,39 @@ "textMode": "value", "wideLayout": true }, - "pluginVersion": "11.0.0", + "pluginVersion": "11.6.1", "targets": [ { - "format": "table", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "SELECT\n\tCOUNT(DISTINCT state)\nFROM\n\taddresses;", - "refId": "A", - "select": [ - [ - { - "params": [ - "latitude" - ], - "type": "column" - } - ] - ], - "table": "addresses", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ], "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" + "type": "datasource", + "uid": "-- Dashboard --" + }, + "panelId": 12, + "refId": "A" + } + ], + "title": "# of States", + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": { + "city_count": true, + "count": true, + "country_count": true + }, + "includeByName": {}, + "indexByName": {}, + "renameByName": {} } } ], - "title": "States", "type": "stat" }, { "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" + "type": "datasource", + "uid": "-- Dashboard --" }, "fieldConfig": { "defaults": { @@ -352,8 +307,7 @@ "mode": "absolute", "steps": [ { - "color": "#c7d0d9", - "value": null + "color": "#c7d0d9" } ] }, @@ -363,8 +317,8 @@ }, "gridPos": { "h": 3, - "w": 5, - "x": 19, + "w": 6, + "x": 18, "y": 0 }, "id": 16, @@ -379,6 +333,7 @@ "graphMode": "none", "justifyMode": "auto", "orientation": "horizontal", + "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "mean" @@ -390,42 +345,33 @@ "textMode": "value", "wideLayout": true }, - "pluginVersion": "11.0.0", + "pluginVersion": "11.6.1", "targets": [ { - "format": "table", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "SELECT\n\tCOUNT(DISTINCT country)\nFROM\n\taddresses;", - "refId": "A", - "select": [ - [ - { - "params": [ - "latitude" - ], - "type": "column" - } - ] - ], - "table": "addresses", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ], "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" + "type": "datasource", + "uid": "-- Dashboard --" + }, + "panelId": 12, + "refId": "A" + } + ], + "title": "# of Countries", + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": { + "city_count": true, + "count": true, + "state_count": true + }, + "includeByName": {}, + "indexByName": {}, + "renameByName": {} } } ], - "title": "Countries", "type": "stat" }, { @@ -445,8 +391,7 @@ "mode": "absolute", "steps": [ { - "color": "semi-dark-green", - "value": null + "color": "semi-dark-green" }, { "color": "super-light-green", @@ -466,6 +411,12 @@ "id": 10, "options": { "displayMode": "gradient", + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, "maxVizHeight": 300, "minVizHeight": 16, "minVizWidth": 8, @@ -482,36 +433,34 @@ "sizing": "auto", "valueMode": "color" }, - "pluginVersion": "11.0.0", + "pluginVersion": "11.6.1", "targets": [ { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT\n\tcity,\n\tcount(*) as \"# addresses\"\nFROM\n\taddresses\nWHERE\n\tcity IS NOT NULL\nGROUP BY\n\t1\nORDER BY\n\t2 DESC\nLIMIT 10;", + "rawSql": "SELECT\n\tcity,\n\tcount(*) as \"# of Addresses\"\nFROM\n\taddresses\nWHERE\n\tcity IS NOT NULL and\n id in (\n\t\tselect start_address_id from drives where car_id in ($car_id) and $__timeFilter(start_date) \n\t\tunion\n\t\tselect end_address_id from drives where car_id in ($car_id) and $__timeFilter(end_date) \n\t\tunion\n\t\tselect address_id from charging_processes where car_id in ($car_id) and ($__timeFilter(start_date) or $__timeFilter(end_date))\n\t)\nGROUP BY\n\t1\nORDER BY\n\t2 DESC\nLIMIT 10;", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "value" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ], - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 } } ], @@ -535,8 +484,7 @@ "mode": "absolute", "steps": [ { - "color": "semi-dark-orange", - "value": null + "color": "semi-dark-orange" }, { "color": "super-light-orange", @@ -556,6 +504,12 @@ "id": 14, "options": { "displayMode": "gradient", + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, "maxVizHeight": 300, "minVizHeight": 16, "minVizWidth": 8, @@ -572,38 +526,34 @@ "sizing": "auto", "valueMode": "color" }, - "pluginVersion": "11.0.0", + "pluginVersion": "11.6.1", "targets": [ { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT\n\tstate,\n\tcount(*) as \"# addresses\"\nFROM\n\taddresses\nWHERE\n\tstate IS NOT NULL\nGROUP BY\n\t1\nORDER BY\n\t2 DESC\nLIMIT 10;", + "rawSql": "SELECT\n\tstate,\n\tcount(*) as \"# of Addresses\"\nFROM\n\taddresses\nWHERE\n\tstate IS NOT NULL and\n id in (\n\t\tselect start_address_id from drives where car_id in ($car_id) and $__timeFilter(start_date)\n\t\tunion\n\t\tselect end_address_id from drives where car_id in ($car_id) and $__timeFilter(end_date)\n\t\tunion\n\t\tselect address_id from charging_processes where car_id in ($car_id) and ($__timeFilter(start_date) or $__timeFilter(end_date))\n\t)\nGROUP BY\n\t1\nORDER BY\n\t2 DESC\nLIMIT 10;", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "latitude" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "addresses", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ], - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 } } ], @@ -635,8 +585,7 @@ "mode": "absolute", "steps": [ { - "color": "semi-dark-blue", - "value": null + "color": "semi-dark-blue" }, { "color": "light-red", @@ -659,7 +608,7 @@ }, { "id": "custom.width", - "value": 180 + "value": 210 } ] }, @@ -701,54 +650,38 @@ } ] }, - "pluginVersion": "11.0.0", + "pluginVersion": "11.6.1", "targets": [ { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT\n max(end_date) as \"Date\",\n count(*) as visited,\n COALESCE(g.name, array_to_string(((string_to_array(a.display_name, ', ', ''))[0:2]), ', ')) AS \"Address\",\n\tCOALESCE(city, neighbourhood) as \"City\"\nFROM drives t\nINNER JOIN addresses a ON end_address_id = a.id\nLEFT JOIN geofences g ON end_geofence_id = g.id\nWHERE a.display_name ilike '%$address_filter%' or g.name ilike '%$address_filter%'\nGROUP BY 3,4\nORDER BY visited DESC\nLIMIT 100", + "rawSql": "with locations as (\n\n select address_id, geofence_id, start_date as end_date from charging_processes where car_id in ($car_id) and ($__timeFilter(start_date) or $__timeFilter(end_date))\n union\n select end_address_id as address_id, end_geofence_id as geofence_id, end_date from drives where car_id in ($car_id) and $__timeFilter(end_date)\n\n)\n\nSELECT\n max(l.end_date) as \"Date\",\n COALESCE(g.name, array_to_string(((string_to_array(a.display_name, ', ', ''))[0:2]), ', ')) AS \"Address\",\n\tCOALESCE(city, neighbourhood) as \"City\"\nFROM locations l\nINNER JOIN addresses a ON l.address_id = a.id\nLEFT JOIN geofences g ON l.geofence_id = g.id\nWHERE\n (a.display_name ilike '%$address_filter%' or g.name ilike '%$address_filter%')\nGROUP BY 2,3\nLIMIT 100", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "latitude" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "addresses", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ], - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 } } ], "title": "Last visited", - "transformations": [ - { - "id": "organize", - "options": { - "excludeByName": { - "visited": true - }, - "indexByName": {}, - "renameByName": {} - } - } - ], "type": "table" }, { @@ -775,8 +708,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -830,7 +762,7 @@ { "targetBlank": true, "title": "", - "url": "[[base_url:raw]]/geo-fences/${__data.fields.path}" + "url": "${base_url:raw}/geo-fences/${__data.fields.path}" } ] }, @@ -967,38 +899,34 @@ }, "showHeader": true }, - "pluginVersion": "11.0.0", + "pluginVersion": "11.6.1", "targets": [ { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT\n CONCAT('new?lat=', latitude, '&lng=', longitude) as path,\n\tCOALESCE(name, CONCAT(road, ' ', house_number)) AS name,\n\tneighbourhood,\n\tcity,\n\tstate,\n\tcountry\nFROM addresses\nWHERE display_name ilike '%$address_filter%'\nORDER BY inserted_at DESC\nLIMIT 100;", + "rawSql": "SELECT\n CONCAT('new?lat=', latitude, '&lng=', longitude) as path,\n\tCOALESCE(name, CONCAT(road, ' ', house_number)) AS name,\n\tneighbourhood,\n\tcity,\n\tstate,\n\tcountry\nFROM addresses\nWHERE display_name ilike '%$address_filter%' and id in (\n select start_address_id from drives where car_id in ($car_id) and $__timeFilter(start_date)\n union\n select end_address_id from drives where car_id in ($car_id) and $__timeFilter(end_date)\n union\n select address_id from charging_processes where car_id in ($car_id) and ($__timeFilter(start_date) or $__timeFilter(end_date))\n)\nORDER BY inserted_at DESC\nLIMIT 100;", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "latitude" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "addresses", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ], - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 } } ], @@ -1037,8 +965,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -1092,7 +1019,7 @@ { "targetBlank": true, "title": "", - "url": "[[base_url:raw]]/geo-fences/${__data.fields.id.numeric:raw}/edit" + "url": "${base_url:raw}/geo-fences/${__data.fields.id.numeric:raw}/edit" } ] }, @@ -1137,36 +1064,34 @@ }, "showHeader": true }, - "pluginVersion": "11.0.0", + "pluginVersion": "11.6.1", "targets": [ { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT id, name \nFROM geofences \nORDER BY inserted_at DESC\nLIMIT 512;", + "rawSql": "SELECT id, name \nFROM geofences where id in (\n select start_geofence_id from drives where car_id in ($car_id) and $__timeFilter(start_date)\n union\n select end_geofence_id from drives where car_id in ($car_id) and $__timeFilter(end_date)\n union\n select geofence_id from charging_processes where car_id in ($car_id) and ($__timeFilter(start_date) or $__timeFilter(end_date))\n)\nORDER BY inserted_at DESC\nLIMIT 100;", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "value" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ], - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 } } ], @@ -1182,7 +1107,9 @@ "type": "table" } ], - "schemaVersion": 39, + "preload": false, + "refresh": "", + "schemaVersion": 41, "tags": [ "tesla" ], @@ -1194,22 +1121,17 @@ "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, - "definition": "SELECT name AS __text, id AS __value FROM cars ORDER BY display_priority ASC, name ASC;", + "definition": "SELECT\n id as __value,\n CASE WHEN COUNT(id) OVER (PARTITION BY name) > 1 AND name IS NOT NULL THEN CONCAT(name, ' - ', RIGHT(vin, 6)) ELSE COALESCE(name, CONCAT('VIN ', vin)) end as __text \nFROM cars\nORDER BY display_priority ASC, name ASC, vin ASC;", "hide": 1, "includeAll": true, "label": "Car", "multi": true, "name": "car_id", "options": [], - "query": "SELECT name AS __text, id AS __value FROM cars ORDER BY display_priority ASC, name ASC;", + "query": "SELECT\n id as __value,\n CASE WHEN COUNT(id) OVER (PARTITION BY name) > 1 AND name IS NOT NULL THEN CONCAT(name, ' - ', RIGHT(vin, 6)) ELSE COALESCE(name, CONCAT('VIN ', vin)) end as __text \nFROM cars\nORDER BY display_priority ASC, name ASC, vin ASC;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -1220,27 +1142,18 @@ "definition": "select base_url from settings limit 1;", "hide": 2, "includeAll": false, - "label": "", - "multi": false, "name": "base_url", "options": [], "query": "select base_url from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": { - "selected": false, "text": "", "value": "" }, - "hide": 0, "label": "Address", "name": "address_filter", "options": [ @@ -1251,45 +1164,17 @@ } ], "query": "", - "skipUrlSync": false, "type": "textbox" } ] }, "time": { - "from": "now-30d", + "from": "now-1y", "to": "now" }, - "timeRangeUpdatedDuringEditOrView": false, - "timepicker": { - "hidden": true, - "refresh_intervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "time_options": [ - "5m", - "15m", - "1h", - "6h", - "12h", - "24h", - "2d", - "7d", - "30d" - ] - }, + "timepicker": {}, "timezone": "", "title": "Locations", "uid": "ZzhF-aRWz", - "version": 2, - "weekStart": "" + "version": 1 } \ No newline at end of file diff --git a/grafana/dashboards/mileage.json b/grafana/dashboards/mileage.json index 21703fc2e7..08dfdb66df 100644 --- a/grafana/dashboards/mileage.json +++ b/grafana/dashboards/mileage.json @@ -1,40 +1,16 @@ { - "__elements": {}, - "__requires": [ - { - "type": "grafana", - "id": "grafana", - "name": "Grafana", - "version": "11.0.0" - }, - { - "type": "datasource", - "id": "grafana-postgresql-datasource", - "name": "PostgreSQL", - "version": "1.0.0" - }, - { - "type": "panel", - "id": "timeseries", - "name": "Time series", - "version": "" - } - ], "annotations": { "list": [ { "builtIn": 1, - "datasource": "-- Grafana --", + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, "enable": true, "hide": true, "iconColor": "rgba(0, 211, 255, 1)", "name": "Annotations & Alerts", - "target": { - "limit": 100, - "matchAny": false, - "tags": [], - "type": "dashboard" - }, "type": "dashboard" } ] @@ -42,7 +18,6 @@ "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 0, - "id": null, "links": [ { "icon": "dashboard", @@ -50,7 +25,7 @@ "title": "TeslaMate", "tooltip": "", "type": "link", - "url": "[[base_url:raw]]" + "url": "${base_url:raw}" }, { "asDropdown": true, @@ -62,10 +37,9 @@ "type": "dashboards" } ], - "liveNow": false, "panels": [ { - "datasource": "TeslaMate", + "collapsed": false, "gridPos": { "h": 1, "w": 24, @@ -95,6 +69,7 @@ "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, + "barWidthFactor": 0.6, "drawStyle": "line", "fillOpacity": 20, "gradientMode": "opacity", @@ -127,8 +102,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -195,52 +169,50 @@ "showLegend": true }, "tooltip": { + "hideZeros": false, "maxHeight": 600, "mode": "multi", "sort": "none" } }, - "pluginVersion": "8.5.4", + "pluginVersion": "11.6.1", "targets": [ { - "alias": "", "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "time_series", - "group": [], - "hide": false, - "metricColumn": "none", "rawQuery": true, "rawSql": "WITH o AS (SELECT\n start_date AS time,\n car_id,\n start_km AS \"odometer\"\nFROM drives\nUNION ALL\nSELECT\n end_date,\n car_id,\n end_km AS \"odometer\"\nFROM drives)\n\nSELECT\n time, \n convert_km(odometer::numeric, '$length_unit') as mileage_$length_unit\nFROM o\nWHERE\n\tcar_id = $car_id AND\n\t$__timeFilter(time)\norder by 1;", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "value" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } } ], "title": "Mileage", "type": "timeseries" } ], - "refresh": false, - "schemaVersion": 39, + "preload": false, + "refresh": "", + "schemaVersion": 41, "tags": [ "tesla" ], @@ -252,22 +224,16 @@ "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, - "definition": "SELECT name AS __text, id AS __value FROM cars ORDER BY display_priority ASC, name ASC;", + "definition": "SELECT\n id as __value,\n CASE WHEN COUNT(id) OVER (PARTITION BY name) > 1 AND name IS NOT NULL THEN CONCAT(name, ' - ', RIGHT(vin, 6)) ELSE COALESCE(name, CONCAT('VIN ', vin)) end as __text \nFROM cars\nORDER BY display_priority ASC, name ASC, vin ASC;", "hide": 2, "includeAll": true, "label": "Car", - "multi": false, "name": "car_id", "options": [], - "query": "SELECT name AS __text, id AS __value FROM cars ORDER BY display_priority ASC, name ASC;", + "query": "SELECT\n id as __value,\n CASE WHEN COUNT(id) OVER (PARTITION BY name) > 1 AND name IS NOT NULL THEN CONCAT(name, ' - ', RIGHT(vin, 6)) ELSE COALESCE(name, CONCAT('VIN ', vin)) end as __text \nFROM cars\nORDER BY display_priority ASC, name ASC, vin ASC;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -278,19 +244,12 @@ "definition": "select unit_of_length from settings limit 1;", "hide": 2, "includeAll": false, - "label": "", - "multi": false, "name": "length_unit", "options": [], "query": "select unit_of_length from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -301,19 +260,12 @@ "definition": "select base_url from settings limit 1;", "hide": 2, "includeAll": false, - "label": "", - "multi": false, "name": "base_url", "options": [], "query": "select base_url from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" } ] }, @@ -321,35 +273,9 @@ "from": "now-6M", "to": "now" }, - "timeRangeUpdatedDuringEditOrView": false, - "timepicker": { - "refresh_intervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "time_options": [ - "5m", - "15m", - "1h", - "6h", - "12h", - "24h", - "2d", - "7d", - "30d" - ] - }, + "timepicker": {}, "timezone": "", "title": "Mileage", "uid": "NjtMTFggz", - "version": 1, - "weekStart": "" + "version": 1 } \ No newline at end of file diff --git a/grafana/dashboards/overview.json b/grafana/dashboards/overview.json index ae384b04a3..5c19e67e23 100644 --- a/grafana/dashboards/overview.json +++ b/grafana/dashboards/overview.json @@ -1,47 +1,7 @@ { - "__elements": {}, - "__requires": [ - { - "type": "panel", - "id": "gauge", - "name": "Gauge", - "version": "" - }, - { - "type": "grafana", - "id": "grafana", - "name": "Grafana", - "version": "11.1.0" - }, - { - "type": "datasource", - "id": "grafana-postgresql-datasource", - "name": "PostgreSQL", - "version": "1.0.0" - }, - { - "type": "panel", - "id": "stat", - "name": "Stat", - "version": "" - }, - { - "type": "panel", - "id": "state-timeline", - "name": "State timeline", - "version": "" - }, - { - "type": "panel", - "id": "timeseries", - "name": "Time series", - "version": "" - } - ], "annotations": { "list": [ { - "$$hashKey": "object:286", "builtIn": 1, "datasource": "-- Grafana --", "enable": true, @@ -62,7 +22,6 @@ "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 1, - "id": null, "links": [ { "icon": "dashboard", @@ -70,7 +29,7 @@ "title": "TeslaMate", "tooltip": "", "type": "link", - "url": "[[base_url:raw]]" + "url": "${base_url:raw}" }, { "asDropdown": true, @@ -82,11 +41,9 @@ "type": "dashboards" } ], - "liveNow": false, "panels": [ { "collapsed": false, - "datasource": "TeslaMate", "gridPos": { "h": 1, "w": 24, @@ -117,8 +74,7 @@ "mode": "absolute", "steps": [ { - "color": "transparent", - "value": null + "color": "transparent" } ] }, @@ -148,7 +104,7 @@ "showThresholdMarkers": true, "sizing": "auto" }, - "pluginVersion": "11.1.0", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { @@ -157,21 +113,9 @@ }, "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, - "rawSql": "(SELECT battery_level, date\nFROM positions\nWHERE car_id = $car_id\nORDER BY date DESC\nLIMIT 1)\nUNION\nSELECT battery_level, date\nFROM charges c\nJOIN charging_processes p ON p.id = c.charging_process_id\nWHERE $__timeFilter(date) AND p.car_id = $car_id\nORDER BY date DESC\nLIMIT 1", + "rawSql": "(SELECT battery_level, date\nFROM positions\nWHERE car_id = $car_id and ideal_battery_range_km is not null\nORDER BY date DESC\nLIMIT 1)\nUNION\nSELECT battery_level, date\nFROM charges c\nJOIN charging_processes p ON p.id = c.charging_process_id\nWHERE $__timeFilter(date) AND p.car_id = $car_id\nORDER BY date DESC\nLIMIT 1", "refId": "A", - "select": [ - [ - { - "params": [ - "battery_level" - ], - "type": "column" - } - ] - ], "sql": { "columns": [ { @@ -188,11 +132,7 @@ } ], "limit": 50 - }, - "table": "positions", - "timeColumn": "date", - "timeColumnType": "timestamp", - "where": [] + } }, { "datasource": { @@ -201,9 +141,8 @@ }, "editorMode": "code", "format": "table", - "hide": false, "rawQuery": true, - "rawSql": "SELECT\r\n 0 as lowest,\r\n 10 as low,\r\n 20 as mid,\r\n CASE WHEN lfp_battery THEN 100 ELSE 81 END as high,\r\n CASE WHEN lfp_battery THEN 100 ELSE 91 END as highest\r\nfrom cars inner join car_settings on cars.settings_id = car_settings.id\r\nwhere cars.id = $car_id", + "rawSql": "SELECT\r\n 0 as lowest,\r\n 10 as low,\r\n 20 as mid,\r\n CASE WHEN lfp_battery THEN 101 ELSE 81 END as high,\r\n CASE WHEN lfp_battery THEN 101 ELSE 91 END as highest\r\nfrom cars inner join car_settings on cars.settings_id = car_settings.id\r\nwhere cars.id = $car_id", "refId": "B", "sql": { "columns": [ @@ -299,8 +238,7 @@ "mode": "absolute", "steps": [ { - "color": "semi-dark-green", - "value": null + "color": "semi-dark-green" } ] }, @@ -330,39 +268,35 @@ "showThresholdMarkers": true, "sizing": "auto" }, - "pluginVersion": "11.1.0", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "time_series", - "group": [], - "metricColumn": "none", "rawQuery": true, "rawSql": "WITH charging_process AS (\n SELECT id, end_date\n FROM charging_processes\n WHERE car_id = $car_id\n ORDER BY start_date DESC\n LIMIT 1\n)\nSELECT\n $__time(date),\n CASE WHEN charging_process.end_date IS NULL THEN charger_voltage\n ELSE 0\n END AS \"Charging Voltage [V]\"\nFROM charges, charging_process\nWHERE charging_process.id = charging_process_id\nORDER BY date DESC\nLIMIT 1;", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "outside_temp" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "positions", - "timeColumn": "date", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } } ], "title": "Charging Voltage", @@ -386,8 +320,7 @@ "mode": "absolute", "steps": [ { - "color": "semi-dark-green", - "value": null + "color": "semi-dark-green" } ] }, @@ -417,39 +350,35 @@ "showThresholdMarkers": true, "sizing": "auto" }, - "pluginVersion": "11.1.0", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, "rawSql": "WITH charging_process AS (\n SELECT id, end_date\n FROM charging_processes\n WHERE car_id = $car_id\n ORDER BY start_date DESC\n LIMIT 1\n)\nSELECT\n $__time(date),\n CASE WHEN charging_process.end_date IS NULL THEN charger_power\n ELSE 0\n END AS \"Power [kW]\"\nFROM charges, charging_process\nWHERE charging_process.id = charging_process_id\nORDER BY date DESC\nLIMIT 1;", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "outside_temp" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "positions", - "timeColumn": "date", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } }, { "datasource": { @@ -458,7 +387,6 @@ }, "editorMode": "code", "format": "table", - "hide": false, "rawQuery": true, "rawSql": "SELECT\r\n CASE WHEN lfp_battery THEN 170 ELSE 250 END as max_charging_kw\r\nfrom cars inner join car_settings on cars.settings_id = car_settings.id\r\nwhere cars.id = $car_id", "refId": "B", @@ -481,7 +409,7 @@ } } ], - "title": "Charging kW", + "title": "Charging Power", "transformations": [ { "id": "configFromData", @@ -515,6 +443,7 @@ "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, + "barWidthFactor": 0.6, "drawStyle": "line", "fillOpacity": 10, "gradientMode": "opacity", @@ -547,8 +476,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -585,44 +513,41 @@ "showLegend": true }, "tooltip": { + "hideZeros": false, "maxHeight": 600, "mode": "multi", "sort": "none" } }, - "pluginVersion": "8.5.4", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "time_series", - "group": [], - "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT $__time(date), battery_level AS \"SOC\"\nFROM (\n\tSELECT battery_level, date\n\tFROM positions\n\tWHERE car_id = $car_id AND $__timeFilter(date)\n\tUNION ALL\n\tSELECT battery_level, date\n\tFROM charges c \n JOIN charging_processes p ON p.id = c.charging_process_id\n\tWHERE $__timeFilter(date) AND p.car_id = $car_id) AS data\nORDER BY date ASC;", + "rawSql": "SELECT $__time(date), battery_level AS \"SOC\"\nFROM (\n\tSELECT battery_level, date\n\tFROM positions\n\tWHERE car_id = $car_id AND ideal_battery_range_km IS NOT NULL AND $__timeFilter(date)\n\tUNION ALL\n\tSELECT battery_level, date\n\tFROM charges c \n JOIN charging_processes p ON p.id = c.charging_process_id\n\tWHERE $__timeFilter(date) AND p.car_id = $car_id) AS data\nORDER BY date ASC;", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "latitude" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "positions", - "timeColumn": "date", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } } ], "title": "Charge Level", @@ -641,8 +566,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" } ] }, @@ -705,42 +629,38 @@ "textMode": "value", "wideLayout": true }, - "pluginVersion": "11.1.0", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT\n sum(GREATEST(start_[[preferred_range]]_range_km - end_[[preferred_range]]_range_km, 0) * car.efficiency * 1000) / \n convert_km(sum(distance)::numeric, '$length_unit') as \"consumption_$length_unit\"\nFROM drives\nJOIN cars car ON car.id = car_id\nWHERE $__timeFilter(start_date) AND car_id = $car_id", + "rawSql": "SELECT\n sum(GREATEST(start_${preferred_range}_range_km - end_${preferred_range}_range_km, 0) * car.efficiency * 1000) / \n convert_km(sum(distance)::numeric, '$length_unit') as \"consumption_$length_unit\"\nFROM drives\nJOIN cars car ON car.id = car_id\nWHERE $__timeFilter(start_date) AND car_id = $car_id", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "start_km" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "drives", - "timeColumn": "start_date", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } } ], - "title": "Net", + "title": "Ø Consumption (net)", "type": "stat" }, { @@ -755,8 +675,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" } ] } @@ -812,42 +731,38 @@ "textMode": "auto", "wideLayout": true }, - "pluginVersion": "11.1.0", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH d AS (\n\tSELECT\n\t\tc.car_id,\n\t\tlag(end_[[preferred_range]]_range_km) OVER (ORDER BY start_date) - start_[[preferred_range]]_range_km AS range_loss,\n\t\tp.odometer - lag(p.odometer) OVER (ORDER BY start_date) AS distance\n\tFROM charging_processes c\n\tLEFT JOIN positions p ON p.id = c.position_id \n\tWHERE\n\t end_date IS NOT NULL AND\n\t c.car_id = $car_id AND\n\t $__timeFilter(start_date)\n\tORDER BY start_date\n),\n\nrange_loss_between_charges AS (\n SELECT sum(range_loss) AS range_loss\n FROM d\n WHERE distance >= 0 AND range_loss >= 0\n GROUP BY car_id\n),\n\ncharge_dates AS (\n\tSELECT\n\t\tmin(start_date) as first_charge,\n\t\tmax(end_date) as last_charge\n\tFROM\n\t\tcharging_processes\n\tWHERE\n\t\tend_date IS NOT NULL\n\t\tAND car_id = $car_id\n\t\tAND $__timeFilter(start_date)\n),\n\nrange_loss_before_first_charge AS (\n\tSELECT\n\t\tmax([[preferred_range]]_battery_range_km) - min([[preferred_range]]_battery_range_km) AS range_loss\n\tFROM positions, charge_dates\n\tWHERE\n\t\tcar_id = $car_id\n\t\tAND $__timeFilter(date)\n\t\tAND ((select first_charge from charge_dates) is null OR date < (select first_charge from charge_dates))\n),\n\nrange_loss_after_last_charge AS (\n\tSELECT\n\t\tmax([[preferred_range]]_battery_range_km) - min([[preferred_range]]_battery_range_km) AS range_loss\n\tFROM positions, charge_dates\n\tWHERE\n\t\tcar_id = $car_id\n\t\tAND $__timeFilter(date)\n\t\tAND date > (select last_charge from charge_dates)\t\n),\n\ntotal_range_loss AS (\n SELECT sum(range_loss) as range_loss\n FROM (\n SELECT range_loss FROM range_loss_between_charges\n UNION ALL\n SELECT range_loss FROM range_loss_before_first_charge\n UNION ALL\n SELECT range_loss FROM range_loss_after_last_charge\n ) r\n),\n\ndistance AS (\n SELECT max(odometer) - min(odometer) as distance\n FROM positions\n WHERE car_id = $car_id AND $__timeFilter(date)\n)\n\nSELECT \n NULLIF(range_loss, 0) * (c.efficiency * 1000) / convert_km(NULLIF(distance::numeric, 0), '$length_unit') as \"consumption_$length_unit\"\nFROM total_range_loss, distance\nLEFT JOIN cars c ON c.id = $car_id", + "rawSql": "WITH d AS (\n\tSELECT\n\t\tc.car_id,\n\t\tlag(end_${preferred_range}_range_km) OVER (ORDER BY start_date) - start_${preferred_range}_range_km AS range_loss,\n\t\tp.odometer - lag(p.odometer) OVER (ORDER BY start_date) AS distance\n\tFROM charging_processes c\n\tLEFT JOIN positions p ON p.id = c.position_id \n\tWHERE\n\t end_date IS NOT NULL AND\n\t c.car_id = $car_id AND\n\t $__timeFilter(start_date)\n\tORDER BY start_date\n),\n\nrange_loss_between_charges AS (\n SELECT sum(range_loss) AS range_loss\n FROM d\n WHERE distance >= 0 AND range_loss >= 0\n GROUP BY car_id\n),\n\ncharge_dates AS (\n\tSELECT\n\t\tmin(start_date) as first_charge,\n\t\tmax(end_date) as last_charge\n\tFROM\n\t\tcharging_processes\n\tWHERE\n\t\tend_date IS NOT NULL\n\t\tAND car_id = $car_id\n\t\tAND $__timeFilter(start_date)\n),\n\nrange_loss_before_first_charge AS (\n\tSELECT\n\t\tmax(${preferred_range}_battery_range_km) - min(${preferred_range}_battery_range_km) AS range_loss\n\tFROM positions, charge_dates\n\tWHERE\n\t\tcar_id = $car_id\n\t\tAND $__timeFilter(date)\n\t\tAND ((select first_charge from charge_dates) is null OR date < (select first_charge from charge_dates))\n),\n\nrange_loss_after_last_charge AS (\n\tSELECT\n\t\tmax(${preferred_range}_battery_range_km) - min(${preferred_range}_battery_range_km) AS range_loss\n\tFROM positions, charge_dates\n\tWHERE\n\t\tcar_id = $car_id\n\t\tAND $__timeFilter(date)\n\t\tAND date > (select last_charge from charge_dates)\t\n),\n\ntotal_range_loss AS (\n SELECT sum(range_loss) as range_loss\n FROM (\n SELECT range_loss FROM range_loss_between_charges\n UNION ALL\n SELECT range_loss FROM range_loss_before_first_charge\n UNION ALL\n SELECT range_loss FROM range_loss_after_last_charge\n ) r\n),\n\ndistance AS (\n SELECT max(odometer) - min(odometer) as distance\n FROM positions\n WHERE car_id = $car_id AND $__timeFilter(date)\n)\n\nSELECT \n NULLIF(range_loss, 0) * (c.efficiency * 1000) / convert_km(NULLIF(distance::numeric, 0), '$length_unit') as \"consumption_$length_unit\"\nFROM total_range_loss, distance\nLEFT JOIN cars c ON c.id = $car_id", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "start_km" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "drives", - "timeColumn": "start_date", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } } ], - "title": "Gross", + "title": "Ø Consumption (gross)", "type": "stat" }, { @@ -865,8 +780,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -927,7 +841,7 @@ "textMode": "auto", "wideLayout": true }, - "pluginVersion": "11.1.0", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { @@ -958,7 +872,7 @@ } } ], - "title": "Distance", + "title": "Total Distance logged", "type": "stat" }, { @@ -984,8 +898,7 @@ "mode": "absolute", "steps": [ { - "color": "#c7d0d9", - "value": null + "color": "#c7d0d9" } ] }, @@ -1048,37 +961,35 @@ "textMode": "value", "wideLayout": true }, - "pluginVersion": "11.1.0", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "time_series", - "group": [], - "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT $__time(date), range as \"range_$length_unit\"\nFROM (\n\t(SELECT date, convert_km([[preferred_range]]_battery_range_km, '$length_unit') AS range\n\tFROM positions\n\tWHERE car_id = $car_id AND [[preferred_range]]_battery_range_km IS NOT NULL\n ORDER BY date DESC\n\tLIMIT 1)\n\tUNION ALL\n\t(SELECT date, convert_km([[preferred_range]]_battery_range_km, '$length_unit') AS range\n\tFROM charges c\n\tJOIN charging_processes p ON p.id = c.charging_process_id\n\tWHERE p.car_id = $car_id\n\tORDER BY date DESC\n\tLIMIT 1)\n) AS data\nORDER BY date DESC\nLIMIT 1;", + "rawSql": "SELECT $__time(date), range as \"range_$length_unit\"\nFROM (\n\t(SELECT date, convert_km(${preferred_range}_battery_range_km, '$length_unit') AS range\n\tFROM positions\n\tWHERE car_id = $car_id AND ideal_battery_range_km IS NOT NULL\n ORDER BY date DESC\n\tLIMIT 1)\n\tUNION ALL\n\t(SELECT date, convert_km(${preferred_range}_battery_range_km, '$length_unit') AS range\n\tFROM charges c\n\tJOIN charging_processes p ON p.id = c.charging_process_id\n\tWHERE p.car_id = $car_id\n\tORDER BY date DESC\n\tLIMIT 1)\n) AS data\nORDER BY date DESC\nLIMIT 1;", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "value" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } } ], "title": "Range", @@ -1096,8 +1007,7 @@ "mode": "absolute", "steps": [ { - "color": "#c7d0d9", - "value": null + "color": "#c7d0d9" } ] }, @@ -1155,39 +1065,35 @@ "textMode": "value", "wideLayout": true }, - "pluginVersion": "11.1.0", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, "rawSql": "select split_part(version, ' ', 1) as version \nfrom updates \nwhere car_id = $car_id \norder by start_date desc \nlimit 1", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "efficiency" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "cars", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } } ], "title": "Firmware", @@ -1206,8 +1112,7 @@ "mode": "absolute", "steps": [ { - "color": "#c7d0d9", - "value": null + "color": "#c7d0d9" } ] }, @@ -1277,37 +1182,35 @@ "textMode": "value", "wideLayout": true }, - "pluginVersion": "11.1.0", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "time_series", - "group": [], - "metricColumn": "none", "rawQuery": true, - "rawSql": "select $__time(date), convert_km(odometer::numeric, '$length_unit') as \"odometer_$length_unit\"\nfrom positions \nwhere car_id = $car_id \norder by date desc \nlimit 1;", + "rawSql": "select $__time(date), convert_km(odometer::numeric, '$length_unit') as \"odometer_$length_unit\"\nfrom positions \nwhere car_id = $car_id and ideal_battery_range_km is not null\norder by date desc \nlimit 1;", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ { - "params": [ - "value" - ], - "type": "column" + "property": { + "type": "string" + }, + "type": "groupBy" } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "limit": 50 + } } ], "title": "Odometer", @@ -1330,6 +1233,7 @@ "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, + "barWidthFactor": 0.6, "drawStyle": "line", "fillOpacity": 10, "gradientMode": "opacity", @@ -1360,8 +1264,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -1491,77 +1394,69 @@ "showLegend": true }, "tooltip": { + "hideZeros": false, "maxHeight": 600, "mode": "multi", "sort": "none" } }, - "pluginVersion": "8.5.4", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "time_series", - "group": [], - "metricColumn": "none", "rawQuery": true, "rawSql": "SELECT\n $__time(date),\n charger_power,\n (case when battery_heater_on then 1 else 0 end) as battery_heater,\n charger_actual_current,\n c.charge_energy_added\nFROM\n charges c\njoin\n charging_processes p ON p.id = c.charging_process_id \nWHERE\n $__timeFilter(date) and\n p.car_id = $car_id\nORDER BY\n date ASC", "refId": "B", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "latitude" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "positions", - "timeColumn": "date", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } }, { "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "time_series", - "group": [], - "hide": false, - "metricColumn": "none", "rawQuery": true, "rawSql": "SELECT\n $__time(date),\n charger_voltage as \"Charging Voltage [V]\"\nFROM\n charges c\njoin\n charging_processes p ON p.id = c.charging_process_id \nWHERE\n $__timeFilter(date) and\n p.car_id = $car_id\nORDER BY\n date ASC", "refId": "C", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "latitude" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "positions", - "timeColumn": "date", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } } ], "title": "Charging Details", @@ -1585,8 +1480,7 @@ "mode": "absolute", "steps": [ { - "color": "semi-dark-green", - "value": null + "color": "semi-dark-green" } ] }, @@ -1616,7 +1510,7 @@ "showThresholdMarkers": true, "sizing": "auto" }, - "pluginVersion": "11.1.0", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { @@ -1625,21 +1519,9 @@ }, "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT\r\n\t$__time(date),\r\n\tconvert_celsius(driver_temp_setting, '$temp_unit') as \"Driver Temperature [°$temp_unit]\",\r\n\tconvert_celsius(inside_temp, '$temp_unit') AS \"Inside Temperature [°$temp_unit]\"\r\nFROM positions\r\nWHERE driver_temp_setting IS NOT NULL AND inside_temp IS NOT NULL AND car_id = $car_id AND date >= (NOW() AT TIME ZONE 'UTC' - INTERVAL '60m')\r\nORDER BY date DESC\r\nLIMIT 1;", + "rawSql": "SELECT\r\n\t$__time(date),\r\n\tconvert_celsius(driver_temp_setting, '$temp_unit') as \"Driver Temperature [°$temp_unit]\",\r\n\tconvert_celsius(inside_temp, '$temp_unit') AS \"Inside Temperature [°$temp_unit]\"\r\nFROM positions\r\nWHERE driver_temp_setting IS NOT NULL AND inside_temp IS NOT NULL AND car_id = $car_id AND date >= (TIMEZONE('UTC', NOW()) - INTERVAL '60m')\r\nORDER BY date DESC\r\nLIMIT 1;", "refId": "A", - "select": [ - [ - { - "params": [ - "outside_temp" - ], - "type": "column" - } - ] - ], "sql": { "columns": [ { @@ -1656,17 +1538,7 @@ } ], "limit": 50 - }, - "table": "positions", - "timeColumn": "date", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + } } ], "title": "Driver Temp", @@ -1701,8 +1573,7 @@ "mode": "absolute", "steps": [ { - "color": "semi-dark-green", - "value": null + "color": "semi-dark-green" } ] }, @@ -1732,7 +1603,7 @@ "showThresholdMarkers": true, "sizing": "auto" }, - "pluginVersion": "11.1.0", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { @@ -1741,21 +1612,9 @@ }, "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH last_position AS (\n\tSELECT date, convert_celsius(outside_temp, '$temp_unit') AS \"Outside Temperature [°$temp_unit]\"\n\tFROM positions\n\tWHERE car_id = $car_id AND outside_temp IS NOT NULL AND date >= (NOW() AT TIME ZONE 'UTC' - INTERVAL '60m')\n\tORDER BY date DESC\n\tLIMIT 1\n),\nlast_charge AS (\n\tSELECT date, convert_celsius(outside_temp, '$temp_unit') AS \"Outside Temperature [°$temp_unit]\"\n\tFROM charges\n\tJOIN charging_processes ON charges.charging_process_id = charging_processes.id\n\tWHERE car_id = $car_id AND outside_temp IS NOT NULL AND date >= (NOW() AT TIME ZONE 'UTC' - INTERVAL '60m')\n\tORDER BY date DESC\n\tLIMIT 1\n)\nSELECT * FROM last_position\nUNION ALL\nSELECT * FROM last_charge\nORDER BY date DESC\nLIMIT 1;", + "rawSql": "WITH last_position AS (\n\tSELECT date, convert_celsius(outside_temp, '$temp_unit') AS \"Outside Temperature [°$temp_unit]\"\n\tFROM positions\n\tWHERE car_id = $car_id AND outside_temp IS NOT NULL AND date >= (TIMEZONE('UTC', NOW()) - INTERVAL '60m')\n\tORDER BY date DESC\n\tLIMIT 1\n),\nlast_charge AS (\n\tSELECT date, convert_celsius(outside_temp, '$temp_unit') AS \"Outside Temperature [°$temp_unit]\"\n\tFROM charges\n\tJOIN charging_processes ON charges.charging_process_id = charging_processes.id\n\tWHERE car_id = $car_id AND outside_temp IS NOT NULL AND date >= (TIMEZONE('UTC', NOW()) - INTERVAL '60m')\n\tORDER BY date DESC\n\tLIMIT 1\n)\nSELECT * FROM last_position\nUNION ALL\nSELECT * FROM last_charge\nORDER BY date DESC\nLIMIT 1;", "refId": "A", - "select": [ - [ - { - "params": [ - "outside_temp" - ], - "type": "column" - } - ] - ], "sql": { "columns": [ { @@ -1772,17 +1631,7 @@ } ], "limit": 50 - }, - "table": "positions", - "timeColumn": "date", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + } } ], "title": "Outside Temp", @@ -1806,8 +1655,7 @@ "mode": "absolute", "steps": [ { - "color": "semi-dark-green", - "value": null + "color": "semi-dark-green" } ] }, @@ -1837,7 +1685,7 @@ "showThresholdMarkers": true, "sizing": "auto" }, - "pluginVersion": "11.1.0", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { @@ -1872,6 +1720,7 @@ "mode": "continuous-GrYlRd" }, "custom": { + "axisPlacement": "auto", "fillOpacity": 100, "hideFrom": { "legend": false, @@ -1932,8 +1781,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -1969,51 +1817,50 @@ "rowHeight": 0.9, "showValue": "auto", "tooltip": { + "hideZeros": false, "maxHeight": 600, "mode": "single", "sort": "none" } }, + "pluginVersion": "11.6.1", "targets": [ { "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "time_series", - "group": [], - "metricColumn": "none", "rawQuery": true, "rawSql": "WITH states AS (\n SELECT\n unnest(ARRAY [start_date + interval '1 second', end_date]) AS date,\n unnest(ARRAY [2, 0]) AS state\n FROM charging_processes\n WHERE\n car_id = $car_id AND \n ($__timeFrom() :: timestamp - interval '30 day') < start_date AND \n (end_date < ($__timeTo() :: timestamp + interval '30 day') OR end_date IS NULL)\n UNION\n SELECT\n unnest(ARRAY [start_date + interval '1 second', end_date]) AS date,\n unnest(ARRAY [1, 0]) AS state\n FROM drives\n WHERE\n car_id = $car_id AND \n ($__timeFrom() :: timestamp - interval '30 day') < start_date AND \n (end_date < ($__timeTo() :: timestamp + interval '30 day') OR end_date IS NULL)\n UNION\n SELECT\n start_date AS date,\n CASE\n WHEN state = 'offline' THEN 3\n WHEN state = 'asleep' THEN 4\n WHEN state = 'online' THEN 5\n END AS state\n FROM states\n WHERE\n car_id = $car_id AND \n ($__timeFrom() :: timestamp - interval '30 day') < start_date AND \n (end_date < ($__timeTo() :: timestamp + interval '30 day') OR end_date IS NULL)\n UNION\n SELECT\n unnest(ARRAY [start_date + interval '1 second', end_date]) AS date,\n unnest(ARRAY [6, 0]) AS state\n FROM updates\n WHERE\n car_id = $car_id AND \n ($__timeFrom() :: timestamp - interval '30 day') < start_date AND \n (end_date < ($__timeTo() :: timestamp + interval '30 day') OR end_date IS NULL)\n)\nSELECT date AS \"time\", state\nFROM states\nWHERE \n date IS NOT NULL AND\n ($__timeFrom() :: timestamp - interval '30 day') < date AND \n date < ($__timeTo() :: timestamp + interval '30 day') \nORDER BY date ASC, state ASC;", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "latitude" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "geofences", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } } ], "title": "States", "type": "state-timeline" } ], + "preload": false, "refresh": "30s", - "schemaVersion": 39, + "schemaVersion": 41, "tags": [ "tesla" ], @@ -2025,22 +1872,16 @@ "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, - "definition": "SELECT name AS __text, id AS __value FROM cars ORDER BY display_priority ASC, name ASC;", + "definition": "SELECT\n id as __value,\n CASE WHEN COUNT(id) OVER (PARTITION BY name) > 1 AND name IS NOT NULL THEN CONCAT(name, ' - ', RIGHT(vin, 6)) ELSE COALESCE(name, CONCAT('VIN ', vin)) end as __text \nFROM cars\nORDER BY display_priority ASC, name ASC, vin ASC;", "hide": 2, "includeAll": true, "label": "Car", - "multi": false, "name": "car_id", "options": [], - "query": "SELECT name AS __text, id AS __value FROM cars ORDER BY display_priority ASC, name ASC;", + "query": "SELECT\n id as __value,\n CASE WHEN COUNT(id) OVER (PARTITION BY name) > 1 AND name IS NOT NULL THEN CONCAT(name, ' - ', RIGHT(vin, 6)) ELSE COALESCE(name, CONCAT('VIN ', vin)) end as __text \nFROM cars\nORDER BY display_priority ASC, name ASC, vin ASC;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -2051,18 +1892,12 @@ "definition": "select unit_of_length from settings limit 1;", "hide": 2, "includeAll": false, - "multi": false, "name": "length_unit", "options": [], "query": "select unit_of_length from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -2073,18 +1908,12 @@ "definition": "select unit_of_temperature from settings limit 1;", "hide": 2, "includeAll": false, - "multi": false, "name": "temp_unit", "options": [], "query": "select unit_of_temperature from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -2095,19 +1924,12 @@ "definition": "select preferred_range from settings limit 1;", "hide": 2, "includeAll": false, - "label": "", - "multi": false, "name": "preferred_range", "options": [], "query": "select preferred_range from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -2118,19 +1940,12 @@ "definition": "select base_url from settings limit 1;", "hide": 2, "includeAll": false, - "label": "", - "multi": false, "name": "base_url", "options": [], "query": "select base_url from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" } ] }, @@ -2138,35 +1953,9 @@ "from": "now-24h", "to": "now" }, - "timepicker": { - "hidden": false, - "refresh_intervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "time_options": [ - "5m", - "15m", - "1h", - "6h", - "12h", - "24h", - "2d", - "7d", - "30d" - ] - }, + "timepicker": {}, "timezone": "", "title": "Overview", "uid": "kOuP_Fggz", - "version": 9, - "weekStart": "" + "version": 1 } \ No newline at end of file diff --git a/grafana/dashboards/projected-range.json b/grafana/dashboards/projected-range.json index 19ac772f45..a86d7342df 100644 --- a/grafana/dashboards/projected-range.json +++ b/grafana/dashboards/projected-range.json @@ -1,30 +1,12 @@ { - "__elements": {}, - "__requires": [ - { - "type": "grafana", - "id": "grafana", - "name": "Grafana", - "version": "11.0.0" - }, - { - "type": "datasource", - "id": "grafana-postgresql-datasource", - "name": "PostgreSQL", - "version": "1.0.0" - }, - { - "type": "panel", - "id": "timeseries", - "name": "Time series", - "version": "" - } - ], "annotations": { "list": [ { "builtIn": 1, - "datasource": "-- Grafana --", + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, "enable": true, "hide": true, "iconColor": "rgba(0, 211, 255, 1)", @@ -51,7 +33,6 @@ "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 1, - "id": null, "links": [ { "icon": "dashboard", @@ -59,7 +40,7 @@ "title": "TeslaMate", "tooltip": "", "type": "link", - "url": "[[base_url:raw]]" + "url": "${base_url:raw}" }, { "asDropdown": true, @@ -71,11 +52,9 @@ "type": "dashboards" } ], - "liveNow": false, "panels": [ { "collapsed": false, - "datasource": "TeslaMate", "gridPos": { "h": 1, "w": 24, @@ -85,12 +64,6 @@ "id": 4, "panels": [], "repeat": "car_id", - "targets": [ - { - "datasource": "TeslaMate", - "refId": "A" - } - ], "title": "$car_id", "type": "row" }, @@ -111,6 +84,7 @@ "axisLabel": "Projected Range", "axisPlacement": "auto", "barAlignment": 0, + "barWidthFactor": 0.6, "drawStyle": "line", "fillOpacity": 30, "gradientMode": "opacity", @@ -144,8 +118,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -196,76 +169,69 @@ "showLegend": true }, "tooltip": { + "hideZeros": false, "maxHeight": 600, "mode": "multi", "sort": "none" } }, - "pluginVersion": "10.2.1", + "pluginVersion": "11.6.1", "targets": [ { - "alias": "", "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "time_series", - "group": [], - "hide": false, - "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT\n\t$__timeGroup(date, [[interval]]) AS time,\n\tconvert_km((sum([[preferred_range]]_battery_range_km) / nullif(sum(coalesce(usable_battery_level,battery_level)),0) * 100)::numeric, '$length_unit') AS \"Projected [[preferred_range]] range [$length_unit]\"\nFROM\n\t(\n select battery_level, usable_battery_level, date,\n rated_battery_range_km, ideal_battery_range_km, outside_temp\n from positions\n where\n car_id = $car_id and $__timeFilter(date) and ideal_battery_range_km is not null\n union all\n select battery_level, coalesce(usable_battery_level,battery_level) as usable_battery_level, date,\n rated_battery_range_km, ideal_battery_range_km, outside_temp\n from charges c\n join\n charging_processes p ON p.id = c.charging_process_id \n where\n $__timeFilter(date) and p.car_id = $car_id\n ) as data\n\nGROUP BY\n\t1\nhaving convert_km((sum([[preferred_range]]_battery_range_km) / nullif(sum(coalesce(usable_battery_level,battery_level)),0) * 100)::numeric, '$length_unit') is not null\nORDER BY\n\t1,2 DESC", + "rawSql": "SELECT\n\t$__timeGroup(date, $interval) AS time,\n\tconvert_km((sum(${preferred_range}_battery_range_km) / nullif(sum(coalesce(usable_battery_level,battery_level)),0) * 100)::numeric, '$length_unit') AS \"Projected ${preferred_range} range [$length_unit]\"\nFROM\n\t(\n select battery_level, usable_battery_level, date,\n rated_battery_range_km, ideal_battery_range_km, outside_temp\n from positions\n where\n car_id = $car_id and $__timeFilter(date) and ideal_battery_range_km is not null\n union all\n select battery_level, coalesce(usable_battery_level,battery_level) as usable_battery_level, date,\n rated_battery_range_km, ideal_battery_range_km, outside_temp\n from charges c\n join\n charging_processes p ON p.id = c.charging_process_id \n where\n $__timeFilter(date) and p.car_id = $car_id\n ) as data\n\nGROUP BY\n\t1\nhaving convert_km((sum(${preferred_range}_battery_range_km) / nullif(sum(coalesce(usable_battery_level,battery_level)),0) * 100)::numeric, '$length_unit') is not null\nORDER BY\n\t1,2 DESC", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "value" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } }, { "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "time_series", - "group": [], - "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT\n\t$__timeGroup(date,[[interval]]) AS time,\n\tconvert_km(avg(odometer)::numeric, '$length_unit') AS \"Mileage [$length_unit]\"\nFROM\n\tpositions\nWHERE\n\t$__timeFilter(date) and\n\tcar_id = $car_id and ideal_battery_range_km is not null\nGROUP BY\n\t1\nORDER BY\n\t1,2 DESC;", + "rawSql": "SELECT\n\t$__timeGroup(date, $interval) AS time,\n\tconvert_km(avg(odometer)::numeric, '$length_unit') AS \"Mileage [$length_unit]\"\nFROM\n\tpositions\nWHERE\n\t$__timeFilter(date) and\n\tcar_id = $car_id and ideal_battery_range_km is not null\nGROUP BY\n\t1\nORDER BY\n\t1,2 DESC;", "refId": "B", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "efficiency" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "cars", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } } ], "title": "Projected Range - Mileage", @@ -288,6 +254,7 @@ "axisLabel": "Projected Range", "axisPlacement": "auto", "barAlignment": 0, + "barWidthFactor": 0.6, "drawStyle": "line", "fillOpacity": 30, "gradientMode": "opacity", @@ -321,8 +288,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -374,37 +340,24 @@ "showLegend": true }, "tooltip": { + "hideZeros": false, "maxHeight": 600, "mode": "multi", "sort": "none" } }, - "pluginVersion": "10.2.1", + "pluginVersion": "11.6.1", "targets": [ { - "alias": "", "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, "editorMode": "code", "format": "time_series", - "group": [], - "hide": false, - "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT\n\t$__timeGroup(date,[[interval]]) AS time,\n\tconvert_km((sum([[preferred_range]]_battery_range_km) / sum(battery_level) * 100 ) - (sum([[preferred_range]]_battery_range_km) / sum(battery_level) * 100 * (avg(battery_level)-avg(coalesce(usable_battery_level,battery_level)))/100 ), '$length_unit') AS \"Projected Range (using usable_battery_level) [$length_unit]\",\n\tconvert_km(max([[preferred_range]]_battery_range_km) / max(battery_level) * 100, '$length_unit') AS \"Projected Range (using battery_level)[$length_unit]\"\nFROM\n\t(\n select battery_level, usable_battery_level, date,\n rated_battery_range_km, ideal_battery_range_km, outside_temp\n from positions\n where\n car_id = $car_id and $__timeFilter(date) and ideal_battery_range_km is not null\n union all\n select battery_level, coalesce(usable_battery_level,battery_level) as usable_battery_level, date,\n rated_battery_range_km, ideal_battery_range_km, outside_temp\n from charges c\n join\n charging_processes p ON p.id = c.charging_process_id \n where\n $__timeFilter(date) and p.car_id = $car_id\n ) as data\n\nGROUP BY\n\t1\nORDER BY\n\t1,2 DESC", + "rawSql": "SELECT\n\t$__timeGroup(date, $interval) AS time,\n\tconvert_km((sum(${preferred_range}_battery_range_km) / sum(battery_level) * 100 ) - (sum(${preferred_range}_battery_range_km) / sum(battery_level) * 100 * (avg(battery_level)-avg(coalesce(usable_battery_level,battery_level)))/100 ), '$length_unit') AS \"Projected Range (using usable_battery_level) [$length_unit]\",\n\tconvert_km(max(${preferred_range}_battery_range_km) / max(battery_level) * 100, '$length_unit') AS \"Projected Range (using battery_level)[$length_unit]\"\nFROM\n\t(\n select battery_level, usable_battery_level, date,\n rated_battery_range_km, ideal_battery_range_km, outside_temp\n from positions\n where\n car_id = $car_id and $__timeFilter(date) and ideal_battery_range_km is not null\n union all\n select battery_level, coalesce(usable_battery_level,battery_level) as usable_battery_level, date,\n rated_battery_range_km, ideal_battery_range_km, outside_temp\n from charges c\n join\n charging_processes p ON p.id = c.charging_process_id \n where\n $__timeFilter(date) and p.car_id = $car_id\n ) as data\n\nGROUP BY\n\t1\nORDER BY\n\t1,2 DESC", "refId": "A", - "select": [ - [ - { - "params": [ - "value" - ], - "type": "column" - } - ] - ], "sql": { "columns": [ { @@ -421,47 +374,35 @@ } ], "limit": 50 - }, - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + } }, { "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "time_series", - "group": [], - "metricColumn": "none", "rawQuery": true, - "rawSql": "select \n\t$__timeGroup(date,[[interval]]) AS time,\n avg(battery_level) AS \"Battery Level [%]\", avg(coalesce(usable_battery_level, battery_level)) as \"Usable Battery Level [%]\"\nfrom\n (SELECT\n battery_level, usable_battery_level\n , date\n FROM\n positions\n WHERE\n car_id = $car_id AND\n $__timeFilter(date) and ideal_battery_range_km is not null\n UNION ALL\n select\n battery_level, null as usable_battery_level\n , date\n from charges c\njoin\n charging_processes p ON p.id = c.charging_process_id \nWHERE\n $__timeFilter(date) and\n p.car_id = $car_id) as data\n\nGROUP BY\n 1\nORDER BY\n 1 ASC", + "rawSql": "select \n\t$__timeGroup(date, $interval) AS time,\n avg(battery_level) AS \"Battery Level [%]\", avg(coalesce(usable_battery_level, battery_level)) as \"Usable Battery Level [%]\"\nfrom\n (SELECT\n battery_level, usable_battery_level\n , date\n FROM\n positions\n WHERE\n car_id = $car_id AND\n $__timeFilter(date) and ideal_battery_range_km is not null\n UNION ALL\n select\n battery_level, null as usable_battery_level\n , date\n from charges c\njoin\n charging_processes p ON p.id = c.charging_process_id \nWHERE\n $__timeFilter(date) and\n p.car_id = $car_id) as data\n\nGROUP BY\n 1\nORDER BY\n 1 ASC", "refId": "B", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "efficiency" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "cars", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } } ], "title": "Projected Range - Battery Level", @@ -484,6 +425,7 @@ "axisLabel": "Projected Range", "axisPlacement": "auto", "barAlignment": 0, + "barWidthFactor": 0.6, "drawStyle": "line", "fillOpacity": 30, "gradientMode": "opacity", @@ -598,36 +540,23 @@ "showLegend": true }, "tooltip": { + "hideZeros": false, "mode": "multi", "sort": "none" } }, - "pluginVersion": "10.2.1", + "pluginVersion": "11.6.1", "targets": [ { - "alias": "", "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, "editorMode": "code", "format": "time_series", - "group": [], - "hide": false, - "metricColumn": "none", "rawQuery": true, - "rawSql": "\nSELECT\n\t$__timeGroup(date,[[interval]]) AS time,\n\tconvert_km((sum([[preferred_range]]_battery_range_km) / sum(battery_level) * 100 ) - (sum([[preferred_range]]_battery_range_km) / sum(battery_level) * 100 * (avg(battery_level)-avg(coalesce(usable_battery_level,battery_level)))/100 ), '$length_unit') AS \"Projected Range (using usable_battery_level) [$length_unit]\",\n\tconvert_km(max([[preferred_range]]_battery_range_km) / max(battery_level) * 100, '$length_unit') AS \"Projected Range (using battery_level)[$length_unit]\"\nFROM\n\t(\n select battery_level, usable_battery_level, date,\n rated_battery_range_km, ideal_battery_range_km, outside_temp\n from positions\n where\n car_id = $car_id and $__timeFilter(date) and ideal_battery_range_km is not null\n union all\n select battery_level, coalesce(usable_battery_level,battery_level) as usable_battery_level, date,\n rated_battery_range_km, ideal_battery_range_km, outside_temp\n from charges c\n join\n charging_processes p ON p.id = c.charging_process_id \n where\n $__timeFilter(date) and p.car_id = $car_id\n ) as data\n\nGROUP BY\n\t1\nORDER BY\n\t1,2 DESC", + "rawSql": "\nSELECT\n\t$__timeGroup(date, $interval) AS time,\n\tconvert_km((sum(${preferred_range}_battery_range_km) / sum(battery_level) * 100 ) - (sum(${preferred_range}_battery_range_km) / sum(battery_level) * 100 * (avg(battery_level)-avg(coalesce(usable_battery_level,battery_level)))/100 ), '$length_unit') AS \"Projected Range (using usable_battery_level) [$length_unit]\",\n\tconvert_km(max(${preferred_range}_battery_range_km) / max(battery_level) * 100, '$length_unit') AS \"Projected Range (using battery_level)[$length_unit]\"\nFROM\n\t(\n select battery_level, usable_battery_level, date,\n rated_battery_range_km, ideal_battery_range_km, outside_temp\n from positions\n where\n car_id = $car_id and $__timeFilter(date) and ideal_battery_range_km is not null\n union all\n select battery_level, coalesce(usable_battery_level,battery_level) as usable_battery_level, date,\n rated_battery_range_km, ideal_battery_range_km, outside_temp\n from charges c\n join\n charging_processes p ON p.id = c.charging_process_id \n where\n $__timeFilter(date) and p.car_id = $car_id\n ) as data\n\nGROUP BY\n\t1\nORDER BY\n\t1,2 DESC", "refId": "A", - "select": [ - [ - { - "params": [ - "value" - ], - "type": "column" - } - ] - ], "sql": { "columns": [ { @@ -644,55 +573,44 @@ } ], "limit": 50 - }, - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + } }, { - "alias": "", "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "time_series", - "group": [], - "hide": false, - "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT\n\t$__timeGroup(date,[[interval]]) AS time,\n\tavg(convert_celsius(outside_temp, '$temp_unit')) as \"Outdoor Temperature [°$temp_unit]\"\n\nFROM\n\tpositions\nWHERE\n\t$__timeFilter(date) and\n\tcar_id = $car_id and ideal_battery_range_km is not null\nGROUP BY\n\t1\nORDER BY\n\t1,2 DESC", + "rawSql": "SELECT\n\t$__timeGroup(date, $interval) AS time,\n\tavg(convert_celsius(outside_temp, '$temp_unit')) as \"Outdoor Temperature [°$temp_unit]\"\n\nFROM\n\tpositions\nWHERE\n\t$__timeFilter(date) and\n\tcar_id = $car_id and ideal_battery_range_km is not null\nGROUP BY\n\t1\nORDER BY\n\t1,2 DESC", "refId": "B", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "value" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } } ], "title": "Projected Range - Outdoor Temp", "type": "timeseries" } ], + "preload": false, "refresh": "", - "schemaVersion": 39, + "schemaVersion": 41, "tags": [ "tesla" ], @@ -704,22 +622,16 @@ "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, - "definition": "SELECT name AS __text, id AS __value FROM cars ORDER BY display_priority ASC, name ASC;", + "definition": "SELECT\n id as __value,\n CASE WHEN COUNT(id) OVER (PARTITION BY name) > 1 AND name IS NOT NULL THEN CONCAT(name, ' - ', RIGHT(vin, 6)) ELSE COALESCE(name, CONCAT('VIN ', vin)) end as __text \nFROM cars\nORDER BY display_priority ASC, name ASC, vin ASC;", "hide": 2, "includeAll": true, "label": "Car", - "multi": false, "name": "car_id", "options": [], - "query": "SELECT name AS __text, id AS __value FROM cars ORDER BY display_priority ASC, name ASC;", + "query": "SELECT\n id as __value,\n CASE WHEN COUNT(id) OVER (PARTITION BY name) > 1 AND name IS NOT NULL THEN CONCAT(name, ' - ', RIGHT(vin, 6)) ELSE COALESCE(name, CONCAT('VIN ', vin)) end as __text \nFROM cars\nORDER BY display_priority ASC, name ASC, vin ASC;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -730,19 +642,12 @@ "definition": "select unit_of_length from settings limit 1;", "hide": 2, "includeAll": false, - "label": "", - "multi": false, "name": "length_unit", "options": [], "query": "select unit_of_length from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -753,18 +658,12 @@ "definition": "select preferred_range from settings limit 1;", "hide": 2, "includeAll": false, - "multi": false, "name": "preferred_range", "options": [], "query": "select preferred_range from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -775,19 +674,12 @@ "definition": "select unit_of_temperature from settings limit 1;", "hide": 2, "includeAll": false, - "label": "", - "multi": false, "name": "temp_unit", "options": [], "query": "select unit_of_temperature from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -798,26 +690,18 @@ "definition": "select base_url from settings limit 1;", "hide": 2, "includeAll": false, - "label": "", - "multi": false, "name": "base_url", "options": [], "query": "select base_url from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "auto": false, "auto_count": 30, "auto_min": "10s", "current": { - "selected": false, "text": "6h", "value": "6h" }, @@ -858,7 +742,6 @@ ], "query": "5m,15m,30m,1h,3h,6h", "refresh": 2, - "skipUrlSync": false, "type": "interval" } ] @@ -867,35 +750,9 @@ "from": "now-6M", "to": "now" }, - "timeRangeUpdatedDuringEditOrView": false, - "timepicker": { - "refresh_intervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "time_options": [ - "5m", - "15m", - "1h", - "6h", - "12h", - "24h", - "2d", - "7d", - "30d" - ] - }, + "timepicker": {}, "timezone": "", "title": "Projected Range", "uid": "riqUfXgRz", - "version": 2, - "weekStart": "" + "version": 1 } \ No newline at end of file diff --git a/grafana/dashboards/reports/dutch-tax.json b/grafana/dashboards/reports/dutch-tax.json index 93c3185f05..e92dd1f9a7 100644 --- a/grafana/dashboards/reports/dutch-tax.json +++ b/grafana/dashboards/reports/dutch-tax.json @@ -2,9 +2,11 @@ "annotations": { "list": [ { - "$$hashKey": "object:24", "builtIn": 1, - "datasource": "-- Grafana --", + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, "enable": true, "hide": true, "iconColor": "rgba(0, 211, 255, 1)", @@ -14,9 +16,8 @@ ] }, "editable": true, - "gnetId": null, + "fiscalYearStartMonth": 0, "graphTooltip": 0, - "iteration": 1602596446244, "links": [ { "icon": "dashboard", @@ -24,7 +25,7 @@ "title": "TeslaMate", "tooltip": "", "type": "link", - "url": "[[base_url:raw]]" + "url": "${base_url:raw}" }, { "asDropdown": true, @@ -39,7 +40,6 @@ "panels": [ { "collapsed": false, - "datasource": "TeslaMate", "gridPos": { "h": 1, "w": 24, @@ -49,31 +49,30 @@ "id": 4, "panels": [], "repeat": "car_id", - "scopedVars": { - "car_id": { - "selected": false, - "text": "1", - "value": "1" - } - }, "title": "$car_id", "type": "row" }, { - "datasource": "TeslaMate", + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, "fieldConfig": { "defaults": { "custom": { - "align": null, - "filterable": false + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "filterable": false, + "inspect": false }, "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -115,7 +114,7 @@ }, { "id": "custom.width", - "value": 200 + "value": 210 } ] }, @@ -242,7 +241,7 @@ }, { "id": "displayName", - "value": "km" + "value": "Distance" } ] }, @@ -258,7 +257,7 @@ }, { "id": "displayName", - "value": "mi" + "value": "Distance" } ] }, @@ -295,8 +294,16 @@ "y": 1 }, "id": 2, - "links": [], "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, "showHeader": true, "sortBy": [ { @@ -305,206 +312,139 @@ } ] }, - "pluginVersion": "7.2.2", - "repeat": "car_id", - "scopedVars": { - "car_id": { - "selected": false, - "text": "1", - "value": "1" - } - }, + "pluginVersion": "11.6.1", "targets": [ { - "alias": "", + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, "rawSql": "WITH data AS (\n SELECT\n drives.id as drive_id,\n round(extract(epoch FROM start_date)) * 1000 AS start_date_ts,\n round(extract(epoch FROM end_date)) * 1000 AS end_date_ts,\n start_km,\n end_km,\n CONCAT_WS(', ', CONCAT_WS(' ', start_address.road, start_address.house_number), start_address.city) AS start_address,\n CONCAT_WS(', ', CONCAT_WS(' ', end_address.road, end_address.house_number), end_address.city) AS end_address,\n duration_min,\n distance\n FROM drives\n LEFT JOIN addresses start_address ON start_address_id = start_address.id\n LEFT JOIN addresses end_address ON end_address_id = end_address.id\n LEFT JOIN cars car ON car.id = drives.car_id\n WHERE $__timeFilter(start_date) AND drives.car_id = $car_id\n ORDER BY drive_id DESC\n)\nSELECT\n drive_id,\n start_date_ts,\n convert_km(start_km::numeric, '$length_unit') as start_$length_unit,\n start_address,\n end_date_ts,\n convert_km(end_km::numeric, '$length_unit') as end_$length_unit,\n end_address,\n duration_min,\n convert_km(distance::numeric, '$length_unit') AS distance_$length_unit\nFROM data;", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "value" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } } ], "title": "Drive", "type": "table" } ], - "schemaVersion": 26, - "style": "dark", + "preload": false, + "refresh": "", + "schemaVersion": 41, "tags": [], "templating": { "list": [ { - "allValue": null, - "current": { - "selected": false, - "text": "All", - "value": "$__all" + "current": {}, + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" }, - "datasource": "TeslaMate", - "definition": "SELECT name AS __text, id AS __value FROM cars ORDER BY display_priority ASC, name ASC;", + "definition": "SELECT\n id as __value,\n CASE WHEN COUNT(id) OVER (PARTITION BY name) > 1 AND name IS NOT NULL THEN CONCAT(name, ' - ', RIGHT(vin, 6)) ELSE COALESCE(name, CONCAT('VIN ', vin)) end as __text \nFROM cars\nORDER BY display_priority ASC, name ASC, vin ASC;", "hide": 2, "includeAll": true, "label": "Car", - "multi": false, "name": "car_id", "options": [], - "query": "SELECT name AS __text, id AS __value FROM cars ORDER BY display_priority ASC, name ASC;", + "query": "SELECT\n id as __value,\n CASE WHEN COUNT(id) OVER (PARTITION BY name) > 1 AND name IS NOT NULL THEN CONCAT(name, ' - ', RIGHT(vin, 6)) ELSE COALESCE(name, CONCAT('VIN ', vin)) end as __text \nFROM cars\nORDER BY display_priority ASC, name ASC, vin ASC;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tags": [], - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { - "allValue": null, - "current": { - "selected": false, - "text": "C", - "value": "C" + "current": {}, + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" }, - "datasource": "TeslaMate", "definition": "select unit_of_temperature from settings limit 1;", "hide": 2, "includeAll": false, "label": "temperature unit", - "multi": false, "name": "temp_unit", "options": [], "query": "select unit_of_temperature from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tags": [], - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { - "allValue": null, - "current": { - "selected": false, - "text": "km", - "value": "km" + "current": {}, + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" }, - "datasource": "TeslaMate", "definition": "select unit_of_length from settings limit 1;", "hide": 2, "includeAll": false, "label": "length unit", - "multi": false, "name": "length_unit", "options": [], "query": "select unit_of_length from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tags": [], - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { - "allValue": null, - "current": { - "selected": false, - "text": "rated", - "value": "rated" + "current": {}, + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" }, - "datasource": "TeslaMate", "definition": "select preferred_range from settings limit 1;", "hide": 2, "includeAll": false, - "label": "", - "multi": false, "name": "preferred_range", "options": [], "query": "select preferred_range from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tags": [], - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { - "allValue": null, - "current": { - "selected": false, - "value": null + "current": {}, + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" }, - "datasource": "TeslaMate", "definition": "select base_url from settings limit 1;", "hide": 2, "includeAll": false, - "label": "", - "multi": false, "name": "base_url", "options": [], "query": "select base_url from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tags": [], - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" } ] }, "time": { - "from": "now/y", - "to": "now/y" - }, - "timepicker": { - "refresh_intervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] + "from": "now-1y", + "to": "now" }, + "timepicker": {}, "timezone": "", "title": "Drives - Dutch tax", "uid": "lBIoQIggk", "version": 1 -} - +} \ No newline at end of file diff --git a/grafana/dashboards/states.json b/grafana/dashboards/states.json index fa23dac62a..ec97f7aaff 100644 --- a/grafana/dashboards/states.json +++ b/grafana/dashboards/states.json @@ -1,47 +1,16 @@ { - "__elements": {}, - "__requires": [ - { - "type": "grafana", - "id": "grafana", - "name": "Grafana", - "version": "11.0.0" - }, - { - "type": "datasource", - "id": "grafana-postgresql-datasource", - "name": "PostgreSQL", - "version": "1.0.0" - }, - { - "type": "panel", - "id": "stat", - "name": "Stat", - "version": "" - }, - { - "type": "panel", - "id": "state-timeline", - "name": "State timeline", - "version": "" - } - ], "annotations": { "list": [ { - "$$hashKey": "object:427", "builtIn": 1, - "datasource": "-- Grafana --", + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, "enable": true, "hide": true, "iconColor": "rgba(0, 211, 255, 1)", "name": "Annotations & Alerts", - "target": { - "limit": 100, - "matchAny": false, - "tags": [], - "type": "dashboard" - }, "type": "dashboard" } ] @@ -49,7 +18,6 @@ "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 0, - "id": null, "links": [ { "icon": "dashboard", @@ -57,7 +25,7 @@ "title": "TeslaMate", "tooltip": "", "type": "link", - "url": "[[base_url:raw]]" + "url": "${base_url:raw}" }, { "asDropdown": true, @@ -69,10 +37,9 @@ "type": "dashboards" } ], - "liveNow": false, "panels": [ { - "datasource": "TeslaMate", + "collapsed": false, "gridPos": { "h": 1, "w": 24, @@ -98,8 +65,7 @@ "mode": "absolute", "steps": [ { - "color": "#c7d0d9", - "value": null + "color": "#c7d0d9" } ] }, @@ -125,6 +91,7 @@ "graphMode": "none", "justifyMode": "auto", "orientation": "horizontal", + "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "mean" @@ -136,38 +103,34 @@ "textMode": "value", "wideLayout": true }, - "pluginVersion": "11.0.0", + "pluginVersion": "11.6.1", "targets": [ { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, "rawSql": "select $__time(start_date), state from states where car_id = $car_id order by start_date desc limit 1;", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "latitude" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "addresses", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ], - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 } } ], @@ -187,8 +150,7 @@ "mode": "absolute", "steps": [ { - "color": "#c7d0d9", - "value": null + "color": "#c7d0d9" } ] }, @@ -214,6 +176,7 @@ "graphMode": "none", "justifyMode": "auto", "orientation": "horizontal", + "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "mean" @@ -225,38 +188,34 @@ "textMode": "value", "wideLayout": true }, - "pluginVersion": "11.0.0", + "pluginVersion": "11.6.1", "targets": [ { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, "rawSql": "select $__time(start_date), state from states where car_id = $car_id order by start_date desc limit 1;", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "latitude" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "addresses", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ], - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 } } ], @@ -288,8 +247,7 @@ "mode": "absolute", "steps": [ { - "color": "#c7d0d9", - "value": null + "color": "#c7d0d9" } ] }, @@ -315,6 +273,7 @@ "graphMode": "none", "justifyMode": "auto", "orientation": "horizontal", + "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "mean" @@ -326,38 +285,34 @@ "textMode": "value", "wideLayout": true }, - "pluginVersion": "11.0.0", + "pluginVersion": "11.6.1", "targets": [ { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", "format": "time_series", - "group": [], - "metricColumn": "none", "rawQuery": true, "rawSql": "select 1 - sum(duration_min) / (EXTRACT(EPOCH FROM (max(end_date) - min(start_date))) / 60), 1 as time from drives where car_id = $car_id;", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "latitude" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "addresses", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ], - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 } } ], @@ -375,6 +330,7 @@ "mode": "continuous-GrYlRd" }, "custom": { + "axisPlacement": "auto", "fillOpacity": 100, "hideFrom": { "legend": false, @@ -435,8 +391,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" } ] }, @@ -462,42 +417,40 @@ "rowHeight": 0.9, "showValue": "auto", "tooltip": { + "hideZeros": false, "maxHeight": 600, "mode": "single", "sort": "none" } }, + "pluginVersion": "11.6.1", "targets": [ { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", "format": "time_series", - "group": [], - "metricColumn": "none", "rawQuery": true, "rawSql": "WITH states AS (\n SELECT\n unnest(ARRAY [start_date + interval '1 second', end_date]) AS date,\n unnest(ARRAY [2, 0]) AS state\n FROM charging_processes\n WHERE\n car_id = $car_id AND \n ($__timeFrom() :: timestamp - interval '30 day') < start_date AND \n (end_date < ($__timeTo() :: timestamp + interval '30 day') OR end_date IS NULL)\n UNION\n SELECT\n unnest(ARRAY [start_date + interval '1 second', end_date]) AS date,\n unnest(ARRAY [1, 0]) AS state\n FROM drives\n WHERE\n car_id = $car_id AND \n ($__timeFrom() :: timestamp - interval '30 day') < start_date AND \n (end_date < ($__timeTo() :: timestamp + interval '30 day') OR end_date IS NULL)\n UNION\n SELECT\n start_date AS date,\n CASE\n WHEN state = 'offline' THEN 3\n WHEN state = 'asleep' THEN 4\n WHEN state = 'online' THEN 5\n END AS state\n FROM states\n WHERE\n car_id = $car_id AND \n ($__timeFrom() :: timestamp - interval '30 day') < start_date AND \n (end_date < ($__timeTo() :: timestamp + interval '30 day') OR end_date IS NULL)\n UNION\n SELECT\n unnest(ARRAY [start_date + interval '1 second', end_date]) AS date,\n unnest(ARRAY [6, 0]) AS state\n FROM updates\n WHERE\n car_id = $car_id AND \n ($__timeFrom() :: timestamp - interval '30 day') < start_date AND \n (end_date < ($__timeTo() :: timestamp + interval '30 day') OR end_date IS NULL)\n)\nSELECT date AS \"time\", state\nFROM states\nWHERE \n date IS NOT NULL AND\n ($__timeFrom() :: timestamp - interval '30 day') < date AND \n date < ($__timeTo() :: timestamp + interval '30 day') \nORDER BY date ASC, state ASC;", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "latitude" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "addresses", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ], - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 } } ], @@ -505,8 +458,9 @@ "type": "state-timeline" } ], - "refresh": false, - "schemaVersion": 39, + "preload": false, + "refresh": "", + "schemaVersion": 41, "tags": [ "tesla" ], @@ -518,22 +472,16 @@ "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, - "definition": "SELECT name AS __text, id AS __value FROM cars ORDER BY display_priority ASC, name ASC;", + "definition": "SELECT\n id as __value,\n CASE WHEN COUNT(id) OVER (PARTITION BY name) > 1 AND name IS NOT NULL THEN CONCAT(name, ' - ', RIGHT(vin, 6)) ELSE COALESCE(name, CONCAT('VIN ', vin)) end as __text \nFROM cars\nORDER BY display_priority ASC, name ASC, vin ASC;", "hide": 2, "includeAll": true, "label": "Car", - "multi": false, "name": "car_id", "options": [], - "query": "SELECT name AS __text, id AS __value FROM cars ORDER BY display_priority ASC, name ASC;", + "query": "SELECT\n id as __value,\n CASE WHEN COUNT(id) OVER (PARTITION BY name) > 1 AND name IS NOT NULL THEN CONCAT(name, ' - ', RIGHT(vin, 6)) ELSE COALESCE(name, CONCAT('VIN ', vin)) end as __text \nFROM cars\nORDER BY display_priority ASC, name ASC, vin ASC;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -544,19 +492,12 @@ "definition": "select base_url from settings limit 1;", "hide": 2, "includeAll": false, - "label": "", - "multi": false, "name": "base_url", "options": [], "query": "select base_url from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" } ] }, @@ -564,35 +505,9 @@ "from": "now-2d", "to": "now" }, - "timeRangeUpdatedDuringEditOrView": false, - "timepicker": { - "refresh_intervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "time_options": [ - "5m", - "15m", - "1h", - "6h", - "12h", - "24h", - "2d", - "7d", - "30d" - ] - }, + "timepicker": {}, "timezone": "", "title": "States", "uid": "xo4BNRkZz", - "version": 1, - "weekStart": "" + "version": 1 } \ No newline at end of file diff --git a/grafana/dashboards/statistics.json b/grafana/dashboards/statistics.json index 04097fd2a4..472c89a067 100644 --- a/grafana/dashboards/statistics.json +++ b/grafana/dashboards/statistics.json @@ -1,40 +1,16 @@ { - "__elements": {}, - "__requires": [ - { - "type": "grafana", - "id": "grafana", - "name": "Grafana", - "version": "11.0.0" - }, - { - "type": "datasource", - "id": "grafana-postgresql-datasource", - "name": "PostgreSQL", - "version": "1.0.0" - }, - { - "type": "panel", - "id": "table", - "name": "Table", - "version": "" - } - ], "annotations": { "list": [ { "builtIn": 1, - "datasource": "-- Grafana --", + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, "enable": true, "hide": true, "iconColor": "rgba(0, 211, 255, 1)", "name": "Annotations & Alerts", - "target": { - "limit": 100, - "matchAny": false, - "tags": [], - "type": "dashboard" - }, "type": "dashboard" } ] @@ -42,14 +18,13 @@ "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 0, - "id": null, "links": [ { "icon": "dashboard", "tags": [], "title": "TeslaMate", "type": "link", - "url": "[[base_url:raw]]" + "url": "${base_url:raw}" }, { "asDropdown": true, @@ -61,11 +36,9 @@ "type": "dashboards" } ], - "liveNow": false, "panels": [ { "collapsed": false, - "datasource": "TeslaMate", "gridPos": { "h": 1, "w": 24, @@ -87,30 +60,20 @@ "fieldConfig": { "defaults": { "custom": { - "align": "left", + "align": "auto", "cellOptions": { "type": "auto" }, "filterable": false, - "inspect": false, - "width": 120 + "inspect": false }, "mappings": [], "noValue": "--", "thresholds": { - "mode": "percentage", + "mode": "absolute", "steps": [ { - "color": "red", - "value": null - }, - { - "color": "#EAB839", - "value": 50 - }, - { - "color": "green", - "value": 90 + "color": "red" } ] } @@ -124,11 +87,15 @@ "properties": [ { "id": "unit", - "value": "clocks" + "value": "dtdurations" }, { "id": "decimals", "value": 1 + }, + { + "id": "custom.minWidth", + "value": 170 } ] }, @@ -138,26 +105,26 @@ "options": "Period" }, "properties": [ - { - "id": "custom.width", - "value": 195 - }, { "id": "links", "value": [ { "targetBlank": true, "title": "Trip", - "url": "d/FkUpJpQZk/trip?from=${__data.fields.date_from}&to=${__data.fields.date_to}&var-car_id=$car_id" + "url": "/d/FkUpJpQZk/trip?from=${__data.fields.date_from}&to=${__data.fields.date_to}&var-car_id=$car_id" } ] + }, + { + "id": "custom.minWidth", + "value": 195 } ] }, { "matcher": { "id": "byName", - "options": "Efficiency" + "options": "Driving Efficiency" }, "properties": [ { @@ -167,33 +134,46 @@ { "id": "thresholds", "value": { - "mode": "percentage", + "mode": "absolute", "steps": [ { - "color": "super-light-orange", - "value": null + "color": "super-light-orange" }, { "color": "light-orange", - "value": 65 + "value": 0.65 }, { "color": "light-green", - "value": 99 + "value": 0.99 } ] } }, { "id": "max", - "value": 1 + "value": 1.15 + }, + { + "id": "min", + "value": 0 + }, + { + "id": "custom.cellOptions", + "value": { + "mode": "lcd", + "type": "gauge" + } + }, + { + "id": "decimals" } ] }, { "matcher": { "id": "byName", - "options": "Energy charged" + "options": "Energy used" }, "properties": [ { @@ -206,20 +186,24 @@ { "targetBlank": true, "title": "Charging stats", - "url": "d/-pkIkhmRz/charging-stats?from=${__data.fields.date_from}&to=${__data.fields.date_to}&var-car_id=$car_id" + "url": "/d/-pkIkhmRz/charging-stats?from=${__data.fields.date_from}&to=${__data.fields.date_to}&var-car_id=$car_id" } ] }, { "id": "unit", "value": "kwatth" + }, + { + "id": "custom.minWidth", + "value": 120 } ] }, { "matcher": { "id": "byName", - "options": "Avg charged" + "options": "Ø Energy used / Charge" }, "properties": [ { @@ -229,6 +213,10 @@ { "id": "decimals", "value": 1 + }, + { + "id": "custom.minWidth", + "value": 190 } ] }, @@ -241,13 +229,17 @@ { "id": "decimals", "value": 2 + }, + { + "id": "custom.minWidth", + "value": 75 } ] }, { "matcher": { "id": "byName", - "options": "# charges" + "options": "# of Charges" }, "properties": [ { @@ -256,16 +248,20 @@ { "targetBlank": true, "title": "Charges", - "url": "d/TSmNYvRRk/charges?from=${__data.fields.date_from}&to=${__data.fields.date_to}&var-car_id=$car_id" + "url": "/d/TSmNYvRRk/charges?from=${__data.fields.date_from}&to=${__data.fields.date_to}&var-car_id=$car_id" } ] + }, + { + "id": "custom.minWidth", + "value": 110 } ] }, { "matcher": { "id": "byName", - "options": "# drives" + "options": "# of Drives" }, "properties": [ { @@ -274,9 +270,13 @@ { "targetBlank": true, "title": "Drives", - "url": "d/Y8upc6ZRk/drives?from=${__data.fields.date_from}&to=${__data.fields.date_to}&var-car_id=$car_id" + "url": "/d/Y8upc6ZRk/drives?from=${__data.fields.date_from}&to=${__data.fields.date_to}&var-car_id=$car_id" } ] + }, + { + "id": "custom.minWidth", + "value": 95 } ] }, @@ -293,6 +293,10 @@ { "id": "displayName", "value": "Distance" + }, + { + "id": "custom.minWidth", + "value": 100 } ] }, @@ -308,7 +312,7 @@ }, { "id": "displayName", - "value": "Temperature" + "value": "Ø Temp" }, { "id": "thresholds", @@ -316,8 +320,7 @@ "mode": "absolute", "steps": [ { - "color": "super-light-blue", - "value": null + "color": "super-light-blue" }, { "color": "super-light-green", @@ -329,6 +332,16 @@ } ] } + }, + { + "id": "custom.cellOptions", + "value": { + "type": "color-text" + } + }, + { + "id": "custom.minWidth", + "value": 80 } ] }, @@ -345,97 +358,90 @@ { "id": "unit", "value": "mi" + }, + { + "id": "custom.minWidth", + "value": 100 } ] }, { "matcher": { "id": "byRegexp", - "options": "/efficiency_net_mi/" + "options": "/consumption_net_mi/" }, "properties": [ - { - "id": "displayName", - "value": "Avg consumption (drives)" - }, { "id": "unit", "value": "Wh/mi" }, { - "id": "custom.width" + "id": "custom.width", + "value": 170 + }, + { + "id": "displayName", + "value": "Ø Consumption (net)" } ] }, { "matcher": { "id": "byRegexp", - "options": "/efficiency_charged_net_mi/" + "options": "/consumption_gross_mi/" }, "properties": [ { "id": "displayName", - "value": "Avg consumption (charges)" + "value": "Ø Consumption (gross)" }, { "id": "unit", "value": "Wh/mi" }, { - "id": "custom.width" - } - ] - }, - { - "matcher": { - "id": "byRegexp", - "options": "/.* at/" - }, - "properties": [ - { - "id": "unit", - "value": "dateTimeAsLocal" - }, - { - "id": "custom.width" + "id": "custom.width", + "value": 190 } ] }, { "matcher": { "id": "byRegexp", - "options": "/efficiency_net_km/" + "options": "/consumption_net_km/" }, "properties": [ { "id": "displayName", - "value": "Avg consumption (drives)" + "value": "Ø Consumption (net)" }, { "id": "unit", "value": "Wh/km" }, { - "id": "custom.width" + "id": "custom.width", + "value": 170 } ] }, { "matcher": { "id": "byRegexp", - "options": "/efficiency_charged_net_km/" + "options": "/consumption_gross_km/" }, "properties": [ { "id": "displayName", - "value": "Avg consumption (charges)" + "value": "Ø Consumption (gross)" }, { "id": "unit", "value": "Wh/km" }, { - "id": "custom.width" + "id": "custom.width", + "value": 190 } ] }, @@ -447,7 +453,7 @@ "properties": [ { "id": "displayName", - "value": "Temperature" + "value": "Ø Temp" }, { "id": "unit", @@ -465,8 +471,7 @@ "mode": "absolute", "steps": [ { - "color": "super-light-blue", - "value": null + "color": "super-light-blue" }, { "color": "super-light-green", @@ -478,6 +483,10 @@ } ] } + }, + { + "id": "custom.minWidth", + "value": 80 } ] }, @@ -508,12 +517,94 @@ { "matcher": { "id": "byName", - "options": "Avg cost per kWh" + "options": "Ø Cost / kWh" }, "properties": [ { - "id": "custom.width", - "value": 137 + "id": "decimals", + "value": 2 + }, + { + "id": "mappings", + "value": [ + { + "options": { + "NaN": { + "index": 0, + "text": "--" + } + }, + "type": "value" + } + ] + }, + { + "id": "custom.minWidth", + "value": 115 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Ø Cost / 100 km" + }, + "properties": [ + { + "id": "decimals", + "value": 2 + }, + { + "id": "custom.minWidth", + "value": 135 + }, + { + "id": "mappings", + "value": [ + { + "options": { + "NaN": { + "index": 0, + "text": "--" + } + }, + "type": "value" + } + ] + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Consumption OH" + }, + "properties": [ + { + "id": "custom.minWidth", + "value": 140 + }, + { + "id": "unit", + "value": "percentunit" + }, + { + "id": "decimals", + "value": 0 + }, + { + "id": "mappings", + "value": [ + { + "options": { + "NaN": { + "index": 0, + "text": "--" + } + }, + "type": "value" + } + ] } ] } @@ -546,100 +637,118 @@ } ] }, - "pluginVersion": "11.0.0", - "repeatDirection": "h", + "pluginVersion": "11.6.1", "targets": [ { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH data AS (\nSELECT\n duration_min > 1 AND\n distance > 1 AND\n ( \n start_position.usable_battery_level IS NULL OR\n (end_position.battery_level - end_position.usable_battery_level) = 0 \n ) AS is_sufficiently_precise,\n NULLIF(GREATEST(start_ideal_range_km - end_ideal_range_km, 0), 0) AS range_diff,\n date_trunc('$period', TIMEZONE('$__timezone', TIMEZONE('UTC', start_date))) as date,\n drives.*\nFROM drives\n LEFT JOIN positions start_position ON start_position_id = start_position.id\n LEFT JOIN positions end_position ON end_position_id = end_position.id)\nSELECT\n EXTRACT(EPOCH FROM TIMEZONE('$__timezone', date))*1000 AS date_from,\n EXTRACT(EPOCH FROM (TIMEZONE('$__timezone', date) + ('1 ' || '$period')::INTERVAL))*1000 AS date_to,\n CASE '$period'\n WHEN 'month' THEN to_char(date, 'YYYY Month')\n WHEN 'year' THEN to_char(date, 'YYYY')\n WHEN 'week' THEN 'week ' || to_char(date, 'WW') || ' starting ' || to_char(date, 'YYYY-MM-DD')\n ELSE to_char(date, 'YYYY-MM-DD')\n END AS display,\n TIMEZONE('$__timezone', date) AS date,\n sum(duration_min)*60 AS sum_duration_h, \n convert_km(max(end_km)::integer - min(start_km)::integer, '$length_unit') AS sum_distance_$length_unit,\n convert_celsius(avg(outside_temp_avg), '$temp_unit') AS avg_outside_temp_$temp_unit,\n count(*) AS cnt,\n sum(distance)/sum(range_diff) AS efficiency\nFROM data WHERE\n car_id = $car_id AND\n $__timeFilter(start_date)\nGROUP BY date\nORDER BY date", + "rawSql": "WITH data AS (\nSELECT\n duration_min > 1 AND\n distance > 1 AND\n ( \n start_position.usable_battery_level IS NULL OR\n (end_position.battery_level - end_position.usable_battery_level) = 0 \n ) AS is_sufficiently_precise,\n NULLIF(GREATEST(start_${preferred_range}_range_km - end_${preferred_range}_range_km, 0), 0) AS range_diff,\n date_trunc('$period', timezone('UTC', start_date), '$__timezone') as date,\n drives.*\nFROM drives\n LEFT JOIN positions start_position ON start_position_id = start_position.id\n LEFT JOIN positions end_position ON end_position_id = end_position.id)\nSELECT\n EXTRACT(EPOCH FROM date)*1000 AS date_from,\n EXTRACT(EPOCH FROM date + interval '1 $period')*1000 AS date_to,\n CASE '$period'\n WHEN 'month' THEN to_char(timezone('$__timezone', date), 'YYYY Month')\n WHEN 'year' THEN to_char(timezone('$__timezone', date), 'YYYY')\n WHEN 'week' THEN 'week ' || to_char(timezone('$__timezone', date), 'WW') || ' starting ' || to_char(timezone('$__timezone', date), 'YYYY-MM-DD')\n ELSE to_char(timezone('$__timezone', date), 'YYYY-MM-DD')\n END AS display,\n date,\n sum(duration_min)*60 AS sum_duration_h, \n convert_km(max(end_km)::integer - min(start_km)::integer, '$length_unit') AS sum_distance_$length_unit,\n convert_celsius(avg(outside_temp_avg), '$temp_unit') AS avg_outside_temp_$temp_unit,\n count(*) AS cnt,\n sum(distance)/sum(range_diff) AS efficiency\nFROM data WHERE\n car_id = $car_id AND\n $__timeFilter(start_date)\nGROUP BY date", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "start_km" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "drives", - "timeColumn": "start_date", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ], - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 } }, { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH data AS (\n SELECT\n charging_processes.*,\n \tdate_trunc('$period', TIMEZONE('$__timezone', TIMEZONE('UTC', start_date))) as date\n FROM charging_processes)\nSELECT\n EXTRACT(EPOCH FROM TIMEZONE('$__timezone', date))*1000 AS date_from,\n EXTRACT(EPOCH FROM (TIMEZONE('$__timezone', date) + ('1 ' || '$period')::INTERVAL))*1000 AS date_to,\n CASE '$period'\n WHEN 'month' THEN to_char(date, 'YYYY Month')\n WHEN 'year' THEN to_char(date, 'YYYY')\n WHEN 'week' THEN 'week ' || to_char(date, 'WW') || ' starting ' || to_char(date, 'YYYY-MM-DD')\n ELSE to_char(date, 'YYYY-MM-DD')\n END AS display,\n TIMEZONE('$__timezone', date) AS date,\n sum(greatest(charge_energy_added,charge_energy_used)) AS sum_consumption_kwh,\n sum(greatest(charge_energy_added,charge_energy_used)) / count(*) AS avg_consumption_kwh,\n sum(cost) AS cost_charges,\n count(*) AS cnt_charges\nFROM data WHERE\n car_id = $car_id AND\n $__timeFilter(start_date) AND\n (charge_energy_added IS NULL OR charge_energy_added > 0.1)\nGROUP BY date\nORDER BY date", + "rawSql": "WITH data AS (\n SELECT\n charging_processes.*,\n \tdate_trunc('$period', timezone('UTC', start_date), '$__timezone') as date\n FROM charging_processes)\nSELECT\n EXTRACT(EPOCH FROM date)*1000 AS date_from,\n EXTRACT(EPOCH FROM date + interval '1 $period')*1000 AS date_to,\n CASE '$period'\n WHEN 'month' THEN to_char(timezone('$__timezone', date), 'YYYY Month')\n WHEN 'year' THEN to_char(timezone('$__timezone', date), 'YYYY')\n WHEN 'week' THEN 'week ' || to_char(timezone('$__timezone', date), 'WW') || ' starting ' || to_char(timezone('$__timezone', date), 'YYYY-MM-DD')\n ELSE to_char(timezone('$__timezone', date), 'YYYY-MM-DD')\n END AS display,\n date,\n sum(greatest(charge_energy_added,charge_energy_used)) AS sum_energy_used_kwh,\n sum(greatest(charge_energy_added,charge_energy_used)) / count(*) AS avg_energy_charged_kwh,\n sum(cost) AS cost_charges,\n count(*) AS cnt_charges\nFROM data WHERE\n car_id = $car_id AND\n $__timeFilter(start_date) AND\n (charge_energy_added IS NULL OR charge_energy_added > 0.1)\nGROUP BY date", "refId": "B", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "value" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ], - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 } }, { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", "format": "table", - "group": [], - "hide": false, - "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH data AS (\n SELECT\n drives.*,\n date_trunc('$period', TIMEZONE('$__timezone', TIMEZONE('UTC', start_date))) as date\n FROM drives)\nSELECT\n EXTRACT(EPOCH FROM TIMEZONE('$__timezone', date))*1000 AS date_from,\n EXTRACT(EPOCH FROM (TIMEZONE('$__timezone', date) + ('1 ' || '$period')::INTERVAL))*1000 AS date_to,\n CASE '$period'\n WHEN 'month' THEN to_char(date, 'YYYY Month')\n WHEN 'year' THEN to_char(date, 'YYYY')\n WHEN 'week' THEN 'week ' || to_char(date, 'WW') || ' starting ' || to_char(date, 'YYYY-MM-DD')\n ELSE to_char(date, 'YYYY-MM-DD')\n END AS display,\n TIMEZONE('$__timezone', date) AS date,\n sum(GREATEST(start_[[preferred_range]]_range_km - end_[[preferred_range]]_range_km, 0) * car.efficiency * 1000) / \n convert_km(sum(distance)::numeric, '$length_unit') as efficiency_net_$length_unit\nFROM data\nJOIN cars car ON car.id = car_id\nWHERE\n car_id = $car_id AND\n $__timeFilter(start_date)\nGROUP BY date\nORDER BY date", + "rawSql": "WITH data AS (\n SELECT\n drives.*,\n date_trunc('$period', timezone('UTC', start_date), '$__timezone') as date\n FROM drives)\nSELECT\n EXTRACT(EPOCH FROM date)*1000 AS date_from,\n EXTRACT(EPOCH FROM date + interval '1 $period')*1000 AS date_to,\n CASE '$period'\n WHEN 'month' THEN to_char(timezone('$__timezone', date), 'YYYY Month')\n WHEN 'year' THEN to_char(timezone('$__timezone', date), 'YYYY')\n WHEN 'week' THEN 'week ' || to_char(timezone('$__timezone', date), 'WW') || ' starting ' || to_char(timezone('$__timezone', date), 'YYYY-MM-DD')\n ELSE to_char(timezone('$__timezone', date), 'YYYY-MM-DD')\n END AS display,\n date,\n sum(GREATEST(start_${preferred_range}_range_km - end_${preferred_range}_range_km, 0) * car.efficiency * 1000) / \n convert_km(sum(distance)::numeric, '$length_unit') as consumption_net_$length_unit\nFROM data\nJOIN cars car ON car.id = car_id\nWHERE\n car_id = $car_id AND\n $__timeFilter(start_date)\nGROUP BY date", "refId": "C", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "value" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ], + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + }, + { "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "with drives_start_event as (\n\n select\n 'drive_start' as event, start_date as date, start_${preferred_range}_range_km as range, start_km as odometer, car_id\n from drives\n where car_id = $car_id and $__timeFilter(start_date) and 0 = $high_precision\n\n),\n\ndrives_end_event as (\n\n select\n 'drive_end' as event, end_date as date, end_${preferred_range}_range_km as range, end_km as odometer, car_id\n from drives\n where car_id = $car_id and $__timeFilter(end_date) and 0 = $high_precision\n\n),\n\ncharging_processes_start_event as (\n\n select\n 'charging_process_start' as event, start_date as date, start_${preferred_range}_range_km as range, p.odometer, cp.car_id\n from charging_processes cp\n inner join positions p on cp.position_id = p.id\n where cp.car_id = $car_id and $__timeFilter(start_date) and 0 = $high_precision\n\n),\n\ncharging_processes_end_event as (\n\n select\n 'charging_process_end' as event, end_date as date, end_${preferred_range}_range_km as range, p.odometer, cp.car_id\n from charging_processes cp\n inner join positions p on cp.position_id = p.id\n where cp.car_id = $car_id and $__timeFilter(end_date) and 0 = $high_precision\n\n),\n\npositions as (\n\n select\n case\n when drive_id is not null and lead(drive_id) over w is not null then 'drive_start'\n else 'something'\n end as event,\n date, ${preferred_range}_battery_range_km as range, p.odometer, p.car_id\n from positions p\n where ideal_battery_range_km is not null and car_id = $car_id and $__timeFilter(date) and 1 = $high_precision\n window w as (order by date)\n\n),\n\ncombined as (\n\n select * from drives_start_event\n union all\n select * from drives_end_event\n union all\n select * from charging_processes_start_event\n union all\n select * from charging_processes_end_event\n union all\n select * from positions\n\n),\n\nfinal as (\n\n select\n car_id,\n date_trunc('$period', timezone('UTC', date), '$__timezone') as date,\n lead(odometer) over w - odometer as distance,\n case when event != 'drive_start' then greatest(range - lead(range) over w, 0) else range - lead(range) over w end as range_loss\n from combined\n window w as (order by date asc)\n\n)\n\nselect\n EXTRACT(EPOCH FROM date)*1000 AS date_from,\n EXTRACT(EPOCH FROM date + interval '1 $period')*1000 AS date_to,\n CASE '$period'\n WHEN 'month' THEN to_char(timezone('$__timezone', date), 'YYYY Month')\n WHEN 'year' THEN to_char(timezone('$__timezone', date), 'YYYY')\n WHEN 'week' THEN 'week ' || to_char(timezone('$__timezone', date), 'WW') || ' starting ' || to_char(timezone('$__timezone', date), 'YYYY-MM-DD')\n ELSE to_char(timezone('$__timezone', date), 'YYYY-MM-DD')\n END AS display,\n date,\n (sum(range_loss) * c.efficiency * 1000) / nullif(convert_km(sum(distance)::numeric, '$length_unit'), 0) as consumption_gross_$length_unit\nfrom final\n inner join cars c on car_id = c.id\ngroup by 1, 2, 3, 4, c.efficiency", + "refId": "D", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 } } ], @@ -655,32 +764,43 @@ "byField": "date" } }, + { + "id": "sortBy", + "options": { + "fields": {}, + "sort": [ + { + "desc": true, + "field": "date" + } + ] + } + }, { "id": "calculateField", "options": { - "alias": "efficiency_charged_net_km_temp", + "alias": "avg_cost_kwh", "binary": { - "left": "sum_consumption_kwh", + "left": "cost_charges", "operator": "/", "reducer": "sum", - "right": "sum_distance_km" + "right": "sum_energy_used_kwh" }, "mode": "binary", "reduce": { "reducer": "sum" - }, - "replaceFields": false + } } }, { "id": "calculateField", "options": { - "alias": "efficiency_charged_net_km", + "alias": "avg_cost_km_temp", "binary": { - "left": "efficiency_charged_net_km_temp", - "operator": "*", + "left": "cost_charges", + "operator": "/", "reducer": "sum", - "right": "1000" + "right": "sum_distance_km" }, "mode": "binary", "reduce": { @@ -691,9 +811,9 @@ { "id": "calculateField", "options": { - "alias": "efficiency_charged_net_mi_temp", + "alias": "avg_cost_mi_temp", "binary": { - "left": "sum_consumption_kwh", + "left": "cost_charges", "operator": "/", "reducer": "sum", "right": "sum_distance_mi" @@ -701,19 +821,32 @@ "mode": "binary", "reduce": { "reducer": "sum" + } + } + }, + { + "id": "calculateField", + "options": { + "alias": "avg_cost_km", + "binary": { + "left": "avg_cost_km_temp", + "operator": "*", + "right": "100" }, - "replaceFields": false + "mode": "binary", + "reduce": { + "reducer": "sum" + } } }, { "id": "calculateField", "options": { - "alias": "efficiency_charged_net_mi", + "alias": "avg_cost_mi", "binary": { - "left": "efficiency_charged_net_mi_temp", + "left": "avg_cost_mi_temp", "operator": "*", - "reducer": "sum", - "right": "1000" + "right": "100" }, "mode": "binary", "reduce": { @@ -724,12 +857,11 @@ { "id": "calculateField", "options": { - "alias": "avg_cost_kwh", + "alias": "overhead_pct_km_temp", "binary": { - "left": "cost_charges", + "left": "consumption_net_km", "operator": "/", - "reducer": "sum", - "right": "sum_consumption_kwh" + "right": "consumption_gross_km" }, "mode": "binary", "reduce": { @@ -740,12 +872,11 @@ { "id": "calculateField", "options": { - "alias": "avg_cost_km", + "alias": "overhead_pct_km", "binary": { - "left": "cost_charges", - "operator": "/", - "reducer": "sum", - "right": "sum_distance_km" + "left": "1", + "operator": "-", + "right": "overhead_pct_km_temp" }, "mode": "binary", "reduce": { @@ -756,12 +887,26 @@ { "id": "calculateField", "options": { - "alias": "avg_cost_mi", + "alias": "overhead_pct_mi_temp", "binary": { - "left": "cost_charges", + "left": "consumption_net_mi", "operator": "/", - "reducer": "sum", - "right": "sum_distance_mi" + "right": "consumption_gross_mi" + }, + "mode": "binary", + "reduce": { + "reducer": "sum" + } + } + }, + { + "id": "calculateField", + "options": { + "alias": "overhead_pct_mi", + "binary": { + "left": "1", + "operator": "-", + "right": "overhead_pct_mi_temp" }, "mode": "binary", "reduce": { @@ -773,55 +918,64 @@ "id": "organize", "options": { "excludeByName": { - "date": false, - "date_from": false, - "date_to": false, - "efficiency_charged_net_km_temp": true, - "efficiency_charged_net_mi_temp": true, - "timezone": true + "avg_cost_km_temp": true, + "avg_cost_mi_temp": true, + "date": true, + "overhead_pct_km_temp": true, + "overhead_pct_mi_temp": true }, + "includeByName": {}, "indexByName": { - "avg_consumption_kwh": 9, - "avg_cost_km": 19, + "avg_cost_km": 12, "avg_cost_kwh": 11, - "avg_cost_mi": 20, - "avg_outside_temp_c": 5, - "avg_outside_temp_f": 5, - "cnt": 6, - "cnt_charges": 11, - "cost_charges": 10, + "avg_cost_mi": 12, + "avg_energy_charged_kwh": 8, + "avg_outside_temp_c": 4, + "avg_outside_temp_f": 4, + "cnt": 5, + "cnt_charges": 10, + "consumption_gross_km": 14, + "consumption_gross_mi": 14, + "consumption_net_km": 13, + "consumption_net_mi": 13, + "cost_charges": 9, "date": 1, - "date_from": 17, - "date_to": 18, + "date_from": 15, + "date_to": 16, "display": 0, - "efficiency": 7, - "efficiency_charged_net_km": 15, - "efficiency_charged_net_mi": 16, - "efficiency_net_km": 13, - "efficiency_net_mi": 14, - "sum_consumption_kwh": 8, + "efficiency": 6, + "overhead_pct_km": 17, + "overhead_pct_mi": 17, "sum_distance_km": 3, - "sum_distance_mi": 4, - "sum_duration_h": 2 + "sum_distance_mi": 3, + "sum_duration_h": 2, + "sum_energy_used_kwh": 7 }, "renameByName": { - "avg_consumption_kwh": "Avg charged", - "avg_cost_km": "Avg cost per km", - "avg_cost_kwh": "Avg cost per kWh", - "avg_cost_mi": "Avg cost per mi", + "avg_cost_km": "Ø Cost / 100 km", + "avg_cost_kwh": "Ø Cost / kWh", + "avg_cost_mi": "Ø Cost / 100 mi", + "avg_energy_charged_kwh": "Ø Energy used / Charge", "avg_outside_temp_c": "", - "cnt": "# drives", - "cnt_charges": "# charges", + "avg_outside_temp_f": "", + "cnt": "# of Drives", + "cnt_charges": "# of Charges", + "consumption_gross_km": "", + "consumption_gross_mi": "", + "consumption_net_km": "", + "consumption_net_mi": "", "cost_charges": "Costs", - "date": "Starting at", + "date": "", "date_from": "", "date_to": "", "display": "Period", - "efficiency": "Efficiency", - "efficiency_net_km": "", - "sum_consumption_kwh": "Energy charged", + "efficiency": "Driving Efficiency", + "overhead_pct_km": "Consumption OH", + "overhead_pct_mi": "Consumption OH", "sum_distance_km": "", - "sum_duration_h": "Time driven" + "sum_distance_mi": "", + "sum_duration_h": "Time driven", + "sum_energy_used_kwh": "Energy used" } } } @@ -829,8 +983,9 @@ "type": "table" } ], - "refresh": false, - "schemaVersion": 39, + "preload": false, + "refresh": "", + "schemaVersion": 41, "tags": [ "tesla" ], @@ -842,22 +997,16 @@ "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, - "definition": "SELECT name AS __text, id AS __value FROM cars ORDER BY display_priority ASC, name ASC;", + "definition": "SELECT\n id as __value,\n CASE WHEN COUNT(id) OVER (PARTITION BY name) > 1 AND name IS NOT NULL THEN CONCAT(name, ' - ', RIGHT(vin, 6)) ELSE COALESCE(name, CONCAT('VIN ', vin)) end as __text \nFROM cars\nORDER BY display_priority ASC, name ASC, vin ASC;", "hide": 2, "includeAll": true, "label": "Car", - "multi": false, "name": "car_id", "options": [], - "query": "SELECT name AS __text, id AS __value FROM cars ORDER BY display_priority ASC, name ASC;", + "query": "SELECT\n id as __value,\n CASE WHEN COUNT(id) OVER (PARTITION BY name) > 1 AND name IS NOT NULL THEN CONCAT(name, ' - ', RIGHT(vin, 6)) ELSE COALESCE(name, CONCAT('VIN ', vin)) end as __text \nFROM cars\nORDER BY display_priority ASC, name ASC, vin ASC;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -869,19 +1018,12 @@ "hide": 2, "includeAll": false, "label": "length unit", - "multi": false, "name": "length_unit", "options": [], "query": "select unit_of_length from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tags": [], - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -893,29 +1035,20 @@ "hide": 2, "includeAll": false, "label": "temperature unit", - "multi": false, "name": "temp_unit", "options": [], "query": "select unit_of_temperature from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": { - "selected": false, "text": "month", "value": "month" }, - "hide": 0, "includeAll": false, "label": "Period", - "multi": false, "name": "period", "options": [ { @@ -940,8 +1073,6 @@ } ], "query": "day,week,month,year", - "queryValue": "", - "skipUrlSync": false, "type": "custom" }, { @@ -953,18 +1084,12 @@ "definition": "select preferred_range from settings limit 1;", "hide": 2, "includeAll": false, - "multi": false, "name": "preferred_range", "options": [], "query": "select preferred_range from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -975,18 +1100,36 @@ "definition": "select base_url from settings limit 1;", "hide": 2, "includeAll": false, - "multi": false, "name": "base_url", "options": [], "query": "select base_url from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" + }, + { + "current": { + "text": "no", + "value": "0" + }, + "description": "When enabled \"Ø Consumption (gross)\" will be calculated via Positions instead of Charging Processes and Drives.\n\nWhile being more accurate (especially for shorter periods) this will be slow on slow hardware!", + "includeAll": false, + "label": "High Precision", + "name": "high_precision", + "options": [ + { + "selected": true, + "text": "no", + "value": "0" + }, + { + "selected": false, + "text": "yes", + "value": "1" + } + ], + "query": "no : 0, yes : 1", + "type": "custom" } ] }, @@ -994,24 +1137,9 @@ "from": "now-10y", "to": "now" }, - "timeRangeUpdatedDuringEditOrView": false, - "timepicker": { - "refresh_intervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ] - }, + "timepicker": {}, "timezone": "", "title": "Statistics", "uid": "1EZnXszMk", - "version": 1, - "weekStart": "" + "version": 1 } \ No newline at end of file diff --git a/grafana/dashboards/timeline.json b/grafana/dashboards/timeline.json index f0a5664c3b..ebb2722a85 100644 --- a/grafana/dashboards/timeline.json +++ b/grafana/dashboards/timeline.json @@ -1,41 +1,16 @@ { - "__elements": {}, - "__requires": [ - { - "type": "grafana", - "id": "grafana", - "name": "Grafana", - "version": "11.1.0" - }, - { - "type": "datasource", - "id": "grafana-postgresql-datasource", - "name": "PostgreSQL", - "version": "1.0.0" - }, - { - "type": "panel", - "id": "table", - "name": "Table", - "version": "" - } - ], "annotations": { "list": [ { "builtIn": 1, - "datasource": "-- Grafana --", - "definition": "TeslaMate|U2FsdGVkX1/cEWK+8cz7pjEKXtzJnDN7b21ZDXt1MGneFGPWTLqOPtxKmu02mJPLzi/f29I+NBHd3vi0FB8R4Xn0+GtobWDgk6VAVSBTdSNniOKO8i2WPlhRaOsl2+hG7gnZ7wrf1Th2nxR7f1uYCrbwOek0IzkfLzrkjh7gkr6inT6bbDuJqrmogZajLxmAMrQ6V+/vHxBRGiwjJhgiEeq3hM1q2h04JKkNiZ8RHbsF5Cd/xd8Q9u0JVrZzIrtnhM/SFlaApU7RtRMu8CSj1llTX7WEOj6VDZAMSf+XUAanWdk725kEPN9MNu89o2zEq5P3E3cju8IbbBdPzXLV3oVuzD6/tMnxFToIIV1E/BrpF7s2RtNa8+KJJ1PF8xgs6m+/KTD2hy+WsP0636AgObRAmYg7+qotGrgNvpNPdE0EgrB7WHYlV7R/1q66bcq6tCe51X1Un70k+zo+K6AK0o4B1H6IyMlEVuRH/Fz8QVl9aYu2ztd08RbuKJlYVKpkH+pxVETAO9MclYQ90tzE6TfwDZrQZzsAlMenr4s1ZB1OlFXjLjVjnddnUilzO76cqv4yI2THQEuyQ47nuVQ4gUbx02K59vMQhns3C01JOAYokOaSXe66Y7QYdMlk09Lf|aes-256-cbc", + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, "enable": true, "hide": true, "iconColor": "rgba(0, 211, 255, 1)", "name": "Annotations & Alerts", - "target": { - "limit": 100, - "matchAny": false, - "tags": [], - "type": "dashboard" - }, "type": "dashboard" } ] @@ -43,7 +18,6 @@ "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 0, - "id": null, "links": [ { "asDropdown": false, @@ -55,7 +29,7 @@ "title": "TeslaMate", "tooltip": "", "type": "link", - "url": "[[base_url:raw]]" + "url": "${base_url:raw}" }, { "asDropdown": true, @@ -72,11 +46,9 @@ "url": "" } ], - "liveNow": false, "panels": [ { "collapsed": false, - "datasource": "TeslaMate", "gridPos": { "h": 1, "w": 24, @@ -109,8 +81,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -132,7 +103,7 @@ }, { "id": "custom.width", - "value": 180 + "value": 210 }, { "id": "links", @@ -140,7 +111,7 @@ { "targetBlank": true, "title": "", - "url": "d/FkUpJpQZk/trip?from=${__data.fields.start_date_ts}&to=${__data.fields.end_date_ts}&var-car_id=$car_id" + "url": "/d/FkUpJpQZk/trip?from=${__data.fields.start_date_ts}&to=${__data.fields.end_date_ts}&var-car_id=$car_id" } ] } @@ -154,7 +125,7 @@ "properties": [ { "id": "custom.width", - "value": 70 + "value": 65 }, { "id": "unit", @@ -170,7 +141,7 @@ "properties": [ { "id": "custom.width", - "value": 70 + "value": 80 }, { "id": "unit", @@ -235,22 +206,10 @@ { "id": "decimals", "value": 1 - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "End" - }, - "properties": [ - { - "id": "unit", - "value": "dateTimeAsLocal" }, { - "id": "custom.width", - "value": 152 + "id": "displayName", + "value": "Energy Diff" } ] }, @@ -282,7 +241,7 @@ { "targetBlank": true, "title": "Create or edit geo-fence", - "url": "[[base_url:raw]]/geo-fences/${__data.fields.start_path:raw}" + "url": "${base_url:raw}/geo-fences/${__data.fields.start_path:raw}" } ] }, @@ -308,7 +267,7 @@ { "targetBlank": true, "title": "Create or edit geo-fence", - "url": "[[base_url:raw]]/geo-fences/${__data.fields.end_path:raw}" + "url": "${base_url:raw}/geo-fences/${__data.fields.end_path:raw}" } ] }, @@ -470,11 +429,11 @@ "properties": [ { "id": "displayName", - "value": "Temperature" + "value": "Temp" }, { "id": "custom.width", - "value": 100 + "value": 75 }, { "id": "decimals", @@ -498,18 +457,6 @@ } ] }, - { - "matcher": { - "id": "byName", - "options": "Range" - }, - "properties": [ - { - "id": "custom.width", - "value": 118 - } - ] - }, { "matcher": { "id": "byName", @@ -567,7 +514,7 @@ } ] }, - "pluginVersion": "11.1.0", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { @@ -576,21 +523,9 @@ }, "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT\r\n start_date AS \"Start\",\r\n end_date AS \"End\",\r\n ROUND(EXTRACT(EPOCH FROM start_date))*1000 AS start_date_ts,\r\n ROUND(EXTRACT(EPOCH FROM end_date))*1000 AS end_date_ts,\r\n '🚗 Driving' AS \"Action\",\r\n drives.duration_min AS \"Duration\",\r\n CASE WHEN start_geofence_id IS NULL THEN CONCAT('new?lat=', TP1.latitude, '&lng=', TP1.longitude)\r\n WHEN start_geofence_id IS NOT NULL THEN CONCAT(start_geofence_id, '/edit')\r\n END AS start_path,\r\n CASE WHEN end_geofence_id IS NULL THEN CONCAT('new?lat=', TP2.latitude, '&lng=', TP2.longitude)\r\n WHEN start_geofence_id IS NOT NULL THEN CONCAT(end_geofence_id, '/edit')\r\n END AS end_path,\r\n COALESCE(start_geofence.name, CONCAT_WS(', ', COALESCE(start_address.name, nullif(CONCAT_WS(' ', start_address.road, start_address.house_number), '')), start_address.city)) AS \"Start Address\",\r\n COALESCE(end_geofence.name, CONCAT_WS(', ', COALESCE(end_address.name, nullif(CONCAT_WS(' ', end_address.road, end_address.house_number), '')), end_address.city)) AS \"End Address\",\r\n convert_km(end_km::NUMERIC, '$length_unit') AS odometer_$length_unit,\r\n convert_km(distance::NUMERIC, '$length_unit') AS distance_$length_unit,\r\n convert_km(end_[[preferred_range]]_range_km::NUMERIC, '$length_unit') AS end_range_$length_unit,\r\n convert_km((end_[[preferred_range]]_range_km - start_[[preferred_range]]_range_km)::NUMERIC, '$length_unit') * car.efficiency AS \"kWh\",\r\n convert_km((end_[[preferred_range]]_range_km - start_[[preferred_range]]_range_km)::NUMERIC, '$length_unit') AS range_diff_$length_unit,\r\n TP2.battery_level AS \"SoC\",\r\n TP2.battery_level-TP1.battery_level AS \"SoC Diff\",\r\n convert_celsius(outside_temp_avg, '$temp_unit') AS outside_temp_avg_$temp_unit,\r\n CONCAT('d/zm7wN6Zgz/drive-details?from=', ROUND(EXTRACT(EPOCH FROM start_date))*1000, '&to=', ROUND(EXTRACT(EPOCH FROM end_date))*1000, '&var-car_id=', drives.car_id, '&var-drive_id=', drives.id) AS slotlink\r\nFROM drives\r\n INNER JOIN cars AS car ON drives.car_id = car.id\r\n INNER JOIN positions AS TP1 on drives.start_position_id = TP1.id\r\n INNER JOIN positions AS TP2 on drives.end_position_id = TP2.id\r\n INNER JOIN addresses start_address ON start_address_id = start_address.id\r\n INNER JOIN addresses end_address ON end_address_id = end_address.id\r\n LEFT JOIN geofences start_geofence ON start_geofence_id = start_geofence.id\r\n LEFT JOIN geofences end_geofence ON end_geofence_id = end_geofence.id\r\nWHERE \r\n $__timeFilter(drives.start_date)\r\n AND drives.car_id = $car_id\r\n AND '🚗 Driving' in ($action_filter)\r\n AND\r\n (COALESCE(start_geofence.name, CONCAT_WS(', ', COALESCE(start_address.name, nullif(CONCAT_WS(' ', start_address.road, start_address.house_number), '')), start_address.city))::TEXT ILIKE'%$text_filter%' or\r\n COALESCE(end_geofence.name, CONCAT_WS(', ', COALESCE(end_address.name, nullif(CONCAT_WS(' ', end_address.road, end_address.house_number), '')), end_address.city))::TEXT ILIKE'%$text_filter%')\r\n\r\nUNION\r\nSELECT\r\n start_date AS \"Start\",\r\n end_date AS \"End\",\r\n ROUND(EXTRACT(EPOCH FROM start_date))*1000 AS start_date_ts,\r\n ROUND(EXTRACT(EPOCH FROM end_date))*1000 AS end_date_ts,\r\n '🔋 Charging' AS \"Action\",\r\n charging_processes.duration_min AS \"Duration\",\r\n CASE WHEN geofence_id IS NULL THEN CONCAT('new?lat=', address.latitude, '&lng=', address.longitude)\r\n WHEN geofence_id IS NOT NULL THEN CONCAT(geofence_id, '/edit')\r\n END AS start_path,\r\n NULL AS end_path,\r\n COALESCE(geofence.name, CONCAT_WS(', ', COALESCE(address.name, nullif(CONCAT_WS(' ', address.road, address.house_number), '')), address.city)) AS \"Start Address\",\r\n '' AS \"End Address\",\r\n convert_km(position.odometer::NUMERIC, '$length_unit') AS odometer_$length_unit,\r\n NULL AS distance_$length_unit,\r\n convert_km(end_[[preferred_range]]_range_km::NUMERIC, '$length_unit') AS end_range_$length_unit,\r\n charging_processes.charge_energy_added AS \"kWh\",\r\n convert_km((end_[[preferred_range]]_range_km - start_[[preferred_range]]_range_km)::NUMERIC, '$length_unit') AS range_diff_$length_unit, \r\n end_battery_level AS \"SoC\",\r\n end_battery_level - start_battery_level AS \"SoC Diff\",\r\n convert_celsius(outside_temp_avg, '$temp_unit') AS outside_temp_avg_$temp_unit,\r\n CONCAT('d/BHhxFeZRz/charge-details?from=', ROUND(EXTRACT(EPOCH FROM start_date)-10)*1000, '&to=', ROUND(EXTRACT(EPOCH FROM end_date)+10)*1000, '&var-car_id=', charging_processes.car_id, '&var-charging_process_id=', charging_processes.id) AS slotlink\r\nFROM charging_processes\r\n INNER JOIN positions AS position ON position_id = position.id\r\n INNER JOIN addresses AS address ON address_id = address.id\r\n LEFT JOIN geofences AS geofence ON geofence_id = geofence.id\r\nWHERE\r\n $__timeFilter(charging_processes.start_date)\r\n AND charging_processes.charge_energy_added > 0\r\n AND charging_processes.car_id = $car_id\r\n AND '🔋 Charging' in ($action_filter)\r\n AND COALESCE(geofence.name, CONCAT_WS(', ', COALESCE(address.name, nullif(CONCAT_WS(' ', address.road, address.house_number), '')), address.city))::TEXT ILIKE'%$text_filter%'\r\nUNION\r\nSELECT\r\n d.end_date AS \"Start\",\r\n LEAD(d.start_date) over w AS \"End\",\r\n ROUND(EXTRACT(EPOCH FROM d.end_date)) * 1000 AS start_date_ts,\r\n ROUND(EXTRACT(EPOCH FROM LEAD(d.start_date) over w))*1000 AS end_date_ts,\r\n '🅿️ Parking' AS \"Action\",\r\n EXTRACT(EPOCH FROM LEAD(d.start_date) over w - d.end_date)/60 AS \"Duration\",\r\n CASE WHEN d.end_geofence_id IS NULL THEN CONCAT('new?lat=', end_position.latitude, '&lng=', end_position.longitude)\r\n WHEN d.end_geofence_id IS NOT NULL THEN CONCAT(d.end_geofence_id, '/edit')\r\n END AS start_path,\r\n NULL AS end_path,\r\n COALESCE(geofence.name, CONCAT_WS(', ', COALESCE(address.name, nullif(CONCAT_WS(' ', address.road, address.house_number), '')), address.city)) AS \"Start Address\",\r\n '' AS \"End Address\",\r\n convert_km(end_position.odometer::NUMERIC, '$length_unit') AS odometer_$length_unit,\r\n NULL AS distance_$length_unit,\r\n convert_km(LEAD(d.start_[[preferred_range]]_range_km) over w::NUMERIC, '$length_unit') AS end_range_$length_unit,\r\n convert_km(((LEAD(d.start_[[preferred_range]]_range_km) over w + (LEAD(d.start_km) over w - d.end_km)) - d.end_[[preferred_range]]_range_km)::NUMERIC, '$length_unit') * car.efficiency AS \"kWh\",\r\n convert_km(((LEAD(d.start_[[preferred_range]]_range_km) over w + (LEAD(d.start_km) over w - d.end_km)) - d.end_[[preferred_range]]_range_km)::NUMERIC, '$length_unit') AS range_diff_$length_unit,\r\n LEAD(start_position.battery_level) over w AS \"SoC\",\r\n LEAD(start_position.battery_level) over w - end_position.battery_level AS \"SoC Diff\",\r\n convert_celsius(outside_temp_avg, '$temp_unit') AS outside_temp_avg_$temp_unit,\r\n CONCAT('d/FkUpJpQZk/trip?from=', ROUND(EXTRACT(EPOCH FROM d.end_date))*1000, '&to=', ROUND(EXTRACT(EPOCH FROM LEAD(d.start_date) over w))*1000, '&var-car_id=', d.car_id) AS slotlink\r\nFROM drives AS d\r\n INNER JOIN cars AS car ON d.car_id = car.id\r\n INNER JOIN positions AS start_position on d.start_position_id = start_position.id\r\n INNER JOIN positions AS end_position on d.end_position_id = end_position.id\r\n INNER JOIN addresses AS address ON d.end_address_id = address.id\r\n LEFT JOIN geofences AS geofence ON d.end_geofence_id = geofence.id\r\nWHERE\r\n $__timeFilter(d.end_date)\r\n AND d.car_id=$car_id\r\n AND '🅿️ Parking' in ($action_filter)\r\n AND COALESCE(geofence.name, CONCAT_WS(', ', COALESCE(address.name, nullif(CONCAT_WS(' ', address.road, address.house_number), '')), address.city))::TEXT ILIKE'%$text_filter%'\r\nWINDOW w as (ORDER BY d.id ASC)\r\n\r\nUNION\r\nSELECT\r\n\tT1.end_date +(1 * interval '1 second') AS \"Start\", -- added 1 sec to get it after the corresponding Parking row\r\n\tT2.start_date AS \"End\",\r\n\tROUND(EXTRACT(EPOCH FROM T1.end_date)) * 1000 - 1 AS start_date_ts,\r\n\tROUND(EXTRACT(EPOCH FROM T2.start_date)) * 1000 - 1 AS end_date_ts,\r\n\t'❓ Missing' AS \"Action\",\r\n\t-- EXTRACT(EPOCH FROM T2.start_date - T1.end_date)/60 AS \"Duration\",\r\n\tNULL AS \"Duration\",\r\n\tCASE WHEN T1.end_geofence_id IS NULL THEN CONCAT('new?lat=', TP1.latitude, '&lng=', TP1.longitude)\r\n\t\tWHEN T1.end_geofence_id IS NOT NULL THEN CONCAT(T1.end_geofence_id, '/edit')\r\n\tEND AS start_path,\r\n\tCASE WHEN T2.start_geofence_id IS NULL THEN CONCAT('new?lat=', TP2.latitude, '&lng=', TP2.longitude)\r\n\t\tWHEN T2.start_geofence_id IS NOT NULL THEN CONCAT(T2.start_geofence_id, '/edit')\r\n\tEND AS end_path,\r\n\tCOALESCE(start_geofence.name, CONCAT_WS(', ', COALESCE(start_address.name, nullif(CONCAT_WS(' ', start_address.road, start_address.house_number), '')), start_address.city)) AS \"Start Address\",\r\n\tCOALESCE(end_geofence.name, CONCAT_WS(', ', COALESCE(end_address.name, nullif(CONCAT_WS(' ', end_address.road, end_address.house_number), '')), end_address.city)) AS \"End Address\",\r\n\tconvert_km(TP2.odometer::INTEGER, '$length_unit') AS odometer_$length_unit,\r\n\tconvert_km((TP2.odometer - TP1.odometer)::INTEGER, '$length_unit') AS distance_$length_unit,\r\n convert_km(T2.end_[[preferred_range]]_range_km::NUMERIC, '$length_unit') AS end_range_$length_unit,\r\n\tconvert_km(((TP2.[[preferred_range]]_battery_range_km + (TP2.odometer - TP1.odometer)) - TP1.[[preferred_range]]_battery_range_km)::INTEGER, '$length_unit')::INTEGER * car.efficiency AS \"kWh\",\r\n\tconvert_km(((TP2.[[preferred_range]]_battery_range_km + (TP2.odometer - TP1.odometer)) - TP1.[[preferred_range]]_battery_range_km)::INTEGER, '$length_unit') AS range_diff_$length_unit,\r\n\tNULL AS \"SoC\",\r\n\tNULL AS \"SoC Diff\",\r\n\tNULL AS outside_temp_avg_$temp_unit,\r\n\tNULL AS slotlink\r\n\t-- TP2.battery_level AS \"SoC\",\r\n\t-- TP2.battery_level-TP1.battery_level AS \"SoC Diff\",\r\n\t-- (T1.outside_temp_avg+T2.outside_temp_avg)/2 AS outside_temp_avg_$temp_unit\r\nFROM drives AS T1\r\n INNER JOIN cars AS car ON T1.car_id = car.id\r\n\tINNER JOIN (SELECT d.*, LAG(id) OVER (ORDER BY id ASC) AS previous_id FROM drives d WHERE d.car_id = $car_id) AS T2 ON T1.id = T2.previous_id\r\n\tINNER JOIN positions AS TP1 ON T1.end_position_id = TP1.id\r\n\tINNER JOIN positions AS TP2 ON T2.start_position_id = TP2.id\r\n\tINNER JOIN addresses AS start_address ON T1.end_address_id = start_address.id\r\n\tINNER JOIN addresses AS end_address ON T2.start_address_id = end_address.id\r\n\tLEFT JOIN geofences AS start_geofence ON T1.end_geofence_id = start_geofence.id\r\n\tLEFT JOIN geofences AS end_geofence ON T2.start_geofence_id = end_geofence.id\r\nWHERE\r\n\t$__timeFilter(T1.end_date)\r\n\tAND TP2.odometer - TP1.odometer > 0.5\r\n AND T1.end_address_id <> T2.start_address_id AND ((COALESCE(T1.end_geofence_id, 0) <> COALESCE(T2.start_geofence_id, 0)) OR (T1.end_geofence_id IS NULL AND T2.start_geofence_id IS NULL))\r\n AND '❓ Missing' in ($action_filter)\r\n\tAND (\r\n\t (COALESCE(start_geofence.name, CONCAT_WS(', ', COALESCE(start_address.name, nullif(CONCAT_WS(' ', start_address.road, start_address.house_number), '')), start_address.city))::TEXT ILIKE'%$text_filter%') or\r\n\t (COALESCE(end_geofence.name, CONCAT_WS(', ', COALESCE(end_address.name, nullif(CONCAT_WS(' ', end_address.road, end_address.house_number), '')), end_address.city)))::TEXT ILIKE'%$text_filter%')\r\nUNION\r\nSELECT\r\n start_date AS \"Start\",\r\n end_date AS \"End\",\r\n ROUND(EXTRACT(EPOCH FROM start_date))*1000 AS start_date_ts, \r\n ROUND(EXTRACT(EPOCH FROM end_date))*1000 AS end_date_ts, \r\n '💾 Updating' AS \"Action\",\r\n\tEXTRACT(EPOCH FROM end_date - start_date)/60 AS \"Duration\",\r\n NULL AS start_path,\r\n NULL AS end_path,\r\n version AS \"Start Address\",\r\n '' AS \"End Address\",\r\n NULL AS odometer_$length_unit,\r\n NULL AS distance_$length_unit,\r\n NULL AS end_range_$length_unit,\r\n NULL AS \"kWh\",\r\n NULL AS range_diff_$length_unit,\r\n NULL AS \"SoC\",\r\n NULL AS \"SoC Diff\",\r\n NULL AS outside_temp_avg_$temp_unit,\r\n CONCAT('https://www.notateslaapp.com/software-updates/version/', split_part(version, ' ', 1), '/release-notes') AS slotlink\r\nFROM updates\r\nWHERE \r\n $__timeFilter(start_date)\r\n AND car_id = $car_id \r\n AND '💾 Updating' in ($action_filter)\r\n AND version::TEXT ILIKE'%$text_filter%'\r\n\r\nORDER BY \"Start\" DESC;", + "rawSql": "-- CTE is used in Parking Query\r\nwith drives_and_charging_processes as (\r\n\r\n select 'Drive' as activity, d.start_date, d.end_date, d.start_position_id, d.end_position_id, d.end_address_id, d.end_geofence_id, d.start_${preferred_range}_range_km, d.end_${preferred_range}_range_km, d.car_id, d.outside_temp_avg from drives d\r\n \r\n union all\r\n \r\n select 'Charging Process' as activity, cp.start_date, cp.end_date, cp.position_id as start_position_id, cp.position_id as end_position_id, cp.address_id as end_address_id, cp.geofence_id as end_geofence_id, cp.start_${preferred_range}_range_km, cp.end_${preferred_range}_range_km, cp.car_id, cp.outside_temp_avg from charging_processes cp\r\n\r\n)\r\n\r\nSELECT\r\n start_date AS \"Start\",\r\n end_date AS \"End\",\r\n ROUND(EXTRACT(EPOCH FROM start_date))*1000 AS start_date_ts,\r\n ROUND(EXTRACT(EPOCH FROM end_date))*1000 AS end_date_ts,\r\n '🚗 Driving' AS \"Action\",\r\n drives.duration_min AS \"Duration\",\r\n CASE WHEN start_geofence_id IS NULL THEN CONCAT('new?lat=', TP1.latitude, '&lng=', TP1.longitude)\r\n WHEN start_geofence_id IS NOT NULL THEN CONCAT(start_geofence_id, '/edit')\r\n END AS start_path,\r\n CASE WHEN end_geofence_id IS NULL THEN CONCAT('new?lat=', TP2.latitude, '&lng=', TP2.longitude)\r\n WHEN start_geofence_id IS NOT NULL THEN CONCAT(end_geofence_id, '/edit')\r\n END AS end_path,\r\n COALESCE(start_geofence.name, CONCAT_WS(', ', COALESCE(start_address.name, nullif(CONCAT_WS(' ', start_address.road, start_address.house_number), '')), start_address.city)) AS \"Start Address\",\r\n COALESCE(end_geofence.name, CONCAT_WS(', ', COALESCE(end_address.name, nullif(CONCAT_WS(' ', end_address.road, end_address.house_number), '')), end_address.city)) AS \"End Address\",\r\n convert_km(end_km::NUMERIC, '$length_unit') AS odometer_$length_unit,\r\n convert_km(distance::NUMERIC, '$length_unit') AS distance_$length_unit,\r\n convert_km(end_${preferred_range}_range_km::NUMERIC, '$length_unit') AS end_range_$length_unit,\r\n (end_${preferred_range}_range_km - start_${preferred_range}_range_km) * car.efficiency AS \"kWh\",\r\n convert_km((end_${preferred_range}_range_km - start_${preferred_range}_range_km)::NUMERIC, '$length_unit') AS range_diff_$length_unit,\r\n TP2.battery_level AS \"SoC\",\r\n TP2.battery_level-TP1.battery_level AS \"SoC Diff\",\r\n convert_celsius(outside_temp_avg, '$temp_unit') AS outside_temp_avg_$temp_unit,\r\n CONCAT('d/zm7wN6Zgz/drive-details?from=', ROUND(EXTRACT(EPOCH FROM start_date))*1000, '&to=', ROUND(EXTRACT(EPOCH FROM end_date))*1000, '&var-car_id=', drives.car_id, '&var-drive_id=', drives.id) AS slotlink\r\nFROM drives\r\n INNER JOIN cars AS car ON drives.car_id = car.id\r\n INNER JOIN positions AS TP1 on drives.start_position_id = TP1.id\r\n INNER JOIN positions AS TP2 on drives.end_position_id = TP2.id\r\n INNER JOIN addresses start_address ON start_address_id = start_address.id\r\n INNER JOIN addresses end_address ON end_address_id = end_address.id\r\n LEFT JOIN geofences start_geofence ON start_geofence_id = start_geofence.id\r\n LEFT JOIN geofences end_geofence ON end_geofence_id = end_geofence.id\r\nWHERE \r\n $__timeFilter(drives.start_date)\r\n AND drives.car_id = $car_id\r\n AND '🚗 Driving' in ($action_filter)\r\n AND\r\n (COALESCE(start_geofence.name, CONCAT_WS(', ', COALESCE(start_address.name, nullif(CONCAT_WS(' ', start_address.road, start_address.house_number), '')), start_address.city))::TEXT ILIKE'%$text_filter%' or\r\n COALESCE(end_geofence.name, CONCAT_WS(', ', COALESCE(end_address.name, nullif(CONCAT_WS(' ', end_address.road, end_address.house_number), '')), end_address.city))::TEXT ILIKE'%$text_filter%')\r\n\r\nUNION\r\nSELECT\r\n start_date AS \"Start\",\r\n end_date AS \"End\",\r\n ROUND(EXTRACT(EPOCH FROM start_date))*1000 AS start_date_ts,\r\n ROUND(EXTRACT(EPOCH FROM end_date))*1000 AS end_date_ts,\r\n '🔋 Charging' AS \"Action\",\r\n charging_processes.duration_min AS \"Duration\",\r\n CASE WHEN geofence_id IS NULL THEN CONCAT('new?lat=', address.latitude, '&lng=', address.longitude)\r\n WHEN geofence_id IS NOT NULL THEN CONCAT(geofence_id, '/edit')\r\n END AS start_path,\r\n NULL AS end_path,\r\n COALESCE(geofence.name, CONCAT_WS(', ', COALESCE(address.name, nullif(CONCAT_WS(' ', address.road, address.house_number), '')), address.city)) AS \"Start Address\",\r\n '' AS \"End Address\",\r\n convert_km(position.odometer::NUMERIC, '$length_unit') AS odometer_$length_unit,\r\n NULL AS distance_$length_unit,\r\n convert_km(end_${preferred_range}_range_km::NUMERIC, '$length_unit') AS end_range_$length_unit,\r\n charging_processes.charge_energy_added AS \"kWh\",\r\n convert_km((end_${preferred_range}_range_km - start_${preferred_range}_range_km)::NUMERIC, '$length_unit') AS range_diff_$length_unit, \r\n end_battery_level AS \"SoC\",\r\n end_battery_level - start_battery_level AS \"SoC Diff\",\r\n convert_celsius(outside_temp_avg, '$temp_unit') AS outside_temp_avg_$temp_unit,\r\n CONCAT('d/BHhxFeZRz/charge-details?from=', ROUND(EXTRACT(EPOCH FROM start_date)-10)*1000, '&to=', ROUND(EXTRACT(EPOCH FROM end_date)+10)*1000, '&var-car_id=', charging_processes.car_id, '&var-charging_process_id=', charging_processes.id) AS slotlink\r\nFROM charging_processes\r\n INNER JOIN positions AS position ON position_id = position.id\r\n INNER JOIN addresses AS address ON address_id = address.id\r\n LEFT JOIN geofences AS geofence ON geofence_id = geofence.id\r\nWHERE\r\n $__timeFilter(charging_processes.start_date)\r\n AND charging_processes.charge_energy_added > 0\r\n AND charging_processes.car_id = $car_id\r\n AND '🔋 Charging' in ($action_filter)\r\n AND COALESCE(geofence.name, CONCAT_WS(', ', COALESCE(address.name, nullif(CONCAT_WS(' ', address.road, address.house_number), '')), address.city))::TEXT ILIKE'%$text_filter%'\r\nUNION\r\nSELECT\r\n d.end_date AS \"Start\",\r\n LEAD(d.start_date) over w AS \"End\",\r\n ROUND(EXTRACT(EPOCH FROM d.end_date)) * 1000 AS start_date_ts,\r\n ROUND(EXTRACT(EPOCH FROM LEAD(d.start_date) over w))*1000 AS end_date_ts,\r\n '🅿️ Parking' AS \"Action\",\r\n EXTRACT(EPOCH FROM LEAD(d.start_date) over w - d.end_date)/60 AS \"Duration\",\r\n CASE WHEN d.end_geofence_id IS NULL THEN CONCAT('new?lat=', end_position.latitude, '&lng=', end_position.longitude)\r\n WHEN d.end_geofence_id IS NOT NULL THEN CONCAT(d.end_geofence_id, '/edit')\r\n END AS start_path,\r\n NULL AS end_path,\r\n COALESCE(geofence.name, CONCAT_WS(', ', COALESCE(address.name, nullif(CONCAT_WS(' ', address.road, address.house_number), '')), address.city)) AS \"Start Address\",\r\n '' AS \"End Address\",\r\n convert_km(end_position.odometer::NUMERIC, '$length_unit') AS odometer_$length_unit,\r\n NULL AS distance_$length_unit,\r\n convert_km(LEAD(d.start_${preferred_range}_range_km) over w::NUMERIC, '$length_unit') AS end_range_$length_unit,\r\n ((LEAD(d.start_${preferred_range}_range_km) over w + (LEAD(start_position.odometer) over w - end_position.odometer)) - d.end_${preferred_range}_range_km) * car.efficiency AS \"kWh\",\r\n convert_km(((LEAD(d.start_${preferred_range}_range_km) over w + (LEAD(start_position.odometer) over w - end_position.odometer)) - d.end_${preferred_range}_range_km)::NUMERIC, '$length_unit') AS range_diff_$length_unit,\r\n LEAD(start_position.battery_level) over w AS \"SoC\",\r\n LEAD(start_position.battery_level) over w - end_position.battery_level AS \"SoC Diff\",\r\n convert_celsius(outside_temp_avg, '$temp_unit') AS outside_temp_avg_$temp_unit,\r\n CONCAT('d/FkUpJpQZk/trip?from=', ROUND(EXTRACT(EPOCH FROM d.end_date))*1000, '&to=', ROUND(EXTRACT(EPOCH FROM LEAD(d.start_date) over w))*1000, '&var-car_id=', d.car_id) AS slotlink\r\nFROM drives_and_charging_processes AS d\r\n INNER JOIN cars AS car ON d.car_id = car.id\r\n INNER JOIN positions AS start_position on d.start_position_id = start_position.id\r\n INNER JOIN positions AS end_position on d.end_position_id = end_position.id\r\n INNER JOIN addresses AS address ON d.end_address_id = address.id\r\n LEFT JOIN geofences AS geofence ON d.end_geofence_id = geofence.id\r\nWHERE\r\n $__timeFilter(d.end_date)\r\n AND d.car_id=$car_id\r\n AND '🅿️ Parking' in ($action_filter)\r\n AND COALESCE(geofence.name, CONCAT_WS(', ', COALESCE(address.name, nullif(CONCAT_WS(' ', address.road, address.house_number), '')), address.city))::TEXT ILIKE'%$text_filter%'\r\nWINDOW w as (ORDER BY d.start_date ASC)\r\n\r\nUNION\r\nSELECT\r\n\tT1.end_date +(1 * interval '1 second') AS \"Start\", -- added 1 sec to get it after the corresponding Parking row\r\n\tT2.start_date AS \"End\",\r\n\tROUND(EXTRACT(EPOCH FROM T1.end_date)) * 1000 - 1 AS start_date_ts,\r\n\tROUND(EXTRACT(EPOCH FROM T2.start_date)) * 1000 - 1 AS end_date_ts,\r\n\t'❓ Missing' AS \"Action\",\r\n\t-- EXTRACT(EPOCH FROM T2.start_date - T1.end_date)/60 AS \"Duration\",\r\n\tNULL AS \"Duration\",\r\n\tCASE WHEN T1.end_geofence_id IS NULL THEN CONCAT('new?lat=', TP1.latitude, '&lng=', TP1.longitude)\r\n\t\tWHEN T1.end_geofence_id IS NOT NULL THEN CONCAT(T1.end_geofence_id, '/edit')\r\n\tEND AS start_path,\r\n\tCASE WHEN T2.start_geofence_id IS NULL THEN CONCAT('new?lat=', TP2.latitude, '&lng=', TP2.longitude)\r\n\t\tWHEN T2.start_geofence_id IS NOT NULL THEN CONCAT(T2.start_geofence_id, '/edit')\r\n\tEND AS end_path,\r\n\tCOALESCE(start_geofence.name, CONCAT_WS(', ', COALESCE(start_address.name, nullif(CONCAT_WS(' ', start_address.road, start_address.house_number), '')), start_address.city)) AS \"Start Address\",\r\n\tCOALESCE(end_geofence.name, CONCAT_WS(', ', COALESCE(end_address.name, nullif(CONCAT_WS(' ', end_address.road, end_address.house_number), '')), end_address.city)) AS \"End Address\",\r\n\tconvert_km(TP2.odometer::INTEGER, '$length_unit') AS odometer_$length_unit,\r\n\tconvert_km((TP2.odometer - TP1.odometer)::INTEGER, '$length_unit') AS distance_$length_unit,\r\n convert_km(T2.end_${preferred_range}_range_km::NUMERIC, '$length_unit') AS end_range_$length_unit,\r\n\t((TP2.${preferred_range}_battery_range_km + (TP2.odometer - TP1.odometer)) - TP1.${preferred_range}_battery_range_km) * car.efficiency AS \"kWh\",\r\n\tconvert_km(((TP2.${preferred_range}_battery_range_km + (TP2.odometer - TP1.odometer)) - TP1.${preferred_range}_battery_range_km)::INTEGER, '$length_unit') AS range_diff_$length_unit,\r\n\tNULL AS \"SoC\",\r\n\tNULL AS \"SoC Diff\",\r\n\tNULL AS outside_temp_avg_$temp_unit,\r\n\tNULL AS slotlink\r\n\t-- TP2.battery_level AS \"SoC\",\r\n\t-- TP2.battery_level-TP1.battery_level AS \"SoC Diff\",\r\n\t-- (T1.outside_temp_avg+T2.outside_temp_avg)/2 AS outside_temp_avg_$temp_unit\r\nFROM drives AS T1\r\n INNER JOIN cars AS car ON T1.car_id = car.id\r\n\tINNER JOIN (SELECT d.*, LAG(id) OVER (ORDER BY id ASC) AS previous_id FROM drives d WHERE d.car_id = $car_id) AS T2 ON T1.id = T2.previous_id\r\n\tINNER JOIN positions AS TP1 ON T1.end_position_id = TP1.id\r\n\tINNER JOIN positions AS TP2 ON T2.start_position_id = TP2.id\r\n\tINNER JOIN addresses AS start_address ON T1.end_address_id = start_address.id\r\n\tINNER JOIN addresses AS end_address ON T2.start_address_id = end_address.id\r\n\tLEFT JOIN geofences AS start_geofence ON T1.end_geofence_id = start_geofence.id\r\n\tLEFT JOIN geofences AS end_geofence ON T2.start_geofence_id = end_geofence.id\r\nWHERE\r\n\t$__timeFilter(T1.end_date)\r\n\tAND TP2.odometer - TP1.odometer > 0.5\r\n AND T1.end_address_id <> T2.start_address_id AND ((COALESCE(T1.end_geofence_id, 0) <> COALESCE(T2.start_geofence_id, 0)) OR (T1.end_geofence_id IS NULL AND T2.start_geofence_id IS NULL))\r\n AND '❓ Missing' in ($action_filter)\r\n\tAND (\r\n\t (COALESCE(start_geofence.name, CONCAT_WS(', ', COALESCE(start_address.name, nullif(CONCAT_WS(' ', start_address.road, start_address.house_number), '')), start_address.city))::TEXT ILIKE'%$text_filter%') or\r\n\t (COALESCE(end_geofence.name, CONCAT_WS(', ', COALESCE(end_address.name, nullif(CONCAT_WS(' ', end_address.road, end_address.house_number), '')), end_address.city)))::TEXT ILIKE'%$text_filter%')\r\nUNION\r\nSELECT\r\n start_date AS \"Start\",\r\n end_date AS \"End\",\r\n ROUND(EXTRACT(EPOCH FROM start_date))*1000 AS start_date_ts, \r\n ROUND(EXTRACT(EPOCH FROM end_date))*1000 AS end_date_ts, \r\n '💾 Updating' AS \"Action\",\r\n\tEXTRACT(EPOCH FROM end_date - start_date)/60 AS \"Duration\",\r\n NULL AS start_path,\r\n NULL AS end_path,\r\n version AS \"Start Address\",\r\n '' AS \"End Address\",\r\n NULL AS odometer_$length_unit,\r\n NULL AS distance_$length_unit,\r\n NULL AS end_range_$length_unit,\r\n NULL AS \"kWh\",\r\n NULL AS range_diff_$length_unit,\r\n NULL AS \"SoC\",\r\n NULL AS \"SoC Diff\",\r\n NULL AS outside_temp_avg_$temp_unit,\r\n CONCAT('https://www.notateslaapp.com/software-updates/version/', split_part(version, ' ', 1), '/release-notes') AS slotlink\r\nFROM updates\r\nWHERE \r\n $__timeFilter(start_date)\r\n AND car_id = $car_id \r\n AND '💾 Updating' in ($action_filter)\r\n AND version::TEXT ILIKE'%$text_filter%'\r\n\r\nORDER BY \"Start\" DESC;", "refId": "A", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - } - ] - ], "sql": { "columns": [ { @@ -607,17 +542,7 @@ } ], "limit": 50 - }, - "table": "candata", - "timeColumn": "datum", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + } } ], "title": "Timeline", @@ -675,8 +600,9 @@ "type": "table" } ], + "preload": false, "refresh": "", - "schemaVersion": 39, + "schemaVersion": 41, "tags": [ "tesla" ], @@ -688,22 +614,16 @@ "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, - "definition": "SELECT name AS __text, id AS __value FROM cars ORDER BY display_priority ASC, name ASC;", + "definition": "SELECT\n id as __value,\n CASE WHEN COUNT(id) OVER (PARTITION BY name) > 1 AND name IS NOT NULL THEN CONCAT(name, ' - ', RIGHT(vin, 6)) ELSE COALESCE(name, CONCAT('VIN ', vin)) end as __text \nFROM cars\nORDER BY display_priority ASC, name ASC, vin ASC;", "hide": 2, "includeAll": true, "label": "Car", - "multi": false, "name": "car_id", "options": [], - "query": "SELECT name AS __text, id AS __value FROM cars ORDER BY display_priority ASC, name ASC;", + "query": "SELECT\n id as __value,\n CASE WHEN COUNT(id) OVER (PARTITION BY name) > 1 AND name IS NOT NULL THEN CONCAT(name, ' - ', RIGHT(vin, 6)) ELSE COALESCE(name, CONCAT('VIN ', vin)) end as __text \nFROM cars\nORDER BY display_priority ASC, name ASC, vin ASC;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -714,40 +634,20 @@ "definition": "select base_url from settings limit 1;", "hide": 2, "includeAll": false, - "multi": false, "name": "base_url", "options": [], "query": "select base_url from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { - "current": { - "selected": true, - "text": [ - "All" - ], - "value": [ - "$__all" - ] - }, - "hide": 0, + "current": {}, "includeAll": true, "label": "Action", "multi": true, "name": "action_filter", "options": [ - { - "selected": true, - "text": "All", - "value": "$__all" - }, { "selected": false, "text": "🚗 Driving", @@ -775,17 +675,13 @@ } ], "query": "🚗 Driving,🔋 Charging,🅿️ Parking,❓ Missing,💾 Updating", - "queryValue": "", - "skipUrlSync": false, "type": "custom" }, { "current": { - "selected": false, "text": "", "value": "" }, - "hide": 0, "label": "Address Filter", "name": "text_filter", "options": [ @@ -796,7 +692,6 @@ } ], "query": "", - "skipUrlSync": false, "type": "textbox" }, { @@ -809,18 +704,12 @@ "hide": 2, "includeAll": false, "label": "length unit", - "multi": false, "name": "length_unit", "options": [], "query": "select unit_of_length from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -832,18 +721,12 @@ "hide": 2, "includeAll": false, "label": "temperature unit", - "multi": false, "name": "temp_unit", "options": [], "query": "select unit_of_temperature from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -854,18 +737,12 @@ "definition": "select preferred_range from settings limit 1;", "hide": 2, "includeAll": false, - "multi": false, "name": "preferred_range", "options": [], "query": "select preferred_range from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" } ] }, @@ -877,6 +754,5 @@ "timezone": "", "title": "Timeline", "uid": "SUBgwtigz", - "version": 6, - "weekStart": "" + "version": 1 } \ No newline at end of file diff --git a/grafana/dashboards/trip.json b/grafana/dashboards/trip.json index c0b020fbf0..73e3167a04 100644 --- a/grafana/dashboards/trip.json +++ b/grafana/dashboards/trip.json @@ -1,77 +1,16 @@ { - "__elements": {}, - "__requires": [ - { - "type": "panel", - "id": "bargauge", - "name": "Bar gauge", - "version": "" - }, - { - "type": "panel", - "id": "geomap", - "name": "Geomap", - "version": "" - }, - { - "type": "grafana", - "id": "grafana", - "name": "Grafana", - "version": "11.0.0" - }, - { - "type": "datasource", - "id": "grafana-postgresql-datasource", - "name": "PostgreSQL", - "version": "1.0.0" - }, - { - "type": "panel", - "id": "piechart", - "name": "Pie chart", - "version": "" - }, - { - "type": "panel", - "id": "stat", - "name": "Stat", - "version": "" - }, - { - "type": "panel", - "id": "state-timeline", - "name": "State timeline", - "version": "" - }, - { - "type": "panel", - "id": "table", - "name": "Table", - "version": "" - }, - { - "type": "panel", - "id": "timeseries", - "name": "Time series", - "version": "" - } - ], "annotations": { "list": [ { - "$$hashKey": "object:30", "builtIn": 1, - "datasource": "-- Grafana --", + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, "enable": true, "hide": true, "iconColor": "rgba(0, 211, 255, 1)", "name": "Annotations & Alerts", - "target": { - "limit": 100, - "matchAny": false, - "tags": [], - "type": "dashboard" - }, "type": "dashboard" } ] @@ -79,7 +18,6 @@ "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 1, - "id": null, "links": [ { "icon": "doc", @@ -87,7 +25,7 @@ "targetBlank": true, "title": "Select last three drives", "type": "link", - "url": "/d/FkUpJpQZk?from=$from" + "url": "/d/FkUpJpQZk/trip?from=$from" }, { "icon": "dashboard", @@ -95,7 +33,7 @@ "title": "TeslaMate", "tooltip": "", "type": "link", - "url": "[[base_url:raw]]" + "url": "${base_url:raw}" }, { "asDropdown": true, @@ -107,11 +45,9 @@ "type": "dashboards" } ], - "liveNow": false, "panels": [ { "collapsed": false, - "datasource": "TeslaMate", "gridPos": { "h": 1, "w": 24, @@ -146,8 +82,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" } ] } @@ -177,6 +112,49 @@ "showZoom": true }, "layers": [ + { + "config": { + "showLegend": false, + "style": { + "color": { + "fixed": "transparent" + }, + "opacity": 0, + "rotation": { + "fixed": 0, + "max": 360, + "min": -360, + "mode": "mod" + }, + "size": { + "fixed": 5, + "max": 15, + "min": 2 + }, + "symbol": { + "fixed": "img/icons/marker/circle.svg", + "mode": "fixed" + }, + "symbolAlign": { + "horizontal": "center", + "vertical": "center" + }, + "textConfig": { + "fontSize": 12, + "offsetX": 0, + "offsetY": 0, + "textAlign": "center", + "textBaseline": "middle" + } + } + }, + "location": { + "mode": "auto" + }, + "name": "Workaround for Grafana Issue #89777, fixed in 11.6.0 via #101391, extra layer to be removed on broad availability of 11.6.0", + "tooltip": false, + "type": "markers" + }, { "config": { "arrow": 0, @@ -230,7 +208,7 @@ "zoom": 15 } }, - "pluginVersion": "11.0.0", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { @@ -239,21 +217,9 @@ }, "editorMode": "code", "format": "time_series", - "group": [], - "metricColumn": "none", "rawQuery": true, "rawSql": "SELECT\n\t$__timeGroup(date, '5s') AS time,\n\tavg(latitude) AS latitude,\n\tavg(longitude) AS longitude\nFROM\n\tpositions\nWHERE\n car_id = $car_id AND\n\t$__timeFilter(date)\nGROUP BY\n\t1\nORDER BY\n\t1 ASC", "refId": "A", - "select": [ - [ - { - "params": [ - "latitude" - ], - "type": "column" - } - ] - ], "sql": { "columns": [ { @@ -270,19 +236,10 @@ } ], "limit": 50 - }, - "table": "addresses", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + } } ], + "title": "", "transparent": true, "type": "geomap" }, @@ -298,8 +255,7 @@ "mode": "absolute", "steps": [ { - "color": "#c7d0d9", - "value": null + "color": "#c7d0d9" } ] }, @@ -318,7 +274,7 @@ }, { "id": "displayName", - "value": "Distance" + "value": "Mileage" } ] }, @@ -334,7 +290,7 @@ }, { "id": "displayName", - "value": "Distance" + "value": "Mileage" } ] } @@ -358,6 +314,7 @@ "graphMode": "area", "justifyMode": "auto", "orientation": "auto", + "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "mean" @@ -369,41 +326,38 @@ "textMode": "auto", "wideLayout": true }, - "pluginVersion": "11.0.0", + "pluginVersion": "11.6.1", "targets": [ { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT convert_km((max(odometer) - min(odometer))::numeric, '$length_unit') as \"distance_$length_unit\"\nFROM positions\nWHERE car_id = $car_id AND $__timeFilter(date)\nORDER BY 1;", + "rawSql": "SELECT convert_km((max(odometer) - min(odometer))::numeric, '$length_unit') as \"distance_$length_unit\"\nFROM positions\nWHERE car_id = $car_id AND $__timeFilter(date) AND ideal_battery_range_km IS NOT NULL\nORDER BY 1;", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "latitude" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "addresses", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ], - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 } } ], + "title": "", "type": "stat" }, { @@ -506,77 +460,72 @@ "values": true }, "tooltip": { + "hideZeros": false, "maxHeight": 600, "mode": "single", "sort": "none" } }, + "pluginVersion": "11.6.1", "targets": [ { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", "format": "time_series", - "group": [], - "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT\n\tnow() AS time,\n\tsum(extract(epoch FROM end_position.date - start_position.date)) as duration_sec,\n\t'driving' as metric\nFROM\n\tdrives\n\tJOIN positions start_position ON start_position_id = start_position.id\n\tJOIN positions end_position ON end_position_id = end_position.id\nWHERE\n\tdrives.car_id = $car_id\n\tAND $__timeFilter(start_date);", + "rawSql": "SELECT\n\tnow() AS time,\n\tsum(extract(epoch FROM end_position.date - start_position.date)) as duration_sec,\n\t'driving' as metric\nFROM\n\tdrives\n\tJOIN positions start_position ON start_position_id = start_position.id\n\tJOIN positions end_position ON end_position_id = end_position.id\nWHERE\n\tdrives.car_id = $car_id\n\tAND $__timeFilter(start_date)\n\tAND end_date IS NOT NULL;", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ { - "params": [ - "latitude" - ], - "type": "column" + "property": { + "type": "string" + }, + "type": "groupBy" } - ] - ], - "table": "addresses", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ], - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" + ], + "limit": 50 } }, { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", "format": "time_series", - "group": [], - "metricColumn": "none", "rawQuery": true, "rawSql": "WITH charges_current AS (\n SELECT\n\t\tcp.id,\n \textract(epoch FROM LEAST(end_date, $__timeTo()) - GREATEST(start_date, $__timeFrom())) as duration_sec,\n\t\tCASE WHEN NULLIF(mode() within group (order by charger_phases),0) is null THEN 'charging (DC)'\n\t\t\t\t ELSE 'charging (AC)'\n\t\tEND AS current\n\tFROM charging_processes cp\n RIGHT JOIN charges ON cp.id = charges.charging_process_id\n WHERE\n\t cp.car_id = $car_id\n\t AND cp.charge_energy_added > 0\n \tAND ($__timeFilter(start_date) OR $__timeFilter(end_date))\n GROUP BY 1,2\n),\n\ncharges_total AS (\n SELECT\n \tsum(duration_sec) AS duration_sec,\n \tcurrent AS metric\n FROM charges_current\n GROUP BY 2\n ORDER BY metric\n)\n\nSELECT\n\tnow() AS time,\n\tcoalesce(duration_sec, 0) as duration_sec,\n metric\nFROM\n\tcharges_total;", "refId": "B", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "latitude" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "addresses", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ], - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 } } ], + "title": "Time spent", "type": "piechart" }, { @@ -602,8 +551,7 @@ "mode": "absolute", "steps": [ { - "color": "#c7d0d9", - "value": null + "color": "#c7d0d9" } ] }, @@ -622,7 +570,7 @@ }, { "id": "displayName", - "value": "excl. breaks" + "value": "Ø Speed excl. breaks" } ] }, @@ -638,7 +586,7 @@ }, { "id": "displayName", - "value": "excl. breaks" + "value": "Ø Speed excl. breaks" } ] } @@ -662,6 +610,7 @@ "graphMode": "none", "justifyMode": "auto", "orientation": "horizontal", + "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "mean" @@ -673,41 +622,38 @@ "textMode": "value_and_name", "wideLayout": true }, - "pluginVersion": "11.0.0", + "pluginVersion": "11.6.1", "targets": [ { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT\n convert_km(sum(end_position.odometer - start_position.odometer)::numeric, '$length_unit') / (sum(extract(epoch FROM end_position.date - start_position.date)) / 3600) as \"speed_$length_unit\"\nFROM\n\tdrives\n\tJOIN positions start_position ON start_position_id = start_position.id\n\tJOIN positions end_position ON end_position_id = end_position.id\nWHERE\n\tdrives.car_id = $car_id\n\tAND $__timeFilter(start_date)", + "rawSql": "SELECT\n convert_km(sum(end_position.odometer - start_position.odometer)::numeric, '$length_unit') / (sum(extract(epoch FROM end_position.date - start_position.date)) / 3600) as \"speed_$length_unit\"\nFROM\n\tdrives\n\tJOIN positions start_position ON start_position_id = start_position.id\n\tJOIN positions end_position ON end_position_id = end_position.id\nWHERE\n\tdrives.car_id = $car_id\n\tAND $__timeFilter(start_date)\n\tAND end_date IS NOT NULL;", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "latitude" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "addresses", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ], - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 } } ], + "title": "", "type": "stat" }, { @@ -722,8 +668,7 @@ "mode": "absolute", "steps": [ { - "color": "#c7d0d9", - "value": null + "color": "#c7d0d9" } ] }, @@ -742,7 +687,7 @@ }, { "id": "displayName", - "value": "incl. DC charging" + "value": "Ø Speed incl. DC charging" } ] }, @@ -758,7 +703,7 @@ }, { "id": "displayName", - "value": "incl. DC charging" + "value": "Ø Speed incl. DC charging" } ] } @@ -782,6 +727,7 @@ "graphMode": "none", "justifyMode": "auto", "orientation": "horizontal", + "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "mean" @@ -793,41 +739,38 @@ "textMode": "value_and_name", "wideLayout": true }, - "pluginVersion": "11.0.0", + "pluginVersion": "11.6.1", "targets": [ { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, "rawSql": "WITH dc_charges AS (\n SELECT\n\t\tcp.id,\n extract(epoch FROM cp.end_date - cp.start_date) as duration_sec,\n\t\tCASE WHEN NULLIF(mode() within group (order by charger_phases),0) is null THEN 'DC'\n\t\t\t\t ELSE 'AC'\n\t\tEND AS current\n\tFROM charging_processes cp\n RIGHT JOIN charges ON cp.id = charges.charging_process_id\n WHERE\n\t cp.car_id = $car_id\n\t AND cp.charge_energy_added > 0\n AND ($__timeFilter(start_date) OR $__timeFilter(end_date))\n GROUP BY 1,2\n),\n\ndata AS (\n (\n SELECT\n sum(end_position.odometer - start_position.odometer) as distance, \n sum(extract(epoch FROM end_position.date - start_position.date)) as duration_sec\n FROM\n drives\n JOIN positions start_position ON start_position_id = start_position.id\n JOIN positions end_position ON end_position_id = end_position.id\n WHERE\n drives.car_id = $car_id\n AND $__timeFilter(start_date)\n ) UNION ALL (\n SELECT\n NULL as distance,\n sum(duration_sec)\n FROM\n dc_charges\n WHERE\n current = 'DC'\n )\n)\n\nSELECT convert_km(sum(distance)::numeric, '$length_unit') / (sum(duration_sec) / 3600) as \"speed_$length_unit\"\nfrom data", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "latitude" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "addresses", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ], - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 } } ], + "title": "", "type": "stat" }, { @@ -844,8 +787,7 @@ "mode": "absolute", "steps": [ { - "color": "#c7d0d9", - "value": null + "color": "#c7d0d9" } ] }, @@ -864,7 +806,7 @@ }, { "id": "displayName", - "value": "net" + "value": "Ø Consumption (net)" } ] }, @@ -880,7 +822,7 @@ }, { "id": "displayName", - "value": "net" + "value": "Ø Consumption (net)" } ] } @@ -904,6 +846,7 @@ "graphMode": "none", "justifyMode": "auto", "orientation": "auto", + "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "mean" @@ -916,41 +859,38 @@ "textMode": "auto", "wideLayout": true }, - "pluginVersion": "11.0.0", + "pluginVersion": "11.6.1", "targets": [ { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT\n sum(GREATEST(start_[[preferred_range]]_range_km - end_[[preferred_range]]_range_km, 0) * car.efficiency * 1000) / \n convert_km(sum(distance)::numeric, '$length_unit') as \"consumption_$length_unit\"\nFROM drives\nJOIN cars car ON car.id = car_id\nWHERE $__timeFilter(start_date) AND car_id = $car_id", + "rawSql": "SELECT\n sum(GREATEST(start_${preferred_range}_range_km - end_${preferred_range}_range_km, 0) * car.efficiency * 1000) / \n convert_km(sum(distance)::numeric, '$length_unit') as \"consumption_$length_unit\"\nFROM drives\nJOIN cars car ON car.id = car_id\nWHERE $__timeFilter(start_date) AND car_id = $car_id", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "latitude" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "addresses", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ], - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 } } ], + "title": "", "type": "stat" }, { @@ -967,8 +907,7 @@ "mode": "absolute", "steps": [ { - "color": "#c7d0d9", - "value": null + "color": "#c7d0d9" } ] }, @@ -987,7 +926,7 @@ }, { "id": "displayName", - "value": "gross" + "value": "Ø Consumption (gross)" } ] }, @@ -1003,7 +942,7 @@ }, { "id": "displayName", - "value": "gross" + "value": "Ø Consumption (gross)" } ] } @@ -1027,6 +966,7 @@ "graphMode": "none", "justifyMode": "auto", "orientation": "auto", + "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "mean" @@ -1038,41 +978,38 @@ "textMode": "value_and_name", "wideLayout": true }, - "pluginVersion": "11.0.0", + "pluginVersion": "11.6.1", "targets": [ { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH d AS (\n\tSELECT\n\t\tc.car_id,\n\t\tlag(end_[[preferred_range]]_range_km) OVER (ORDER BY start_date) - start_[[preferred_range]]_range_km AS range_loss,\n\t\tp.odometer - lag(p.odometer) OVER (ORDER BY start_date) AS distance\n\tFROM charging_processes c\n\tLEFT JOIN positions p ON p.id = c.position_id \n\tWHERE\n\t end_date IS NOT NULL AND\n\t c.car_id = $car_id AND\n\t $__timeFilter(start_date)\n\tORDER BY start_date\n),\n\nrange_loss_between_charges AS (\n SELECT sum(range_loss) AS range_loss\n FROM d\n WHERE distance >= 0 AND range_loss >= 0\n GROUP BY car_id\n),\n\ncharge_dates AS (\n\tSELECT\n\t\tmin(start_date) as first_charge,\n\t\tmax(end_date) as last_charge\n\tFROM\n\t\tcharging_processes\n\tWHERE\n\t\tcar_id = $car_id\n\t\tAND $__timeFilter(start_date)\n),\n\nrange_loss_before_first_charge AS (\n\tSELECT\n\t\tmax([[preferred_range]]_battery_range_km) - min([[preferred_range]]_battery_range_km) AS range_loss\n\tFROM positions, charge_dates\n\tWHERE\n\t\tcar_id = $car_id\n\t\tAND $__timeFilter(date)\n\t\tAND ((select first_charge from charge_dates) is null OR date < (select first_charge from charge_dates))\n),\n\nrange_loss_after_last_charge AS (\n\tSELECT\n\t\tmax([[preferred_range]]_battery_range_km) - min([[preferred_range]]_battery_range_km) AS range_loss\n\tFROM positions, charge_dates\n\tWHERE\n\t\tcar_id = $car_id\n\t\tAND $__timeFilter(date)\n\t\tAND date > (select last_charge from charge_dates)\t\n),\n\ntotal_range_loss AS (\n SELECT sum(range_loss) as range_loss\n FROM (\n SELECT range_loss FROM range_loss_between_charges\n UNION ALL\n SELECT range_loss FROM range_loss_before_first_charge\n UNION ALL\n SELECT range_loss FROM range_loss_after_last_charge\n ) r\n),\n\ndistance AS (\n SELECT max(odometer) - min(odometer) as distance\n FROM positions\n WHERE car_id = $car_id AND $__timeFilter(date)\n)\n\nSELECT \n NULLIF(range_loss, 0) * (c.efficiency * 1000) / convert_km(NULLIF(distance::numeric, 0), '$length_unit') as \"consumption_$length_unit\"\nFROM total_range_loss, distance\nLEFT JOIN cars c ON c.id = $car_id", + "rawSql": "with drives_start_event as (\n\n select\n 'drive_start' as event, start_date as date, start_${preferred_range}_range_km as range, start_km as odometer, car_id\n from drives\n where car_id = $car_id and $__timeFilter(start_date) and 48 <= extract(hour FROM to_timestamp(${__to:date:seconds}) - to_timestamp(${__from:date:seconds}))\n\n),\n\ndrives_end_event as (\n\n select\n 'drive_end' as event, end_date as date, end_${preferred_range}_range_km as range, end_km as odometer, car_id\n from drives\n where car_id = $car_id and $__timeFilter(end_date) and 48 <= extract(hour FROM to_timestamp(${__to:date:seconds}) - to_timestamp(${__from:date:seconds}))\n\n),\n\ncharging_processes_start_event as (\n\n select\n 'charging_process_start' as event, start_date as date, start_${preferred_range}_range_km as range, p.odometer, cp.car_id\n from charging_processes cp\n inner join positions p on cp.position_id = p.id\n where cp.car_id = $car_id and $__timeFilter(start_date) and 48 <= extract(hour FROM to_timestamp(${__to:date:seconds}) - to_timestamp(${__from:date:seconds}))\n\n),\n\ncharging_processes_end_event as (\n\n select\n 'charging_process_end' as event, end_date as date, end_${preferred_range}_range_km as range, p.odometer, cp.car_id\n from charging_processes cp\n inner join positions p on cp.position_id = p.id\n where cp.car_id = $car_id and $__timeFilter(end_date) and 48 <= extract(hour FROM to_timestamp(${__to:date:seconds}) - to_timestamp(${__from:date:seconds}))\n\n),\n\npositions as (\n\n select\n case\n when drive_id is not null and lead(drive_id) over w is not null then 'drive_start'\n else 'something'\n end as event,\n date, ${preferred_range}_battery_range_km as range, p.odometer, p.car_id\n from positions p\n where ideal_battery_range_km is not null and car_id = $car_id and $__timeFilter(date) and 48 > extract(hour FROM to_timestamp(${__to:date:seconds}) - to_timestamp(${__from:date:seconds}))\n window w as (order by date)\n\n),\n\ncombined as (\n\n select * from drives_start_event\n union all\n select * from drives_end_event\n union all\n select * from charging_processes_start_event\n union all\n select * from charging_processes_end_event\n union all\n select * from positions\n\n),\n\nfinal as (\n\n select\n car_id,\n lead(odometer) over w - odometer as distance,\n case when event != 'drive_start' then greatest(range - lead(range) over w, 0) else range - lead(range) over w end as range_loss\n from combined\n window w as (order by date asc)\n\n)\n\nselect\n (sum(range_loss) * c.efficiency * 1000) / nullif(convert_km(sum(distance)::numeric, '$length_unit'), 0) as consumption_$length_unit\nfrom final\n inner join cars c on car_id = c.id\ngroup by c.efficiency", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ { - "params": [ - "latitude" - ], - "type": "column" + "property": { + "type": "string" + }, + "type": "groupBy" } - ] - ], - "table": "addresses", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ], - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" + ], + "limit": 50 } } ], + "title": "", "type": "stat" }, { @@ -1083,7 +1020,7 @@ "description": "", "fieldConfig": { "defaults": { - "displayName": "Cost", + "decimals": 2, "mappings": [ { "options": { @@ -1099,8 +1036,7 @@ "mode": "absolute", "steps": [ { - "color": "#c7d0d9", - "value": null + "color": "#c7d0d9" } ] }, @@ -1126,6 +1062,7 @@ "graphMode": "none", "justifyMode": "auto", "orientation": "auto", + "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "mean" @@ -1137,41 +1074,38 @@ "textMode": "value_and_name", "wideLayout": true }, - "pluginVersion": "11.0.0", + "pluginVersion": "11.6.1", "targets": [ { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, - "rawSql": "select sum(cost) as \"Cost\" from charging_processes where $__timeFilter(start_date) AND car_id = $car_id;", + "rawSql": "select sum(cost) as \"Total Charging Cost\" from charging_processes where $__timeFilter(end_date) AND car_id = $car_id;", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "latitude" - ], - "type": "column" + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" } - ] - ], - "table": "addresses", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ], - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" + ], + "limit": 50 } } ], + "title": "", "type": "stat" }, { @@ -1181,7 +1115,7 @@ }, "fieldConfig": { "defaults": { - "decimals": 1, + "decimals": 2, "displayName": "${__cell_0}", "links": [], "mappings": [], @@ -1190,8 +1124,7 @@ "mode": "absolute", "steps": [ { - "color": "light-yellow", - "value": null + "color": "light-yellow" }, { "color": "semi-dark-yellow", @@ -1216,6 +1149,12 @@ "id": 40, "options": { "displayMode": "gradient", + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, "maxVizHeight": 300, "minVizHeight": 10, "minVizWidth": 0, @@ -1232,74 +1171,66 @@ "sizing": "auto", "valueMode": "color" }, - "pluginVersion": "11.0.0", + "pluginVersion": "11.6.1", "targets": [ { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH d AS (\n\tSELECT\n\t\tc.car_id,\n\t\tlag(end_[[preferred_range]]_range_km) OVER (ORDER BY start_date) - start_[[preferred_range]]_range_km AS range_loss,\n\t\tp.odometer - lag(p.odometer) OVER (ORDER BY start_date) AS distance\n\tFROM charging_processes c\n\tLEFT JOIN positions p ON p.id = c.position_id \n\tWHERE\n\t end_date IS NOT NULL AND\n\t c.car_id = $car_id AND\n\t $__timeFilter(start_date)\n\tORDER BY start_date\n),\n\nrange_loss_between_charges AS (\n SELECT sum(range_loss) AS range_loss\n FROM d\n WHERE distance >= 0 AND range_loss >= 0\n GROUP BY car_id\n),\n\ncharge_dates AS (\n\tSELECT\n\t\tmin(start_date) as first_charge,\n\t\tmax(end_date) as last_charge\n\tFROM\n\t\tcharging_processes\n\tWHERE\n\t\tend_date IS NOT NULL\n\t\tAND car_id = $car_id\n\t\tAND $__timeFilter(start_date)\n),\n\nrange_loss_before_first_charge AS (\n\tSELECT\n\t\tmax([[preferred_range]]_battery_range_km) - min([[preferred_range]]_battery_range_km) AS range_loss\n\tFROM positions, charge_dates\n\tWHERE\n\t\tcar_id = $car_id\n\t\tAND $__timeFilter(date)\n\t\tAND ((select first_charge from charge_dates) is null OR date < (select first_charge from charge_dates))\n),\n\nrange_loss_after_last_charge AS (\n\tSELECT\n\t\tmax([[preferred_range]]_battery_range_km) - min([[preferred_range]]_battery_range_km) AS range_loss\n\tFROM positions, charge_dates\n\tWHERE\n\t\tcar_id = $car_id\n\t\tAND $__timeFilter(date)\n\t\tAND date > (select last_charge from charge_dates)\t\n),\n\ntotal_range_loss AS (\n\tSELECT range_loss FROM range_loss_between_charges\n\tUNION ALL\n\tSELECT range_loss FROM range_loss_before_first_charge\n\tUNION ALL\n\tSELECT range_loss FROM range_loss_after_last_charge\n)\n\nSELECT 'used' as metric, sum(range_loss * c.efficiency) AS value\nFROM total_range_loss\nLEFT JOIN cars c ON c.id = $car_id;", + "rawSql": "WITH d AS (\n\tSELECT\n\t\tc.car_id,\n\t\tlag(end_${preferred_range}_range_km) OVER (ORDER BY start_date) - start_${preferred_range}_range_km AS range_loss,\n\t\tp.odometer - lag(p.odometer) OVER (ORDER BY start_date) AS distance\n\tFROM charging_processes c\n\tLEFT JOIN positions p ON p.id = c.position_id \n\tWHERE\n\t end_date IS NOT NULL AND\n\t c.car_id = $car_id AND\n\t $__timeFilter(start_date)\n\tORDER BY start_date\n),\n\nrange_loss_between_charges AS (\n SELECT sum(range_loss) AS range_loss\n FROM d\n WHERE distance >= 0 AND range_loss >= 0\n GROUP BY car_id\n),\n\ncharge_dates AS (\n\tSELECT\n\t\tmin(start_date) as first_charge,\n\t\tmax(end_date) as last_charge\n\tFROM\n\t\tcharging_processes\n\tWHERE\n\t\tend_date IS NOT NULL\n\t\tAND car_id = $car_id\n\t\tAND $__timeFilter(start_date)\n),\n\nrange_loss_before_first_charge AS (\n\tSELECT\n\t\tmax(${preferred_range}_battery_range_km) - min(${preferred_range}_battery_range_km) AS range_loss\n\tFROM positions, charge_dates\n\tWHERE\n\t\tcar_id = $car_id\n\t\tAND $__timeFilter(date)\n\t\tAND ((select first_charge from charge_dates) is null OR date < (select first_charge from charge_dates))\n),\n\nrange_loss_after_last_charge AS (\n\tSELECT\n\t\tmax(${preferred_range}_battery_range_km) - min(${preferred_range}_battery_range_km) AS range_loss\n\tFROM positions, charge_dates\n\tWHERE\n\t\tcar_id = $car_id\n\t\tAND $__timeFilter(date)\n\t\tAND date > (select last_charge from charge_dates)\t\n),\n\ntotal_range_loss AS (\n\tSELECT range_loss FROM range_loss_between_charges\n\tUNION ALL\n\tSELECT range_loss FROM range_loss_before_first_charge\n\tUNION ALL\n\tSELECT range_loss FROM range_loss_after_last_charge\n)\n\nSELECT 'Total Energy consumed (gross)' as metric, sum(range_loss * c.efficiency) AS value\nFROM total_range_loss\nLEFT JOIN cars c ON c.id = $car_id;", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "latitude" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "addresses", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ], - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 } }, { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", "format": "table", - "group": [], - "hide": false, - "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH charges_current AS (\n SELECT\n\t\tcp.id,\n\t\tcp.charge_energy_added as energy_added,\n\t\tCASE WHEN NULLIF(mode() within group (order by charger_phases),0) is null THEN 'added (DC)'\n\t\t\t\t ELSE 'added (AC)'\n\t\tEND AS metric\n\tFROM charging_processes cp\n RIGHT JOIN charges ON cp.id = charges.charging_process_id\n WHERE\n\t cp.car_id = $car_id\n\t AND cp.charge_energy_added > 0\n \tAND ($__timeFilter(start_date) OR $__timeFilter(end_date))\n GROUP BY 1,2\n)\n\nSELECT metric, sum(energy_added) AS energy_added\nFROM charges_current\nGROUP BY 1\nORDER BY 1 DESC;", + "rawSql": "WITH charges_current AS (\n SELECT\n\t\tcp.id,\n\t\tcp.charge_energy_added as energy_added,\n\t\tCASE WHEN NULLIF(mode() within group (order by charger_phases),0) is null THEN 'Total Energy added (DC)'\n\t\t\t\t ELSE 'Total Energy added (AC)'\n\t\tEND AS metric\n\tFROM charging_processes cp\n RIGHT JOIN charges ON cp.id = charges.charging_process_id\n WHERE\n\t cp.car_id = $car_id\n\t AND cp.charge_energy_added > 0\n \tAND ($__timeFilter(start_date) OR $__timeFilter(end_date))\n GROUP BY 1,2\n)\n\nSELECT metric, sum(energy_added) AS energy_added\nFROM charges_current\nGROUP BY 1\nORDER BY 1 DESC;", "refId": "B", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "latitude" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "addresses", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ], - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 } } ], + "title": "", "type": "bargauge" }, { @@ -1314,6 +1245,7 @@ "mode": "continuous-GrYlRd" }, "custom": { + "axisPlacement": "auto", "fillOpacity": 100, "hideFrom": { "legend": false, @@ -1374,8 +1306,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" } ] } @@ -1400,45 +1331,44 @@ "rowHeight": 1, "showValue": "never", "tooltip": { + "hideZeros": false, "maxHeight": 600, "mode": "single", "sort": "none" } }, + "pluginVersion": "11.6.1", "targets": [ { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", "format": "time_series", - "group": [], - "metricColumn": "none", "rawQuery": true, "rawSql": "WITH states AS (\n SELECT\n unnest(ARRAY [start_date + interval '1 second', end_date]) AS date,\n unnest(ARRAY [2, 0]) AS state\n FROM charging_processes\n WHERE\n car_id = $car_id AND \n ($__timeFrom() :: timestamp - interval '30 day') < start_date AND \n (end_date < ($__timeTo() :: timestamp + interval '30 day') OR end_date IS NULL)\n UNION\n SELECT\n unnest(ARRAY [start_date + interval '1 second', end_date]) AS date,\n unnest(ARRAY [1, 0]) AS state\n FROM drives\n WHERE\n car_id = $car_id AND \n ($__timeFrom() :: timestamp - interval '30 day') < start_date AND \n (end_date < ($__timeTo() :: timestamp + interval '30 day') OR end_date IS NULL)\n UNION\n SELECT\n start_date AS date,\n CASE\n WHEN state = 'offline' THEN 3\n WHEN state = 'asleep' THEN 4\n WHEN state = 'online' THEN 5\n END AS state\n FROM states\n WHERE\n car_id = $car_id AND \n ($__timeFrom() :: timestamp - interval '30 day') < start_date AND \n (end_date < ($__timeTo() :: timestamp + interval '30 day') OR end_date IS NULL)\n UNION\n SELECT\n unnest(ARRAY [start_date + interval '1 second', end_date]) AS date,\n unnest(ARRAY [6, 0]) AS state\n FROM updates\n WHERE\n car_id = $car_id AND \n ($__timeFrom() :: timestamp - interval '30 day') < start_date AND \n (end_date < ($__timeTo() :: timestamp + interval '30 day') OR end_date IS NULL)\n)\nSELECT date AS \"time\", state\nFROM states\nWHERE \n date IS NOT NULL AND\n ($__timeFrom() :: timestamp - interval '30 day') < date AND \n date < ($__timeTo() :: timestamp + interval '30 day') \nORDER BY date ASC, state ASC;", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "latitude" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "addresses", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ], - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 } } ], + "title": "", "transparent": true, "type": "state-timeline" }, @@ -1466,8 +1396,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -1498,13 +1427,13 @@ { "targetBlank": false, "title": "View drive details", - "url": "d/zm7wN6Zgz?from=${__data.fields.start_date_ts.numeric}&to=${__data.fields.end_date_ts.numeric}&var-car_id=${__data.fields.car_id.numeric}&var-drive_id=${__data.fields.drive_id.numeric}" + "url": "/d/zm7wN6Zgz/drive-details?from=${__data.fields.start_date_ts.numeric}&to=${__data.fields.end_date_ts.numeric}&var-car_id=${__data.fields.car_id.numeric}&var-drive_id=${__data.fields.drive_id.numeric}" } ] }, { "id": "custom.width", - "value": 180 + "value": 210 } ] }, @@ -1516,7 +1445,7 @@ "properties": [ { "id": "displayName", - "value": "Consumption" + "value": "Ø Consumption (net)" }, { "id": "unit", @@ -1528,7 +1457,7 @@ }, { "id": "custom.width", - "value": 100 + "value": 165 } ] }, @@ -1540,7 +1469,7 @@ "properties": [ { "id": "displayName", - "value": "Consumption" + "value": "Ø Consumption (net)" }, { "id": "unit", @@ -1552,7 +1481,7 @@ }, { "id": "custom.width", - "value": 100 + "value": 165 } ] }, @@ -1564,7 +1493,7 @@ "properties": [ { "id": "displayName", - "value": "km" + "value": "Distance" }, { "id": "unit", @@ -1596,7 +1525,7 @@ { "targetBlank": true, "title": "Create or edit geo-fence", - "url": "[[base_url:raw]]/geo-fences/${__data.fields.start_path}" + "url": "${base_url:raw}/geo-fences/${__data.fields.start_path}" } ] }, @@ -1622,7 +1551,7 @@ { "targetBlank": true, "title": "Create or edit geo-fence", - "url": "[[base_url:raw]]/geo-fences/${__data.fields.end_path}" + "url": "${base_url:raw}/geo-fences/${__data.fields.end_path}" } ] }, @@ -1685,7 +1614,7 @@ "properties": [ { "id": "displayName", - "value": "mi" + "value": "Distance" }, { "id": "unit", @@ -1712,7 +1641,8 @@ "value": "percent" }, { - "id": "custom.align" + "id": "custom.align", + "value": "auto" }, { "id": "decimals", @@ -1720,7 +1650,7 @@ }, { "id": "custom.width", - "value": 65 + "value": 75 } ] }, @@ -1735,7 +1665,8 @@ "value": "percent" }, { - "id": "custom.align" + "id": "custom.align", + "value": "auto" }, { "id": "decimals", @@ -1786,38 +1717,35 @@ } ] }, - "pluginVersion": "11.0.0", + "pluginVersion": "11.6.1", "targets": [ { - "alias": "", "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH data AS (\n SELECT\n round(extract(epoch FROM start_date)) * 1000 AS start_date_ts,\n round(extract(epoch FROM end_date)) * 1000 AS end_date_ts,\n car.id as car_id,\n CASE WHEN start_geofence.id IS NULL THEN CONCAT('new?lat=', start_position.latitude, '&lng=', start_position.longitude)\n WHEN start_geofence.id IS NOT NULL THEN CONCAT(start_geofence.id, '/edit')\n END as start_path,\n CASE WHEN end_geofence.id IS NULL THEN CONCAT('new?lat=', end_position.latitude, '&lng=', end_position.longitude)\n WHEN end_geofence.id IS NOT NULL THEN CONCAT(end_geofence.id, '/edit')\n END as end_path,\n TO_CHAR((duration_min * INTERVAL '1 minute'), 'HH24:MI') as duration_str,\n drives.id as drive_id,\n -- Columns\n start_date,\n COALESCE(start_geofence.name, CONCAT_WS(', ', COALESCE(start_address.name, nullif(CONCAT_WS(' ', start_address.road, start_address.house_number), '')), start_address.city)) AS start_address,\n COALESCE(end_geofence.name, CONCAT_WS(', ', COALESCE(end_address.name, nullif(CONCAT_WS(' ', end_address.road, end_address.house_number), '')), end_address.city)) AS end_address,\n duration_min,\n distance,\n start_position.usable_battery_level as start_usable_battery_level,\n start_position.battery_level as start_battery_level,\n end_position.usable_battery_level as end_usable_battery_level,\n end_position.battery_level as end_battery_level,\n start_position.battery_level != start_position.usable_battery_level OR end_position.battery_level != end_position.usable_battery_level as reduced_range,\n duration_min > 1 AND distance > 1 AND ( \n start_position.usable_battery_level IS NULL OR end_position.usable_battery_level IS NULL\tOR\n (end_position.battery_level - end_position.usable_battery_level) = 0 \n ) as is_sufficiently_precise,\n NULLIF(GREATEST(start_[[preferred_range]]_range_km - end_[[preferred_range]]_range_km, 0), 0) as range_diff,\n car.efficiency as car_efficiency,\n outside_temp_avg,\n distance / NULLIF(duration_min, 0) * 60 AS avg_speed\n FROM drives\n LEFT JOIN addresses start_address ON start_address_id = start_address.id\n LEFT JOIN addresses end_address ON end_address_id = end_address.id\n LEFT JOIN positions start_position ON start_position_id = start_position.id\n LEFT JOIN positions end_position ON end_position_id = end_position.id\n LEFT JOIN geofences start_geofence ON start_geofence_id = start_geofence.id\n LEFT JOIN geofences end_geofence ON end_geofence_id = end_geofence.id\n LEFT JOIN cars car ON car.id = drives.car_id\n WHERE $__timeFilter(start_date) AND drives.car_id = $car_id\n ORDER BY start_date DESC\n)\nSELECT\n start_date_ts,\n end_date_ts,\n car_id,\n start_path,\n end_path,\n duration_str,\n drive_id,\n -- Columns\n start_date,\n start_address,\n end_address,\n duration_min,\n convert_km(distance::numeric, '$length_unit') AS distance_$length_unit,\n start_battery_level as \"% Start\",\n end_battery_level as \"% End\",\n CASE WHEN is_sufficiently_precise THEN range_diff * car_efficiency / distance * 1000 * CASE WHEN '$length_unit' = 'km' THEN 1\n WHEN '$length_unit' = 'mi' THEN 1.60934\n END\n END AS consumption_kWh_$length_unit\nFROM data;", + "rawSql": "WITH data AS (\n SELECT\n round(extract(epoch FROM start_date)) * 1000 AS start_date_ts,\n round(extract(epoch FROM end_date)) * 1000 AS end_date_ts,\n car.id as car_id,\n CASE WHEN start_geofence.id IS NULL THEN CONCAT('new?lat=', start_position.latitude, '&lng=', start_position.longitude)\n WHEN start_geofence.id IS NOT NULL THEN CONCAT(start_geofence.id, '/edit')\n END as start_path,\n CASE WHEN end_geofence.id IS NULL THEN CONCAT('new?lat=', end_position.latitude, '&lng=', end_position.longitude)\n WHEN end_geofence.id IS NOT NULL THEN CONCAT(end_geofence.id, '/edit')\n END as end_path,\n TO_CHAR((duration_min * INTERVAL '1 minute'), 'HH24:MI') as duration_str,\n drives.id as drive_id,\n -- Columns\n start_date,\n COALESCE(start_geofence.name, CONCAT_WS(', ', COALESCE(start_address.name, nullif(CONCAT_WS(' ', start_address.road, start_address.house_number), '')), start_address.city)) AS start_address,\n COALESCE(end_geofence.name, CONCAT_WS(', ', COALESCE(end_address.name, nullif(CONCAT_WS(' ', end_address.road, end_address.house_number), '')), end_address.city)) AS end_address,\n duration_min,\n distance,\n start_position.usable_battery_level as start_usable_battery_level,\n start_position.battery_level as start_battery_level,\n end_position.usable_battery_level as end_usable_battery_level,\n end_position.battery_level as end_battery_level,\n start_position.battery_level != start_position.usable_battery_level OR end_position.battery_level != end_position.usable_battery_level as reduced_range,\n duration_min > 1 AND distance > 1 AND ( \n start_position.usable_battery_level IS NULL OR end_position.usable_battery_level IS NULL\tOR\n (end_position.battery_level - end_position.usable_battery_level) = 0 \n ) as is_sufficiently_precise,\n NULLIF(GREATEST(start_${preferred_range}_range_km - end_${preferred_range}_range_km, 0), 0) as range_diff,\n car.efficiency as car_efficiency,\n outside_temp_avg,\n distance / NULLIF(duration_min, 0) * 60 AS avg_speed\n FROM drives\n LEFT JOIN addresses start_address ON start_address_id = start_address.id\n LEFT JOIN addresses end_address ON end_address_id = end_address.id\n LEFT JOIN positions start_position ON start_position_id = start_position.id\n LEFT JOIN positions end_position ON end_position_id = end_position.id\n LEFT JOIN geofences start_geofence ON start_geofence_id = start_geofence.id\n LEFT JOIN geofences end_geofence ON end_geofence_id = end_geofence.id\n LEFT JOIN cars car ON car.id = drives.car_id\n WHERE $__timeFilter(start_date) AND drives.car_id = $car_id\n ORDER BY start_date DESC\n)\nSELECT\n start_date_ts,\n end_date_ts,\n car_id,\n start_path,\n end_path,\n duration_str,\n drive_id,\n -- Columns\n start_date,\n start_address,\n end_address,\n duration_min,\n convert_km(distance::numeric, '$length_unit') AS distance_$length_unit,\n start_battery_level as \"% Start\",\n end_battery_level as \"% End\",\n CASE WHEN is_sufficiently_precise THEN range_diff * car_efficiency / convert_km(distance::numeric, '$length_unit') * 1000\n END AS consumption_kWh_$length_unit\nFROM data;", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "value" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } } ], "title": "Drives", @@ -1878,16 +1806,13 @@ { "targetBlank": false, "title": "View charge details", - "url": "d/BHhxFeZRz?from=${__data.fields.start_date_ts.numeric}&to=${__data.fields.end_date_ts.numeric}&var-car_id=${__data.fields.car_id.numeric}&var-charging_process_id=${__data.fields.id.numeric:raw}" + "url": "/d/BHhxFeZRz/charge-details?from=${__data.fields.start_date_ts.numeric}&to=${__data.fields.end_date_ts.numeric}&var-car_id=${__data.fields.car_id.numeric}&var-charging_process_id=${__data.fields.id.numeric:raw}" } ] }, - { - "id": "custom.align" - }, { "id": "custom.width", - "value": 180 + "value": 210 } ] }, @@ -1899,22 +1824,15 @@ "properties": [ { "id": "displayName", - "value": "Added" - }, - { - "id": "unit", - "value": "kwatth" + "value": "Energy added" }, { "id": "decimals", "value": 2 }, - { - "id": "custom.align" - }, { "id": "custom.width", - "value": 100 + "value": 115 } ] }, @@ -1932,16 +1850,13 @@ "id": "unit", "value": "percent" }, - { - "id": "custom.align" - }, { "id": "decimals", "value": 0 }, { "id": "custom.width", - "value": 65 + "value": 72 } ] }, @@ -1959,16 +1874,13 @@ "id": "unit", "value": "percent" }, - { - "id": "custom.align" - }, { "id": "decimals", "value": 0 }, { "id": "custom.width", - "value": 65 + "value": 62 } ] }, @@ -1990,12 +1902,9 @@ "id": "decimals", "value": 1 }, - { - "id": "custom.align" - }, { "id": "custom.width", - "value": 100 + "value": 80 } ] }, @@ -2023,16 +1932,13 @@ { "targetBlank": false, "title": "Set Cost", - "url": "[[base_url:raw]]/charge-cost/${__data.fields.id.numeric:raw}" + "url": "${base_url:raw}/charge-cost/${__data.fields.id.numeric:raw}" } ] }, - { - "id": "custom.align" - }, { "id": "custom.width", - "value": 80 + "value": 70 } ] }, @@ -2042,17 +1948,6 @@ "options": "/.*_ts/" }, "properties": [ - { - "id": "unit", - "value": "short" - }, - { - "id": "decimals", - "value": 2 - }, - { - "id": "custom.align" - }, { "id": "custom.hidden", "value": true @@ -2065,9 +1960,6 @@ "options": "id" }, "properties": [ - { - "id": "custom.align" - }, { "id": "custom.hidden", "value": true @@ -2090,13 +1982,10 @@ { "targetBlank": true, "title": "Create or edit geo-fence", - "url": "[[base_url:raw]]/geo-fences/${__data.fields.path}" + "url": "${base_url:raw}/geo-fences/${__data.fields.path}" } ] }, - { - "id": "custom.align" - }, { "id": "custom.minWidth", "value": 200 @@ -2106,27 +1995,24 @@ { "matcher": { "id": "byName", - "options": "distance_km" + "options": "range_added_km" }, "properties": [ { "id": "displayName", - "value": "Driven" + "value": "Range gained" }, { "id": "unit", "value": "lengthkm" }, - { - "id": "custom.align" - }, { "id": "decimals", "value": 0 }, { "id": "custom.width", - "value": 80 + "value": 120 } ] }, @@ -2138,7 +2024,7 @@ "properties": [ { "id": "displayName", - "value": "kW" + "value": "Ø Power" }, { "id": "unit", @@ -2154,9 +2040,6 @@ "type": "color-text" } }, - { - "id": "custom.align" - }, { "id": "thresholds", "value": { @@ -2185,23 +2068,24 @@ { "matcher": { "id": "byName", - "options": "distance_mi" + "options": "range_added_mi" }, "properties": [ { "id": "displayName", - "value": "Driven" + "value": "Range gained" }, { "id": "unit", "value": "lengthmi" }, - { - "id": "custom.align" - }, { "id": "decimals", "value": 0 + }, + { + "id": "custom.minWidth", + "value": 120 } ] }, @@ -2211,9 +2095,6 @@ "options": "path" }, "properties": [ - { - "id": "custom.align" - }, { "id": "custom.hidden", "value": true @@ -2228,22 +2109,15 @@ "properties": [ { "id": "displayName", - "value": "Used" - }, - { - "id": "unit", - "value": "kwatth" + "value": "Energy used" }, { "id": "decimals", "value": 2 }, - { - "id": "custom.align" - }, { "id": "custom.width", - "value": 100 + "value": 105 } ] }, @@ -2253,9 +2127,6 @@ "options": "car_id" }, "properties": [ - { - "id": "custom.align" - }, { "id": "custom.hidden", "value": true @@ -2272,7 +2143,9 @@ }, "id": 36, "options": { + "cellHeight": "sm", "footer": { + "countRows": false, "fields": "", "reducer": [ "sum" @@ -2287,37 +2160,35 @@ } ] }, - "pluginVersion": "8.5.4", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH data AS (\n SELECT\n (round(extract(epoch FROM start_date) - 10) * 1000) AS start_date_ts,\n (round(extract(epoch FROM end_date) + 10) * 1000) AS end_date_ts,\n start_date,\n end_date,\n CONCAT_WS(', ', COALESCE(addresses.name, CONCAT_WS(' ', addresses.road, addresses.house_number)), addresses.city) AS address,\n g.name as geofence_name,\n g.id as geofence_id,\n p.latitude,\n p.longitude,\n charge_energy_added,\n charge_energy_used,\n duration_min,\n start_battery_level,\n end_battery_level,\n start_[[preferred_range]]_range_km,\n end_[[preferred_range]]_range_km,\n outside_temp_avg,\n c.id,\n lag(end_[[preferred_range]]_range_km) OVER (ORDER BY start_date) - start_[[preferred_range]]_range_km AS range_loss,\n p.odometer - lag(p.odometer) OVER (ORDER BY start_date) AS distance,\n cars.efficiency,\n c.car_id,\n cost\n FROM\n charging_processes c\n LEFT JOIN positions p ON p.id = c.position_id\n LEFT JOIN cars ON cars.id = c.car_id\n LEFT JOIN addresses ON addresses.id = c.address_id\n LEFT JOIN geofences g ON g.id = geofence_id\nWHERE \n (charge_energy_added IS NULL OR charge_energy_added > 0) AND\n c.car_id = $car_id AND\n $__timeFilter(start_date)\nORDER BY\n start_date\n)\nSELECT\n start_date_ts,\n end_date_ts,\n CASE WHEN geofence_id IS NULL THEN CONCAT('new?lat=', latitude, '&lng=', longitude)\n WHEN geofence_id IS NOT NULL THEN CONCAT(geofence_id, '/edit')\n END as path,\n car_id,\n id,\n -- Columns\n start_date,\n COALESCE(geofence_name, address) as address, \n duration_min,\n cost,\n charge_energy_added,\n charge_energy_used,\n charge_energy_added * 60 / NULLIF (duration_min, 0) AS charge_energy_added_per_hour,\n start_battery_level,\n end_battery_level,\n convert_km(distance::numeric, '$length_unit') AS distance_$length_unit\n FROM\n data\nWHERE\n (distance >= 0 OR distance IS NULL)\nORDER BY\n start_date DESC;\n ", + "rawSql": "WITH data AS (\n SELECT\n (round(extract(epoch FROM start_date) - 10) * 1000) AS start_date_ts,\n (round(extract(epoch FROM end_date) + 10) * 1000) AS end_date_ts,\n start_date,\n end_date,\n CONCAT_WS(', ', COALESCE(addresses.name, CONCAT_WS(' ', addresses.road, addresses.house_number)), addresses.city) AS address,\n g.name as geofence_name,\n g.id as geofence_id,\n p.latitude,\n p.longitude,\n charge_energy_added,\n charge_energy_used,\n duration_min,\n start_battery_level,\n end_battery_level,\n end_${preferred_range}_range_km - start_${preferred_range}_range_km as range_added,\n outside_temp_avg,\n c.id,\n p.odometer - lag(p.odometer) OVER (ORDER BY start_date) AS distance,\n cars.efficiency,\n c.car_id,\n cost\n FROM\n charging_processes c\n LEFT JOIN positions p ON p.id = c.position_id\n LEFT JOIN cars ON cars.id = c.car_id\n LEFT JOIN addresses ON addresses.id = c.address_id\n LEFT JOIN geofences g ON g.id = geofence_id\nWHERE \n (charge_energy_added IS NULL OR charge_energy_added > 0) AND\n c.car_id = $car_id AND\n $__timeFilter(start_date)\nORDER BY\n start_date\n)\nSELECT\n start_date_ts,\n end_date_ts,\n CASE WHEN geofence_id IS NULL THEN CONCAT('new?lat=', latitude, '&lng=', longitude)\n WHEN geofence_id IS NOT NULL THEN CONCAT(geofence_id, '/edit')\n END as path,\n car_id,\n id,\n -- Columns\n start_date,\n COALESCE(geofence_name, address) as address, \n duration_min,\n cost,\n charge_energy_added,\n charge_energy_used,\n charge_energy_added * 60 / NULLIF (duration_min, 0) AS charge_energy_added_per_hour,\n convert_km(range_added, '$length_unit') AS range_added_$length_unit,\n start_battery_level,\n end_battery_level\nFROM\n data\nWHERE\n (distance >= 0 OR distance IS NULL)\nORDER BY\n start_date DESC;\n ", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "value" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } } ], "title": "Charges", @@ -2342,9 +2213,13 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, + "barWidthFactor": 0.6, "drawStyle": "line", "fillOpacity": 10, "gradientMode": "opacity", @@ -2353,6 +2228,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, @@ -2471,43 +2347,40 @@ "showLegend": true }, "tooltip": { + "hideZeros": false, "mode": "multi", "sort": "none" } }, - "pluginVersion": "8.5.4", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "time_series", - "group": [], - "metricColumn": "none", "rawQuery": true, - "rawSql": "(\n SELECT $__timeGroup(date, '5s'), avg(battery_level) as battery_level, convert_km(avg([[preferred_range]]_battery_range_km), '$length_unit') as range_[[preferred_range]]_[[length_unit]]\n FROM positions\n WHERE date BETWEEN ($__timeFrom()::timestamp - interval '1 day') AND ($__timeTo()::timestamp + interval '1 day') AND car_id = $car_id\n GROUP BY 1\n) UNION ALL (\n SELECT $__timeGroup(date, '5s'), avg(battery_level) as battery_level, convert_km(avg([[preferred_range]]_battery_range_km), '$length_unit') as range_[[preferred_range]]_[[length_unit]]\n FROM charges c\n LEFT JOIN charging_processes p ON c.charging_process_id = p.id\n WHERE date BETWEEN ($__timeFrom()::timestamp - interval '1 day') AND ($__timeTo()::timestamp + interval '1 day') AND p.car_id = $car_id\n GROUP BY 1\n)\nORDER BY 1", + "rawSql": "(\n SELECT $__timeGroup(date, '5s'), avg(battery_level) as battery_level, convert_km(avg(${preferred_range}_battery_range_km), '$length_unit') as range_${preferred_range}_${length_unit}\n FROM positions\n WHERE date BETWEEN ($__timeFrom()::timestamp - interval '1 day') AND ($__timeTo()::timestamp + interval '1 day') AND car_id = $car_id\n GROUP BY 1\n) UNION ALL (\n SELECT $__timeGroup(date, '5s'), avg(battery_level) as battery_level, convert_km(avg(${preferred_range}_battery_range_km), '$length_unit') as range_${preferred_range}_${length_unit}\n FROM charges c\n LEFT JOIN charging_processes p ON c.charging_process_id = p.id\n WHERE date BETWEEN ($__timeFrom()::timestamp - interval '1 day') AND ($__timeTo()::timestamp + interval '1 day') AND p.car_id = $car_id\n GROUP BY 1\n)\nORDER BY 1", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "latitude" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "addresses", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } } ], "title": "Battery Level & Range", @@ -2524,9 +2397,13 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, + "barWidthFactor": 0.6, "drawStyle": "line", "fillOpacity": 10, "gradientMode": "opacity", @@ -2535,6 +2412,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, @@ -2631,51 +2509,49 @@ "showLegend": true }, "tooltip": { + "hideZeros": false, "mode": "multi", "sort": "none" } }, - "pluginVersion": "8.5.4", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "time_series", - "group": [], - "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT\n\t$__timeGroup(date, '5s'),\n\tROUND(convert_m(avg(elevation), '$alternative_length_unit')) AS elevation_[[alternative_length_unit]]\nFROM\n\tpositions\nWHERE\n car_id = $car_id AND\n date BETWEEN ($__timeFrom()::timestamp - interval '1 day') AND ($__timeTo()::timestamp + interval '1 day')\nGROUP BY\n 1\nORDER BY\n 1 ASC", + "rawSql": "SELECT\n\t$__timeGroup(date, '5s'),\n\tROUND(convert_m(avg(elevation), '$alternative_length_unit')) AS elevation_${alternative_length_unit}\nFROM\n\tpositions\nWHERE\n car_id = $car_id AND\n date BETWEEN ($__timeFrom()::timestamp - interval '1 day') AND ($__timeTo()::timestamp + interval '1 day')\nGROUP BY\n 1\nORDER BY\n 1 ASC", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "latitude" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "addresses", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } } ], "title": "Elevation", "type": "timeseries" } ], + "preload": false, "refresh": "", - "schemaVersion": 39, + "schemaVersion": 41, "tags": [ "tesla" ], @@ -2687,22 +2563,16 @@ "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, - "definition": "SELECT name AS __text, id AS __value FROM cars ORDER BY display_priority ASC, name ASC;", + "definition": "SELECT\n id as __value,\n CASE WHEN COUNT(id) OVER (PARTITION BY name) > 1 AND name IS NOT NULL THEN CONCAT(name, ' - ', RIGHT(vin, 6)) ELSE COALESCE(name, CONCAT('VIN ', vin)) end as __text \nFROM cars\nORDER BY display_priority ASC, name ASC, vin ASC;", "hide": 2, "includeAll": true, "label": "Car", - "multi": false, "name": "car_id", "options": [], - "query": "SELECT name AS __text, id AS __value FROM cars ORDER BY display_priority ASC, name ASC;", + "query": "SELECT\n id as __value,\n CASE WHEN COUNT(id) OVER (PARTITION BY name) > 1 AND name IS NOT NULL THEN CONCAT(name, ' - ', RIGHT(vin, 6)) ELSE COALESCE(name, CONCAT('VIN ', vin)) end as __text \nFROM cars\nORDER BY display_priority ASC, name ASC, vin ASC;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -2714,18 +2584,12 @@ "hide": 2, "includeAll": false, "label": "temperature unit", - "multi": false, "name": "temp_unit", "options": [], "query": "select unit_of_temperature from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -2737,18 +2601,12 @@ "hide": 2, "includeAll": false, "label": "length unit", - "multi": false, "name": "length_unit", "options": [], "query": "select unit_of_length from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -2759,19 +2617,12 @@ "definition": "select case when unit_of_length = 'km' then 'm' when unit_of_length = 'mi' then 'ft' end from settings limit 1;", "hide": 2, "includeAll": false, - "label": "", - "multi": false, "name": "alternative_length_unit", "options": [], "query": "select case when unit_of_length = 'km' then 'm' when unit_of_length = 'mi' then 'ft' end from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -2782,19 +2633,12 @@ "definition": "select preferred_range from settings limit 1;", "hide": 2, "includeAll": false, - "label": "", - "multi": false, "name": "preferred_range", "options": [], "query": "select preferred_range from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -2805,19 +2649,12 @@ "definition": "select base_url from settings limit 1;", "hide": 2, "includeAll": false, - "label": "", - "multi": false, "name": "base_url", "options": [], "query": "select base_url from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -2828,54 +2665,22 @@ "definition": "with last_drives as (select start_date from drives order by start_date desc limit 3)\nselect extract(epoch from min(start_date)) * 1000 from last_drives;", "hide": 2, "includeAll": false, - "multi": false, "name": "from", "options": [], "query": "with last_drives as (select start_date from drives order by start_date desc limit 3)\nselect extract(epoch from min(start_date)) * 1000 from last_drives;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" } ] }, "time": { - "from": "now/d", + "from": "now-24h", "to": "now" }, - "timeRangeUpdatedDuringEditOrView": false, - "timepicker": { - "refresh_intervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "time_options": [ - "5m", - "15m", - "1h", - "6h", - "12h", - "24h", - "2d", - "7d", - "30d" - ] - }, + "timepicker": {}, "timezone": "", "title": "Trip", "uid": "FkUpJpQZk", - "version": 2, - "weekStart": "" + "version": 1 } \ No newline at end of file diff --git a/grafana/dashboards/updates.json b/grafana/dashboards/updates.json index b33016b250..257e2bc0ef 100644 --- a/grafana/dashboards/updates.json +++ b/grafana/dashboards/updates.json @@ -1,47 +1,16 @@ { - "__elements": {}, - "__requires": [ - { - "type": "grafana", - "id": "grafana", - "name": "Grafana", - "version": "11.1.0" - }, - { - "type": "datasource", - "id": "grafana-postgresql-datasource", - "name": "PostgreSQL", - "version": "1.0.0" - }, - { - "type": "panel", - "id": "stat", - "name": "Stat", - "version": "" - }, - { - "type": "panel", - "id": "table", - "name": "Table", - "version": "" - } - ], "annotations": { "list": [ { - "$$hashKey": "object:15", "builtIn": 1, - "datasource": "-- Grafana --", + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, "enable": true, "hide": true, "iconColor": "rgba(0, 211, 255, 1)", "name": "Annotations & Alerts", - "target": { - "limit": 100, - "matchAny": false, - "tags": [], - "type": "dashboard" - }, "type": "dashboard" } ] @@ -49,7 +18,6 @@ "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 0, - "id": null, "links": [ { "icon": "dashboard", @@ -57,7 +25,7 @@ "title": "TeslaMate", "tooltip": "", "type": "link", - "url": "[[base_url:raw]]" + "url": "${base_url:raw}" }, { "asDropdown": true, @@ -69,10 +37,9 @@ "type": "dashboards" } ], - "liveNow": false, "panels": [ { - "datasource": "TeslaMate", + "collapsed": false, "gridPos": { "h": 1, "w": 24, @@ -97,8 +64,7 @@ "mode": "absolute", "steps": [ { - "color": "#c7d0d9", - "value": null + "color": "#c7d0d9" } ] } @@ -129,38 +95,34 @@ "textMode": "value", "wideLayout": true }, - "pluginVersion": "11.1.0", + "pluginVersion": "11.6.1", "targets": [ { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, "rawSql": "SELECT count(*)\nFROM updates\nWHERE $__timeFilter(start_date) AND car_id = $car_id", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "start_km" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "drives", - "timeColumn": "start_date", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ], - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 } } ], @@ -180,8 +142,7 @@ "mode": "absolute", "steps": [ { - "color": "#c7d0d9", - "value": null + "color": "#c7d0d9" } ] }, @@ -213,38 +174,34 @@ "textMode": "value", "wideLayout": true }, - "pluginVersion": "11.1.0", + "pluginVersion": "11.6.1", "targets": [ { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "TeslaMate" + }, + "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, "rawSql": "SELECT percentile_disc(0.5) WITHIN GROUP (ORDER BY since_last_update) FROM (\n\tSELECT extract(EPOCH FROM start_date - lag(start_date) OVER (ORDER BY start_date)) AS since_last_update\n\tFROM updates\n\tWHERE $__timeFilter(start_date) AND car_id = $car_id\n) d;", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "start_km" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "table": "drives", - "timeColumn": "start_date", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ], - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 } } ], @@ -271,8 +228,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -289,8 +245,8 @@ }, "properties": [ { - "id": "custom.width", - "value": 200 + "id": "custom.minWidth", + "value": 210 }, { "id": "displayName", @@ -309,8 +265,8 @@ }, "properties": [ { - "id": "custom.width", - "value": 100 + "id": "custom.minWidth", + "value": 120 }, { "id": "displayName", @@ -329,16 +285,12 @@ }, "properties": [ { - "id": "custom.width", - "value": 160 + "id": "custom.minWidth", + "value": 180 }, { "id": "displayName", "value": "Since Previous Update" - }, - { - "id": "unit", - "value": "dtdurations" } ] }, @@ -373,6 +325,10 @@ { "id": "unit", "value": "string" + }, + { + "id": "custom.minWidth", + "value": 150 } ] }, @@ -383,8 +339,8 @@ }, "properties": [ { - "id": "custom.width", - "value": 100 + "id": "custom.minWidth", + "value": 120 }, { "id": "displayName", @@ -399,8 +355,8 @@ }, "properties": [ { - "id": "custom.width", - "value": 150 + "id": "custom.minWidth", + "value": 130 }, { "id": "decimals", @@ -412,7 +368,7 @@ }, { "id": "displayName", - "value": "Avg ideal range" + "value": "Ø Ideal range" } ] }, @@ -423,8 +379,8 @@ }, "properties": [ { - "id": "custom.width", - "value": 150 + "id": "custom.minWidth", + "value": 130 }, { "id": "decimals", @@ -436,7 +392,7 @@ }, { "id": "displayName", - "value": "Avg rated range" + "value": "Ø Rated range" } ] }, @@ -447,8 +403,8 @@ }, "properties": [ { - "id": "custom.width", - "value": 150 + "id": "custom.minWidth", + "value": 130 }, { "id": "decimals", @@ -460,7 +416,7 @@ }, { "id": "displayName", - "value": "Avg ideal range" + "value": "Ø Ideal range" } ] }, @@ -471,8 +427,8 @@ }, "properties": [ { - "id": "custom.width", - "value": 150 + "id": "custom.minWidth", + "value": 130 }, { "id": "decimals", @@ -484,7 +440,7 @@ }, { "id": "displayName", - "value": "Avg rated range" + "value": "Ø Rated range" } ] } @@ -515,110 +471,7 @@ } ] }, - "pluginVersion": "11.1.0", - "scroll": true, - "showHeader": true, - "sort": { - "col": 0, - "desc": true - }, - "styles": [ - { - "$$hashKey": "object:68", - "alias": "Date", - "align": "auto", - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "pattern": "start_date", - "type": "date" - }, - { - "$$hashKey": "object:69", - "alias": "End Date", - "align": "auto", - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 2, - "mappingType": 1, - "pattern": "end_date", - "thresholds": [], - "type": "hidden", - "unit": "short" - }, - { - "$$hashKey": "object:70", - "alias": "Installed Version", - "align": "auto", - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 2, - "link": true, - "linkTargetBlank": true, - "linkTooltip": "${__cell} release info", - "linkUrl": "https://www.notateslaapp.com/software-updates/version/${__cell}/release-notes", - "mappingType": 1, - "pattern": "version", - "thresholds": [], - "type": "string", - "unit": "short" - }, - { - "$$hashKey": "object:202", - "alias": "Duration", - "align": "auto", - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 0, - "mappingType": 1, - "pattern": "update_duration", - "thresholds": [], - "type": "number", - "unit": "dtdurations" - }, - { - "$$hashKey": "object:392", - "alias": "Since Previous Update", - "align": "auto", - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 0, - "mappingType": 1, - "pattern": "since_last_update", - "thresholds": [], - "type": "number", - "unit": "dtdurations" - }, - { - "$$hashKey": "object:71", - "alias": "# of Charges", - "align": "auto", - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "decimals": 0, - "pattern": "chg_ct", - "thresholds": [], - "type": "number", - "unit": "short" - } - ], + "pluginVersion": "11.6.1", "targets": [ { "datasource": { @@ -627,21 +480,9 @@ }, "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, - "rawSql": "with u as (\r\n select *, coalesce(lag(start_date) over(order by start_date desc), now()) as next_start_date \r\n from updates\r\n where car_id = $car_id and $__timeFilter(start_date)\r\n),\r\nrng as (\r\n SELECT\r\n\t date_trunc('hour', date) AS date,\r\n\t (sum([[preferred_range]]_battery_range_km)/ nullif(sum(usable_battery_level),0) * 100 ) AS \"battery_rng\",\r\n\t sum(case when action = 'Charge' then 1 else 0 end) as chg_ct\r\n FROM (\r\n select coalesce(usable_battery_level, battery_level) as usable_battery_level, start_date as date, start_rated_range_km as rated_battery_range_km, start_ideal_range_km as ideal_battery_range_km, 'Drive' as action\r\n from drives d\r\n inner join positions p on d.start_position_id = p.id \r\n where d.car_id = $car_id and $__timeFilter(start_date)\r\n union all\r\n select end_battery_level as usable_battery_level, end_date, end_rated_range_km as rated_battery_range_km, end_ideal_range_km as ideal_battery_range_km, 'Charge' as action\r\n from charging_processes p\r\n where $__timeFilter(end_date) and p.car_id = $car_id\r\n ) as data\r\n GROUP BY 1\r\n)\r\n\r\nselect\t\r\n u.start_date as time,\r\n\textract(EPOCH FROM u.end_date - u.start_date) AS update_duration,\r\n\textract(EPOCH FROM u.start_date - lag(u.start_date) OVER (ORDER BY u.start_date)) AS since_last_update,\r\n\tsplit_part(u.version, ' ', 1) as version,\r\n\tsum(r.chg_ct) as chg_ct,\r\n\tconvert_km(avg(r.battery_rng), '$length_unit')::numeric(6,2) AS avg_[[preferred_range]]_range_[[length_unit]]\r\nfrom u u\r\nleft join rng r\r\n\tON r.date between u.start_date and u.next_start_date\r\ngroup by u.car_id,\r\n\tu.start_date,\r\n\tu.end_date,\r\n\tnext_start_date,\r\n\tsplit_part(u.version, ' ', 1)", + "rawSql": "with u as (\r\n select *, coalesce(lag(start_date) over(order by start_date desc), now()) as next_start_date \r\n from updates\r\n where car_id = $car_id and $__timeFilter(start_date)\r\n),\r\nrng as (\r\n SELECT\r\n\t date_trunc('hour', timezone('UTC', date), '$__timezone') AS date,\r\n\t (sum(${preferred_range}_battery_range_km)/ nullif(sum(usable_battery_level),0) * 100 ) AS \"battery_rng\",\r\n\t sum(case when action = 'Charge' then 1 else 0 end) as chg_ct\r\n FROM (\r\n select usable_battery_level, start_date as date, start_rated_range_km as rated_battery_range_km, start_ideal_range_km as ideal_battery_range_km, 'Drive' as action\r\n from drives d\r\n inner join positions p on d.start_position_id = p.id \r\n where d.car_id = $car_id and $__timeFilter(start_date) and usable_battery_level > 0\r\n union all\r\n select end_battery_level as usable_battery_level, end_date, end_rated_range_km as rated_battery_range_km, end_ideal_range_km as ideal_battery_range_km, 'Charge' as action\r\n from charging_processes p\r\n where $__timeFilter(end_date) and p.car_id = $car_id\r\n ) as data\r\n GROUP BY 1\r\n)\r\n\r\nselect\t\r\n u.start_date as time,\r\n\textract(EPOCH FROM u.end_date - u.start_date) AS update_duration,\r\n\tage(date(u.start_date), date(lag(u.start_date) OVER (ORDER BY u.start_date))) AS since_last_update,\r\n\tsplit_part(u.version, ' ', 1) as version,\r\n\tsum(r.chg_ct) as chg_ct,\r\n\tconvert_km(avg(r.battery_rng), '$length_unit')::numeric(6,2) AS avg_${preferred_range}_range_${length_unit}\r\nfrom u u\r\nleft join rng r\r\n\tON r.date between u.start_date and u.next_start_date\r\ngroup by u.car_id,\r\n\tu.start_date,\r\n\tu.end_date,\r\n\tnext_start_date,\r\n\tsplit_part(u.version, ' ', 1)", "refId": "A", - "select": [ - [ - { - "params": [ - "efficiency" - ], - "type": "column" - } - ] - ], "sql": { "columns": [ { @@ -658,24 +499,16 @@ } ], "limit": 50 - }, - "table": "cars", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + } } ], "title": "Updates", "type": "table" } ], - "schemaVersion": 39, + "preload": false, + "refresh": "", + "schemaVersion": 41, "tags": [ "tesla" ], @@ -687,22 +520,16 @@ "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, - "definition": "SELECT name AS __text, id AS __value FROM cars ORDER BY display_priority ASC, name ASC;", + "definition": "SELECT\n id as __value,\n CASE WHEN COUNT(id) OVER (PARTITION BY name) > 1 AND name IS NOT NULL THEN CONCAT(name, ' - ', RIGHT(vin, 6)) ELSE COALESCE(name, CONCAT('VIN ', vin)) end as __text \nFROM cars\nORDER BY display_priority ASC, name ASC, vin ASC;", "hide": 2, "includeAll": true, "label": "Car", - "multi": false, "name": "car_id", "options": [], - "query": "SELECT name AS __text, id AS __value FROM cars ORDER BY display_priority ASC, name ASC;", + "query": "SELECT\n id as __value,\n CASE WHEN COUNT(id) OVER (PARTITION BY name) > 1 AND name IS NOT NULL THEN CONCAT(name, ' - ', RIGHT(vin, 6)) ELSE COALESCE(name, CONCAT('VIN ', vin)) end as __text \nFROM cars\nORDER BY display_priority ASC, name ASC, vin ASC;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -713,18 +540,12 @@ "definition": "select preferred_range from settings limit 1;", "hide": 2, "includeAll": false, - "multi": false, "name": "preferred_range", "options": [], "query": "select preferred_range from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -735,18 +556,12 @@ "definition": "select unit_of_length from settings limit 1;", "hide": 2, "includeAll": false, - "multi": false, "name": "length_unit", "options": [], "query": "select unit_of_length from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -757,19 +572,12 @@ "definition": "select base_url from settings limit 1;", "hide": 2, "includeAll": false, - "label": "", - "multi": false, "name": "base_url", "options": [], "query": "select base_url from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" } ] }, @@ -788,22 +596,10 @@ "1h", "2h", "1d" - ], - "time_options": [ - "5m", - "15m", - "1h", - "6h", - "12h", - "24h", - "2d", - "7d", - "30d" ] }, "timezone": "", "title": "Updates", "uid": "IiC07mgWz", - "version": 2, - "weekStart": "" + "version": 1 } \ No newline at end of file diff --git a/grafana/dashboards/vampire-drain.json b/grafana/dashboards/vampire-drain.json index 7d16a3d38f..eb33ed700d 100644 --- a/grafana/dashboards/vampire-drain.json +++ b/grafana/dashboards/vampire-drain.json @@ -1,40 +1,16 @@ { - "__elements": {}, - "__requires": [ - { - "type": "grafana", - "id": "grafana", - "name": "Grafana", - "version": "11.0.0" - }, - { - "type": "datasource", - "id": "grafana-postgresql-datasource", - "name": "PostgreSQL", - "version": "1.0.0" - }, - { - "type": "panel", - "id": "table", - "name": "Table", - "version": "" - } - ], "annotations": { "list": [ { "builtIn": 1, - "datasource": "-- Grafana --", + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, "enable": true, "hide": true, "iconColor": "rgba(0, 211, 255, 1)", "name": "Annotations & Alerts", - "target": { - "limit": 100, - "matchAny": false, - "tags": [], - "type": "dashboard" - }, "type": "dashboard" } ] @@ -42,7 +18,6 @@ "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 0, - "id": null, "links": [ { "icon": "dashboard", @@ -50,7 +25,7 @@ "title": "TeslaMate", "tooltip": "", "type": "link", - "url": "[[base_url:raw]]" + "url": "${base_url:raw}" }, { "asDropdown": true, @@ -62,11 +37,9 @@ "type": "dashboards" } ], - "liveNow": false, "panels": [ { "collapsed": false, - "datasource": "TeslaMate", "gridPos": { "h": 1, "w": 24, @@ -101,8 +74,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -132,16 +104,13 @@ { "targetBlank": false, "title": "", - "url": "d/zm7wN6Zgz?from=${__data.fields.start_date_ts.numeric}&to=${__data.fields.end_date_ts.numeric}" + "url": "/d/zm7wN6Zgz/drive-details?from=${__data.fields.start_date_ts.numeric}&to=${__data.fields.end_date_ts.numeric}" } ] }, { - "id": "custom.align" - }, - { - "id": "custom.width", - "value": 180 + "id": "custom.minWidth", + "value": 210 } ] }, @@ -160,11 +129,8 @@ "value": "dateTimeAsLocal" }, { - "id": "custom.align" - }, - { - "id": "custom.width", - "value": 180 + "id": "custom.minWidth", + "value": 210 } ] }, @@ -176,7 +142,7 @@ "properties": [ { "id": "displayName", - "value": "TR Loss" + "value": "Range loss" }, { "id": "unit", @@ -187,11 +153,8 @@ "value": 2 }, { - "id": "custom.align" - }, - { - "id": "custom.width", - "value": 90 + "id": "custom.minWidth", + "value": 95 } ] }, @@ -219,17 +182,13 @@ "type": "color-text" } }, - { - "id": "custom.align" - }, { "id": "thresholds", "value": { "mode": "absolute", "steps": [ { - "color": "rgb(133, 142, 133)", - "value": null + "color": "rgb(133, 142, 133)" }, { "color": "#56A64B", @@ -252,7 +211,7 @@ "properties": [ { "id": "displayName", - "value": "TR Loss / h" + "value": "Ø Range loss / h" }, { "id": "unit", @@ -263,11 +222,8 @@ "value": 2 }, { - "id": "custom.align" - }, - { - "id": "custom.width", - "value": 90 + "id": "custom.minWidth", + "value": 135 } ] }, @@ -277,17 +233,6 @@ "options": "/.*_ts/" }, "properties": [ - { - "id": "unit", - "value": "short" - }, - { - "id": "decimals", - "value": 2 - }, - { - "id": "custom.align" - }, { "id": "custom.hidden", "value": true @@ -314,17 +259,13 @@ "type": "color-text" } }, - { - "id": "custom.align" - }, { "id": "thresholds", "value": { "mode": "absolute", "steps": [ { - "color": "#FF7383", - "value": null + "color": "#FF7383" }, { "color": "#FFB357", @@ -342,7 +283,7 @@ "value": 0 }, { - "id": "custom.width", + "id": "custom.minWidth", "value": 75 } ] @@ -355,7 +296,7 @@ "properties": [ { "id": "displayName", - "value": "kWh" + "value": "Energy drained" }, { "id": "unit", @@ -366,11 +307,8 @@ "value": 2 }, { - "id": "custom.align" - }, - { - "id": "custom.width", - "value": 100 + "id": "custom.minWidth", + "value": 125 } ] }, @@ -382,7 +320,7 @@ "properties": [ { "id": "displayName", - "value": "Ø-Power" + "value": "Ø Power" }, { "id": "unit", @@ -393,10 +331,7 @@ "value": 1 }, { - "id": "custom.align" - }, - { - "id": "custom.width", + "id": "custom.minWidth", "value": 80 } ] @@ -409,7 +344,7 @@ "properties": [ { "id": "displayName", - "value": "TR Loss / h" + "value": "Ø Range loss / h" }, { "id": "unit", @@ -420,11 +355,8 @@ "value": 2 }, { - "id": "custom.align" - }, - { - "id": "custom.width", - "value": 90 + "id": "custom.minWidth", + "value": 135 } ] }, @@ -436,7 +368,7 @@ "properties": [ { "id": "displayName", - "value": "TR Loss" + "value": "Range loss" }, { "id": "unit", @@ -447,11 +379,8 @@ "value": 2 }, { - "id": "custom.align" - }, - { - "id": "custom.width", - "value": 90 + "id": "custom.minWidth", + "value": 95 } ] }, @@ -463,18 +392,15 @@ "properties": [ { "id": "displayName", - "value": "SOC" + "value": "SoC Diff" }, { "id": "unit", "value": "percent" }, { - "id": "custom.align" - }, - { - "id": "custom.width", - "value": 65 + "id": "custom.minWidth", + "value": 80 } ] }, @@ -523,7 +449,7 @@ }, { "id": "custom.width", - "value": 5 + "value": 50 } ] } @@ -548,38 +474,35 @@ }, "showHeader": true }, - "pluginVersion": "11.0.0", + "pluginVersion": "11.6.1", "targets": [ { - "alias": "", "datasource": { "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, + "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, - "rawSql": "with merge as (\n SELECT \n c.start_date AS start_date,\n c.end_date AS end_date,\n c.start_ideal_range_km AS start_ideal_range_km,\n c.end_ideal_range_km AS end_ideal_range_km,\n c.start_rated_range_km AS start_rated_range_km,\n c.end_rated_range_km AS end_rated_range_km,\n start_battery_level,\n end_battery_level,\n p.usable_battery_level AS start_usable_battery_level,\n NULL AS end_usable_battery_level,\n p.odometer AS start_km,\n p.odometer AS end_km\n FROM charging_processes c\n JOIN positions p ON c.position_id = p.id\n WHERE c.car_id = $car_id AND $__timeFilter(start_date)\n UNION\n SELECT \n d.start_date AS start_date,\n d.end_date AS end_date,\n d.start_ideal_range_km AS start_ideal_range_km,\n d.end_ideal_range_km AS end_ideal_range_km,\n d.start_rated_range_km AS start_rated_range_km,\n d.end_rated_range_km AS end_rated_range_km,\n start_position.battery_level AS start_battery_level,\n end_position.battery_level AS end_battery_level,\n start_position.usable_battery_level AS start_usable_battery_level,\n end_position.usable_battery_level AS end_usable_battery_level,\n d.start_km AS start_km,\n d.end_km AS end_km\n FROM drives d\n JOIN positions start_position ON d.start_position_id = start_position.id\n JOIN positions end_position ON d.end_position_id = end_position.id\n WHERE d.car_id = $car_id AND $__timeFilter(start_date)\n), \nv as (\n SELECT\n lag(t.end_date) OVER w AS start_date,\n t.start_date AS end_date,\n lag(t.end_[[preferred_range]]_range_km) OVER w AS start_range,\n t.start_[[preferred_range]]_range_km AS end_range,\n lag(t.end_km) OVER w AS start_km,\n t.start_km AS end_km,\n EXTRACT(EPOCH FROM age(t.start_date, lag(t.end_date) OVER w)) AS duration,\n lag(t.end_battery_level) OVER w AS start_battery_level,\n lag(t.end_usable_battery_level) OVER w AS start_usable_battery_level,\n\t\tstart_battery_level AS end_battery_level,\n\t\tstart_usable_battery_level AS end_usable_battery_level,\n\t\tstart_battery_level > COALESCE(start_usable_battery_level, start_battery_level) AS has_reduced_range\n FROM merge t\n WINDOW w AS (ORDER BY t.start_date ASC)\n ORDER BY start_date DESC\n)\n\nSELECT\n round(extract(epoch FROM v.start_date)) * 1000 AS start_date_ts,\n round(extract(epoch FROM v.end_date)) * 1000 AS end_date_ts,\n -- Columns\n v.start_date,\n v.end_date,\n v.duration,\n (coalesce(s_asleep.sleep, 0) + coalesce(s_offline.sleep, 0)) / v.duration as standby,\n\t-greatest(v.start_battery_level - v.end_battery_level, 0) as soc_diff,\n\tCASE WHEN has_reduced_range THEN 1 ELSE 0 END as has_reduced_range,\n\tconvert_km(CASE WHEN has_reduced_range THEN NULL ELSE (v.start_range - v.end_range)::numeric END, '$length_unit') AS range_diff_$length_unit,\n CASE WHEN has_reduced_range THEN NULL ELSE (v.start_range - v.end_range) * c.efficiency END AS consumption,\n CASE WHEN has_reduced_range THEN NULL ELSE ((v.start_range - v.end_range) * c.efficiency) / (v.duration / 3600) * 1000 END as avg_power,\n convert_km(CASE WHEN has_reduced_range THEN NULL ELSE ((v.start_range - v.end_range) / (v.duration / 3600))::numeric END, '$length_unit') AS range_lost_per_hour_[[length_unit]]\nFROM v,\n LATERAL (\n SELECT EXTRACT(EPOCH FROM sum(age(s.end_date, s.start_date))) as sleep\n FROM states s\n WHERE\n state = 'asleep' AND\n v.start_date <= s.start_date AND s.end_date <= v.end_date AND\n s.car_id = $car_id\n ) s_asleep,\n LATERAL (\n SELECT EXTRACT(EPOCH FROM sum(age(s.end_date, s.start_date))) as sleep\n FROM states s\n WHERE\n state = 'offline' AND\n v.start_date <= s.start_date AND s.end_date <= v.end_date AND\n s.car_id = $car_id\n ) s_offline\nJOIN cars c ON c.id = $car_id\nWHERE\n v.duration > ($duration * 60 * 60)\n AND v.start_range - v.end_range >= 0\n AND v.end_km - v.start_km < 1;", + "rawSql": "with merge as (\n SELECT \n c.start_date AS start_date,\n c.end_date AS end_date,\n c.start_ideal_range_km AS start_ideal_range_km,\n c.end_ideal_range_km AS end_ideal_range_km,\n c.start_rated_range_km AS start_rated_range_km,\n c.end_rated_range_km AS end_rated_range_km,\n start_battery_level,\n end_battery_level,\n p.usable_battery_level AS start_usable_battery_level,\n NULL AS end_usable_battery_level,\n p.odometer AS start_km,\n p.odometer AS end_km\n FROM charging_processes c\n JOIN positions p ON c.position_id = p.id\n WHERE c.car_id = $car_id AND $__timeFilter(start_date)\n UNION\n SELECT \n d.start_date AS start_date,\n d.end_date AS end_date,\n d.start_ideal_range_km AS start_ideal_range_km,\n d.end_ideal_range_km AS end_ideal_range_km,\n d.start_rated_range_km AS start_rated_range_km,\n d.end_rated_range_km AS end_rated_range_km,\n start_position.battery_level AS start_battery_level,\n end_position.battery_level AS end_battery_level,\n start_position.usable_battery_level AS start_usable_battery_level,\n end_position.usable_battery_level AS end_usable_battery_level,\n d.start_km AS start_km,\n d.end_km AS end_km\n FROM drives d\n JOIN positions start_position ON d.start_position_id = start_position.id\n JOIN positions end_position ON d.end_position_id = end_position.id\n WHERE d.car_id = $car_id AND $__timeFilter(start_date)\n), \nv as (\n SELECT\n lag(t.end_date) OVER w AS start_date,\n t.start_date AS end_date,\n lag(t.end_${preferred_range}_range_km) OVER w AS start_range,\n t.start_${preferred_range}_range_km AS end_range,\n lag(t.end_km) OVER w AS start_km,\n t.start_km AS end_km,\n EXTRACT(EPOCH FROM age(t.start_date, lag(t.end_date) OVER w)) AS duration,\n lag(t.end_battery_level) OVER w AS start_battery_level,\n lag(t.end_usable_battery_level) OVER w AS start_usable_battery_level,\n\t\tstart_battery_level AS end_battery_level,\n\t\tstart_usable_battery_level AS end_usable_battery_level,\n\t\tstart_battery_level > COALESCE(start_usable_battery_level, start_battery_level) AS has_reduced_range\n FROM merge t\n WINDOW w AS (ORDER BY t.start_date ASC)\n ORDER BY start_date DESC\n)\n\nSELECT\n round(extract(epoch FROM v.start_date)) * 1000 AS start_date_ts,\n round(extract(epoch FROM v.end_date)) * 1000 AS end_date_ts,\n -- Columns\n v.start_date,\n v.end_date,\n v.duration,\n (coalesce(s_asleep.sleep, 0) + coalesce(s_offline.sleep, 0)) / v.duration as standby,\n\t-greatest(v.start_battery_level - v.end_battery_level, 0) as soc_diff,\n\tCASE WHEN has_reduced_range THEN 1 ELSE 0 END as has_reduced_range,\n\tconvert_km(CASE WHEN has_reduced_range THEN NULL ELSE (v.start_range - v.end_range)::numeric END, '$length_unit') AS range_diff_$length_unit,\n CASE WHEN has_reduced_range THEN NULL ELSE (v.start_range - v.end_range) * c.efficiency END AS consumption,\n CASE WHEN has_reduced_range THEN NULL ELSE ((v.start_range - v.end_range) * c.efficiency) / (v.duration / 3600) * 1000 END as avg_power,\n convert_km(CASE WHEN has_reduced_range THEN NULL ELSE ((v.start_range - v.end_range) / (v.duration / 3600))::numeric END, '$length_unit') AS range_lost_per_hour_${length_unit}\nFROM v,\n LATERAL (\n SELECT EXTRACT(EPOCH FROM sum(age(s.end_date, s.start_date))) as sleep\n FROM states s\n WHERE\n state = 'asleep' AND\n v.start_date <= s.start_date AND s.end_date <= v.end_date AND\n s.car_id = $car_id\n ) s_asleep,\n LATERAL (\n SELECT EXTRACT(EPOCH FROM sum(age(s.end_date, s.start_date))) as sleep\n FROM states s\n WHERE\n state = 'offline' AND\n v.start_date <= s.start_date AND s.end_date <= v.end_date AND\n s.car_id = $car_id\n ) s_offline\nJOIN cars c ON c.id = $car_id\nWHERE\n v.duration > ($duration * 60 * 60)\n AND v.start_range - v.end_range >= 0\n AND v.end_km - v.start_km < 1;", "refId": "A", - "select": [ - [ + "sql": { + "columns": [ { - "params": [ - "value" - ], - "type": "column" + "parameters": [], + "type": "function" } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } } ], "title": "Vampire Drain", @@ -594,7 +517,9 @@ "type": "table" } ], - "schemaVersion": 39, + "preload": false, + "refresh": "", + "schemaVersion": 41, "tags": [ "tesla" ], @@ -606,33 +531,24 @@ "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, - "definition": "SELECT name AS __text, id AS __value FROM cars ORDER BY display_priority ASC, name ASC;", + "definition": "SELECT\n id as __value,\n CASE WHEN COUNT(id) OVER (PARTITION BY name) > 1 AND name IS NOT NULL THEN CONCAT(name, ' - ', RIGHT(vin, 6)) ELSE COALESCE(name, CONCAT('VIN ', vin)) end as __text \nFROM cars\nORDER BY display_priority ASC, name ASC, vin ASC;", "hide": 2, "includeAll": true, "label": "Car", - "multi": false, "name": "car_id", "options": [], - "query": "SELECT name AS __text, id AS __value FROM cars ORDER BY display_priority ASC, name ASC;", + "query": "SELECT\n id as __value,\n CASE WHEN COUNT(id) OVER (PARTITION BY name) > 1 AND name IS NOT NULL THEN CONCAT(name, ' - ', RIGHT(vin, 6)) ELSE COALESCE(name, CONCAT('VIN ', vin)) end as __text \nFROM cars\nORDER BY display_priority ASC, name ASC, vin ASC;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": { - "selected": false, "text": "6", "value": "6" }, - "hide": 0, "includeAll": false, "label": "min. Idle Time (h)", - "multi": false, "name": "duration", "options": [ { @@ -672,7 +588,6 @@ } ], "query": "0,1,3,6,12,18,24", - "skipUrlSync": false, "type": "custom" }, { @@ -684,19 +599,12 @@ "definition": "select unit_of_length from settings limit 1;", "hide": 2, "includeAll": false, - "label": "", - "multi": false, "name": "length_unit", "options": [], "query": "select unit_of_length from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -707,18 +615,12 @@ "definition": "select preferred_range from settings limit 1;", "hide": 2, "includeAll": false, - "multi": false, "name": "preferred_range", "options": [], "query": "select preferred_range from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -729,19 +631,12 @@ "definition": "select base_url from settings limit 1;", "hide": 2, "includeAll": false, - "label": "", - "multi": false, "name": "base_url", "options": [], "query": "select base_url from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" } ] }, @@ -749,35 +644,9 @@ "from": "now-90d", "to": "now" }, - "timeRangeUpdatedDuringEditOrView": false, - "timepicker": { - "refresh_intervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "time_options": [ - "5m", - "15m", - "1h", - "6h", - "12h", - "24h", - "2d", - "7d", - "30d" - ] - }, + "timepicker": {}, "timezone": "", "title": "Vampire Drain", "uid": "zhHx2Fggk", - "version": 1, - "weekStart": "" + "version": 1 } \ No newline at end of file diff --git a/grafana/dashboards/visited.json b/grafana/dashboards/visited.json index ed7cea3c5b..dafd67577a 100644 --- a/grafana/dashboards/visited.json +++ b/grafana/dashboards/visited.json @@ -1,31 +1,4 @@ { - "__elements": {}, - "__requires": [ - { - "type": "panel", - "id": "geomap", - "name": "Geomap", - "version": "" - }, - { - "type": "grafana", - "id": "grafana", - "name": "Grafana", - "version": "11.0.0" - }, - { - "type": "datasource", - "id": "grafana-postgresql-datasource", - "name": "PostgreSQL", - "version": "1.0.0" - }, - { - "type": "panel", - "id": "stat", - "name": "Stat", - "version": "" - } - ], "annotations": { "list": [ { @@ -34,7 +7,6 @@ "type": "grafana", "uid": "-- Grafana --" }, - "definition": "TeslaMate|U2FsdGVkX1/cEWK+8cz7pjEKXtzJnDN7b21ZDXt1MGneFGPWTLqOPtxKmu02mJPLzi/f29I+NBHd3vi0FB8R4Xn0+GtobWDgk6VAVSBTdSNniOKO8i2WPlhRaOsl2+hG7gnZ7wrf1Th2nxR7f1uYCrbwOek0IzkfLzrkjh7gkr6inT6bbDuJqrmogZajLxmAMrQ6V+/vHxBRGiwjJhgiEeq3hM1q2h04JKkNiZ8RHbsF5Cd/xd8Q9u0JVrZzIrtnhM/SFlaApU7RtRMu8CSj1llTX7WEOj6VDZAMSf+XUAanWdk725kEPN9MNu89o2zEq5P3E3cju8IbbBdPzXLV3oVuzD6/tMnxFToIIV1E/BrpF7s2RtNa8+KJJ1PF8xgs6m+/KTD2hy+WsP0636AgObRAmYg7+qotGrgNvpNPdE0EgrB7WHYlV7R/1q66bcq6tCe51X1Un70k+zo+K6AK0o4B1H6IyMlEVuRH/Fz8QVl9aYu2ztd08RbuKJlYVKpkH+pxVETAO9MclYQ90tzE6TfwDZrQZzsAlMenr4s1ZB1OlFXjLjVjnddnUilzO76cqv4yI2THQEuyQ47nuVQ4gUbx02K59vMQhns3C01JOAYokOaSXe66Y7QYdMlk09Lf|aes-256-cbc", "enable": true, "hide": true, "iconColor": "rgba(0, 211, 255, 1)", @@ -46,7 +18,6 @@ "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 0, - "id": null, "links": [ { "icon": "dashboard", @@ -54,7 +25,7 @@ "title": "TeslaMate", "tooltip": "", "type": "link", - "url": "[[base_url:raw]]" + "url": "${base_url:raw}" }, { "asDropdown": true, @@ -66,11 +37,9 @@ "type": "dashboards" } ], - "liveNow": false, "panels": [ { "collapsed": false, - "datasource": "TeslaMate", "gridPos": { "h": 1, "w": 24, @@ -80,12 +49,6 @@ "id": 4, "panels": [], "repeat": "car_id", - "targets": [ - { - "datasource": "TeslaMate", - "refId": "A" - } - ], "title": "$car_id", "type": "row" }, @@ -111,8 +74,7 @@ "mode": "absolute", "steps": [ { - "color": "dark-red", - "value": null + "color": "dark-red" } ] } @@ -129,11 +91,9 @@ "maxDataPoints": 10000000, "options": { "basemap": { - "config": { - "server": "streets" - }, + "config": {}, "name": "Layer 0", - "type": "esri-xyz" + "type": "osm-standard" }, "controls": { "mouseWheelZoom": true, @@ -144,6 +104,49 @@ "showZoom": true }, "layers": [ + { + "config": { + "showLegend": false, + "style": { + "color": { + "fixed": "transparent" + }, + "opacity": 0, + "rotation": { + "fixed": 0, + "max": 360, + "min": -360, + "mode": "mod" + }, + "size": { + "fixed": 3, + "max": 15, + "min": 2 + }, + "symbol": { + "fixed": "img/icons/marker/circle.svg", + "mode": "fixed" + }, + "symbolAlign": { + "horizontal": "center", + "vertical": "center" + }, + "textConfig": { + "fontSize": 12, + "offsetX": 0, + "offsetY": 0, + "textAlign": "center", + "textBaseline": "middle" + } + } + }, + "location": { + "mode": "auto" + }, + "name": "Workaround for Grafana Issue #89777, fixed in 11.6.0 via #101391, extra layer to be removed on broad availability of 11.6.0", + "tooltip": false, + "type": "markers" + }, { "config": { "arrow": 0, @@ -202,7 +205,7 @@ "zoom": 15 } }, - "pluginVersion": "11.0.0", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { @@ -211,9 +214,8 @@ }, "editorMode": "code", "format": "table", - "hide": false, "rawQuery": true, - "rawSql": "SELECT\n date_trunc('minute', date) as time,\n avg(latitude) as latitude,\n avg(longitude) as longitude\nFROM\n positions\nWHERE\n car_id = $car_id AND $__timeFilter(date)\nGROUP BY 1\nORDER BY 1", + "rawSql": "SELECT\n date_trunc('minute', timezone('UTC', date), '$__timezone') as time,\n avg(latitude) as latitude,\n avg(longitude) as longitude\nFROM\n positions\nWHERE\n car_id = $car_id AND $__timeFilter(date) and ideal_battery_range_km is not null\nGROUP BY 1\nORDER BY 1", "refId": "Positions", "sql": { "columns": [ @@ -229,10 +231,12 @@ }, "type": "groupBy" } - ] + ], + "limit": 50 } } ], + "title": "", "type": "geomap" }, { @@ -251,8 +255,7 @@ "mode": "absolute", "steps": [ { - "color": "super-light-blue", - "value": null + "color": "super-light-blue" } ] } @@ -271,18 +274,19 @@ "graphMode": "none", "justifyMode": "auto", "orientation": "horizontal", + "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "lastNotNull" ], "fields": "/.*/", - "values": false + "values": true }, "showPercentChange": false, "textMode": "value_and_name", "wideLayout": true }, - "pluginVersion": "11.0.0", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { @@ -291,22 +295,9 @@ }, "editorMode": "code", "format": "table", - "group": [], - "hide": false, - "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT ROUND(convert_km((max(end_km) - min(start_km))::numeric, '$length_unit'),0)|| ' $length_unit' as \"Distance Traveled\"\nFROM drives WHERE car_id = $car_id AND $__timeFilter(start_date)", + "rawSql": "SELECT ROUND(convert_km((max(end_km) - min(start_km))::numeric, '$length_unit'),0)|| ' $length_unit' as \"Mileage\"\nFROM drives WHERE car_id = $car_id AND $__timeFilter(start_date)", "refId": "distance traveled", - "select": [ - [ - { - "params": [ - "efficiency" - ], - "type": "column" - } - ] - ], "sql": { "columns": [ { @@ -323,19 +314,10 @@ } ], "limit": 50 - }, - "table": "cars", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + } } ], + "title": "", "type": "stat" }, { @@ -354,8 +336,7 @@ "mode": "absolute", "steps": [ { - "color": "light-yellow", - "value": null + "color": "light-yellow" } ] } @@ -364,7 +345,7 @@ { "matcher": { "id": "byName", - "options": "Total energy added" + "options": "Total Energy added" }, "properties": [ { @@ -376,7 +357,7 @@ { "matcher": { "id": "byName", - "options": "Total energy used" + "options": "Total Energy used" }, "properties": [ { @@ -388,7 +369,7 @@ { "matcher": { "id": "byName", - "options": "Charge Efficiency" + "options": "Charging Efficiency" }, "properties": [ { @@ -417,18 +398,19 @@ "graphMode": "none", "justifyMode": "center", "orientation": "vertical", + "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "mean" ], "fields": "", - "values": false + "values": true }, "showPercentChange": false, "textMode": "value_and_name", "wideLayout": true }, - "pluginVersion": "11.0.0", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { @@ -437,89 +419,9 @@ }, "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT\n\tsum(charge_energy_added) as \"Total energy added\"\nFROM\n\tcharging_processes\nWHERE\n\tcar_id = $car_id AND $__timeFilter(start_date) AND charge_energy_added > 0.01", - "refId": "Total energy added", - "select": [ - [ - { - "params": [ - "efficiency" - ], - "type": "column" - } - ] - ], - "sql": { - "columns": [ - { - "parameters": [], - "type": "function" - } - ], - "groupBy": [ - { - "property": { - "type": "string" - }, - "type": "groupBy" - } - ], - "limit": 50 - }, - "table": "cars", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - }, - { - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" - }, - "editorMode": "code", - "format": "table", - "hide": false, - "rawQuery": true, - "rawSql": "SELECT\r\n\tSUM(charge_energy_used) AS \"Total energy used\"\r\nFROM\r\n\tcharging_processes\r\nWHERE\r\n\tcar_id = $car_id AND $__timeFilter(start_date) AND charge_energy_added > 0.01\r\n", - "refId": "Total energy used", - "sql": { - "columns": [ - { - "parameters": [], - "type": "function" - } - ], - "groupBy": [ - { - "property": { - "type": "string" - }, - "type": "groupBy" - } - ], - "limit": 50 - } - }, - { - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "TeslaMate" - }, - "editorMode": "code", - "format": "table", - "hide": false, - "rawQuery": true, - "rawSql": "SELECT\r\n\tSUM(charge_energy_added) * 100 / SUM(charge_energy_used) AS \"Charge Efficiency\"\r\nFROM\r\n\tcharging_processes\r\nWHERE\r\n\tcar_id = $car_id AND $__timeFilter(start_date) AND charge_energy_added > 0.01\r\n", - "refId": "Charge Efficiency", + "rawSql": "SELECT\n\tsum(charge_energy_added) as \"Total Energy added\",\n\tSUM(greatest(charge_energy_added, charge_energy_used)) AS \"Total Energy used\",\n\tSUM(charge_energy_added) * 100 / SUM(greatest(charge_energy_added, charge_energy_used)) AS \"Charging Efficiency\"\nFROM\n\tcharging_processes\nWHERE\n\tcar_id = $car_id AND $__timeFilter(start_date) AND charge_energy_added > 0.01", + "refId": "A", "sql": { "columns": [ { @@ -539,6 +441,7 @@ } } ], + "title": "", "type": "stat" }, { @@ -550,7 +453,6 @@ "fieldConfig": { "defaults": { "decimals": 2, - "displayName": "Cost", "mappings": [ { "options": { @@ -566,12 +468,10 @@ "mode": "absolute", "steps": [ { - "color": "#c7d0d9", - "value": null + "color": "#c7d0d9" } ] - }, - "unit": "none" + } }, "overrides": [] }, @@ -592,10 +492,11 @@ }, "graphMode": "none", "justifyMode": "auto", - "orientation": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ - "mean" + "lastNotNull" ], "fields": "", "values": true @@ -604,7 +505,7 @@ "textMode": "value_and_name", "wideLayout": true }, - "pluginVersion": "11.0.0", + "pluginVersion": "11.6.1", "targets": [ { "datasource": { @@ -613,21 +514,9 @@ }, "editorMode": "code", "format": "table", - "group": [], - "metricColumn": "none", "rawQuery": true, - "rawSql": "select sum(cost) as \"Cost\" from charging_processes where $__timeFilter(start_date) AND car_id = $car_id;", + "rawSql": "select sum(cost) as \"Total Charging Cost\" from charging_processes where $__timeFilter(start_date) AND car_id = $car_id;", "refId": "A", - "select": [ - [ - { - "params": [ - "latitude" - ], - "type": "column" - } - ] - ], "sql": { "columns": [ { @@ -644,24 +533,16 @@ } ], "limit": 50 - }, - "table": "addresses", - "timeColumn": "inserted_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] + } } ], + "title": "", "type": "stat" } ], - "refresh": false, - "schemaVersion": 39, + "preload": false, + "refresh": "", + "schemaVersion": 41, "tags": [ "tesla" ], @@ -673,22 +554,16 @@ "type": "grafana-postgresql-datasource", "uid": "TeslaMate" }, - "definition": "SELECT name AS __text, id AS __value FROM cars ORDER BY display_priority ASC, name ASC;", + "definition": "SELECT\n id as __value,\n CASE WHEN COUNT(id) OVER (PARTITION BY name) > 1 AND name IS NOT NULL THEN CONCAT(name, ' - ', RIGHT(vin, 6)) ELSE COALESCE(name, CONCAT('VIN ', vin)) end as __text \nFROM cars\nORDER BY display_priority ASC, name ASC, vin ASC;", "hide": 2, "includeAll": true, "label": "Car", - "multi": false, "name": "car_id", "options": [], - "query": "SELECT name AS __text, id AS __value FROM cars ORDER BY display_priority ASC, name ASC;", + "query": "SELECT\n id as __value,\n CASE WHEN COUNT(id) OVER (PARTITION BY name) > 1 AND name IS NOT NULL THEN CONCAT(name, ' - ', RIGHT(vin, 6)) ELSE COALESCE(name, CONCAT('VIN ', vin)) end as __text \nFROM cars\nORDER BY display_priority ASC, name ASC, vin ASC;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -699,19 +574,12 @@ "definition": "select base_url from settings limit 1;", "hide": 2, "includeAll": false, - "label": "", - "multi": false, "name": "base_url", "options": [], "query": "select base_url from settings limit 1;", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false + "type": "query" }, { "current": {}, @@ -722,14 +590,11 @@ "definition": "SELECT unit_of_length FROM settings LIMIT 1", "hide": 2, "includeAll": false, - "multi": false, "name": "length_unit", "options": [], "query": "SELECT unit_of_length FROM settings LIMIT 1", "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, "type": "query" } ] @@ -738,35 +603,9 @@ "from": "now-90d", "to": "now" }, - "timeRangeUpdatedDuringEditOrView": false, - "timepicker": { - "refresh_intervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "time_options": [ - "5m", - "15m", - "1h", - "6h", - "12h", - "24h", - "2d", - "7d", - "30d" - ] - }, + "timepicker": {}, "timezone": "", "title": "Visited", "uid": "RG_DxSmgk", - "version": 3, - "weekStart": "" + "version": 1 } \ No newline at end of file diff --git a/grafana/datasource.yml b/grafana/datasource.yml index 7e9572d3b4..8c21a3823c 100644 --- a/grafana/datasource.yml +++ b/grafana/datasource.yml @@ -5,7 +5,6 @@ datasources: type: postgres url: $DATABASE_HOST:$DATABASE_PORT user: $DATABASE_USER - database: $DATABASE_NAME access: proxy basicAuth: false withCredentials: false @@ -15,5 +14,6 @@ datasources: jsonData: postgresVersion: 1500 sslmode: $DATABASE_SSL_MODE + database: $DATABASE_NAME version: 1 editable: true diff --git a/lib/tesla_api/auth.ex b/lib/tesla_api/auth.ex index a343840af1..e045710b6d 100644 --- a/lib/tesla_api/auth.ex +++ b/lib/tesla_api/auth.ex @@ -6,7 +6,7 @@ defmodule TeslaApi.Auth do @web_client_id "ownerapi" @redirect_uri "https://auth.tesla.com/void/callback" - def web_client_id, do: System.get_env("TESLA_AUTH_CLIENT_ID") || @web_client_id + def web_client_id, do: @web_client_id def redirect_uri, do: @redirect_uri @default_headers [ diff --git a/lib/tesla_api/auth/refresh.ex b/lib/tesla_api/auth/refresh.ex index f61110b1af..c86159cbcb 100644 --- a/lib/tesla_api/auth/refresh.ex +++ b/lib/tesla_api/auth/refresh.ex @@ -16,7 +16,7 @@ defmodule TeslaApi.Auth.Refresh do data = %{ grant_type: "refresh_token", scope: "openid email offline_access", - client_id: @web_client_id, + client_id: System.get_env("TESLA_AUTH_CLIENT_ID", @web_client_id), refresh_token: auth.refresh_token } diff --git a/lib/teslamate/application.ex b/lib/teslamate/application.ex index a0b3fe3efc..8c0d12f581 100644 --- a/lib/teslamate/application.ex +++ b/lib/teslamate/application.ex @@ -11,6 +11,8 @@ defmodule TeslaMate.Application do :ok = :telemetry.detach({Phoenix.Logger, [:phoenix, :socket_connected]}) :ok = :telemetry.detach({Phoenix.Logger, [:phoenix, :channel_joined]}) + TeslaMate.DatabaseCheck.check_postgres_version() + Supervisor.start_link(children(), strategy: :one_for_one, name: TeslaMate.Supervisor) end diff --git a/lib/teslamate/database_check.ex b/lib/teslamate/database_check.ex new file mode 100644 index 0000000000..019fe16bff --- /dev/null +++ b/lib/teslamate/database_check.ex @@ -0,0 +1,71 @@ +defmodule TeslaMate.DatabaseCheck do + alias Ecto.Adapters.SQL + alias TeslaMate.Repo + + # Minimum versions for supported major releases + @min_version_16 "16.7" + @min_version_17 "17.3" + + def check_postgres_version do + # Start the Repo manually without running migrations + {:ok, _pid} = Repo.start_link() + + # Query the PostgreSQL version + {:ok, result} = + SQL.query( + Repo, + "SELECT regexp_replace(version(), 'PostgreSQL ([^ ]+) .*', '\\1') AS version", + [] + ) + + raw_version = result.rows |> List.first() |> List.first() + + # Normalize to SemVer by appending .0 if needed + version = normalize_version(raw_version) + + # Split into major and minor parts + [major, _minor] = String.split(version, ".", parts: 2) + major_int = String.to_integer(major) + + # Check based on major version + case major_int do + 16 -> + case Version.compare(version, normalize_version(@min_version_16)) do + :lt -> + raise "PostgreSQL version #{raw_version} is not supported. Minimum required for 16.x is #{@min_version_16}." + + _ -> + IO.puts("PostgreSQL version #{raw_version} is compatible (16.x series).") + end + + 17 -> + case Version.compare(version, normalize_version(@min_version_17)) do + :lt -> + raise "PostgreSQL version #{raw_version} is not supported. Minimum required for 17.x is #{@min_version_17}." + + _ -> + IO.puts("PostgreSQL version #{raw_version} is compatible (17.x series).") + end + + major_int when major_int > 17 -> + IO.puts( + "PostgreSQL version #{raw_version} is not officially tested or supported yet. Use at your own risk." + ) + + _ -> + raise "PostgreSQL version #{raw_version} is not supported. Only 16.x (min #{@min_version_16}) and 17.x (min #{@min_version_17}) are supported." + end + + # Stop the Repo after the check + Repo.stop() + end + + # Helper function to normalize PostgreSQL version to SemVer + defp normalize_version(version) do + case String.split(version, ".") do + [major, minor] -> "#{major}.#{minor}.0" + [major, minor, patch | _] -> "#{major}.#{minor}.#{patch}" + _ -> raise "Invalid PostgreSQL version format: #{version}" + end + end +end diff --git a/lib/teslamate/log.ex b/lib/teslamate/log.ex index 8875f0a11d..74631c274b 100644 --- a/lib/teslamate/log.ex +++ b/lib/teslamate/log.ex @@ -173,25 +173,36 @@ defmodule TeslaMate.Log do def get_positions_without_elevation(min_id \\ 0, opts \\ []) do limit = Keyword.get(opts, :limit, 100) + date_earliest = + cond do + min_id == 0 -> + DateTime.add(DateTime.utc_now(), -10, :day) + + true -> + {:ok, default_date_earliest, _} = DateTime.from_iso8601("2003-07-01T00:00:00Z") + default_date_earliest + end + + naive_date_earliest = DateTime.to_naive(date_earliest) + non_streamed_drives = Repo.all( - from d in subquery( - from p in Position, - select: %{ - drive_id: p.drive_id, - streamed_count: - count() - |> filter(not is_nil(p.odometer) and is_nil(p.ideal_battery_range_km)) - }, - where: not is_nil(p.drive_id), - group_by: p.drive_id - ), - select: d.drive_id, - where: d.streamed_count == 0 + from p in Position, + select: p.drive_id, + inner_join: d in assoc(p, :drive), + where: d.start_date > ^naive_date_earliest and p.id > ^min_id, + having: + count() + |> filter(not is_nil(p.odometer) and is_nil(p.ideal_battery_range_km)) == 0, + group_by: p.drive_id ) Position - |> where([p], p.id > ^min_id and is_nil(p.elevation) and p.drive_id in ^non_streamed_drives) + |> where( + [p], + p.id > ^min_id and is_nil(p.elevation) and p.drive_id in ^non_streamed_drives and + p.date > ^naive_date_earliest + ) |> order_by(asc: :id) |> limit(^limit) |> Repo.all() diff --git a/lib/teslamate/vehicles/vehicle.ex b/lib/teslamate/vehicles/vehicle.ex index 558c250ace..bb0875433c 100644 --- a/lib/teslamate/vehicles/vehicle.ex +++ b/lib/teslamate/vehicles/vehicle.ex @@ -65,6 +65,7 @@ defmodule TeslaMate.Vehicles.Vehicle do with str when is_binary(str) <- type do case String.downcase(str) do "models" <> _ -> "S" + "models2" <> _ -> "S" "model3" <> _ -> "3" "modelx" <> _ -> "X" "modely" <> _ -> "Y" @@ -78,6 +79,7 @@ defmodule TeslaMate.Vehicles.Vehicle do case {model, trim_badging, type} do {"S", "100D", "lychee"} -> "LR" {"S", "P100D", "lychee"} -> "Plaid" + {"S", "100D", "models2"} -> "LR+" {"3", "P74D", _} -> "LR AWD Performance" {"3", "74D", _} -> "LR AWD" {"3", "74", _} -> "LR" @@ -318,6 +320,7 @@ defmodule TeslaMate.Vehicles.Vehicle do {:keep_state, data, [broadcast_fetch(false), schedule_fetch(0, data)]} + # Handle fetch of vehicle_data {%Vehicle{ drive_state: %Drive{}, charge_state: %Charge{}, @@ -328,6 +331,7 @@ defmodule TeslaMate.Vehicles.Vehicle do {:keep_state, %Data{data | last_response: vehicle}, [broadcast_fetch(false), {:next_event, :internal, {:update, {:online, vehicle}}}]} + # Handle fetch of vehicle/id (non-vehicle_data) {%Vehicle{}, %Data{}} -> Logger.warning("Discarded incomplete fetch result", car_id: data.car.id) {:keep_state, data, [broadcast_fetch(false), schedule_fetch(data)]} @@ -440,11 +444,14 @@ defmodule TeslaMate.Vehicles.Vehicle do case stream_data do %Stream.Data{} when stale_stream_data? -> - Logger.warning("Received stale stream data: #{inspect(stream_data)}", car_id: data.car.id) + Logger.warning("Online / Received stale stream data: #{inspect(stream_data)}", + car_id: data.car.id + ) + :keep_state_and_data %Stream.Data{shift_state: shift_state} when shift_state in ~w(D N R) -> - Logger.info("Start of drive initiated by: #{inspect(stream_data)}") + Logger.info("Online / Start of drive initiated by: #{inspect(stream_data)}") %{elevation: elevation} = position = create_position(stream_data, data) {drive, data} = start_drive(position, data) @@ -456,8 +463,21 @@ defmodule TeslaMate.Vehicles.Vehicle do [broadcast_summary(), schedule_fetch(0, data)]} %Stream.Data{shift_state: nil, power: power} when is_number(power) and power < 0 -> - Logger.info("Charging detected: #{power} kW", car_id: data.car.id) - {:keep_state_and_data, schedule_fetch(0, data)} + vehicle = merge(data.last_response, stream_data, time: true) + + # Only detect as charging if we are not doing something else while plugged in. + # In case we are doing both charging and other thing a normal fetch will discover it later + case {vehicle} do + {%Vehicle{climate_state: %Climate{is_preconditioning: true}}} -> + :keep_state_and_data + + {%Vehicle{climate_state: %Climate{climate_keeper_mode: "dog"}}} -> + :keep_state_and_data + + {%Vehicle{}} -> + Logger.info("Online / Charging detected: #{power} kW", car_id: data.car.id) + {:keep_state_and_data, schedule_fetch(0, data)} + end %Stream.Data{} -> Logger.debug(inspect(stream_data), car_id: data.car.id) @@ -491,11 +511,14 @@ defmodule TeslaMate.Vehicles.Vehicle do case stream_data do %Stream.Data{} when stale_stream_data? -> - Logger.warning("Received stale stream data: #{inspect(stream_data)}", car_id: data.car.id) + Logger.warning("Suspended / Received stale stream data: #{inspect(stream_data)}", + car_id: data.car.id + ) + :keep_state_and_data %Stream.Data{shift_state: shift_state} when shift_state in ~w(D N R) -> - Logger.info("Start of drive initiated by: #{inspect(stream_data)}") + Logger.info("Suspended / Start of drive initiated by: #{inspect(stream_data)}") %{elevation: elevation} = position = create_position(stream_data, data) {drive, data} = start_drive(position, data) @@ -508,8 +531,21 @@ defmodule TeslaMate.Vehicles.Vehicle do %Stream.Data{shift_state: s, power: power} when s in [nil, "P"] and is_number(power) and power < 0 -> - Logger.info("Charging detected: #{power} kW", car_id: data.car.id) - {:next_state, prev_state, data, schedule_fetch(0, data)} + Logger.info("Suspended / Charging detected: #{power} kW", car_id: data.car.id) + + {:next_state, prev_state, %Data{data | last_used: DateTime.utc_now()}, + schedule_fetch(0, data)} + + %Stream.Data{shift_state: s, power: power} + when s in [nil, "P"] and is_number(power) and power > 0 -> + Logger.info("Suspended / Usage detected: #{power} kW", car_id: data.car.id) + + # update power to be used in can_fall_asleep / try_to_suspend + vehicle = merge(data.last_response, stream_data, time: true) + + {:next_state, prev_state, + %Data{data | last_response: vehicle, last_used: DateTime.utc_now()}, + schedule_fetch(0, data)} %Stream.Data{} -> Logger.debug(inspect(stream_data), car_id: data.car.id) @@ -518,7 +554,7 @@ defmodule TeslaMate.Vehicles.Vehicle do end def handle_event(:info, {:stream, :inactive}, {:suspended, _prev_state}, data) do - Logger.info("Fetching vehicle state ...", car_id: data.car.id) + Logger.info("Stream :inactive in suspended, fetching vehicle state ...", car_id: data.car.id) {:keep_state_and_data, {:next_event, :internal, :fetch_state}} end @@ -1423,6 +1459,12 @@ defmodule TeslaMate.Vehicles.Vehicle do {:keep_state, %Data{data | last_used: DateTime.utc_now()}, [broadcast_summary(), schedule_fetch(default_interval() * i, data)]} + {:error, :power_usage} -> + if suspend?, do: Logger.warning("Power usage ...", car_id: car.id) + + {:keep_state, %Data{data | last_used: DateTime.utc_now()}, + [broadcast_summary(), schedule_fetch(default_interval() * i, data)]} + {:error, :unlocked} -> if suspend?, do: Logger.warning("Unlocked ...", car_id: car.id) @@ -1491,6 +1533,10 @@ defmodule TeslaMate.Vehicles.Vehicle do %CarSettings{req_not_unlocked: true}} -> {:error, :unlocked} + {%Vehicle{drive_state: %Drive{power: power}}, _} + when is_number(power) and power > 0 -> + {:error, :power_usage} + {%Vehicle{}, %CarSettings{}} -> :ok end @@ -1615,7 +1661,7 @@ defmodule TeslaMate.Vehicles.Vehicle do defp streaming?(%Data{stream_pid: pid}), do: is_pid(pid) and Process.alive?(pid) defp connect_stream(%Data{car: car} = data) do - Logger.info("Connecting ...", car_id: car.id) + Logger.info("Stream connecting ...", car_id: car.id) me = self() @@ -1635,7 +1681,7 @@ defmodule TeslaMate.Vehicles.Vehicle do defp disconnect_stream(%Data{stream_pid: nil}), do: :ok defp disconnect_stream(%Data{stream_pid: pid} = data) when is_pid(pid) do - Logger.info("Disconnecting ...", car_id: data.car.id) + Logger.info("Stream disconnecting ...", car_id: data.car.id) Stream.disconnect(pid) end diff --git a/lib/teslamate_web.ex b/lib/teslamate_web.ex index 19a134b2c3..03ef6e2fbe 100644 --- a/lib/teslamate_web.ex +++ b/lib/teslamate_web.ex @@ -27,7 +27,7 @@ defmodule TeslaMateWeb do use Phoenix.Controller, namespace: TeslaMateWeb import Plug.Conn - import TeslaMateWeb.Gettext + use Gettext, backend: TeslaMateWeb.Gettext alias TeslaMateWeb.Router.Helpers, as: Routes unquote(verified_routes()) @@ -79,14 +79,16 @@ defmodule TeslaMateWeb do def channel do quote do use Phoenix.Channel - import TeslaMateWeb.Gettext + use Gettext, backend: TeslaMateWeb.Gettext end end defp view_helpers do quote do - # Use all HTML functionality (forms, tags, etc) - use Phoenix.HTML + # Import all HTML functionality (forms, tags, etc) + import Phoenix.HTML + import Phoenix.HTML.Form + use PhoenixHTMLHelpers # Import convenience functions for LiveView rendering import Phoenix.LiveView.Helpers @@ -94,7 +96,7 @@ defmodule TeslaMateWeb do import Phoenix.View import TeslaMateWeb.ErrorHelpers - import TeslaMateWeb.Gettext + use Gettext, backend: TeslaMateWeb.Gettext alias TeslaMateWeb.Router.Helpers, as: Routes unquote(verified_routes()) diff --git a/lib/teslamate_web/gettext.ex b/lib/teslamate_web/gettext.ex index 2ca985dc39..3eb139dcaf 100644 --- a/lib/teslamate_web/gettext.ex +++ b/lib/teslamate_web/gettext.ex @@ -5,7 +5,7 @@ defmodule TeslaMateWeb.Gettext do By using [Gettext](https://hexdocs.pm/gettext), your module gains a set of macros for translations, for example: - import TeslaMateWeb.Gettext + use Gettext, backend: TeslaMateWeb.Gettext # Simple translation gettext("Here is the string to translate") @@ -20,5 +20,5 @@ defmodule TeslaMateWeb.Gettext do See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage. """ - use Gettext, otp_app: :teslamate + use Gettext.Backend, otp_app: :teslamate end diff --git a/lib/teslamate_web/live/car_live/summary.ex b/lib/teslamate_web/live/car_live/summary.ex index e241222082..7dfe36c66b 100644 --- a/lib/teslamate_web/live/car_live/summary.ex +++ b/lib/teslamate_web/live/car_live/summary.ex @@ -1,7 +1,7 @@ defmodule TeslaMateWeb.CarLive.Summary do use TeslaMateWeb, :live_view - import TeslaMateWeb.Gettext + use Gettext, backend: TeslaMateWeb.Gettext alias TeslaMate.Vehicles.Vehicle.Summary alias TeslaMate.Vehicles.Vehicle @@ -121,6 +121,14 @@ defmodule TeslaMateWeb.CarLive.Summary do {:noreply, assign(socket, fetch_status: false)} end + def format_tpms(bar, :psi) when is_number(bar) do + "#{Float.round(bar * 14.5038, 1)} PSI" + end + + def format_tpms(bar, :bar) when is_number(bar) do + "#{Float.round(bar, 1)} Bar" + end + defp translate_state(:start), do: "" defp translate_state(:driving), do: gettext("driving") defp translate_state(:charging), do: gettext("charging") diff --git a/lib/teslamate_web/live/car_live/summary.html.heex b/lib/teslamate_web/live/car_live/summary.html.heex index 80d6d6c228..ee4868857e 100644 --- a/lib/teslamate_web/live/car_live/summary.html.heex +++ b/lib/teslamate_web/live/car_live/summary.html.heex @@ -147,33 +147,41 @@ <% end %> <%= unless is_nil(@summary.tpms_soft_warning_fl) or is_nil(@summary.tpms_soft_warning_fr) or is_nil(@summary.tpms_soft_warning_rl) or is_nil(@summary.tpms_soft_warning_rr) do %> <%= if @summary.tpms_soft_warning_fl or @summary.tpms_soft_warning_fr or @summary.tpms_soft_warning_rl or @summary.tpms_soft_warning_rr do %> + <% tires = [ + {:fl, {@summary.tpms_soft_warning_fl, @summary.tpms_pressure_fl}}, + {:fr, {@summary.tpms_soft_warning_fr, @summary.tpms_pressure_fr}}, + {:rl, {@summary.tpms_soft_warning_rl, @summary.tpms_pressure_rl}}, + {:rr, {@summary.tpms_soft_warning_rr, @summary.tpms_pressure_rr}} + ] %> + + <% tire_warnings = + Enum.filter(tires, fn {_tire, {warn, _}} -> warn end) + |> Enum.map(fn + {:fl, {true, val}} -> + "Front left (#{format_tpms(val, @settings.unit_of_pressure)})" + + {:fr, {true, val}} -> + "Front right (#{format_tpms(val, @settings.unit_of_pressure)})" + + {:rl, {true, val}} -> + "Rear left (#{format_tpms(val, @settings.unit_of_pressure)})" + + {:rr, {true, val}} -> + "Rear right (#{format_tpms(val, @settings.unit_of_pressure)})" + + _ -> + nil + end) + |> Enum.filter(& &1) %> + pressure end - ) - |> Enum.map(fn - {:fl, true} -> "Front left" - {:fr, true} -> "Front right" - {:rl, true} -> "Rear left" - {:rr, true} -> "Rear right" - _ -> nil - end) - |> Enum.filter(&(&1 != nil)) - |> Enum.join(", ") - } - ) + if length(tire_warnings) == 1 do + "Tire with low pressure: #{Enum.join(tire_warnings, ", ")}" + else + "Tires with low pressure: #{Enum.join(tire_warnings, ", ")}" + end } > diff --git a/lib/teslamate_web/live/charge_live/cost.ex b/lib/teslamate_web/live/charge_live/cost.ex index c53a9b61a7..810d0bf432 100644 --- a/lib/teslamate_web/live/charge_live/cost.ex +++ b/lib/teslamate_web/live/charge_live/cost.ex @@ -7,7 +7,7 @@ defmodule TeslaMateWeb.ChargeLive.Cost do alias TeslaMate.Log.ChargingProcess alias TeslaMate.Log - import TeslaMateWeb.Gettext + use Gettext, backend: TeslaMateWeb.Gettext on_mount {TeslaMateWeb.InitAssigns, :locale} diff --git a/lib/teslamate_web/live/charge_live/cost.html.heex b/lib/teslamate_web/live/charge_live/cost.html.heex index 0c15e8b482..ba25a1aaeb 100644 --- a/lib/teslamate_web/live/charge_live/cost.html.heex +++ b/lib/teslamate_web/live/charge_live/cost.html.heex @@ -1,7 +1,7 @@ diff --git a/lib/teslamate_web/live/geofence_live/form.ex b/lib/teslamate_web/live/geofence_live/form.ex index 90c5f1dbfe..87b762cd71 100644 --- a/lib/teslamate_web/live/geofence_live/form.ex +++ b/lib/teslamate_web/live/geofence_live/form.ex @@ -161,7 +161,7 @@ defmodule TeslaMateWeb.GeoFenceLive.Form do socket |> assign(geofence: geofence) |> put_flash(:success, flash_msg(action, name)) - |> push_redirect(to: Routes.live_path(socket, GeoFenceLive.Index)) + |> push_navigate(to: Routes.live_path(socket, GeoFenceLive.Index)) {:ok, socket} end diff --git a/lib/teslamate_web/live/geofence_live/form.html.heex b/lib/teslamate_web/live/geofence_live/form.html.heex index 245c84c82f..9498c4edad 100644 --- a/lib/teslamate_web/live/geofence_live/form.html.heex +++ b/lib/teslamate_web/live/geofence_live/form.html.heex @@ -1,7 +1,7 @@