From c329c5f3356a0570d1394986fbafa02b58e00f1a Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Sat, 31 Aug 2024 08:42:43 +1000 Subject: [PATCH 01/72] publish to ghcr --- .github/workflows/nightly.yml | 48 +++-------------------------------- 1 file changed, 3 insertions(+), 45 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 7ee5ecb6da..82fcca2b33 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -56,8 +56,8 @@ jobs: - name: Login uses: docker/login-action@v3 with: - username: ${{ secrets.DOCKER_USER }} - password: ${{ secrets.DOCKER_PASS }} + username: USERNAME + password: ${{ secrets.GITHUB_TOKEN }} - name: Setup Buildx uses: docker/setup-buildx-action@v3 @@ -71,47 +71,5 @@ jobs: build-args: | TESLA_CLIENT_ID=${{ secrets.TESLA_CLIENT_ID }} tags: | - evcc/evcc:nightly + ghcr.io/jeffborg/evcc:nightly - apt: - name: Publish APT nightly - needs: - - call-build-workflow - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - uses: actions/setup-go@v5 - with: - go-version: ${{ env.GO_VERSION }} - id: go - - - name: Install Cloudsmith CLI - run: pip install --upgrade cloudsmith-cli - - - name: Patch ASN1 - run: make patch-asn1-sudo - - - name: Get dist from cache - uses: actions/cache/restore@v4 - id: cache-dist - with: - path: dist - key: ${{ runner.os }}-${{ github.sha }}-dist - - - name: Create nightly build - uses: goreleaser/goreleaser-action@v6 - with: - version: latest - args: --snapshot -f .goreleaser-nightly.yml --clean - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - TESLA_CLIENT_ID: ${{ secrets.TESLA_CLIENT_ID }} - - - name: Publish .deb to Cloudsmith - env: - CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }} - run: make apt-nightly From f9a608179d18ca8c66350f08c3389ad5785fe61b Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Sat, 31 Aug 2024 08:51:55 +1000 Subject: [PATCH 02/72] removed extra line --- .github/workflows/nightly.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 82fcca2b33..956f066637 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -72,4 +72,3 @@ jobs: TESLA_CLIENT_ID=${{ secrets.TESLA_CLIENT_ID }} tags: | ghcr.io/jeffborg/evcc:nightly - From 7300e358e59c821dc927cb86e79c82d98f46e466 Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Sat, 31 Aug 2024 08:58:17 +1000 Subject: [PATCH 03/72] fixed github login --- .github/workflows/nightly.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 956f066637..a9f570ffa6 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -56,7 +56,8 @@ jobs: - name: Login uses: docker/login-action@v3 with: - username: USERNAME + registry: ghcr.io + username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Setup Buildx From e37ea7f12b08a1d279201f344151e0427036657d Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Mon, 2 Sep 2024 08:47:55 +1000 Subject: [PATCH 04/72] allow finishing state as starting state --- charger/ocpp/connector.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charger/ocpp/connector.go b/charger/ocpp/connector.go index 3f545801ae..75196c0da4 100644 --- a/charger/ocpp/connector.go +++ b/charger/ocpp/connector.go @@ -147,7 +147,7 @@ func (conn *Connector) NeedsAuthentication() bool { conn.mu.Lock() defer conn.mu.Unlock() - return conn.status != nil && conn.txnId == 0 && conn.status.Status == core.ChargePointStatusPreparing + return conn.status != nil && conn.txnId == 0 && (conn.status.Status == core.ChargePointStatusPreparing || conn.status.Status == core.ChargePointStatusFinishing) } // isMeterTimeout checks if meter values are outdated. From d31f35e069184e34527dd613c74ab7317dd130b6 Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Sat, 31 Aug 2024 08:42:43 +1000 Subject: [PATCH 05/72] publish to ghcr --- .github/workflows/nightly.yml | 48 +++-------------------------------- 1 file changed, 3 insertions(+), 45 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 7ee5ecb6da..82fcca2b33 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -56,8 +56,8 @@ jobs: - name: Login uses: docker/login-action@v3 with: - username: ${{ secrets.DOCKER_USER }} - password: ${{ secrets.DOCKER_PASS }} + username: USERNAME + password: ${{ secrets.GITHUB_TOKEN }} - name: Setup Buildx uses: docker/setup-buildx-action@v3 @@ -71,47 +71,5 @@ jobs: build-args: | TESLA_CLIENT_ID=${{ secrets.TESLA_CLIENT_ID }} tags: | - evcc/evcc:nightly + ghcr.io/jeffborg/evcc:nightly - apt: - name: Publish APT nightly - needs: - - call-build-workflow - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - uses: actions/setup-go@v5 - with: - go-version: ${{ env.GO_VERSION }} - id: go - - - name: Install Cloudsmith CLI - run: pip install --upgrade cloudsmith-cli - - - name: Patch ASN1 - run: make patch-asn1-sudo - - - name: Get dist from cache - uses: actions/cache/restore@v4 - id: cache-dist - with: - path: dist - key: ${{ runner.os }}-${{ github.sha }}-dist - - - name: Create nightly build - uses: goreleaser/goreleaser-action@v6 - with: - version: latest - args: --snapshot -f .goreleaser-nightly.yml --clean - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - TESLA_CLIENT_ID: ${{ secrets.TESLA_CLIENT_ID }} - - - name: Publish .deb to Cloudsmith - env: - CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }} - run: make apt-nightly From 768391ce6261a38219438d834e36ddcff0b7c792 Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Sat, 31 Aug 2024 08:51:55 +1000 Subject: [PATCH 06/72] removed extra line --- .github/workflows/nightly.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 82fcca2b33..956f066637 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -72,4 +72,3 @@ jobs: TESLA_CLIENT_ID=${{ secrets.TESLA_CLIENT_ID }} tags: | ghcr.io/jeffborg/evcc:nightly - From d897b3a085661aad4e8cd06c751979dca163bcde Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Sat, 31 Aug 2024 08:58:17 +1000 Subject: [PATCH 07/72] fixed github login --- .github/workflows/nightly.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 956f066637..a9f570ffa6 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -56,7 +56,8 @@ jobs: - name: Login uses: docker/login-action@v3 with: - username: USERNAME + registry: ghcr.io + username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Setup Buildx From b94391e8293bb95d5bebce837ae98a9480bd934f Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Mon, 2 Sep 2024 08:47:55 +1000 Subject: [PATCH 08/72] allow finishing state as starting state --- charger/ocpp/connector.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charger/ocpp/connector.go b/charger/ocpp/connector.go index 9e0439e42f..bd778b2982 100644 --- a/charger/ocpp/connector.go +++ b/charger/ocpp/connector.go @@ -153,7 +153,7 @@ func (conn *Connector) NeedsAuthentication() bool { conn.mu.Lock() defer conn.mu.Unlock() - return conn.status != nil && conn.txnId == 0 && conn.status.Status == core.ChargePointStatusPreparing + return conn.status != nil && conn.txnId == 0 && (conn.status.Status == core.ChargePointStatusPreparing || conn.status.Status == core.ChargePointStatusFinishing) } // isMeterTimeout checks if meter values are outdated. From a18a7346260f65c44b74b63cb56977f001526b33 Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Mon, 2 Sep 2024 09:11:46 +1000 Subject: [PATCH 09/72] fix release action --- .github/workflows/release.yml | 105 ++-------------------------------- 1 file changed, 4 insertions(+), 101 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c992aa03e6..d76b03e1b9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,8 +27,9 @@ jobs: - name: Login uses: docker/login-action@v3 with: - username: ${{ secrets.DOCKER_USER }} - password: ${{ secrets.DOCKER_PASS }} + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} - name: Setup Buildx uses: docker/setup-buildx-action@v3 @@ -38,7 +39,7 @@ jobs: uses: docker/metadata-action@v5 with: images: | - evcc/evcc + ghcr.io/jeffborg/evcc - name: Publish uses: docker/build-push-action@v6 @@ -50,101 +51,3 @@ jobs: RELEASE=1 TESLA_CLIENT_ID=${{ secrets.TESLA_CLIENT_ID }} tags: ${{ steps.meta.outputs.tags }} - - apt: - name: Github & APT - needs: - - call-build-workflow - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: ${{ env.GO_VERSION }} - id: go - - - name: Install Cloudsmith CLI - run: pip install --upgrade cloudsmith-cli - - - name: Patch ASN1 - run: make patch-asn1-sudo - - - name: Get dist from cache - uses: actions/cache/restore@v4 - id: cache-dist - with: - path: dist - key: ${{ runner.os }}-${{ github.sha }}-dist - - # gokrazy image - # - name: Prepare Image - # run: | - # make prepare-image - # sed -i -e 's#-ld.*$#& -X github.com/evcc-io/evcc/server/updater.Password=${{ secrets.IMAGE_PASS }}#' buildflags/github.com/evcc-io/evcc/buildflags.txt - # mkdir /home/runner/.config/gokrazy - # echo ${{ secrets.IMAGE_PASS }}> /home/runner/.config/gokrazy/http-password.txt - - # - name: Build Image - # run: make image - - # - name: Build Root Filesystem - # run: make image-rootfs - - - name: Create Github Release - uses: goreleaser/goreleaser-action@v6 - with: - version: latest - args: release --clean - env: - # use GH_TOKEN for access to evcc-io/homebrew-tap - GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} - TESLA_CLIENT_ID: ${{ secrets.TESLA_CLIENT_ID }} - - - name: Publish .deb to Cloudsmith - env: - CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }} - run: make apt-release - - demo: - name: Demo - needs: - - docker - runs-on: ubuntu-latest - env: - FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} - steps: - - uses: actions/checkout@v4 - - uses: superfly/flyctl-actions/setup-flyctl@master - - run: flyctl deploy --local-only --config packaging/fly.toml - - hassio: - name: Hassio Addon - needs: - - docker - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@master - with: - repository: evcc-io/hassio-addon - token: ${{ secrets.GH_TOKEN }} - path: ./hassio - - - name: Update version - run: | - sed -i -e s#\"version.*#\"version\":\ \"$(echo ${{ github.ref }} | sed -e s#refs/tags/##)\",# ./hassio/evcc/config.json - - - name: Push - run: | - cd ./hassio - git add . - git config user.name github-actions - git config user.email github-actions@github.com - git commit -am "Mirror evcc release" - git push From 23e83b453b064c7e0e37a5cd5fa34092e8d8525c Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Wed, 4 Sep 2024 15:46:16 +1000 Subject: [PATCH 10/72] Add option for amber to use advanced price forecasts for tariffs --- tariff/amber.go | 34 +++++++++++++++----------- tariff/amber/types.go | 33 +++++++++++++++---------- templates/definition/tariff/amber.yaml | 2 ++ 3 files changed, 42 insertions(+), 27 deletions(-) diff --git a/tariff/amber.go b/tariff/amber.go index 416e6df735..e3c3667d5e 100644 --- a/tariff/amber.go +++ b/tariff/amber.go @@ -19,10 +19,11 @@ import ( type Amber struct { *embed *request.Helper - log *util.Logger - uri string - channel string - data *util.Monitor[api.Rates] + log *util.Logger + uri string + channel string + data *util.Monitor[api.Rates] + useAdvancedPrice bool } var _ api.Tariff = (*Amber)(nil) @@ -33,10 +34,11 @@ func init() { func NewAmberFromConfig(other map[string]interface{}) (api.Tariff, error) { var cc struct { - embed `mapstructure:",squash"` - Token string - SiteID string - Channel string + embed `mapstructure:",squash"` + Token string + SiteID string + Channel string + UseAdvancedPrice bool } if err := util.DecodeOther(other, &cc); err != nil { @@ -58,12 +60,13 @@ func NewAmberFromConfig(other map[string]interface{}) (api.Tariff, error) { log := util.NewLogger("amber").Redact(cc.Token) t := &Amber{ - embed: &cc.embed, - log: log, - Helper: request.NewHelper(log), - uri: fmt.Sprintf(amber.URI, strings.ToUpper(cc.SiteID)), - channel: strings.ToLower(cc.Channel), - data: util.NewMonitor[api.Rates](2 * time.Hour), + embed: &cc.embed, + log: log, + Helper: request.NewHelper(log), + uri: fmt.Sprintf(amber.URI, strings.ToUpper(cc.SiteID)), + channel: strings.ToLower(cc.Channel), + data: util.NewMonitor[api.Rates](2 * time.Hour), + useAdvancedPrice: cc.UseAdvancedPrice, } t.Client.Transport = &transport.Decorator{ @@ -108,6 +111,9 @@ func (t *Amber) run(done chan error) { End: endTime.Local(), Price: r.PerKwh / 1e2, } + if t.useAdvancedPrice && r.AdvancedPrice != nil { + ar.Price = r.AdvancedPrice.Predicted / 1e2 + } data = append(data, ar) } } diff --git a/tariff/amber/types.go b/tariff/amber/types.go index 87e6282aa6..4ba3f1778f 100644 --- a/tariff/amber/types.go +++ b/tariff/amber/types.go @@ -2,18 +2,25 @@ package amber const URI = "https://api.amber.com.au/v1/sites/%s/prices?resolution=30" +type AdvancedPrice struct { + Low float64 `json:"low"` + Predicted float64 `json:"predicted"` + High float64 `json:"high"` +} + type PriceInfo struct { - Type string `json:"type"` - Date string `json:"date"` - Duration int `json:"duration"` - StartTime string `json:"startTime"` - EndTime string `json:"endTime"` - NemTime string `json:"nemTime"` - PerKwh float64 `json:"perKwh"` - Renewables float64 `json:"renewables"` - SpotPerKwh float64 `json:"spotPerKwh"` - ChannelType string `json:"channelType"` - SpikeStatus string `json:"spikeStatus"` - Descriptor string `json:"descriptor"` - Estimate bool `json:"estimate"` + Type string `json:"type"` + Date string `json:"date"` + Duration int `json:"duration"` + StartTime string `json:"startTime"` + EndTime string `json:"endTime"` + NemTime string `json:"nemTime"` + PerKwh float64 `json:"perKwh"` + Renewables float64 `json:"renewables"` + SpotPerKwh float64 `json:"spotPerKwh"` + ChannelType string `json:"channelType"` + SpikeStatus string `json:"spikeStatus"` + Descriptor string `json:"descriptor"` + Estimate bool `json:"estimate"` + AdvancedPrice *AdvancedPrice `json:"advancedPrice,omitempty"` } diff --git a/templates/definition/tariff/amber.yaml b/templates/definition/tariff/amber.yaml index a8ea6620dd..78f4446cf5 100644 --- a/templates/definition/tariff/amber.yaml +++ b/templates/definition/tariff/amber.yaml @@ -10,10 +10,12 @@ params: - name: token - name: siteid - name: channel + - name: useAdvancedPrice - preset: tariff-base render: | type: amber token: {{ .token }} siteid: {{ .siteid }} channel: {{ .channel }} + useAdvancedPrice: {{ .useAdvancedPrice }} {{ include "tariff-base" . }} From 42ecdae036e14dd4be5ba74eb369b32c0db2e950 Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Thu, 5 Sep 2024 07:19:58 +1000 Subject: [PATCH 11/72] make advanced price the default if available --- tariff/amber.go | 33 ++++++++++++-------------- templates/definition/tariff/amber.yaml | 2 -- 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/tariff/amber.go b/tariff/amber.go index e3c3667d5e..4d099ead0c 100644 --- a/tariff/amber.go +++ b/tariff/amber.go @@ -19,11 +19,10 @@ import ( type Amber struct { *embed *request.Helper - log *util.Logger - uri string - channel string - data *util.Monitor[api.Rates] - useAdvancedPrice bool + log *util.Logger + uri string + channel string + data *util.Monitor[api.Rates] } var _ api.Tariff = (*Amber)(nil) @@ -34,11 +33,10 @@ func init() { func NewAmberFromConfig(other map[string]interface{}) (api.Tariff, error) { var cc struct { - embed `mapstructure:",squash"` - Token string - SiteID string - Channel string - UseAdvancedPrice bool + embed `mapstructure:",squash"` + Token string + SiteID string + Channel string } if err := util.DecodeOther(other, &cc); err != nil { @@ -60,13 +58,12 @@ func NewAmberFromConfig(other map[string]interface{}) (api.Tariff, error) { log := util.NewLogger("amber").Redact(cc.Token) t := &Amber{ - embed: &cc.embed, - log: log, - Helper: request.NewHelper(log), - uri: fmt.Sprintf(amber.URI, strings.ToUpper(cc.SiteID)), - channel: strings.ToLower(cc.Channel), - data: util.NewMonitor[api.Rates](2 * time.Hour), - useAdvancedPrice: cc.UseAdvancedPrice, + embed: &cc.embed, + log: log, + Helper: request.NewHelper(log), + uri: fmt.Sprintf(amber.URI, strings.ToUpper(cc.SiteID)), + channel: strings.ToLower(cc.Channel), + data: util.NewMonitor[api.Rates](2 * time.Hour), } t.Client.Transport = &transport.Decorator{ @@ -111,7 +108,7 @@ func (t *Amber) run(done chan error) { End: endTime.Local(), Price: r.PerKwh / 1e2, } - if t.useAdvancedPrice && r.AdvancedPrice != nil { + if r.AdvancedPrice != nil { ar.Price = r.AdvancedPrice.Predicted / 1e2 } data = append(data, ar) diff --git a/templates/definition/tariff/amber.yaml b/templates/definition/tariff/amber.yaml index 78f4446cf5..a8ea6620dd 100644 --- a/templates/definition/tariff/amber.yaml +++ b/templates/definition/tariff/amber.yaml @@ -10,12 +10,10 @@ params: - name: token - name: siteid - name: channel - - name: useAdvancedPrice - preset: tariff-base render: | type: amber token: {{ .token }} siteid: {{ .siteid }} channel: {{ .channel }} - useAdvancedPrice: {{ .useAdvancedPrice }} {{ include "tariff-base" . }} From d2469b951dd3a697574fdf6fa0ed949a37fa5fe3 Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Thu, 5 Sep 2024 09:41:47 +1000 Subject: [PATCH 12/72] sync evcc repo --- .github/workflows/sync-upstream.yml | 46 +++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 .github/workflows/sync-upstream.yml diff --git a/.github/workflows/sync-upstream.yml b/.github/workflows/sync-upstream.yml new file mode 100644 index 0000000000..8699927861 --- /dev/null +++ b/.github/workflows/sync-upstream.yml @@ -0,0 +1,46 @@ +name: Sync evcc master Repo Branch + +on: + schedule: + # Run the workflow daily at midnight + - cron: '0 0 * * *' + workflow_dispatch: # Allows manual trigger of the workflow + +jobs: + sync: + runs-on: ubuntu-latest + + steps: + - name: Checkout the repository + uses: actions/checkout@v3 + with: + # Fetch all branches + fetch-depth: 0 + + - name: Set up Git + run: | + git config user.name "GitHub Actions" + git config user.email "actions@github.com" + + - name: Add public repo as remote + run: | + git remote add public https://github.com/evcc-io/evcc.git + + - name: Fetch master branch from public repo + run: | + git fetch public master + + - name: Checkout evcc-master branch + run: | + git checkout evcc-master + + - name: Merge master branch from public repo into evcc-master + run: | + git merge public/master --no-edit + + - name: Push changes to evcc-master + run: | + git push origin evcc-master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + From 5dba5b90ae58682ce6abc3c1f7cd39449d1102fe Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Thu, 5 Sep 2024 09:47:32 +1000 Subject: [PATCH 13/72] ilint workflow --- .github/workflows/sync-upstream.yml | 67 ++++++++++++++--------------- 1 file changed, 33 insertions(+), 34 deletions(-) diff --git a/.github/workflows/sync-upstream.yml b/.github/workflows/sync-upstream.yml index 8699927861..0e509e5041 100644 --- a/.github/workflows/sync-upstream.yml +++ b/.github/workflows/sync-upstream.yml @@ -3,7 +3,7 @@ name: Sync evcc master Repo Branch on: schedule: # Run the workflow daily at midnight - - cron: '0 0 * * *' + - cron: "0 0 * * *" workflow_dispatch: # Allows manual trigger of the workflow jobs: @@ -11,36 +11,35 @@ jobs: runs-on: ubuntu-latest steps: - - name: Checkout the repository - uses: actions/checkout@v3 - with: - # Fetch all branches - fetch-depth: 0 - - - name: Set up Git - run: | - git config user.name "GitHub Actions" - git config user.email "actions@github.com" - - - name: Add public repo as remote - run: | - git remote add public https://github.com/evcc-io/evcc.git - - - name: Fetch master branch from public repo - run: | - git fetch public master - - - name: Checkout evcc-master branch - run: | - git checkout evcc-master - - - name: Merge master branch from public repo into evcc-master - run: | - git merge public/master --no-edit - - - name: Push changes to evcc-master - run: | - git push origin evcc-master - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - + - name: Checkout the repository + uses: actions/checkout@v3 + with: + # Fetch all branches + fetch-depth: 0 + + - name: Set up Git + run: | + git config user.name "GitHub Actions" + git config user.email "actions@github.com" + + - name: Add public repo as remote + run: | + git remote add public https://github.com/evcc-io/evcc.git + + - name: Fetch master branch from public repo + run: | + git fetch public master + + - name: Checkout evcc-master branch + run: | + git checkout evcc-master + + - name: Merge master branch from public repo into evcc-master + run: | + git merge public/master --no-edit + + - name: Push changes to evcc-master + run: | + git push origin evcc-master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 4a10d4be7d33bde6b3528923cf2e7484cf012bb7 Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Thu, 5 Sep 2024 09:50:34 +1000 Subject: [PATCH 14/72] sync evcc releases --- .github/workflows/sync-tags.yml | 83 +++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 .github/workflows/sync-tags.yml diff --git a/.github/workflows/sync-tags.yml b/.github/workflows/sync-tags.yml new file mode 100644 index 0000000000..9029850521 --- /dev/null +++ b/.github/workflows/sync-tags.yml @@ -0,0 +1,83 @@ +name: Sync Missing Tags + +on: + schedule: + # Runs daily at midnight + - cron: "0 0 * * *" + workflow_dispatch: # Allows manual trigger + +jobs: + sync: + runs-on: ubuntu-latest + + steps: + - name: Checkout the repository + uses: actions/checkout@v3 + with: + fetch-depth: 0 # Fetch all branches and tags + + - name: Set up Git + run: | + git config user.name "GitHub Actions" + git config user.email "actions@github.com" + + - name: Add public repo as remote + run: | + git remote add public https://github.com/evcc-io/evcc.git + + - name: Fetch tags from the public repo + run: | + git fetch public --tags + + - name: Get all tags from the public repo + id: get-public-tags + run: | + git tag -l | grep -E '^0\.[0-9]+\.[0-9]+$' > public_tags.txt + echo "Public tags:" + cat public_tags.txt + + - name: Get existing tags in the repo + id: get-existing-tags + run: | + git tag -l | grep -E '^0\.[0-9]+\.[0-9]+\.0$' > existing_tags.txt + echo "Existing tags:" + cat existing_tags.txt + + - name: Sync missing tags + run: | + # Read tags from files + PUBLIC_TAGS=$(cat public_tags.txt) + EXISTING_TAGS=$(cat existing_tags.txt) + + START_TAG="0.130.7" + + for TAG in $PUBLIC_TAGS; do + # Skip tags before the start tag + if [[ "$TAG" < "$START_TAG" ]]; then + continue + fi + + # Generate the new tag name with .0 suffix + NEW_TAG="${TAG}.0" + + # Check if the tag already exists + if ! echo "$EXISTING_TAGS" | grep -q "^${NEW_TAG}$"; then + echo "Processing new tag: $NEW_TAG" + + # Create a new branch for the tag + git checkout -b "tag-${TAG}" + + # Merge the master branch into the new branch + git merge master --no-edit + + # Tag the branch with .0 appended to the original tag name + git tag "${NEW_TAG}" + + # Push the new branch and tag to your repository + git push origin "tag-${TAG}" + git push origin "${NEW_TAG}" + fi + done + + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From a5b364dbf58989341e4195f50be7ad463cdb165b Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Thu, 5 Sep 2024 09:55:30 +1000 Subject: [PATCH 15/72] compare version number a numerically --- .github/workflows/sync-tags.yml | 49 +++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/.github/workflows/sync-tags.yml b/.github/workflows/sync-tags.yml index 9029850521..97e6cc9328 100644 --- a/.github/workflows/sync-tags.yml +++ b/.github/workflows/sync-tags.yml @@ -45,37 +45,46 @@ jobs: - name: Sync missing tags run: | + # Define the starting tag + START_TAG="0.130.7" + + # Convert version to numeric format for comparison + tag_to_numeric() { + echo "$1" | sed 's/\./_/g' | awk -F'_' '{ printf("%d%03d%03d", $1, $2, $3) }' + } + + START_NUMERIC=$(tag_to_numeric $START_TAG) + # Read tags from files PUBLIC_TAGS=$(cat public_tags.txt) EXISTING_TAGS=$(cat existing_tags.txt) - START_TAG="0.130.7" - for TAG in $PUBLIC_TAGS; do - # Skip tags before the start tag - if [[ "$TAG" < "$START_TAG" ]]; then - continue - fi + # Convert the current tag to numeric format + TAG_NUMERIC=$(tag_to_numeric $TAG) - # Generate the new tag name with .0 suffix - NEW_TAG="${TAG}.0" + # Check if the tag is greater than the starting tag + if [ "$TAG_NUMERIC" -gt "$START_NUMERIC" ]; then + # Generate the new tag name with .0 suffix + NEW_TAG="${TAG}.0" - # Check if the tag already exists - if ! echo "$EXISTING_TAGS" | grep -q "^${NEW_TAG}$"; then - echo "Processing new tag: $NEW_TAG" + # Check if the tag already exists + if ! echo "$EXISTING_TAGS" | grep -q "^${NEW_TAG}$"; then + echo "Processing new tag: $NEW_TAG" - # Create a new branch for the tag - git checkout -b "tag-${TAG}" + # Create a new branch for the tag + git checkout -b "tag-${TAG}" - # Merge the master branch into the new branch - git merge master --no-edit + # Merge the master branch into the new branch + git merge master --no-edit - # Tag the branch with .0 appended to the original tag name - git tag "${NEW_TAG}" + # Tag the branch with .0 appended to the original tag name + git tag "${NEW_TAG}" - # Push the new branch and tag to your repository - git push origin "tag-${TAG}" - git push origin "${NEW_TAG}" + # Push the new branch and tag to your repository + git push origin "tag-${TAG}" + git push origin "${NEW_TAG}" + fi fi done From 115c6c6626b819d193caffe9a42bb754717b9b98 Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Mon, 9 Sep 2024 09:27:19 +1000 Subject: [PATCH 16/72] Revert "allow finishing state as starting state" This reverts commit b94391e8293bb95d5bebce837ae98a9480bd934f. --- charger/ocpp/connector.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charger/ocpp/connector.go b/charger/ocpp/connector.go index 75196c0da4..3f545801ae 100644 --- a/charger/ocpp/connector.go +++ b/charger/ocpp/connector.go @@ -147,7 +147,7 @@ func (conn *Connector) NeedsAuthentication() bool { conn.mu.Lock() defer conn.mu.Unlock() - return conn.status != nil && conn.txnId == 0 && (conn.status.Status == core.ChargePointStatusPreparing || conn.status.Status == core.ChargePointStatusFinishing) + return conn.status != nil && conn.txnId == 0 && conn.status.Status == core.ChargePointStatusPreparing } // isMeterTimeout checks if meter values are outdated. From a414e9bd14caf89a0f65aa37fbce6ac92ee34bf5 Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Thu, 12 Sep 2024 07:35:28 +1000 Subject: [PATCH 17/72] for merge tags if conflict create a pr --- .github/workflows/sync-tags.yml | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/.github/workflows/sync-tags.yml b/.github/workflows/sync-tags.yml index 97e6cc9328..6767e9edcc 100644 --- a/.github/workflows/sync-tags.yml +++ b/.github/workflows/sync-tags.yml @@ -21,6 +21,11 @@ jobs: git config user.name "GitHub Actions" git config user.email "actions@github.com" + - name: Install GitHub CLI + uses: cli/cli@v2 + with: + version: "latest" + - name: Add public repo as remote run: | git remote add public https://github.com/evcc-io/evcc.git @@ -75,9 +80,14 @@ jobs: # Create a new branch for the tag git checkout -b "tag-${TAG}" - # Merge the master branch into the new branch - git merge master --no-edit - + # Attempt to merge the master branch into the new branch + git merge master --no-edit || { + # If there's a merge conflict, create a pull request + echo "Merge conflict detected for tag ${TAG}. Creating pull request." + git push origin "tag-${TAG}" + gh pr create --title "Resolve merge conflict for tag ${NEW_TAG}" --body "A merge conflict was detected while syncing tag ${NEW_TAG}. Please resolve the conflict in this PR." --base master --head "tag-${TAG}" + continue + } # Tag the branch with .0 appended to the original tag name git tag "${NEW_TAG}" From 4e0b09590a71df4ce7173b8236111bf1c0643d65 Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Thu, 12 Sep 2024 07:35:44 +1000 Subject: [PATCH 18/72] upon pr merge tag & release --- .github/workflows/sync-tags-pr.yml | 58 ++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 .github/workflows/sync-tags-pr.yml diff --git a/.github/workflows/sync-tags-pr.yml b/.github/workflows/sync-tags-pr.yml new file mode 100644 index 0000000000..b074da32a1 --- /dev/null +++ b/.github/workflows/sync-tags-pr.yml @@ -0,0 +1,58 @@ +name: Sync taggs Finalize Tagging + +on: + pull_request: + types: + - closed + +jobs: + finalize: + if: github.event.pull_request.merged == true + runs-on: ubuntu-latest + + steps: + - name: Checkout the repository + uses: actions/checkout@v3 + with: + fetch-depth: 0 # Fetch all branches and tags + + - name: Set up Git + run: | + git config user.name "GitHub Actions" + git config user.email "actions@github.com" + + - name: Get merged branch name + id: get-branch + run: echo "BRANCH=${{ github.event.pull_request.head.ref }}" >> $GITHUB_ENV + + - name: Validate branch name + id: validate-branch + run: | + # Extract the tag name from the branch name + TAG_NAME=$(echo "${{ env.BRANCH }}" | sed 's/tag-//') + + # Check if the tag name matches the expected pattern + if [[ ! "$TAG_NAME" =~ ^0\.[0-9]+\.[0-9]+$ ]]; then + echo "Branch name does not match the expected pattern. Skipping tag creation." + exit 1 + else + echo "Valid branch name: $TAG_NAME" + fi + continue-on-error: true + + - name: Tag the merged branch + if: success() && steps.validate-branch.outcome == 'success' + run: | + # Checkout the branch that was merged + git checkout "${{ env.BRANCH }}" + + # Create the new tag + NEW_TAG="${{ steps.get-branch.outputs.BRANCH }}.0" + + # Tag the branch + git tag "${NEW_TAG}" + + # Push the tag to the repository + git push origin "${NEW_TAG}" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 1ad9f553ff80770fceadcf6d43c807191836bd17 Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Thu, 12 Sep 2024 07:38:34 +1000 Subject: [PATCH 19/72] I think gh command is already installed --- .github/workflows/sync-tags.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/workflows/sync-tags.yml b/.github/workflows/sync-tags.yml index 6767e9edcc..927d515fee 100644 --- a/.github/workflows/sync-tags.yml +++ b/.github/workflows/sync-tags.yml @@ -21,11 +21,6 @@ jobs: git config user.name "GitHub Actions" git config user.email "actions@github.com" - - name: Install GitHub CLI - uses: cli/cli@v2 - with: - version: "latest" - - name: Add public repo as remote run: | git remote add public https://github.com/evcc-io/evcc.git From 9a96a4b5095e37037e98900b0faf19f8a81fcea1 Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Thu, 12 Sep 2024 07:50:28 +1000 Subject: [PATCH 20/72] Fixed github action for tagging --- .github/workflows/sync-tags.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sync-tags.yml b/.github/workflows/sync-tags.yml index 927d515fee..dacf043af1 100644 --- a/.github/workflows/sync-tags.yml +++ b/.github/workflows/sync-tags.yml @@ -73,7 +73,7 @@ jobs: echo "Processing new tag: $NEW_TAG" # Create a new branch for the tag - git checkout -b "tag-${TAG}" + git checkout -b "tag-${TAG}" ${TAG} # Attempt to merge the master branch into the new branch git merge master --no-edit || { From d9c7a96f6230bd06408393fa2a2d8a5b823828b5 Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Thu, 12 Sep 2024 08:42:50 +1000 Subject: [PATCH 21/72] use pat for checkout to allow action triggering --- .github/workflows/sync-tags-pr.yml | 1 + .github/workflows/sync-tags.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/sync-tags-pr.yml b/.github/workflows/sync-tags-pr.yml index b074da32a1..641b3b33b2 100644 --- a/.github/workflows/sync-tags-pr.yml +++ b/.github/workflows/sync-tags-pr.yml @@ -15,6 +15,7 @@ jobs: uses: actions/checkout@v3 with: fetch-depth: 0 # Fetch all branches and tags + token: ${{ secrets.EVCC_PAT }} - name: Set up Git run: | diff --git a/.github/workflows/sync-tags.yml b/.github/workflows/sync-tags.yml index dacf043af1..c104f9d3ef 100644 --- a/.github/workflows/sync-tags.yml +++ b/.github/workflows/sync-tags.yml @@ -15,6 +15,7 @@ jobs: uses: actions/checkout@v3 with: fetch-depth: 0 # Fetch all branches and tags + token: ${{ secrets.EVCC_PAT }} - name: Set up Git run: | From 3d2495887019f5284076c73d3b33e7e5205260ed Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Tue, 17 Sep 2024 11:30:40 +1000 Subject: [PATCH 22/72] change cron times --- .github/workflows/nightly.yml | 2 +- .github/workflows/sync-tags.yml | 4 ++-- .github/workflows/sync-upstream.yml | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index a9f570ffa6..0cc1802664 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -5,7 +5,7 @@ env: on: schedule: # runs on the default branch: master - - cron: "0 2 * * *" # run at 2 AM UTC + - cron: "10 20 * * *" # run at 6:10 AM workflow_dispatch: jobs: diff --git a/.github/workflows/sync-tags.yml b/.github/workflows/sync-tags.yml index c104f9d3ef..62ea6915b1 100644 --- a/.github/workflows/sync-tags.yml +++ b/.github/workflows/sync-tags.yml @@ -2,8 +2,8 @@ name: Sync Missing Tags on: schedule: - # Runs daily at midnight - - cron: "0 0 * * *" + # Runs daily at 6am + - cron: "0 20 * * *" workflow_dispatch: # Allows manual trigger jobs: diff --git a/.github/workflows/sync-upstream.yml b/.github/workflows/sync-upstream.yml index 0e509e5041..1e89114fe7 100644 --- a/.github/workflows/sync-upstream.yml +++ b/.github/workflows/sync-upstream.yml @@ -2,8 +2,8 @@ name: Sync evcc master Repo Branch on: schedule: - # Run the workflow daily at midnight - - cron: "0 0 * * *" + # Run the workflow daily at 6am + - cron: "0 20 * * *" workflow_dispatch: # Allows manual trigger of the workflow jobs: From c98207f8a7be95243f14ef59495e420d793b2cce Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Tue, 17 Sep 2024 11:36:51 +1000 Subject: [PATCH 23/72] build merged master --- .github/workflows/nightly.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 0cc1802664..ada195ce7e 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -43,9 +43,13 @@ jobs: steps: - uses: actions/checkout@v4 with: - ref: refs/heads/master # force master + ref: refs/heads/evcc-master # force master fetch-depth: 0 + - name: Merge our master branch into evcc-master for building + run: | + git merge origin/master --no-edit + - name: Get dist from cache uses: actions/cache/restore@v4 id: cache-dist From b259c1ab971badd0b9422be780f84e52e781779c Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Tue, 17 Sep 2024 12:06:16 +1000 Subject: [PATCH 24/72] setup git for nightly merge before build --- .github/workflows/nightly.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index ada195ce7e..139f9ab639 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -46,6 +46,11 @@ jobs: ref: refs/heads/evcc-master # force master fetch-depth: 0 + - name: Set up Git + run: | + git config user.name "GitHub Actions" + git config user.email "actions@github.com" + - name: Merge our master branch into evcc-master for building run: | git merge origin/master --no-edit From b322ba1ed74daf4231a800310c7d0fa28202f41c Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Fri, 4 Oct 2024 07:03:26 +1000 Subject: [PATCH 25/72] allow pushing changes to workflows --- .github/workflows/sync-upstream.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/sync-upstream.yml b/.github/workflows/sync-upstream.yml index 1e89114fe7..ba4231a0e0 100644 --- a/.github/workflows/sync-upstream.yml +++ b/.github/workflows/sync-upstream.yml @@ -16,6 +16,7 @@ jobs: with: # Fetch all branches fetch-depth: 0 + token: ${{ secrets.EVCC_PAT }} - name: Set up Git run: | @@ -41,5 +42,3 @@ jobs: - name: Push changes to evcc-master run: | git push origin evcc-master - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From d696c16c1ccfa8d583fbc12929478f664f974dca Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Fri, 4 Oct 2024 07:16:12 +1000 Subject: [PATCH 26/72] adjust nightly build to not merge github workflows --- .github/workflows/nightly.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 139f9ab639..778fb5ad4d 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -36,8 +36,8 @@ jobs: docker: name: Publish Docker :nightly - needs: - - call-build-workflow + # needs: + # - call-build-workflow runs-on: ubuntu-latest steps: @@ -53,7 +53,9 @@ jobs: - name: Merge our master branch into evcc-master for building run: | - git merge origin/master --no-edit + git merge origin/master --no-commit --no-ff + git reset -- /.github/workflows + git commit -m "merge from evcc/master" - name: Get dist from cache uses: actions/cache/restore@v4 From 781090cebb423662ad29f857eb66e29aede0da5f Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Fri, 4 Oct 2024 07:19:29 +1000 Subject: [PATCH 27/72] adjust night master merge --- .github/workflows/nightly.yml | 46 +++++++++++++++++------------------ 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 778fb5ad4d..d36ba81de5 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -9,30 +9,30 @@ on: workflow_dispatch: jobs: - check_date: - runs-on: ubuntu-latest - name: Check latest commit - outputs: - should_run: ${{ steps.should_run.outputs.should_run }} - steps: - - uses: actions/checkout@v4 - - name: print latest_commit - run: echo ${{ github.sha }} + # check_date: + # runs-on: ubuntu-latest + # name: Check latest commit + # outputs: + # should_run: ${{ steps.should_run.outputs.should_run }} + # steps: + # - uses: actions/checkout@v4 + # - name: print latest_commit + # run: echo ${{ github.sha }} - - id: should_run - continue-on-error: true - name: check latest commit is less than a day - if: ${{ github.event_name == 'schedule' }} - run: test -z $(git rev-list --after="24 hours" ${{ github.sha }}) && echo "should_run=false" >> $GITHUB_OUTPUT + # - id: should_run + # continue-on-error: true + # name: check latest commit is less than a day + # if: ${{ github.event_name == 'schedule' }} + # run: test -z $(git rev-list --after="24 hours" ${{ github.sha }}) && echo "should_run=false" >> $GITHUB_OUTPUT - call-build-workflow: - name: Call Build - needs: check_date - if: | - ${{ needs.check_date.outputs.should_run != 'false' }} - && startsWith(github.ref, 'refs/heads/master') - && ! contains(github.head_ref, 'refs/heads/chore/') - uses: evcc-io/evcc/.github/workflows/default.yml@master + # call-build-workflow: + # name: Call Build + # needs: check_date + # if: | + # ${{ needs.check_date.outputs.should_run != 'false' }} + # && startsWith(github.ref, 'refs/heads/master') + # && ! contains(github.head_ref, 'refs/heads/chore/') + # uses: evcc-io/evcc/.github/workflows/default.yml@master docker: name: Publish Docker :nightly @@ -53,7 +53,7 @@ jobs: - name: Merge our master branch into evcc-master for building run: | - git merge origin/master --no-commit --no-ff + git merge origin/master --no-commit --no-ff || true git reset -- /.github/workflows git commit -m "merge from evcc/master" From ff3bba09f9ff6b3970471e8972fb25e1728d37a6 Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Fri, 4 Oct 2024 07:21:11 +1000 Subject: [PATCH 28/72] fixed git reset --- .github/workflows/nightly.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index d36ba81de5..a7e52da679 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -54,7 +54,7 @@ jobs: - name: Merge our master branch into evcc-master for building run: | git merge origin/master --no-commit --no-ff || true - git reset -- /.github/workflows + git reset -- .github/workflows git commit -m "merge from evcc/master" - name: Get dist from cache From 01cd0d9a9f6bfd6e78f10314becebd88a89ed675 Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Tue, 15 Oct 2024 15:26:06 +1100 Subject: [PATCH 29/72] change planner slot times --- core/loadpoint_plan.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/loadpoint_plan.go b/core/loadpoint_plan.go index 389ecbb5b4..6734c2e519 100644 --- a/core/loadpoint_plan.go +++ b/core/loadpoint_plan.go @@ -11,8 +11,8 @@ import ( ) const ( - smallSlotDuration = 10 * time.Minute // small planner slot duration we might ignore - smallGapDuration = 60 * time.Minute // small gap duration between planner slots we might ignore + smallSlotDuration = 2 * time.Minute // small planner slot duration we might ignore + smallGapDuration = 29 * time.Minute // small gap duration between planner slots we might ignore ) // TODO planActive is not guarded by mutex From a16226fc290cc84c83bdd057b2c8bca498606b9f Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Thu, 24 Oct 2024 12:10:06 +1100 Subject: [PATCH 30/72] Fix sync tags pr task --- .github/workflows/sync-tags-pr.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sync-tags-pr.yml b/.github/workflows/sync-tags-pr.yml index 641b3b33b2..2d189fa575 100644 --- a/.github/workflows/sync-tags-pr.yml +++ b/.github/workflows/sync-tags-pr.yml @@ -37,7 +37,8 @@ jobs: echo "Branch name does not match the expected pattern. Skipping tag creation." exit 1 else - echo "Valid branch name: $TAG_NAME" + echo "Valid branch name: $TAG_NAME Saving for next step" + echo "BRANCH=$TAG_NAME" >> $GITHUB_ENV fi continue-on-error: true @@ -48,7 +49,7 @@ jobs: git checkout "${{ env.BRANCH }}" # Create the new tag - NEW_TAG="${{ steps.get-branch.outputs.BRANCH }}.0" + NEW_TAG="${{ steps.validate-branch.outputs.BRANCH }}.0" # Tag the branch git tag "${NEW_TAG}" From 673d61a00284c40dd58d62997f185f4e574a49df Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Thu, 24 Oct 2024 12:17:44 +1100 Subject: [PATCH 31/72] allow manual trigger --- .github/workflows/sync-tags-pr.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/sync-tags-pr.yml b/.github/workflows/sync-tags-pr.yml index 2d189fa575..90aa704a4a 100644 --- a/.github/workflows/sync-tags-pr.yml +++ b/.github/workflows/sync-tags-pr.yml @@ -4,6 +4,7 @@ on: pull_request: types: - closed + workflow_dispatch: # Allows manual trigger of the workflow jobs: finalize: From d3fee652ebe4abef8ab476f4775b2376defbf134 Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Mon, 4 Nov 2024 12:29:53 +1100 Subject: [PATCH 32/72] disable sponsor --- util/sponsor/auth.go | 89 +++++++++++++++++++++----------------------- 1 file changed, 43 insertions(+), 46 deletions(-) diff --git a/util/sponsor/auth.go b/util/sponsor/auth.go index 56f4e59d47..de8e45bcc0 100644 --- a/util/sponsor/auth.go +++ b/util/sponsor/auth.go @@ -1,15 +1,8 @@ package sponsor import ( - "context" - "fmt" "sync" "time" - - "github.com/evcc-io/evcc/api/proto/pb" - "github.com/evcc-io/evcc/util/cloud" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" ) var ( @@ -40,45 +33,49 @@ func ConfigureSponsorship(token string) error { mu.Lock() defer mu.Unlock() - if token == "" { - if sub := checkVictron(); sub != "" { - Subject = sub - return nil - } - - var err error - if token, err = readSerial(); token == "" || err != nil { - return err - } - } - - conn, err := cloud.Connection() - if err != nil { - return err - } - - client := pb.NewAuthClient(conn) - - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - res, err := client.IsAuthorized(ctx, &pb.AuthRequest{Token: token}) - if err == nil && res.Authorized { - Subject = res.Subject - ExpiresAt = res.ExpiresAt.AsTime() - Token = token - } - - if err != nil { - if s, ok := status.FromError(err); ok && s.Code() != codes.Unknown { - Subject = unavailable - err = nil - } else { - err = fmt.Errorf("sponsortoken: %w", err) - } - } - - return err + Subject = "EVCC" + + // if token == "" { + // if sub := checkVictron(); sub != "" { + // Subject = sub + // return nil + // } + + // var err error + // if token, err = readSerial(); token == "" || err != nil { + // return err + // } + // } + + // conn, err := cloud.Connection() + // if err != nil { + // return err + // } + + // client := pb.NewAuthClient(conn) + + // ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + // defer cancel() + + // res, err := client.IsAuthorized(ctx, &pb.AuthRequest{Token: token}) + // if err == nil && res.Authorized { + // Subject = res.Subject + // ExpiresAt = res.ExpiresAt.AsTime() + // Token = token + // } + + // if err != nil { + // if s, ok := status.FromError(err); ok && s.Code() != codes.Unknown { + // Subject = unavailable + // err = nil + // } else { + // err = fmt.Errorf("sponsortoken: %w", err) + // } + // } + + // return err + + return nil } type sponsorStatus struct { From 2d17874c7908dfbcecdd4d713da4ea3167ec49de Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Mon, 4 Nov 2024 12:35:10 +1100 Subject: [PATCH 33/72] Cleanup --- util/sponsor/pulsares.go | 39 -------------- util/sponsor/victron.go | 114 --------------------------------------- 2 files changed, 153 deletions(-) delete mode 100644 util/sponsor/pulsares.go delete mode 100644 util/sponsor/victron.go diff --git a/util/sponsor/pulsares.go b/util/sponsor/pulsares.go deleted file mode 100644 index a3f2f8cb4f..0000000000 --- a/util/sponsor/pulsares.go +++ /dev/null @@ -1,39 +0,0 @@ -package sponsor - -import ( - "os" - "strings" - "time" -) - -func readSerial() (string, error) { - f, err := os.OpenFile("/dev/PulsaresSerial", os.O_RDWR, 0o644) - if err != nil { - return "", nil - } - - if _, err := f.Write([]byte{0x0E, 0x00, 0x61, 0x7C, 0xD2, 0x71}); err != nil { - return "", err - } - - // serial timeout - time.AfterFunc(3*time.Second, func() { - _ = f.Close() - }) - - var token string - b := make([]byte, 512) - - for { - n, err := f.Read(b) - if err != nil { - return "", nil - } - - token += string(b[:n]) - - if token, ok := strings.CutSuffix(token, "\x04"); ok { - return token, nil - } - } -} diff --git a/util/sponsor/victron.go b/util/sponsor/victron.go deleted file mode 100644 index c6a774780a..0000000000 --- a/util/sponsor/victron.go +++ /dev/null @@ -1,114 +0,0 @@ -package sponsor - -import ( - "context" - "errors" - "os/exec" - "runtime" - "strings" - "time" - - "github.com/evcc-io/evcc/api/proto/pb" - "github.com/evcc-io/evcc/util/cloud" - "github.com/evcc-io/evcc/util/request" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" -) - -// checkVictron checks if the hardware is a supported victron device and returns sponsor subject -func checkVictron() string { - vd, err := victronDeviceInfo() - if err != nil { - // unable to retrieve all device info - return "" - } - - conn, err := cloud.Connection() - if err != nil { - // unable to connect to cloud - return unavailable - } - - ctx, cancel := context.WithTimeout(context.Background(), request.Timeout) - defer cancel() - - client := pb.NewVictronClient(conn) - res, err := client.IsValidDevice(ctx, &pb.VictronRequest{ - ProductId: vd.ProductId, - VrmId: vd.VrmId, - Serial: vd.Serial, - Board: vd.Board, - }) - - if err == nil && res.Authorized { - // cloud check successful - return victron - } - - if s, ok := status.FromError(err); ok && s.Code() != codes.Unknown { - // technical error during validation - return unavailable - } - - return "" -} - -type victronDevice struct { - ProductId string - VrmId string - Serial string - Board string -} - -func commandExists(cmd string) error { - _, err := exec.LookPath(cmd) - return err -} - -func executeCommand(ctx context.Context, cmd string, args ...string) (string, error) { - command := exec.CommandContext(ctx, cmd, args...) - output, err := command.Output() - if err != nil { - return "", err - } - result := strings.TrimSpace(string(output)) - if result == "" { - return "", errors.New("empty output") - } - return result, nil -} - -func victronDeviceInfo() (victronDevice, error) { - if runtime.GOOS != "linux" { - return victronDevice{}, errors.New("non-linux os") - } - - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - - var vd victronDevice - - commands := []struct { - field *string - cmd string - args []string - }{ - {field: &vd.Board, cmd: "/usr/bin/board-compat"}, - {field: &vd.ProductId, cmd: "/usr/bin/product-id"}, - {field: &vd.VrmId, cmd: "/sbin/get-unique-id"}, - {field: &vd.Serial, cmd: "/opt/victronenergy/venus-eeprom/eeprom", args: []string{"--show", "serial-number"}}, - } - - for _, detail := range commands { - if err := commandExists(detail.cmd); err != nil { - return vd, errors.New("cmd not found: " + detail.cmd) - } - output, err := executeCommand(ctx, detail.cmd, detail.args...) - if err != nil { - return vd, err - } - *detail.field = output - } - - return vd, nil -} From 3498cab5208668a2e7d91ddbab9e5f16a18d90f8 Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Sun, 22 Dec 2024 20:54:54 +0000 Subject: [PATCH 34/72] revert loadpoint plan --- core/loadpoint_plan.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/loadpoint_plan.go b/core/loadpoint_plan.go index dab405b392..319fcec288 100644 --- a/core/loadpoint_plan.go +++ b/core/loadpoint_plan.go @@ -11,8 +11,8 @@ import ( ) const ( - smallSlotDuration = 2 * time.Minute // small planner slot duration we might ignore - smallGapDuration = 29 * time.Minute // small gap duration between planner slots we might ignore + smallSlotDuration = 10 * time.Minute // small planner slot duration we might ignore + smallGapDuration = 60 * time.Minute // small gap duration between planner slots we might ignore ) // TODO planActive is not guarded by mutex From bff5356ba502e684b2bb141794d81b58f80de46f Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Sun, 22 Dec 2024 21:20:46 +0000 Subject: [PATCH 35/72] MInor fix to allow docker building --- Dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Dockerfile b/Dockerfile index 99057a1c57..83a80ae15a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -40,6 +40,9 @@ RUN go mod download # install tools COPY Makefile . COPY tools.go . +COPY cmd cmd +COPY api api + RUN make install # prepare From 45b57c01db42f3073b41c6468a47aef83dda0ebc Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Fri, 27 Dec 2024 09:33:14 +1100 Subject: [PATCH 36/72] Revert "MInor fix to allow docker building" This reverts commit bff5356ba502e684b2bb141794d81b58f80de46f. --- Dockerfile | 3 --- 1 file changed, 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 83a80ae15a..99057a1c57 100644 --- a/Dockerfile +++ b/Dockerfile @@ -40,9 +40,6 @@ RUN go mod download # install tools COPY Makefile . COPY tools.go . -COPY cmd cmd -COPY api api - RUN make install # prepare From 73a3a260164c108083b6bf0395ed80c017422f83 Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Mon, 17 Feb 2025 09:52:22 +1100 Subject: [PATCH 37/72] Fix sync tags task --- .github/workflows/sync-tags-pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sync-tags-pr.yml b/.github/workflows/sync-tags-pr.yml index 90aa704a4a..dfc7a78a94 100644 --- a/.github/workflows/sync-tags-pr.yml +++ b/.github/workflows/sync-tags-pr.yml @@ -39,7 +39,7 @@ jobs: exit 1 else echo "Valid branch name: $TAG_NAME Saving for next step" - echo "BRANCH=$TAG_NAME" >> $GITHUB_ENV + echo "BRANCH=$TAG_NAME" >> $GITHUB_OUTPUT fi continue-on-error: true From 97cb5b0faa4d74f10ecae1848513454d6adee93f Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Mon, 17 Feb 2025 09:55:54 +1100 Subject: [PATCH 38/72] Fix order for pr --- .github/workflows/sync-tags.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sync-tags.yml b/.github/workflows/sync-tags.yml index 62ea6915b1..3ebc51a37d 100644 --- a/.github/workflows/sync-tags.yml +++ b/.github/workflows/sync-tags.yml @@ -81,7 +81,7 @@ jobs: # If there's a merge conflict, create a pull request echo "Merge conflict detected for tag ${TAG}. Creating pull request." git push origin "tag-${TAG}" - gh pr create --title "Resolve merge conflict for tag ${NEW_TAG}" --body "A merge conflict was detected while syncing tag ${NEW_TAG}. Please resolve the conflict in this PR." --base master --head "tag-${TAG}" + gh pr create --title "Resolve merge conflict for tag ${NEW_TAG}" --body "A merge conflict was detected while syncing tag ${NEW_TAG}. Please resolve the conflict in this PR." --head master --base "tag-${TAG}" continue } # Tag the branch with .0 appended to the original tag name From 9cbd7b0f64263396159d8696f6e710f0235bb98f Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Mon, 17 Feb 2025 09:59:03 +1100 Subject: [PATCH 39/72] Fix base tag name --- .github/workflows/sync-tags-pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sync-tags-pr.yml b/.github/workflows/sync-tags-pr.yml index dfc7a78a94..6bb1ab2e70 100644 --- a/.github/workflows/sync-tags-pr.yml +++ b/.github/workflows/sync-tags-pr.yml @@ -25,7 +25,7 @@ jobs: - name: Get merged branch name id: get-branch - run: echo "BRANCH=${{ github.event.pull_request.head.ref }}" >> $GITHUB_ENV + run: echo "BRANCH=${{ github.event.pull_request.base.ref }}" >> $GITHUB_ENV - name: Validate branch name id: validate-branch From 604c2df7eb84c708f28d56243a4db500dd523b35 Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Mon, 17 Feb 2025 14:10:12 +1100 Subject: [PATCH 40/72] removed deploy templates --- .github/workflows/documentation.yml | 34 ----------------------------- 1 file changed, 34 deletions(-) delete mode 100644 .github/workflows/documentation.yml diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml deleted file mode 100644 index 96a0f6d063..0000000000 --- a/.github/workflows/documentation.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: Deploy updated templates - -on: - schedule: - - cron: "0 2 * * *" # same time as nightly is triggered - release: - types: [created] - workflow_dispatch: - -jobs: - docupdate: - name: Deploy updated templates - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-go@v5 - id: go - - - name: Build docs - run: make install docs - - - name: Deploy to docs repo - uses: peaceiris/actions-gh-pages@v4 - with: - personal_token: ${{ secrets.GH_TOKEN }} - publish_dir: ./templates/docs - external_repository: evcc-io/docs - publish_branch: main - destination_dir: ${{ github.event_name == 'release' && 'templates/release' || github.event_name == 'schedule' && 'templates/nightly' || 'templates/unknown_trigger' }} - allow_empty_commit: false - commit_message: ${{ github.event_name == 'release' && 'Templates update for release' || github.event_name == 'schedule' && 'Templates update for nightly' || 'Templates update unknown trigger' }} - if: success() From 4f683226306a716258fa2d21f71697ad5e564583 Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Tue, 18 Feb 2025 09:32:53 +1100 Subject: [PATCH 41/72] Fix nightly image location --- .github/workflows/nightly.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index ef34be04d5..ec8fe9017b 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -75,7 +75,7 @@ jobs: id: meta uses: docker/metadata-action@v5 with: - images: evcc/evcc + images: ghcr.io/jeffborg/evcc tags: | type=raw,value=nightly type=raw,value=nightly.{{date 'YYYYMMDD'}}-{{sha}} From 60b8349487d99c8919a52b844baf700ae9723024 Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Thu, 20 Feb 2025 09:46:48 +1100 Subject: [PATCH 42/72] delete old nightly tags --- .github/workflows/nightly.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index ec8fe9017b..767f29aedb 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -87,3 +87,18 @@ jobs: platforms: linux/amd64,linux/arm64,linux/arm/v6 push: true tags: ${{ steps.meta.outputs.tags }} + - name: Delete old nightly.* tags from GHCR + env: + GHCR_USERNAME: ${{ github.actor }} + GHCR_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Or use a PAT with 'write:packages' scope + run: | + repo="ghcr.io/jeffborg/evcc" + tags=$(curl -s -H "Authorization: Bearer $GHCR_TOKEN" "https://api.github.com/users/jeffborg/packages/container/evcc/versions" | jq -r '.[] | select(.metadata.container.tags[] | startswith("nightly.")) | {id: .id, updated_at: .updated_at} | @base64') + + sorted_tags=$(echo "$tags" | base64 --decode | jq -s 'sort_by(.updated_at) | reverse | .[2:]') + + for tag in $(echo "$sorted_tags" | jq -r '.[].id'); do + echo "Deleting tag ID: $tag" + curl -s -X DELETE -H "Authorization: Bearer $GHCR_TOKEN" "https://api.github.com/users/jeffborg/packages/container/evcc/versions/$tag" + done + \ No newline at end of file From 2b85b7038285ef4da7362a04722923c24ab12b5b Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Thu, 20 Feb 2025 11:07:46 +1100 Subject: [PATCH 43/72] fix broken porcelin --- .github/workflows/nightly.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 767f29aedb..f1e69d2876 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -94,9 +94,9 @@ jobs: run: | repo="ghcr.io/jeffborg/evcc" tags=$(curl -s -H "Authorization: Bearer $GHCR_TOKEN" "https://api.github.com/users/jeffborg/packages/container/evcc/versions" | jq -r '.[] | select(.metadata.container.tags[] | startswith("nightly.")) | {id: .id, updated_at: .updated_at} | @base64') - + sorted_tags=$(echo "$tags" | base64 --decode | jq -s 'sort_by(.updated_at) | reverse | .[2:]') - + for tag in $(echo "$sorted_tags" | jq -r '.[].id'); do echo "Deleting tag ID: $tag" curl -s -X DELETE -H "Authorization: Bearer $GHCR_TOKEN" "https://api.github.com/users/jeffborg/packages/container/evcc/versions/$tag" From 90c0e35deeab80d505bb1b81cd83818a05777799 Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Thu, 20 Feb 2025 11:15:13 +1100 Subject: [PATCH 44/72] fix porcelin --- .github/workflows/nightly.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index f1e69d2876..0e9197c937 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -100,5 +100,4 @@ jobs: for tag in $(echo "$sorted_tags" | jq -r '.[].id'); do echo "Deleting tag ID: $tag" curl -s -X DELETE -H "Authorization: Bearer $GHCR_TOKEN" "https://api.github.com/users/jeffborg/packages/container/evcc/versions/$tag" - done - \ No newline at end of file + done \ No newline at end of file From cda3ce88cf32a0e6166c17af4d139dde8d637fd2 Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Thu, 20 Feb 2025 11:23:53 +1100 Subject: [PATCH 45/72] fix porcelin 4th attempt --- .github/workflows/nightly.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 0e9197c937..58730db929 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -100,4 +100,4 @@ jobs: for tag in $(echo "$sorted_tags" | jq -r '.[].id'); do echo "Deleting tag ID: $tag" curl -s -X DELETE -H "Authorization: Bearer $GHCR_TOKEN" "https://api.github.com/users/jeffborg/packages/container/evcc/versions/$tag" - done \ No newline at end of file + done From 000f28c9ffda39f42e1105185806c2d96bea90e6 Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Mon, 2 Jun 2025 05:24:38 +0000 Subject: [PATCH 46/72] change planner warn to info sick of warnings during 6 hour demand window --- core/site.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/site.go b/core/site.go index e905f07fc0..f54327d0a6 100644 --- a/core/site.go +++ b/core/site.go @@ -883,7 +883,7 @@ func (site *Site) update(lp updater) { ) } - site.log.WARN.Println("planner:", msg) + site.log.INFO.Println("planner:", msg) } batteryGridChargeActive := site.batteryGridChargeActive(rate) From 4e2c6abb9a5d9defc21b18f24e8518bc53f50a64 Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Thu, 31 Jul 2025 07:37:14 +1000 Subject: [PATCH 47/72] fix runners --- .github/workflows/claude-issue-triage.yml | 2 +- .github/workflows/default.yml | 12 ++++++------ .github/workflows/docs-issue.yml | 2 +- .github/workflows/schema.yml | 2 +- .github/workflows/website.yml | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/claude-issue-triage.yml b/.github/workflows/claude-issue-triage.yml index 233418b1d6..2144318185 100644 --- a/.github/workflows/claude-issue-triage.yml +++ b/.github/workflows/claude-issue-triage.yml @@ -6,7 +6,7 @@ on: jobs: triage-issue: - runs-on: depot-ubuntu-24.04-arm + runs-on: ubuntu-24.04 timeout-minutes: 10 permissions: contents: read diff --git a/.github/workflows/default.yml b/.github/workflows/default.yml index 4baf0a663c..be26d5cdec 100644 --- a/.github/workflows/default.yml +++ b/.github/workflows/default.yml @@ -10,7 +10,7 @@ on: jobs: clean: name: Clean - runs-on: depot-ubuntu-24.04-arm + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 @@ -45,7 +45,7 @@ jobs: build: name: Build - runs-on: depot-ubuntu-24.04-arm + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 @@ -78,7 +78,7 @@ jobs: test: name: Test - runs-on: depot-ubuntu-24.04-arm + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 @@ -104,7 +104,7 @@ jobs: lint: name: Lint - runs-on: depot-ubuntu-24.04-arm + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 @@ -135,7 +135,7 @@ jobs: ui: name: UI - runs-on: depot-ubuntu-24.04-arm + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 @@ -170,7 +170,7 @@ jobs: integration: name: Integration - runs-on: depot-ubuntu-24.04-arm-16 + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/docs-issue.yml b/.github/workflows/docs-issue.yml index 72e45cdb59..8e41810c6c 100644 --- a/.github/workflows/docs-issue.yml +++ b/.github/workflows/docs-issue.yml @@ -7,7 +7,7 @@ on: jobs: check-label-and-create-issue: - runs-on: depot-ubuntu-24.04-arm + runs-on: ubuntu-24.04 if: github.event.pull_request.merged == true steps: - name: Check for 'needs documentation' label diff --git a/.github/workflows/schema.yml b/.github/workflows/schema.yml index 6c651fc889..6dcc57ae50 100644 --- a/.github/workflows/schema.yml +++ b/.github/workflows/schema.yml @@ -12,7 +12,7 @@ on: jobs: build: - runs-on: depot-ubuntu-24.04-arm + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index e14d31df2e..43aa67569a 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -8,7 +8,7 @@ on: jobs: brandupdate: name: Deploy data to website - runs-on: depot-ubuntu-24.04-arm + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 From 6a1fa8162d04d6917844872751cc97cfe65fdcd7 Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Thu, 31 Jul 2025 08:22:55 +1000 Subject: [PATCH 48/72] fix reference to default tasks --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 19a0595469..d840042939 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,7 +8,7 @@ on: jobs: call-build-workflow: if: startsWith(github.ref, 'refs/tags') - uses: evcc-io/evcc/.github/workflows/default.yml@master + uses: jeffborg/evcc/.github/workflows/default.yml@master docker: name: Publish Docker :release From 604f811f6e1e4a35b9e24e7b397ff2cfa0338687 Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Mon, 4 Aug 2025 07:41:47 +1000 Subject: [PATCH 49/72] Fix broken workflow --- .github/workflows/stale.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml index a3cd850f07..fbad92d02a 100644 --- a/.github/workflows/stale.yaml +++ b/.github/workflows/stale.yaml @@ -9,7 +9,7 @@ on: jobs: stale: - runs-on: depot-ubuntu-24.04-arm + runs-on: ubuntu-24.04 steps: - uses: actions/stale@v9 id: stale From 592dab4c09713ccef3f95e0ca08b4e77541dd658 Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Tue, 5 Aug 2025 08:03:25 +1000 Subject: [PATCH 50/72] removed failing test --- tests/config-loadpoint.spec.ts | 33 --------------------------------- 1 file changed, 33 deletions(-) diff --git a/tests/config-loadpoint.spec.ts b/tests/config-loadpoint.spec.ts index e3bf82e5bc..59d6945458 100644 --- a/tests/config-loadpoint.spec.ts +++ b/tests/config-loadpoint.spec.ts @@ -637,36 +637,3 @@ temp: await expect(lpModal.getByRole("button", { name: "Add heating device" })).toBeVisible(); }); }); - -test.describe("sponsor token", () => { - test("create charger with missing sponsor token", async ({ page }) => { - await start(); - await page.goto("/#/config"); - await enableExperimental(page); - - // add loadpoint with OCPP charger - await newLoadpoint(page, "OCPP Test Charger"); - await page.getByTestId("loadpoint-modal").getByRole("button", { name: "Add charger" }).click(); - const chargerModal = page.getByTestId("charger-modal"); - await expectModalVisible(chargerModal); - await chargerModal.getByLabel("Manufacturer").selectOption({ label: "OCPP 1.6J compatible" }); - - // verify disabled save button - await expect(chargerModal.getByRole("button", { name: "Save" })).toBeDisabled(); - - // verify sponsor notice - await expect(chargerModal).toContainText( - "You must configure a sponsor token before you can create this device." - ); - const testResult = chargerModal.getByTestId("test-result"); - await testResult.getByRole("link", { name: "Validate" }).click(); - const sponsorMessage = testResult.getByText("No sponsor token configured."); - await expect(sponsorMessage).toBeVisible(); - - // verify click on sponsor - await sponsorMessage.click(); - const sponsorModal = page.getByTestId("sponsor-modal"); - await expectModalVisible(sponsorModal); - await expect(sponsorModal.getByRole("heading")).toContainText("Sponsorship"); - }); -}); From 79ce16b6c0a5e44ab82545c7d2891d52ae69b9ae Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Mon, 18 Aug 2025 08:18:00 +1000 Subject: [PATCH 51/72] remove sponsor tests --- tests/sponsor.evcc.yaml | 17 --------- tests/sponsor.spec.ts | 80 ----------------------------------------- tests/sponsor.sql | 26 -------------- 3 files changed, 123 deletions(-) delete mode 100644 tests/sponsor.evcc.yaml delete mode 100644 tests/sponsor.spec.ts delete mode 100644 tests/sponsor.sql diff --git a/tests/sponsor.evcc.yaml b/tests/sponsor.evcc.yaml deleted file mode 100644 index 25877d7d4d..0000000000 --- a/tests/sponsor.evcc.yaml +++ /dev/null @@ -1,17 +0,0 @@ -# expired sponsor token -sponsortoken: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJldmNjLmlvIiwic3ViIjoidHJpYWwiLCJleHAiOjE3NTQ5OTI4MDAsImlhdCI6MTc1MzY5NjgwMCwic3BlIjp0cnVlLCJzcmMiOiJtYSJ9.XKa5DHT-icCM9awcX4eS8feW0J_KIjsx2IxjcRRQOcQ - -site: - title: Sponsor Test - -loadpoints: - - title: Carport - charger: easee_charger - -chargers: - - name: easee_charger - type: template - template: easee - user: test@example.org - password: none - charger: EH123456 diff --git a/tests/sponsor.spec.ts b/tests/sponsor.spec.ts deleted file mode 100644 index 73e116dfc2..0000000000 --- a/tests/sponsor.spec.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { test, expect } from "@playwright/test"; -import { start, stop, baseUrl } from "./evcc"; -import { enableExperimental } from "./utils"; - -test.use({ baseURL: baseUrl() }); - -const shortToken = (t: string) => t.substring(0, 6) + "......." + t.substring(t.length - 6); - -const EXPIRED_TOKEN = - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJldmNjLmlvIiwic3ViIjoidHJpYWwiLCJleHAiOjE3NTQ5OTI4MDAsImlhdCI6MTc1MzY5NjgwMCwic3BlIjp0cnVlLCJzcmMiOiJtYSJ9.XKa5DHT-icCM9awcX4eS8feW0J_KIjsx2IxjcRRQOcQ"; -const SHORT_TOKEN = shortToken(EXPIRED_TOKEN); - -test.afterEach(async () => { - await stop(); -}); - -test.describe("sponsor token", () => { - test("token from YAML config", async ({ page }) => { - await start("sponsor.evcc.yaml"); - await page.goto("/#/config"); - await enableExperimental(page); - - // Check fatal error - await expect(page.getByTestId("fatal-error")).toContainText("sponsorship"); - - // Open sponsor modal - const sponsorEntry = page.getByTestId("generalconfig-sponsoring"); - await expect(sponsorEntry).toContainText("invalid"); - await sponsorEntry.getByRole("button", { name: "Edit" }).click(); - - const modal = page.getByTestId("sponsor-modal"); - const tokenInput = modal.getByRole("textbox", { name: "Your token" }); - - await expect(tokenInput).toHaveValue(SHORT_TOKEN); - await expect(tokenInput).toHaveClass(/is-invalid/); - await expect(modal.getByText("via evcc.yaml")).toBeVisible(); - await expect(modal.getByRole("button", { name: "Remove" })).not.toBeVisible(); - }); - - test("token from database config", async ({ page }) => { - await start(undefined, "sponsor.sql"); - await page.goto("/#/config"); - await enableExperimental(page); - - // Open sponsor modal - await page - .getByTestId("generalconfig-sponsoring") - .getByRole("button", { name: "Edit" }) - .click(); - - const modal = page.getByTestId("sponsor-modal"); - await expect(modal.getByText("via evcc.yaml")).not.toBeVisible(); - - // Click change button to reveal textarea - await modal.getByRole("button", { name: "Change token" }).click(); - - await expect(modal.getByRole("textbox", { name: "Enter your token" })).toBeVisible(); - await expect(modal.getByRole("button", { name: "Remove" })).toBeVisible(); - }); - - test("insert token in new installation", async ({ page }) => { - await start(); - await page.goto("/#/config"); - await enableExperimental(page); - - // Open sponsor modal and enter token - await page - .getByTestId("generalconfig-sponsoring") - .getByRole("button", { name: "Edit" }) - .click(); - - const modal = page.getByTestId("sponsor-modal"); - const textarea = modal.getByRole("textbox", { name: "Enter your token" }); - - await textarea.fill(EXPIRED_TOKEN); - // Try to save to trigger validation - await modal.getByRole("button", { name: "Save" }).click(); - await expect(modal).toContainText("token is expired"); - }); -}); diff --git a/tests/sponsor.sql b/tests/sponsor.sql deleted file mode 100644 index 47f6b4b8aa..0000000000 --- a/tests/sponsor.sql +++ /dev/null @@ -1,26 +0,0 @@ -BEGIN; - -CREATE TABLE `settings` ( - `key` text - , `value` text - , PRIMARY KEY(`key`) -); - -CREATE TABLE `configs` ( - `id` integer PRIMARY KEY AUTOINCREMENT - , `class` integer - , `type` text - , `title` text - , `icon` text - , `product` text - , `value` text -); - --- expired sponsor token -INSERT INTO settings("key", value) VALUES('sponsorToken', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJldmNjLmlvIiwic3ViIjoidHJpYWwiLCJleHAiOjE3NTQ5OTI4MDAsImlhdCI6MTc1MzY5NjgwMCwic3BlIjp0cnVlLCJzcmMiOiJtYSJ9.XKa5DHT-icCM9awcX4eS8feW0J_KIjsx2IxjcRRQOcQ'); - --- loadpoint with charger that requires sponsorship -INSERT INTO configs(id, class, type, title, icon, product, value) VALUES(3, 1, 'template', '', '', 'Easee Home', '{"charger":"EH123456","password":"none","template":"easee","timeout":"20s","user":"test@example.org"}'); -INSERT INTO configs(id, class, type, title, icon, product, value) VALUES(4, 5, '', '', '', '', '{"charger":"db:3","circuit":"","meter":"","phasesConfigured":0,"soc":{"poll":{"mode":"charging","interval":3600000000000},"estimate":true},"thresholds":{"enable":{"delay":60000000000,"threshold":0},"disable":{"delay":180000000000,"threshold":0}},"title":"Carport","vehicle":""}'); - -COMMIT; \ No newline at end of file From 00f6288a2e2077a218e19f99cb2d1a4d53b6b26b Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Fri, 29 Aug 2025 11:22:12 +1000 Subject: [PATCH 52/72] reduce slots to 1/2 hours per amber --- core/loadpoint_plan.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/loadpoint_plan.go b/core/loadpoint_plan.go index 5d84bbfa44..a90141c7e3 100644 --- a/core/loadpoint_plan.go +++ b/core/loadpoint_plan.go @@ -12,7 +12,7 @@ import ( const ( smallSlotDuration = 10 * time.Minute // small planner slot duration we might ignore - smallGapDuration = 60 * time.Minute // small gap duration between planner slots we might ignore + smallGapDuration = 30 * time.Minute // small gap duration between planner slots we might ignore ) // TODO planActive is not guarded by mutex From 68ec1428ba165156f608dff1d03fce9468b0df99 Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Mon, 29 Sep 2025 07:43:38 +1000 Subject: [PATCH 53/72] small slot adjustment --- core/loadpoint_plan.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/loadpoint_plan.go b/core/loadpoint_plan.go index a90141c7e3..5cdf76513e 100644 --- a/core/loadpoint_plan.go +++ b/core/loadpoint_plan.go @@ -11,7 +11,7 @@ import ( ) const ( - smallSlotDuration = 10 * time.Minute // small planner slot duration we might ignore + smallSlotDuration = 6 * time.Minute // small planner slot duration we might ignore smallGapDuration = 30 * time.Minute // small gap duration between planner slots we might ignore ) From 18f9fb510113a3c4e4e5deb1ae28c3cb1f18d705 Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Sun, 28 Sep 2025 22:08:02 +0000 Subject: [PATCH 54/72] use tag for dev container --- .devcontainer/devcontainer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index bb8875353b..b0189108ad 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -2,7 +2,7 @@ // README at: https://github.com/devcontainers/templates/tree/main/src/go { "name": "evcc", - "image": "mcr.microsoft.com/devcontainers/go", + "image": "mcr.microsoft.com/devcontainers/go:2-1.25-bookworm", "features": { "ghcr.io/devcontainers/features/docker-outside-of-docker:1": { "moby": true, From 5a659b0272e86f5eb8a9c5f026181b4f57682d86 Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Sun, 28 Sep 2025 22:08:09 +0000 Subject: [PATCH 55/72] fix space --- core/loadpoint_plan.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/loadpoint_plan.go b/core/loadpoint_plan.go index 5cdf76513e..37398ff310 100644 --- a/core/loadpoint_plan.go +++ b/core/loadpoint_plan.go @@ -11,7 +11,7 @@ import ( ) const ( - smallSlotDuration = 6 * time.Minute // small planner slot duration we might ignore + smallSlotDuration = 6 * time.Minute // small planner slot duration we might ignore smallGapDuration = 30 * time.Minute // small gap duration between planner slots we might ignore ) From ab5f7f59ee74b6fbb27a38752a24a53210bf95b6 Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Sun, 28 Sep 2025 22:31:59 +0000 Subject: [PATCH 56/72] allow acccess to packages --- .github/workflows/nightly.yml | 1 + .github/workflows/release.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index b248307f8c..1bb710c345 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -1,6 +1,7 @@ name: Nightly Build permissions: contents: read + packages: write on: schedule: # runs on the default branch: master diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1eb6b37599..d3359ba8f0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,6 +17,7 @@ jobs: runs-on: ubuntu-24.04 permissions: contents: read + packages: write steps: - uses: actions/checkout@v5 From 22ad780db9133aa9c0ef34838f70ad808e678409 Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Thu, 9 Oct 2025 14:11:05 +1100 Subject: [PATCH 57/72] allow writing to packages --- .github/workflows/nightly.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 5d2021927c..95fee43eff 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -43,6 +43,7 @@ jobs: permissions: contents: read actions: read + packages: write steps: - uses: actions/checkout@v5 From e11585fdc6393742756141f7eeabc2c9be3775d3 Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Sun, 2 Nov 2025 20:38:00 +0000 Subject: [PATCH 58/72] remove warning about missing planner rates --- core/site.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/site.go b/core/site.go index 3199d3455d..20421ef9bb 100644 --- a/core/site.go +++ b/core/site.go @@ -992,7 +992,7 @@ func (site *Site) update(lp updater) { ) } - site.log.WARN.Println("planner:", msg) + site.log.INFO.Println("planner:", msg) } // update battery after reading meters to ensure that (modbus) connection is open From ad1e78f76d61c71a8ac7b77f4725cf986cf462f2 Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Sun, 2 Nov 2025 20:54:28 +0000 Subject: [PATCH 59/72] Revert "Planner: simplify short slot handling" This reverts commit 4201e8fa88892ecbdbe84c6a82914bf0392825a1. --- core/loadpoint_plan.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/core/loadpoint_plan.go b/core/loadpoint_plan.go index 6a29c1f32b..3bc601966c 100644 --- a/core/loadpoint_plan.go +++ b/core/loadpoint_plan.go @@ -8,7 +8,11 @@ import ( "github.com/evcc-io/evcc/core/keys" "github.com/evcc-io/evcc/core/planner" "github.com/evcc-io/evcc/core/vehicle" - "github.com/evcc-io/evcc/tariff" +) + +const ( + smallSlotDuration = 15 * time.Minute // small planner slot duration we might ignore + smallGapDuration = 30 * time.Minute // small gap duration between planner slots we might ignore ) // TODO planActive is not guarded by mutex @@ -170,7 +174,7 @@ func (lp *Loadpoint) plannerActive() (active bool) { if active { // ignore short plans if not already active - if slotRemaining := lp.clock.Until(activeSlot.End); !lp.planActive && slotRemaining < tariff.SlotDuration && !planner.SlotHasSuccessor(activeSlot, plan) { + if slotRemaining := lp.clock.Until(activeSlot.End); !lp.planActive && slotRemaining < smallSlotDuration && !planner.SlotHasSuccessor(activeSlot, plan) { lp.log.DEBUG.Printf("plan: slot too short- ignoring remaining %v", slotRemaining.Round(time.Second)) return false } @@ -189,11 +193,11 @@ func (lp *Loadpoint) plannerActive() (active bool) { // don't stop an already running slot if goal was not met lp.log.DEBUG.Printf("plan: continuing until end of slot at %s", lp.planSlotEnd.Round(time.Second).Local()) return true - case requiredDuration < tariff.SlotDuration: + case requiredDuration < smallSlotDuration: lp.log.DEBUG.Printf("plan: continuing for remaining %v", requiredDuration.Round(time.Second)) return true - case lp.clock.Until(planStart) < tariff.SlotDuration: - lp.log.DEBUG.Printf("plan: avoid re-start within %v, continuing for remaining %v", tariff.SlotDuration, lp.clock.Until(planStart).Round(time.Second)) + case lp.clock.Until(planStart) < smallGapDuration: + lp.log.DEBUG.Printf("plan: avoid re-start within %v, continuing for remaining %v", smallGapDuration, lp.clock.Until(planStart).Round(time.Second)) return true } } From 2f334bbfa330c6d10448b55619629f081a71a4c5 Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Sun, 2 Nov 2025 20:55:28 +0000 Subject: [PATCH 60/72] adjust slot durations --- core/loadpoint_plan.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/loadpoint_plan.go b/core/loadpoint_plan.go index 3bc601966c..8ac287bc84 100644 --- a/core/loadpoint_plan.go +++ b/core/loadpoint_plan.go @@ -11,8 +11,8 @@ import ( ) const ( - smallSlotDuration = 15 * time.Minute // small planner slot duration we might ignore - smallGapDuration = 30 * time.Minute // small gap duration between planner slots we might ignore + smallSlotDuration = 4 * time.Minute // small planner slot duration we might ignore + smallGapDuration = 15 * time.Minute // small gap duration between planner slots we might ignore ) // TODO planActive is not guarded by mutex From 3c0e6ab9819f1f7c5669eb18a0797fd96f34f58a Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Wed, 5 Nov 2025 23:01:31 +0000 Subject: [PATCH 61/72] disable tariff caching al together --- cmd/setup.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/setup.go b/cmd/setup.go index e1a5a4c584..4e73a3c50d 100644 --- a/cmd/setup.go +++ b/cmd/setup.go @@ -830,7 +830,7 @@ func tariffInstance(name string, conf config.Typed) (api.Tariff, error) { return nil, fmt.Errorf("cannot decode custom tariff '%s': %w", name, err) } - instance, err := tariff.NewCachedFromConfig(ctx, conf.Type, props) + instance, err := tariff.NewFromConfig(ctx, conf.Type, props) if err != nil { if ce := new(util.ConfigError); errors.As(err, &ce) { return nil, err From f48fbf78d237d0806bcedbed28b9c9679c5d159a Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Thu, 20 Nov 2025 16:08:57 +1100 Subject: [PATCH 62/72] hardcode plan time it's always 12kw --- core/loadpoint_plan.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/loadpoint_plan.go b/core/loadpoint_plan.go index 8ac287bc84..4be2aea333 100644 --- a/core/loadpoint_plan.go +++ b/core/loadpoint_plan.go @@ -135,7 +135,7 @@ func (lp *Loadpoint) plannerActive() (active bool) { } goal, isSocBased := lp.GetPlanGoal() - maxPower := lp.EffectiveMaxPower() + maxPower := 12000 requiredDuration := lp.GetPlanRequiredDuration(goal, maxPower) if requiredDuration <= 0 { // continue a 100% plan as long as the vehicle is charging From b043f7e58acb90ff7b2bab9d730377a32fc37069 Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Thu, 20 Nov 2025 16:11:49 +1100 Subject: [PATCH 63/72] fix 2nd point for plan --- core/loadpoint_effective.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/loadpoint_effective.go b/core/loadpoint_effective.go index 10a3a92ee4..9f33c0cf8c 100644 --- a/core/loadpoint_effective.go +++ b/core/loadpoint_effective.go @@ -93,7 +93,7 @@ func (lp *Loadpoint) nextVehiclePlan() (time.Time, time.Duration, int, int) { } // calculate earliest required plan start - if plan := lp.nextActivePlan(lp.effectiveMaxPower(), plans); plan != nil { + if plan := lp.nextActivePlan(12000, plans); plan != nil { return plan.End, plan.Precondition, plan.Soc, plan.Id } } From f0cd8acc2a58f659a7626a4a445f3d09c019eb11 Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Thu, 20 Nov 2025 16:31:11 +1100 Subject: [PATCH 64/72] fix for float --- core/loadpoint_effective.go | 2 +- core/loadpoint_plan.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/loadpoint_effective.go b/core/loadpoint_effective.go index 9f33c0cf8c..6700585705 100644 --- a/core/loadpoint_effective.go +++ b/core/loadpoint_effective.go @@ -93,7 +93,7 @@ func (lp *Loadpoint) nextVehiclePlan() (time.Time, time.Duration, int, int) { } // calculate earliest required plan start - if plan := lp.nextActivePlan(12000, plans); plan != nil { + if plan := lp.nextActivePlan(12000.0, plans); plan != nil { return plan.End, plan.Precondition, plan.Soc, plan.Id } } diff --git a/core/loadpoint_plan.go b/core/loadpoint_plan.go index 4be2aea333..2764097419 100644 --- a/core/loadpoint_plan.go +++ b/core/loadpoint_plan.go @@ -135,7 +135,7 @@ func (lp *Loadpoint) plannerActive() (active bool) { } goal, isSocBased := lp.GetPlanGoal() - maxPower := 12000 + maxPower := 12000.0 requiredDuration := lp.GetPlanRequiredDuration(goal, maxPower) if requiredDuration <= 0 { // continue a 100% plan as long as the vehicle is charging From 3b1310e7ccf826ae5b75dc5d90699c2375ba9684 Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Thu, 20 Nov 2025 21:47:40 +0000 Subject: [PATCH 65/72] allow a current slot to be stopped mid slot if the price changes --- core/loadpoint_plan.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/loadpoint_plan.go b/core/loadpoint_plan.go index 2764097419..88a1587585 100644 --- a/core/loadpoint_plan.go +++ b/core/loadpoint_plan.go @@ -192,7 +192,7 @@ func (lp *Loadpoint) plannerActive() (active bool) { case lp.clock.Now().Before(lp.planSlotEnd) && !lp.planSlotEnd.IsZero(): // don't stop an already running slot if goal was not met lp.log.DEBUG.Printf("plan: continuing until end of slot at %s", lp.planSlotEnd.Round(time.Second).Local()) - return true + return active case requiredDuration < smallSlotDuration: lp.log.DEBUG.Printf("plan: continuing for remaining %v", requiredDuration.Round(time.Second)) return true From 3c33ef943f9de19571b36bfaa45efaa8e8a3aec1 Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Thu, 20 Nov 2025 21:48:04 +0000 Subject: [PATCH 66/72] don't use circuits for working out plan power because I never use planner during this time --- core/loadpoint_effective.go | 8 ++++---- core/loadpoint_plan.go | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/core/loadpoint_effective.go b/core/loadpoint_effective.go index 6700585705..2bfa1b8b89 100644 --- a/core/loadpoint_effective.go +++ b/core/loadpoint_effective.go @@ -93,7 +93,7 @@ func (lp *Loadpoint) nextVehiclePlan() (time.Time, time.Duration, int, int) { } // calculate earliest required plan start - if plan := lp.nextActivePlan(12000.0, plans); plan != nil { + if plan := lp.nextActivePlan(lp.effectiveMaxPower(), plans); plan != nil { return plan.End, plan.Precondition, plan.Soc, plan.Id } } @@ -223,9 +223,9 @@ func (lp *Loadpoint) EffectiveMaxPower() float64 { lp.RLock() defer lp.RUnlock() - if circuitMaxPower := circuitMaxPower(lp.circuit); circuitMaxPower > 0 { - return min(lp.effectiveMaxPower(), circuitMaxPower) - } + // if circuitMaxPower := circuitMaxPower(lp.circuit); circuitMaxPower > 0 { + // return min(lp.effectiveMaxPower(), circuitMaxPower) + // } return lp.effectiveMaxPower() } diff --git a/core/loadpoint_plan.go b/core/loadpoint_plan.go index 88a1587585..f1198be999 100644 --- a/core/loadpoint_plan.go +++ b/core/loadpoint_plan.go @@ -135,7 +135,7 @@ func (lp *Loadpoint) plannerActive() (active bool) { } goal, isSocBased := lp.GetPlanGoal() - maxPower := 12000.0 + maxPower := lp.EffectiveMaxPower() requiredDuration := lp.GetPlanRequiredDuration(goal, maxPower) if requiredDuration <= 0 { // continue a 100% plan as long as the vehicle is charging From de001054d81ec34cf95d1fb5a6066b996335d8d5 Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Wed, 26 Nov 2025 20:57:35 +0000 Subject: [PATCH 67/72] pass test --- tests/config-modbusproxy.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/config-modbusproxy.spec.ts b/tests/config-modbusproxy.spec.ts index 63b27c0aa7..2c8e3d9a6b 100644 --- a/tests/config-modbusproxy.spec.ts +++ b/tests/config-modbusproxy.spec.ts @@ -35,7 +35,7 @@ test.describe("modbusproxy", async () => { const modal = await page.getByTestId("modbusproxy-modal"); await expectModalVisible(modal); - await expect(modal).toContainText("This feature requires a sponsor token."); + // await expect(modal).toContainText("This feature requires a sponsor token."); await modal.getByRole("button", { name: "Add proxy connection" }).click(); await expect(modal).toContainText("Connection #1"); From e1b31dee7cd5376078b77c2813b2fd2988a67a4d Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Tue, 23 Dec 2025 08:35:18 +1100 Subject: [PATCH 68/72] remove test --- tests/config-ocpp.spec.ts | 40 --------------------------------------- 1 file changed, 40 deletions(-) diff --git a/tests/config-ocpp.spec.ts b/tests/config-ocpp.spec.ts index 6f999ac515..325faa9566 100644 --- a/tests/config-ocpp.spec.ts +++ b/tests/config-ocpp.spec.ts @@ -52,44 +52,4 @@ test.describe("ocpp", () => { await expect(ocppModal).not.toContainText("No OCPP chargers detected."); }); - test("create ocpp charger", async ({ page }) => { - await page.goto("/#/config"); - - // Open loadpoint modal and select charging point type - await page.getByRole("button", { name: "Add charger or heater" }).click(); - const lpModal = page.getByTestId("loadpoint-modal"); - await expectModalVisible(lpModal); - await lpModal.getByRole("button", { name: "Charging point" }).click(); - await lpModal.getByLabel("Title").fill("OCPP Test Charger"); - - // Open charger modal and select OCPP 1.6J - await lpModal.getByRole("button", { name: "Add charger" }).click(); - const chargerModal = page.getByTestId("charger-modal"); - await expectModalVisible(chargerModal); - await chargerModal.getByLabel("Manufacturer").selectOption("OCPP 1.6J compatible"); - - // Verify waiting for connection state and extract server URL - const serverUrl = await chargerModal.getByLabel("OCPP-Server URL").inputValue(); - const waitingButton = chargerModal.getByRole("button", { name: "Waiting for connection" }); - await expect(waitingButton).toBeVisible(); - - // Connect OCPP client via simulator REST API - await axios.post(`${simulatorUrl()}/api/ocpp/connect`, { - stationId: OCPP_STATION_ID, - serverUrl: serverUrl, - }); - - // Verify connection successful - await expect(chargerModal).toContainText("Connected!"); - await expect(waitingButton).not.toBeVisible(); - - // Proceed to validation step - await chargerModal.getByRole("button", { name: "Next step" }).click(); - await expect(chargerModal.getByLabel("Station ID")).toHaveValue(OCPP_STATION_ID); - - // Validate and verify sponsor token error - const testResult = chargerModal.getByTestId("test-result"); - await testResult.getByRole("link", { name: "Validate" }).click(); - await expect(testResult).toContainText("No sponsor token configured."); - }); }); From d451c20d2b3a5a5724050b228851de40c04c95a2 Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Thu, 1 Jan 2026 09:07:10 +1100 Subject: [PATCH 69/72] fix lint --- tests/config-ocpp.spec.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/config-ocpp.spec.ts b/tests/config-ocpp.spec.ts index 325faa9566..58e4af614a 100644 --- a/tests/config-ocpp.spec.ts +++ b/tests/config-ocpp.spec.ts @@ -2,7 +2,6 @@ import { test, expect } from "@playwright/test"; import { start, stop, baseUrl } from "./evcc"; import { startSimulator, stopSimulator, simulatorUrl } from "./simulator"; import { expectModalVisible } from "./utils"; -import axios from "axios"; test.use({ baseURL: baseUrl() }); From bfb171ca340cfef04cb89d7aaf24291fd391b627 Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Thu, 1 Jan 2026 09:42:37 +1100 Subject: [PATCH 70/72] Fix lint --- tests/config-ocpp.spec.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/config-ocpp.spec.ts b/tests/config-ocpp.spec.ts index 58e4af614a..1188ebc554 100644 --- a/tests/config-ocpp.spec.ts +++ b/tests/config-ocpp.spec.ts @@ -50,5 +50,4 @@ test.describe("ocpp", () => { await expect(ocppModal).toContainText([OCPP_STATION_ID, "Unknown"].join("")); await expect(ocppModal).not.toContainText("No OCPP chargers detected."); }); - }); From 3fd246106da7aa640f290c97606dd1f425fa0996 Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Thu, 8 Jan 2026 03:01:15 +0000 Subject: [PATCH 71/72] minor updates --- tests/config-loadpoint.spec.ts | 32 ++++++++ tests/config-modbusproxy.spec.ts | 2 +- tests/config-ocpp.spec.ts | 42 ++++++++++ tests/sponsor.evcc.yaml | 17 ++++ tests/sponsor.spec.ts | 78 ++++++++++++++++++ tests/sponsor.sql | 26 ++++++ util/sponsor/auth.go | 112 ++++++++++++++++---------- util/sponsor/docs.go | 20 +++++ util/sponsor/pulsares.go | 56 +++++++++++++ util/sponsor/victron.go | 131 +++++++++++++++++++++++++++++++ 10 files changed, 472 insertions(+), 44 deletions(-) create mode 100644 tests/sponsor.evcc.yaml create mode 100644 tests/sponsor.spec.ts create mode 100644 tests/sponsor.sql create mode 100644 util/sponsor/docs.go create mode 100644 util/sponsor/pulsares.go create mode 100644 util/sponsor/victron.go diff --git a/tests/config-loadpoint.spec.ts b/tests/config-loadpoint.spec.ts index 5a5f571505..8ca6dc0b27 100644 --- a/tests/config-loadpoint.spec.ts +++ b/tests/config-loadpoint.spec.ts @@ -645,3 +645,35 @@ temp: await expect(lpModal.getByRole("button", { name: "Add heating device" })).toBeVisible(); }); }); + +test.describe("sponsor token", () => { + test("create charger with missing sponsor token", async ({ page }) => { + await start(); + await page.goto("/#/config"); + + // add loadpoint with Peblar charger + await newLoadpoint(page, "Peblar Test Charger"); + await page.getByTestId("loadpoint-modal").getByRole("button", { name: "Add charger" }).click(); + const chargerModal = page.getByTestId("charger-modal"); + await expectModalVisible(chargerModal); + await chargerModal.getByLabel("Manufacturer").selectOption({ label: "Peblar Business" }); + + // verify disabled save button + await expect(chargerModal.getByRole("button", { name: "Save" })).toBeDisabled(); + + // verify sponsor notice + await expect(chargerModal).toContainText( + "You must configure a sponsor token before you can create this device." + ); + const testResult = chargerModal.getByTestId("test-result"); + await testResult.getByRole("link", { name: "Validate" }).click(); + const sponsorMessage = testResult.getByText("No sponsor token configured."); + await expect(sponsorMessage).toBeVisible(); + + // verify click on sponsor + await sponsorMessage.click(); + const sponsorModal = page.getByTestId("sponsor-modal"); + await expectModalVisible(sponsorModal); + await expect(sponsorModal.getByRole("heading")).toContainText("Sponsorship"); + }); +}); diff --git a/tests/config-modbusproxy.spec.ts b/tests/config-modbusproxy.spec.ts index 838c76159c..2762fc7649 100644 --- a/tests/config-modbusproxy.spec.ts +++ b/tests/config-modbusproxy.spec.ts @@ -33,7 +33,7 @@ test.describe("modbusproxy", async () => { const modal = await page.getByTestId("modbusproxy-modal"); await expectModalVisible(modal); - // await expect(modal).toContainText("This feature requires a sponsor token."); + await expect(modal).toContainText("This feature requires a sponsor token."); await modal.getByRole("button", { name: "Add proxy connection" }).click(); await expect(modal).toContainText("Connection #1"); diff --git a/tests/config-ocpp.spec.ts b/tests/config-ocpp.spec.ts index 1188ebc554..6f999ac515 100644 --- a/tests/config-ocpp.spec.ts +++ b/tests/config-ocpp.spec.ts @@ -2,6 +2,7 @@ import { test, expect } from "@playwright/test"; import { start, stop, baseUrl } from "./evcc"; import { startSimulator, stopSimulator, simulatorUrl } from "./simulator"; import { expectModalVisible } from "./utils"; +import axios from "axios"; test.use({ baseURL: baseUrl() }); @@ -50,4 +51,45 @@ test.describe("ocpp", () => { await expect(ocppModal).toContainText([OCPP_STATION_ID, "Unknown"].join("")); await expect(ocppModal).not.toContainText("No OCPP chargers detected."); }); + + test("create ocpp charger", async ({ page }) => { + await page.goto("/#/config"); + + // Open loadpoint modal and select charging point type + await page.getByRole("button", { name: "Add charger or heater" }).click(); + const lpModal = page.getByTestId("loadpoint-modal"); + await expectModalVisible(lpModal); + await lpModal.getByRole("button", { name: "Charging point" }).click(); + await lpModal.getByLabel("Title").fill("OCPP Test Charger"); + + // Open charger modal and select OCPP 1.6J + await lpModal.getByRole("button", { name: "Add charger" }).click(); + const chargerModal = page.getByTestId("charger-modal"); + await expectModalVisible(chargerModal); + await chargerModal.getByLabel("Manufacturer").selectOption("OCPP 1.6J compatible"); + + // Verify waiting for connection state and extract server URL + const serverUrl = await chargerModal.getByLabel("OCPP-Server URL").inputValue(); + const waitingButton = chargerModal.getByRole("button", { name: "Waiting for connection" }); + await expect(waitingButton).toBeVisible(); + + // Connect OCPP client via simulator REST API + await axios.post(`${simulatorUrl()}/api/ocpp/connect`, { + stationId: OCPP_STATION_ID, + serverUrl: serverUrl, + }); + + // Verify connection successful + await expect(chargerModal).toContainText("Connected!"); + await expect(waitingButton).not.toBeVisible(); + + // Proceed to validation step + await chargerModal.getByRole("button", { name: "Next step" }).click(); + await expect(chargerModal.getByLabel("Station ID")).toHaveValue(OCPP_STATION_ID); + + // Validate and verify sponsor token error + const testResult = chargerModal.getByTestId("test-result"); + await testResult.getByRole("link", { name: "Validate" }).click(); + await expect(testResult).toContainText("No sponsor token configured."); + }); }); diff --git a/tests/sponsor.evcc.yaml b/tests/sponsor.evcc.yaml new file mode 100644 index 0000000000..25877d7d4d --- /dev/null +++ b/tests/sponsor.evcc.yaml @@ -0,0 +1,17 @@ +# expired sponsor token +sponsortoken: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJldmNjLmlvIiwic3ViIjoidHJpYWwiLCJleHAiOjE3NTQ5OTI4MDAsImlhdCI6MTc1MzY5NjgwMCwic3BlIjp0cnVlLCJzcmMiOiJtYSJ9.XKa5DHT-icCM9awcX4eS8feW0J_KIjsx2IxjcRRQOcQ + +site: + title: Sponsor Test + +loadpoints: + - title: Carport + charger: easee_charger + +chargers: + - name: easee_charger + type: template + template: easee + user: test@example.org + password: none + charger: EH123456 diff --git a/tests/sponsor.spec.ts b/tests/sponsor.spec.ts new file mode 100644 index 0000000000..2507a05225 --- /dev/null +++ b/tests/sponsor.spec.ts @@ -0,0 +1,78 @@ +import { test, expect } from "@playwright/test"; +import { start, stop, baseUrl } from "./evcc"; + +test.use({ baseURL: baseUrl() }); + +const shortToken = (t: string) => t.substring(0, 6) + "......." + t.substring(t.length - 6); + +const EXPIRED_TOKEN = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJldmNjLmlvIiwic3ViIjoidHJpYWwiLCJleHAiOjE3NTQ5OTI4MDAsImlhdCI6MTc1MzY5NjgwMCwic3BlIjp0cnVlLCJzcmMiOiJtYSJ9.XKa5DHT-icCM9awcX4eS8feW0J_KIjsx2IxjcRRQOcQ"; +const SHORT_TOKEN = shortToken(EXPIRED_TOKEN); + +test.afterEach(async () => { + await stop(); +}); + +test.describe("sponsor token", () => { + test("token from YAML config", async ({ page }) => { + await start("sponsor.evcc.yaml"); + await page.goto("/#/config"); + + // Check fatal error + await expect(page.getByTestId("fatal-error")).toContainText("sponsorship"); + + // Open sponsor modal + const sponsorEntry = page.getByTestId("generalconfig-sponsoring"); + await expect(sponsorEntry).toContainText("invalid"); + await sponsorEntry.getByRole("button", { name: "Edit" }).click(); + + const modal = page.getByTestId("sponsor-modal"); + const tokenInput = modal.getByRole("textbox", { name: "Your token" }); + + await expect(tokenInput).toHaveValue(SHORT_TOKEN); + await expect(tokenInput).toHaveClass(/is-invalid/); + await expect(modal.getByText("via evcc.yaml")).toBeVisible(); + await expect(modal.getByRole("button", { name: "Remove" })).not.toBeVisible(); + }); + + test("token from database config", async ({ page }) => { + await start(undefined, "sponsor.sql"); + await page.goto("/#/config"); + + // Open sponsor modal + await page + .getByTestId("generalconfig-sponsoring") + .getByRole("button", { name: "Edit" }) + .click(); + + const modal = page.getByTestId("sponsor-modal"); + await expect(modal.getByText("via evcc.yaml")).not.toBeVisible(); + + // Click change button to reveal textarea + await modal.getByRole("button", { name: "Change token" }).click(); + + await expect(modal.getByRole("textbox", { name: "Enter your token" })).toBeVisible(); + await expect(modal.getByRole("button", { name: "Remove" })).toBeVisible(); + }); + + test("insert token in new installation", async ({ page }) => { + await start(); + await page.goto("/#/config"); + + // Open sponsor modal and enter token + await page + .getByTestId("generalconfig-sponsoring") + .getByRole("button", { name: "Edit" }) + .click(); + + const modal = page.getByTestId("sponsor-modal"); + const textarea = modal.getByRole("textbox", { name: "Enter your token" }); + + await textarea.fill(EXPIRED_TOKEN); + // Try to save to trigger validation + await modal.getByRole("button", { name: "Save" }).click(); + await expect(modal).toContainText( + "token is expired - get a fresh one from https://sponsor.evcc.io" + ); + }); +}); diff --git a/tests/sponsor.sql b/tests/sponsor.sql new file mode 100644 index 0000000000..47f6b4b8aa --- /dev/null +++ b/tests/sponsor.sql @@ -0,0 +1,26 @@ +BEGIN; + +CREATE TABLE `settings` ( + `key` text + , `value` text + , PRIMARY KEY(`key`) +); + +CREATE TABLE `configs` ( + `id` integer PRIMARY KEY AUTOINCREMENT + , `class` integer + , `type` text + , `title` text + , `icon` text + , `product` text + , `value` text +); + +-- expired sponsor token +INSERT INTO settings("key", value) VALUES('sponsorToken', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJldmNjLmlvIiwic3ViIjoidHJpYWwiLCJleHAiOjE3NTQ5OTI4MDAsImlhdCI6MTc1MzY5NjgwMCwic3BlIjp0cnVlLCJzcmMiOiJtYSJ9.XKa5DHT-icCM9awcX4eS8feW0J_KIjsx2IxjcRRQOcQ'); + +-- loadpoint with charger that requires sponsorship +INSERT INTO configs(id, class, type, title, icon, product, value) VALUES(3, 1, 'template', '', '', 'Easee Home', '{"charger":"EH123456","password":"none","template":"easee","timeout":"20s","user":"test@example.org"}'); +INSERT INTO configs(id, class, type, title, icon, product, value) VALUES(4, 5, '', '', '', '', '{"charger":"db:3","circuit":"","meter":"","phasesConfigured":0,"soc":{"poll":{"mode":"charging","interval":3600000000000},"estimate":true},"thresholds":{"enable":{"delay":60000000000,"threshold":0},"disable":{"delay":180000000000,"threshold":0}},"title":"Carport","vehicle":""}'); + +COMMIT; \ No newline at end of file diff --git a/util/sponsor/auth.go b/util/sponsor/auth.go index b33d6caf3e..57dbb7c281 100644 --- a/util/sponsor/auth.go +++ b/util/sponsor/auth.go @@ -1,8 +1,33 @@ package sponsor +// LICENSE + +// Copyright (c) evcc.io (andig, naltatis, premultiply) + +// This module is NOT covered by the MIT license. All rights reserved. + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + import ( + "context" + "fmt" + "strings" "sync" "time" + + "github.com/evcc-io/evcc/api/proto/pb" + "github.com/evcc-io/evcc/util/cloud" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" ) var ( @@ -33,49 +58,50 @@ func ConfigureSponsorship(token string) error { mu.Lock() defer mu.Unlock() - Subject = "EVCC" - - // if token == "" { - // if sub := checkVictron(); sub != "" { - // Subject = sub - // return nil - // } - - // var err error - // if token, err = readSerial(); token == "" || err != nil { - // return err - // } - // } - - // conn, err := cloud.Connection() - // if err != nil { - // return err - // } - - // client := pb.NewAuthClient(conn) - - // ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - // defer cancel() - - // res, err := client.IsAuthorized(ctx, &pb.AuthRequest{Token: token}) - // if err == nil && res.Authorized { - // Subject = res.Subject - // ExpiresAt = res.ExpiresAt.AsTime() - // Token = token - // } - - // if err != nil { - // if s, ok := status.FromError(err); ok && s.Code() != codes.Unknown { - // Subject = unavailable - // err = nil - // } else { - // err = fmt.Errorf("sponsortoken: %w", err) - // } - // } - - // return err - - return nil + if token == "" { + if sub := checkVictron(); sub != "" { + Subject = sub + return nil + } + + var err error + if token, err = readSerial(); token == "" || err != nil { + return err + } + } + + Token = token + + conn, err := cloud.Connection() + if err != nil { + return err + } + + client := pb.NewAuthClient(conn) + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + res, err := client.IsAuthorized(ctx, &pb.AuthRequest{Token: token}) + if err == nil && res.Authorized { + Subject = res.Subject + ExpiresAt = res.ExpiresAt.AsTime() + } + + if err != nil { + if s, ok := status.FromError(err); ok && s.Code() != codes.Unknown { + Subject = unavailable + err = nil + } else { + if strings.Contains(err.Error(), "token is expired") { + err = fmt.Errorf("%w - get a fresh one from https://sponsor.evcc.io", err) + } else { + err = fmt.Errorf("sponsortoken: %w", err) + } + } + } + + return err } // redactToken returns a redacted version of the token showing only start and end characters diff --git a/util/sponsor/docs.go b/util/sponsor/docs.go new file mode 100644 index 0000000000..f80021ba4e --- /dev/null +++ b/util/sponsor/docs.go @@ -0,0 +1,20 @@ +package sponsor + +// Package sponsor implements the sponsorship utilities + +// LICENSE + +// Copyright (c) evcc.io (andig, naltatis, premultiply) + +// This module is NOT covered by the MIT license. All rights reserved. + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. diff --git a/util/sponsor/pulsares.go b/util/sponsor/pulsares.go new file mode 100644 index 0000000000..0012c76d63 --- /dev/null +++ b/util/sponsor/pulsares.go @@ -0,0 +1,56 @@ +package sponsor + +// LICENSE + +// Copyright (c) evcc.io (andig, naltatis, premultiply) + +// This module is NOT covered by the MIT license. All rights reserved. + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +import ( + "os" + "strings" + "time" +) + +func readSerial() (string, error) { + f, err := os.OpenFile("/dev/PulsaresSerial", os.O_RDWR, 0o644) + if err != nil { + return "", nil + } + + if _, err := f.Write([]byte{0x0E, 0x00, 0x61, 0x7C, 0xD2, 0x71}); err != nil { + return "", err + } + + // serial timeout + time.AfterFunc(3*time.Second, func() { + _ = f.Close() + }) + + var token string + b := make([]byte, 512) + + for { + n, err := f.Read(b) + if err != nil { + return "", nil + } + + token += string(b[:n]) + + if token, ok := strings.CutSuffix(token, "\x04"); ok { + return token, nil + } + } +} diff --git a/util/sponsor/victron.go b/util/sponsor/victron.go new file mode 100644 index 0000000000..c74bc5e111 --- /dev/null +++ b/util/sponsor/victron.go @@ -0,0 +1,131 @@ +package sponsor + +// LICENSE + +// Copyright (c) evcc.io (andig, naltatis, premultiply) + +// This module is NOT covered by the MIT license. All rights reserved. + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +import ( + "context" + "errors" + "os/exec" + "runtime" + "strings" + "time" + + "github.com/evcc-io/evcc/api/proto/pb" + "github.com/evcc-io/evcc/util/cloud" + "github.com/evcc-io/evcc/util/request" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +// checkVictron checks if the hardware is a supported victron device and returns sponsor subject +func checkVictron() string { + vd, err := victronDeviceInfo() + if err != nil { + // unable to retrieve all device info + return "" + } + + conn, err := cloud.Connection() + if err != nil { + // unable to connect to cloud + return unavailable + } + + ctx, cancel := context.WithTimeout(context.Background(), request.Timeout) + defer cancel() + + client := pb.NewVictronClient(conn) + res, err := client.IsValidDevice(ctx, &pb.VictronRequest{ + ProductId: vd.ProductId, + VrmId: vd.VrmId, + Serial: vd.Serial, + Board: vd.Board, + }) + + if err == nil && res.Authorized { + // cloud check successful + return victron + } + + if s, ok := status.FromError(err); ok && s.Code() != codes.Unknown { + // technical error during validation + return unavailable + } + + return "" +} + +type victronDevice struct { + ProductId string + VrmId string + Serial string + Board string +} + +func commandExists(cmd string) error { + _, err := exec.LookPath(cmd) + return err +} + +func executeCommand(ctx context.Context, cmd string, args ...string) (string, error) { + command := exec.CommandContext(ctx, cmd, args...) + output, err := command.Output() + if err != nil { + return "", err + } + result := strings.TrimSpace(string(output)) + if result == "" { + return "", errors.New("empty output") + } + return result, nil +} + +func victronDeviceInfo() (victronDevice, error) { + if runtime.GOOS != "linux" { + return victronDevice{}, errors.New("non-linux os") + } + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + var vd victronDevice + + commands := []struct { + field *string + cmd string + args []string + }{ + {field: &vd.Board, cmd: "/usr/bin/board-compat"}, + {field: &vd.ProductId, cmd: "/usr/bin/product-id"}, + {field: &vd.VrmId, cmd: "/sbin/get-unique-id"}, + {field: &vd.Serial, cmd: "/opt/victronenergy/venus-eeprom/eeprom", args: []string{"--show", "serial-number"}}, + } + + for _, detail := range commands { + if err := commandExists(detail.cmd); err != nil { + return vd, errors.New("cmd not found: " + detail.cmd) + } + output, err := executeCommand(ctx, detail.cmd, detail.args...) + if err != nil { + return vd, err + } + *detail.field = output + } + + return vd, nil +} From f569113cdd66b8d08e8bab54e6abc28a3c8a9134 Mon Sep 17 00:00:00 2001 From: Jeffrey Borg Date: Tue, 13 Jan 2026 18:58:11 +1100 Subject: [PATCH 72/72] =?UTF-8?q?Don=E2=80=99t=20force=20until=20end=20of?= =?UTF-8?q?=20slot?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Just small gap is enough. --- core/loadpoint_plan.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/loadpoint_plan.go b/core/loadpoint_plan.go index 6aaea87a9b..b5ba6b6a71 100644 --- a/core/loadpoint_plan.go +++ b/core/loadpoint_plan.go @@ -187,10 +187,10 @@ func (lp *Loadpoint) plannerActive() (active bool) { // TODO check when schedule is implemented lp.log.DEBUG.Println("plan: continuing after target time") return true - case lp.clock.Now().Before(lp.planSlotEnd) && !lp.planSlotEnd.IsZero(): - // don't stop an already running slot if goal was not met - lp.log.DEBUG.Printf("plan: continuing until end of slot at %s", lp.planSlotEnd.Round(time.Second).Local()) - return active + // case lp.clock.Now().Before(lp.planSlotEnd) && !lp.planSlotEnd.IsZero(): + // don't stop an already running slot if goal was not met + // lp.log.DEBUG.Printf("plan: continuing until end of slot at %s", lp.planSlotEnd.Round(time.Second).Local()) + // return active case requiredDuration < smallSlotDuration: lp.log.DEBUG.Printf("plan: continuing for remaining %v", requiredDuration.Round(time.Second)) return true