mirror of
https://github.com/compute-blade-community/compute-blade-agent.git
synced 2026-04-21 17:45:43 +02:00
Compare commits
58 Commits
v0.11.1
...
1161cf3ea2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1161cf3ea2 | ||
|
|
42faaf332e | ||
|
|
33458c075d | ||
|
|
d09abe14f2 | ||
|
|
baa7f813c1 | ||
|
|
bc86e413fc | ||
|
|
105d39be89 | ||
|
|
1f6fecfefe | ||
|
|
03541febb2 | ||
|
|
ed39f8320b | ||
|
|
6c1fb4aeb6 | ||
|
|
75c7df898f | ||
|
|
2b2aab20d4 | ||
|
|
e8b9d888fa | ||
|
|
84346089ca | ||
|
|
9477cc71c2 | ||
|
|
0c8efc3e54 | ||
|
|
d3018868c8 | ||
|
|
85a65ff4fc | ||
|
|
217a796fb9 | ||
|
|
4772faaa46 | ||
|
|
764a530c67 | ||
|
|
8459b80451 | ||
|
|
7508715397 | ||
|
|
659ef04d3f | ||
|
|
3b8362a640 | ||
|
|
0ae498cfd2 | ||
|
|
2e75867a2c | ||
|
|
da33d0767a | ||
|
|
940b8851f3 | ||
|
|
9d37e4d0e0 | ||
|
|
ee845c96da | ||
|
|
682d15abe1 | ||
|
|
c891384ed3 | ||
|
|
731de17934 | ||
|
|
20af75e36a | ||
|
|
bca920c510 | ||
|
|
4610f83e56 | ||
|
|
10839c5bf6 | ||
|
|
c6c7166a87 | ||
|
|
19c58fda60 | ||
|
|
551c3b50ba | ||
|
|
4f56e1a568 | ||
|
|
de8ca4c27b | ||
|
|
339b6881f2 | ||
|
|
7e2cb1d9c3 | ||
|
|
c30a64acd9 | ||
|
|
055fcb98b2 | ||
|
|
a4e4e468d1 | ||
|
|
a710b17abf | ||
|
|
eaf26cabd2 | ||
|
|
b0da67eaae | ||
|
|
0cfdb935b5 | ||
|
|
2679c21423 | ||
|
|
4f3bea45fe | ||
|
|
a2f06d99ec | ||
|
|
24cb5f0d4a | ||
|
|
19805dd5ae |
32
.github/workflows/ci.yaml
vendored
32
.github/workflows/ci.yaml
vendored
@@ -24,18 +24,18 @@ jobs:
|
||||
steps:
|
||||
# Checkout code
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
# Set up Go environment
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
cache-dependency-path: "**/*.sum"
|
||||
cache: true
|
||||
|
||||
- name: GolangCI Lint
|
||||
uses: golangci/golangci-lint-action@v8
|
||||
uses: golangci/golangci-lint-action@v9
|
||||
with:
|
||||
version: latest
|
||||
|
||||
@@ -54,11 +54,11 @@ jobs:
|
||||
steps:
|
||||
# Checkout code
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
# Set up Go environment
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
cache: true
|
||||
@@ -68,7 +68,7 @@ jobs:
|
||||
run: go test -cover -coverprofile=coverage.txt ./...
|
||||
|
||||
- name: Archive code coverage results
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: code-coverage
|
||||
path: "coverage.txt"
|
||||
@@ -87,7 +87,7 @@ jobs:
|
||||
pull-requests: write # write permission needed to comment on PR
|
||||
|
||||
steps:
|
||||
- uses: fgrosse/go-coverage-report@v1.2.0
|
||||
- uses: fgrosse/go-coverage-report@v1.3.0
|
||||
with:
|
||||
coverage-artifact-name: "code-coverage"
|
||||
coverage-file-name: "coverage.txt"
|
||||
@@ -101,12 +101,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
cache-dependency-path: "**/*.sum"
|
||||
@@ -122,7 +122,7 @@ jobs:
|
||||
run: make build-fanunit
|
||||
|
||||
- name: Archive FanUnit Firmware
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: fanunit.uf2
|
||||
path: "fanunit.uf2"
|
||||
@@ -134,7 +134,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -144,7 +144,7 @@ jobs:
|
||||
|
||||
# Install GoLang
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
cache-dependency-path: "**/*.sum"
|
||||
@@ -152,20 +152,20 @@ jobs:
|
||||
|
||||
# Setup docker buildx
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
uses: docker/setup-qemu-action@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
# Download FanUnit Firmware
|
||||
- name: Download fanunit firmware
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
pattern: fanunit.uf2
|
||||
|
||||
# Run goreleaser
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
uses: goreleaser/goreleaser-action@v7
|
||||
with:
|
||||
version: latest
|
||||
args: release --snapshot --clean --skip sign
|
||||
@@ -173,7 +173,7 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: compute-blade-agent
|
||||
path: dist/*-SNAPSHOT-*
|
||||
|
||||
8
.github/workflows/pr-hygiene.yaml
vendored
8
.github/workflows/pr-hygiene.yaml
vendored
@@ -26,12 +26,12 @@ jobs:
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- uses: amannn/action-semantic-pull-request@v5
|
||||
- uses: amannn/action-semantic-pull-request@v6
|
||||
id: lint_pr_title
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- uses: marocchino/sticky-pull-request-comment@v2
|
||||
- uses: marocchino/sticky-pull-request-comment@v3
|
||||
# When the previous steps fail, the workflow would stop. By adding this
|
||||
# condition you can continue the execution with the populated error message.
|
||||
if: always() && (steps.lint_pr_title.outputs.error_message != null)
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
|
||||
# Delete a previous comment when the issue has been resolved
|
||||
- if: ${{ steps.lint_pr_title.outputs.error_message == null }}
|
||||
uses: marocchino/sticky-pull-request-comment@v2
|
||||
uses: marocchino/sticky-pull-request-comment@v3
|
||||
with:
|
||||
header: pr-title-lint-error
|
||||
delete: true
|
||||
@@ -64,7 +64,7 @@ jobs:
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- uses: actions/labeler@v5
|
||||
- uses: actions/labeler@v6
|
||||
id: labeler
|
||||
with:
|
||||
sync-labels: true
|
||||
|
||||
24
.github/workflows/release.yaml
vendored
24
.github/workflows/release.yaml
vendored
@@ -14,11 +14,11 @@ jobs:
|
||||
steps:
|
||||
# Checkout code
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
# Set up Go environment
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
cache: true
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
run: go test -cover -coverprofile=coverage.txt ./...
|
||||
|
||||
- name: Archive code coverage results
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: code-coverage
|
||||
path: "coverage.txt"
|
||||
@@ -62,12 +62,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
cache-dependency-path: "**/*.sum"
|
||||
@@ -83,7 +83,7 @@ jobs:
|
||||
run: make build-fanunit
|
||||
|
||||
- name: Archive FanUnit Firmware
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: fanunit.uf2
|
||||
path: "fanunit.uf2"
|
||||
@@ -99,7 +99,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -109,7 +109,7 @@ jobs:
|
||||
|
||||
# Install GoLang
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
cache-dependency-path: "**/*.sum"
|
||||
@@ -117,13 +117,13 @@ jobs:
|
||||
|
||||
# Setup docker buildx
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
uses: docker/setup-qemu-action@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: "Login to GitHub Container Registry"
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{github.actor}}
|
||||
@@ -131,13 +131,13 @@ jobs:
|
||||
|
||||
# Download FanUnit Firmware
|
||||
- name: Download fanunit firmware
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
pattern: fanunit.uf2
|
||||
|
||||
# Run goreleaser
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
uses: goreleaser/goreleaser-action@v7
|
||||
with:
|
||||
version: "~> v2"
|
||||
args: release --clean
|
||||
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -186,4 +186,8 @@ Temporary Items
|
||||
.history
|
||||
.ionide
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/go,visualstudiocode,goland+all,macos,linux
|
||||
# End of https://www.toptal.com/developers/gitignore/api/go,visualstudiocode,goland+all,macos,linux
|
||||
# Build artifacts
|
||||
agent
|
||||
bladectl
|
||||
compute-blade-agent
|
||||
|
||||
13
CHANGELOG.md
13
CHANGELOG.md
@@ -1,5 +1,18 @@
|
||||
# Changelog
|
||||
|
||||
## [0.11.2](https://github.com/compute-blade-community/compute-blade-agent/compare/v0.11.1...v0.11.2) (2026-03-04)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **hal:** add BCM2712 (CM5/Pi 5) HAL support ([#154](https://github.com/compute-blade-community/compute-blade-agent/issues/154)) ([9477cc7](https://github.com/compute-blade-community/compute-blade-agent/commit/9477cc71c200cf193e467a8d543d8970e733a74f))
|
||||
* **hal:** add RK3588 (Radxa CM5) HAL with sysfs fan control ([#155](https://github.com/compute-blade-community/compute-blade-agent/issues/155)) ([03541fe](https://github.com/compute-blade-community/compute-blade-agent/commit/03541febb2c02ee5cf3d432128e756de4e68dfd4))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **fancontroller:** support more than 2 steps in fan curve ([#156](https://github.com/compute-blade-community/compute-blade-agent/issues/156)) ([8434608](https://github.com/compute-blade-community/compute-blade-agent/commit/84346089caf7cb1b163e9279bdf9fe4cf0519651))
|
||||
|
||||
## [0.11.1](https://github.com/compute-blade-community/compute-blade-agent/compare/v0.11.0...v0.11.1) (2025-07-23)
|
||||
|
||||
|
||||
|
||||
101
go.mod
101
go.mod
@@ -1,91 +1,98 @@
|
||||
module github.com/compute-blade-community/compute-blade-agent
|
||||
|
||||
go 1.24.0
|
||||
go 1.25.0
|
||||
|
||||
require (
|
||||
github.com/charmbracelet/lipgloss v1.1.0
|
||||
github.com/gizak/termui/v3 v3.1.0
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2
|
||||
github.com/olekukonko/tablewriter v1.0.8
|
||||
github.com/prometheus/client_golang v1.22.0
|
||||
github.com/sierrasoftworks/humane-errors-go v0.0.0-20250507223502-4bb667dc1e16
|
||||
github.com/spechtlabs/go-otel-utils/otelprovider v0.0.10
|
||||
github.com/spechtlabs/go-otel-utils/otelzap v0.0.10
|
||||
github.com/spf13/cobra v1.9.1
|
||||
github.com/spf13/pflag v1.0.7
|
||||
github.com/spf13/viper v1.20.1
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3
|
||||
github.com/olekukonko/tablewriter v1.1.4
|
||||
github.com/prometheus/client_golang v1.23.2
|
||||
github.com/sierrasoftworks/humane-errors-go v0.0.0-20260226124905-a7be4ffe4f32
|
||||
github.com/spechtlabs/go-otel-utils/otelprovider v0.0.15
|
||||
github.com/spechtlabs/go-otel-utils/otelzap v0.0.15
|
||||
github.com/spf13/cobra v1.10.2
|
||||
github.com/spf13/pflag v1.0.10
|
||||
github.com/spf13/viper v1.21.0
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/warthog618/gpiod v0.8.1
|
||||
go.bug.st/serial v1.6.4
|
||||
go.uber.org/zap v1.27.0
|
||||
golang.org/x/sync v0.16.0
|
||||
google.golang.org/grpc v1.74.2
|
||||
google.golang.org/protobuf v1.36.6
|
||||
go.uber.org/zap v1.27.1
|
||||
golang.org/x/sync v0.20.0
|
||||
google.golang.org/grpc v1.80.0
|
||||
google.golang.org/protobuf v1.36.11
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
tinygo.org/x/drivers v0.32.0
|
||||
tinygo.org/x/drivers v0.34.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/aws/smithy-go v1.22.3 // indirect
|
||||
github.com/aws/smithy-go v1.22.5 // indirect
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
|
||||
github.com/charmbracelet/x/ansi v0.8.0 // indirect
|
||||
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
|
||||
github.com/charmbracelet/x/term v0.2.1 // indirect
|
||||
github.com/clipperhouse/displaywidth v0.10.0 // indirect
|
||||
github.com/clipperhouse/stringish v0.1.1 // indirect
|
||||
github.com/clipperhouse/uax29/v2 v2.6.0 // indirect
|
||||
github.com/creack/goselect v0.1.3 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/fatih/color v1.15.0 // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.19 // indirect
|
||||
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect
|
||||
github.com/muesli/termenv v0.16.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d // indirect
|
||||
github.com/olekukonko/errors v0.0.0-20250405072817-4e6d85265da6 // indirect
|
||||
github.com/olekukonko/ll v0.0.8 // indirect
|
||||
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect
|
||||
github.com/olekukonko/errors v1.2.0 // indirect
|
||||
github.com/olekukonko/ll v0.1.6 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.63.0 // indirect
|
||||
github.com/prometheus/common v0.66.1 // indirect
|
||||
github.com/prometheus/procfs v0.16.1 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/sagikazarmark/locafero v0.9.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.14.0 // indirect
|
||||
github.com/spf13/cast v1.8.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
||||
github.com/spf13/afero v1.15.0 // indirect
|
||||
github.com/spf13/cast v1.10.0 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/otel v1.36.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.11.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.11.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/log v0.11.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.36.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.36.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/log v0.11.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.36.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.5.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/otel v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.13.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.13.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/log v0.13.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/log v0.13.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.39.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.7.1 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/net v0.40.0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
golang.org/x/text v0.25.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/net v0.49.0 // indirect
|
||||
golang.org/x/sys v0.40.0 // indirect
|
||||
golang.org/x/text v0.33.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260120221211-b8f7ae30c516 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 // indirect
|
||||
)
|
||||
|
||||
245
go.sum
245
go.sum
@@ -1,11 +1,15 @@
|
||||
github.com/aws/smithy-go v1.22.3 h1:Z//5NuZCSW6R4PhQ93hShNbyBbn8BWCmCVCt+Q8Io5k=
|
||||
github.com/aws/smithy-go v1.22.3/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
|
||||
github.com/aws/smithy-go v1.22.5 h1:P9ATCXPMb2mPjYBgueqJNCA5S9UfktsW0tTxi+a7eqw=
|
||||
github.com/aws/smithy-go v1.22.5/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
|
||||
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
|
||||
@@ -18,6 +22,22 @@ github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0G
|
||||
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
|
||||
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
|
||||
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
|
||||
github.com/clipperhouse/displaywidth v0.3.1 h1:k07iN9gD32177o1y4O1jQMzbLdCrsGJh+blirVYybsk=
|
||||
github.com/clipperhouse/displaywidth v0.3.1/go.mod h1:tgLJKKyaDOCadywag3agw4snxS5kYEuYR6Y9+qWDDYM=
|
||||
github.com/clipperhouse/displaywidth v0.6.0 h1:k32vueaksef9WIKCNcoqRNyKbyvkvkysNYnAWz2fN4s=
|
||||
github.com/clipperhouse/displaywidth v0.6.0/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o=
|
||||
github.com/clipperhouse/displaywidth v0.6.2 h1:ZDpTkFfpHOKte4RG5O/BOyf3ysnvFswpyYrV7z2uAKo=
|
||||
github.com/clipperhouse/displaywidth v0.6.2/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o=
|
||||
github.com/clipperhouse/displaywidth v0.10.0 h1:GhBG8WuerxjFQQYeuZAeVTuyxuX+UraiZGD4HJQ3Y8g=
|
||||
github.com/clipperhouse/displaywidth v0.10.0/go.mod h1:XqJajYsaiEwkxOj4bowCTMcT1SgvHo9flfF3jQasdbs=
|
||||
github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
|
||||
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
|
||||
github.com/clipperhouse/uax29/v2 v2.2.0 h1:ChwIKnQN3kcZteTXMgb1wztSgaU+ZemkgWdohwgs8tY=
|
||||
github.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
|
||||
github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4=
|
||||
github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
|
||||
github.com/clipperhouse/uax29/v2 v2.6.0 h1:z0cDbUV+aPASdFb2/ndFnS9ts/WNXgTNNGFoKXuhpos=
|
||||
github.com/clipperhouse/uax29/v2 v2.6.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/creack/goselect v0.1.3 h1:MaGNMclRo7P2Jl21hBpR1Cn33ITSbKP6E49RtfblLKc=
|
||||
github.com/creack/goselect v0.1.3/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY=
|
||||
@@ -25,6 +45,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
|
||||
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
@@ -40,6 +62,8 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
|
||||
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
@@ -50,8 +74,12 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 h1:sGm2vDRFUrQJO/Veii4h4zG2vvqG6uWNkBHSTqXOZk0=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2/go.mod h1:wd1YpapPLivG6nQgbf7ZkG1hhSOXDhhn4MLTknx2aAc=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 h1:B+8ClL/kCQkRiU82d9xajRPKYMrB7E0MbtzWVi1K4ns=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3/go.mod h1:NbCUVmiS4foBGBHOYlCT25+YmGpJ32dZPi75pGEUpj4=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 h1:e9Rjr40Z98/clHv5Yg79Is0NtosR5LXRvdr7o/6NwbA=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1/go.mod h1:tIxuGz/9mpox++sgp9fJjHO0+q1X9/UOWd798aAm22M=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
@@ -66,12 +94,16 @@ github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
|
||||
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM=
|
||||
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
|
||||
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
|
||||
@@ -80,14 +112,42 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d h1:x3S6kxmy49zXVVyhcnrFqxvNVCBPb2KZ9hV2RBdS840=
|
||||
github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ=
|
||||
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 h1:zrbMGy9YXpIeTnGj4EljqMiZsIcE09mmF8XsD5AYOJc=
|
||||
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6/go.mod h1:rEKTHC9roVVicUIfZK7DYrdIoM0EOr8mK1Hj5s3JjH0=
|
||||
github.com/olekukonko/errors v0.0.0-20250405072817-4e6d85265da6 h1:r3FaAI0NZK3hSmtTDrBVREhKULp8oUeqLT5Eyl2mSPo=
|
||||
github.com/olekukonko/errors v0.0.0-20250405072817-4e6d85265da6/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y=
|
||||
github.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM=
|
||||
github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y=
|
||||
github.com/olekukonko/errors v1.2.0 h1:10Zcn4GeV59t/EGqJc8fUjtFT/FuUh5bTMzZ1XwmCRo=
|
||||
github.com/olekukonko/errors v1.2.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y=
|
||||
github.com/olekukonko/ll v0.0.8 h1:sbGZ1Fx4QxJXEqL/6IG8GEFnYojUSQ45dJVwN2FH2fc=
|
||||
github.com/olekukonko/ll v0.0.8/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g=
|
||||
github.com/olekukonko/ll v0.0.9 h1:Y+1YqDfVkqMWuEQMclsF9HUR5+a82+dxJuL1HHSRpxI=
|
||||
github.com/olekukonko/ll v0.0.9/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g=
|
||||
github.com/olekukonko/ll v0.1.2 h1:lkg/k/9mlsy0SxO5aC+WEpbdT5K83ddnNhAepz7TQc0=
|
||||
github.com/olekukonko/ll v0.1.2/go.mod h1:b52bVQRRPObe+yyBl0TxNfhesL0nedD4Cht0/zx55Ew=
|
||||
github.com/olekukonko/ll v0.1.3 h1:sV2jrhQGq5B3W0nENUISCR6azIPf7UBUpVq0x/y70Fg=
|
||||
github.com/olekukonko/ll v0.1.3/go.mod h1:b52bVQRRPObe+yyBl0TxNfhesL0nedD4Cht0/zx55Ew=
|
||||
github.com/olekukonko/ll v0.1.4-0.20260115111900-9e59c2286df0 h1:jrYnow5+hy3WRDCBypUFvVKNSPPCdqgSXIE9eJDD8LM=
|
||||
github.com/olekukonko/ll v0.1.4-0.20260115111900-9e59c2286df0/go.mod h1:b52bVQRRPObe+yyBl0TxNfhesL0nedD4Cht0/zx55Ew=
|
||||
github.com/olekukonko/ll v0.1.6 h1:lGVTHO+Qc4Qm+fce/2h2m5y9LvqaW+DCN7xW9hsU3uA=
|
||||
github.com/olekukonko/ll v0.1.6/go.mod h1:NVUmjBb/aCtUpjKk75BhWrOlARz3dqsM+OtszpY4o88=
|
||||
github.com/olekukonko/tablewriter v1.0.7 h1:HCC2e3MM+2g72M81ZcJU11uciw6z/p82aEnm4/ySDGw=
|
||||
github.com/olekukonko/tablewriter v1.0.7/go.mod h1:H428M+HzoUXC6JU2Abj9IT9ooRmdq9CxuDmKMtrOCMs=
|
||||
github.com/olekukonko/tablewriter v1.0.8 h1:f6wJzHg4QUtJdvrVPKco4QTrAylgaU0+b9br/lJxEiQ=
|
||||
github.com/olekukonko/tablewriter v1.0.8/go.mod h1:H428M+HzoUXC6JU2Abj9IT9ooRmdq9CxuDmKMtrOCMs=
|
||||
github.com/olekukonko/tablewriter v1.0.9 h1:XGwRsYLC2bY7bNd93Dk51bcPZksWZmLYuaTHR0FqfL8=
|
||||
github.com/olekukonko/tablewriter v1.0.9/go.mod h1:5c+EBPeSqvXnLLgkm9isDdzR3wjfBkHR9Nhfp3NWrzo=
|
||||
github.com/olekukonko/tablewriter v1.1.0 h1:N0LHrshF4T39KvI96fn6GT8HEjXRXYNDrDjKFDB7RIY=
|
||||
github.com/olekukonko/tablewriter v1.1.0/go.mod h1:5c+EBPeSqvXnLLgkm9isDdzR3wjfBkHR9Nhfp3NWrzo=
|
||||
github.com/olekukonko/tablewriter v1.1.1 h1:b3reP6GCfrHwmKkYwNRFh2rxidGHcT6cgxj/sHiDDx0=
|
||||
github.com/olekukonko/tablewriter v1.1.1/go.mod h1:De/bIcTF+gpBDB3Alv3fEsZA+9unTsSzAg/ZGADCtn4=
|
||||
github.com/olekukonko/tablewriter v1.1.2 h1:L2kI1Y5tZBct/O/TyZK1zIE9GlBj/TVs+AY5tZDCDSc=
|
||||
github.com/olekukonko/tablewriter v1.1.2/go.mod h1:z7SYPugVqGVavWoA2sGsFIoOVNmEHxUAAMrhXONtfkg=
|
||||
github.com/olekukonko/tablewriter v1.1.3 h1:VSHhghXxrP0JHl+0NnKid7WoEmd9/urKRJLysb70nnA=
|
||||
github.com/olekukonko/tablewriter v1.1.3/go.mod h1:9VU0knjhmMkXjnMKrZ3+L2JhhtsQ/L38BbL3CRNE8tM=
|
||||
github.com/olekukonko/tablewriter v1.1.4 h1:ORUMI3dXbMnRlRggJX3+q7OzQFDdvgbN9nVWj1drm6I=
|
||||
github.com/olekukonko/tablewriter v1.1.4/go.mod h1:+kedxuyTtgoZLwif3P1Em4hARJs+mVnzKxmsCL/C5RY=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pilebones/go-udev v0.9.0 h1:N1uEO/SxUwtIctc0WLU0t69JeBxIYEYnj8lT/Nabl9Q=
|
||||
@@ -96,10 +156,18 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
|
||||
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
|
||||
github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc=
|
||||
github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE=
|
||||
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
|
||||
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
|
||||
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||
github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k=
|
||||
github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18=
|
||||
github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
|
||||
github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
|
||||
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
|
||||
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
|
||||
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
|
||||
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
@@ -110,30 +178,67 @@ github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWN
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sagikazarmark/locafero v0.9.0 h1:GbgQGNtTrEmddYDSAH9QLRyfAHY12md+8YFTqyMTC9k=
|
||||
github.com/sagikazarmark/locafero v0.9.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk=
|
||||
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
|
||||
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
|
||||
github.com/sierrasoftworks/humane-errors-go v0.0.0-20250507223502-4bb667dc1e16 h1:9vtY3febGroV+aPR5OlI3fekkesi+lMVsVWyxBp/rfk=
|
||||
github.com/sierrasoftworks/humane-errors-go v0.0.0-20250507223502-4bb667dc1e16/go.mod h1:CbJLj9L1qHdzLg4YRh2Lzr0noe9pR6QrVEqfLbITRKw=
|
||||
github.com/sierrasoftworks/humane-errors-go v0.0.0-20250811205537-5f14a04ebff5 h1:nlfxPheTxwOE5hEq9iVurbo83/Wie52V5lKIhi73mRw=
|
||||
github.com/sierrasoftworks/humane-errors-go v0.0.0-20250811205537-5f14a04ebff5/go.mod h1:CbJLj9L1qHdzLg4YRh2Lzr0noe9pR6QrVEqfLbITRKw=
|
||||
github.com/sierrasoftworks/humane-errors-go v0.0.0-20251121131909-6b4ca9dd07a7 h1:mD7GjWbIAdxIvDwTXs2uidulnIAfi06h7SZBQvDZga8=
|
||||
github.com/sierrasoftworks/humane-errors-go v0.0.0-20251121131909-6b4ca9dd07a7/go.mod h1:AyTGN4SGjbmEHvVAnBy8SpCnG3hskAfZ7TJW1AzMXgk=
|
||||
github.com/sierrasoftworks/humane-errors-go v0.0.0-20260226124905-a7be4ffe4f32 h1:6qDjIFnVHw6zdtW0t3RuN1PWcVSL6kXZiiDYR/P7id8=
|
||||
github.com/sierrasoftworks/humane-errors-go v0.0.0-20260226124905-a7be4ffe4f32/go.mod h1:AyTGN4SGjbmEHvVAnBy8SpCnG3hskAfZ7TJW1AzMXgk=
|
||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
|
||||
github.com/spechtlabs/go-otel-utils/otelprovider v0.0.10 h1:Q5p+5KGA587GfzR6FdXGje4XBfxhi1u4NSu6lSnWCGA=
|
||||
github.com/spechtlabs/go-otel-utils/otelprovider v0.0.10/go.mod h1:sFuJXEBbNq/pQx9pP5OnVtx9yGJnH4fXi7x3qihW0ak=
|
||||
github.com/spechtlabs/go-otel-utils/otelprovider v0.0.13 h1:DyTnvzLrhGowglrmDO8GFzbC/ZnzHRkMlFCk7p/pEmc=
|
||||
github.com/spechtlabs/go-otel-utils/otelprovider v0.0.13/go.mod h1:63JphMzIY2vGTZ9OMjntS/XUM2sVwLCydSsjVE+M5QU=
|
||||
github.com/spechtlabs/go-otel-utils/otelprovider v0.0.15 h1:LRx9EFzD4bI+kH3NNn6GtqwRrGbJgmyau4IOJcfBTYE=
|
||||
github.com/spechtlabs/go-otel-utils/otelprovider v0.0.15/go.mod h1:ACquqruOxYFRL5H7TCxXKXIJTVj+6eqlEJR6WQAEBeE=
|
||||
github.com/spechtlabs/go-otel-utils/otelzap v0.0.10 h1:RR/WS4b+ABxNL7xzlK4FTvnuXRbGk3yyggvvLnQ+FeM=
|
||||
github.com/spechtlabs/go-otel-utils/otelzap v0.0.10/go.mod h1:IhsBuW+sZwLxX1Ww5LmTlIonBP8GiyhsiZkIRq+ySE0=
|
||||
github.com/spechtlabs/go-otel-utils/otelzap v0.0.11 h1:D3jzku3MMLMt/CgI++Zk3TE/mnQWz9dI9y/sCBb60OA=
|
||||
github.com/spechtlabs/go-otel-utils/otelzap v0.0.11/go.mod h1:Bk/uRNkU1/MsMc2J1NUxF+0lRNDbUUIulioJevLYM74=
|
||||
github.com/spechtlabs/go-otel-utils/otelzap v0.0.13 h1:eKrf3Jd4WurABA+iw4TAZJRF2/Ke1cIzsX1wSUOZZT0=
|
||||
github.com/spechtlabs/go-otel-utils/otelzap v0.0.13/go.mod h1:Bk/uRNkU1/MsMc2J1NUxF+0lRNDbUUIulioJevLYM74=
|
||||
github.com/spechtlabs/go-otel-utils/otelzap v0.0.15 h1:LPo3vmPRVTQDG/ic2r4q1t9irptnffRRTEpaLYjbguo=
|
||||
github.com/spechtlabs/go-otel-utils/otelzap v0.0.15/go.mod h1:0LC7Tzo53EdZ0FY6dUMDDkDmimphYDbjqIg+BY5MJjk=
|
||||
github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA=
|
||||
github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo=
|
||||
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
|
||||
github.com/spf13/cast v1.8.0 h1:gEN9K4b8Xws4EX0+a0reLmhq8moKn7ntRlQYgjPeCDk=
|
||||
github.com/spf13/cast v1.8.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
||||
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
|
||||
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
|
||||
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
|
||||
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
|
||||
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
|
||||
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
|
||||
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
|
||||
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4=
|
||||
github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
|
||||
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
|
||||
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.11.0 h1:ib4sjIrwZKxE5u/Japgo/7SJV3PvgjGiRNAvTVGqQl8=
|
||||
github.com/stretchr/testify v1.11.0/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/warthog618/gpiod v0.8.1 h1:+8iHpHd3fljAd6l4AT8jPbMDQNKdvBIpW/hmLgAcHiM=
|
||||
@@ -144,68 +249,204 @@ go.bug.st/serial v1.6.4 h1:7FmqNPgVp3pu2Jz5PoPtbZ9jJO5gnEnZIvnI1lzve8A=
|
||||
go.bug.st/serial v1.6.4/go.mod h1:nofMJxTeNVny/m6+KaafC6vJGj3miwQZ6vW4BZUGJPI=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
|
||||
go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
|
||||
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
|
||||
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
|
||||
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
|
||||
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
|
||||
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
|
||||
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.11.0 h1:HMUytBT3uGhPKYY/u/G5MR9itrlSO2SMOsSD3Tk3k7A=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.11.0/go.mod h1:hdDXsiNLmdW/9BF2jQpnHHlhFajpWCEYfM6e5m2OAZg=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.13.0 h1:z6lNIajgEBVtQZHjfw2hAccPEBDs+nx58VemmXWa2ec=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.13.0/go.mod h1:+kyc3bRx/Qkq05P6OCu3mTEIOxYRYzoIg+JsUp5X+PM=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.11.0 h1:C/Wi2F8wEmbxJ9Kuzw/nhP+Z9XaHYMkyDmXy6yR2cjw=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.11.0/go.mod h1:0Lr9vmGKzadCTgsiBydxr6GEZ8SsZ7Ks53LzjWG5Ar4=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.13.0 h1:zUfYw8cscHHLwaY8Xz3fiJu+R59xBnkgq2Zr1lwmK/0=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.13.0/go.mod h1:514JLMCcFLQFS8cnTepOk6I09cKWJ5nGHBxHrMJ8Yfg=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 h1:m639+BofXTvcY1q8CGs4ItwQarYtJPOWmVobfM1HpVI=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0/go.mod h1:LjReUci/F4BUyv+y4dwnq3h/26iNOeC3wAIqgvTIZVo=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWSnwW9hGObjkIdmlnWSydO+Qs8OwzfzXLUPg4xOc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 h1:xJ2qHD0C1BeYVTLLR9sX12+Qb95kfeD/byKj6Ky1pXg=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0/go.mod h1:u5BF1xyjstDowA1R5QAO9JHzqK+ublenEW/dyqTjBVk=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0 h1:bDMKF3RUSxshZ5OjOTi8rsHGaPKsAt76FaqgvIUySLc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0/go.mod h1:dDT67G/IkA46Mr2l9Uj7HsQVwsjASyV9SjGofsiUZDA=
|
||||
go.opentelemetry.io/otel/log v0.11.0 h1:c24Hrlk5WJ8JWcwbQxdBqxZdOK7PcP/LFtOtwpDTe3Y=
|
||||
go.opentelemetry.io/otel/log v0.11.0/go.mod h1:U/sxQ83FPmT29trrifhQg+Zj2lo1/IPN1PF6RTFqdwc=
|
||||
go.opentelemetry.io/otel/log v0.13.0 h1:yoxRoIZcohB6Xf0lNv9QIyCzQvrtGZklVbdCoyb7dls=
|
||||
go.opentelemetry.io/otel/log v0.13.0/go.mod h1:INKfG4k1O9CL25BaM1qLe0zIedOpvlS5Z7XgSbmN83E=
|
||||
go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=
|
||||
go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
|
||||
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
|
||||
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
|
||||
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
|
||||
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
|
||||
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
|
||||
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
|
||||
go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=
|
||||
go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY=
|
||||
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
|
||||
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
|
||||
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
|
||||
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
|
||||
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
|
||||
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
|
||||
go.opentelemetry.io/otel/sdk/log v0.11.0 h1:7bAOpjpGglWhdEzP8z0VXc4jObOiDEwr3IYbhBnjk2c=
|
||||
go.opentelemetry.io/otel/sdk/log v0.11.0/go.mod h1:dndLTxZbwBstZoqsJB3kGsRPkpAgaJrWfQg3lhlHFFY=
|
||||
go.opentelemetry.io/otel/sdk/log v0.13.0 h1:I3CGUszjM926OphK8ZdzF+kLqFvfRY/IIoFq/TjwfaQ=
|
||||
go.opentelemetry.io/otel/sdk/log v0.13.0/go.mod h1:lOrQyCCXmpZdN7NchXb6DOZZa1N5G1R2tm5GMMTpDBw=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
|
||||
go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
|
||||
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
|
||||
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
|
||||
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
|
||||
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
|
||||
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
|
||||
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
|
||||
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
|
||||
go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4=
|
||||
go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4=
|
||||
go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4=
|
||||
go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
|
||||
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
|
||||
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d h1:0olWaB5pg3+oychR51GUVCEsGkeCU/2JxjBgIo4f3M0=
|
||||
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
|
||||
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
|
||||
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
|
||||
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||
golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 h1:6/3JGEh1C88g7m+qzzTbl3A0FtsLguXieqofVLU/JAo=
|
||||
golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
|
||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
||||
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
||||
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
||||
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
|
||||
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
||||
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
||||
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
|
||||
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
||||
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 h1:hE3bRWtU6uceqlh4fhrSnUyjKHMKB9KrTLLG+bc0ddM=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463/go.mod h1:U90ffi8eUL9MwPcrJylN5+Mk2v3vuPDptd5yyNUiRR8=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a h1:SGktgSolFCo75dnHJF2yMvnns6jCmHFJ0vE4Vn2JKvQ=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a/go.mod h1:a77HrdMjoeKbnd2jmgcWdaS++ZLZAEq3orIOAEIKiVw=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c h1:AtEkQdl5b6zsybXcbz00j1LwNodDuH6hVifIaNqk7NQ=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c/go.mod h1:ea2MjsO70ssTfCjiwHgI0ZFqcw45Ksuk2ckf9G468GA=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 h1:mepRgnBZa07I4TRuomDE4sTIYieg/osKmzIf4USdWS4=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251029180050-ab9386a59fda h1:+2XxjfsAu6vqFxwGBRcHiMaDCuZiqXGDUDVWVtrFAnE=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251029180050-ab9386a59fda/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260120221211-b8f7ae30c516 h1:vmC/ws+pLzWjj/gzApyoZuSVrDtF1aod4u/+bbj8hgM=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260120221211-b8f7ae30c516/go.mod h1:p3MLuOwURrGBRoEyFHBT3GjUwaCQVKeNqqWxlcISGdw=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 h1:cJfm9zPbe1e873mHJzmQ1nwVEeRDU/T1wXDK2kUSU34=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a h1:v2PbRU4K3llS09c7zodFpNePeamkAwG3mPrAery9VeE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c h1:qXWI/sQtv5UKboZ/zUk7h+mrf/lXORyI+n9DKDAusdg=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 h1:M1rk8KBnUsBDg1oPGHNCxG4vc1f49epmTO7xscSajMk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda h1:i/Q+bfisr7gq6feoJnS/DlpdwEL4ihp41fvRiM3Ork0=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 h1:sNrWoksmOyF5bvJUcnmbeAmQi8baNhqg5IWaI3llQqU=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
||||
google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
|
||||
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
|
||||
google.golang.org/grpc v1.74.0 h1:sxRSkyLxlceWQiqDofxDot3d4u7DyoHPc7SBXMj8gGY=
|
||||
google.golang.org/grpc v1.74.0/go.mod h1:NZUaK8dAMUfzhK6uxZ+9511LtOrk73UGWOFoNvz7z+s=
|
||||
google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4=
|
||||
google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM=
|
||||
google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4=
|
||||
google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
|
||||
google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI=
|
||||
google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
|
||||
google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
|
||||
google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
|
||||
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
|
||||
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
|
||||
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
|
||||
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
|
||||
google.golang.org/grpc v1.79.0 h1:6/+EFlxsMyoSbHbBoEDx94n/Ycx/bi0IhJ5Qh7b7LaA=
|
||||
google.golang.org/grpc v1.79.0/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
||||
google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY=
|
||||
google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
||||
google.golang.org/grpc v1.79.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU=
|
||||
google.golang.org/grpc v1.79.2/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
||||
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
|
||||
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
||||
google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM=
|
||||
google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A=
|
||||
google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
|
||||
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
|
||||
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
@@ -215,3 +456,7 @@ tinygo.org/x/drivers v0.31.0 h1:Q2RpvTRMtdmjHD2Xyn4e8WXsJZKpIny3Lg4hzG1dLu4=
|
||||
tinygo.org/x/drivers v0.31.0/go.mod h1:ZdErNrApSABdVXjA1RejD67R8SNRI6RKVfYgQDZtKtk=
|
||||
tinygo.org/x/drivers v0.32.0 h1:qz7MRR1ZBIUhWC6kc4XuVNPr2+mUT8m7QJwA+Jji4IU=
|
||||
tinygo.org/x/drivers v0.32.0/go.mod h1:ZdErNrApSABdVXjA1RejD67R8SNRI6RKVfYgQDZtKtk=
|
||||
tinygo.org/x/drivers v0.33.0 h1:5r8Ab0IxjWQi7LzYLNWpya6U4nedo9ZtxeMaAzrJTG8=
|
||||
tinygo.org/x/drivers v0.33.0/go.mod h1:ZdErNrApSABdVXjA1RejD67R8SNRI6RKVfYgQDZtKtk=
|
||||
tinygo.org/x/drivers v0.34.0 h1:lw8ePJeUSn9oICKBvQXHC9TIE+J00OfXfkGTrpXM9Iw=
|
||||
tinygo.org/x/drivers v0.34.0/go.mod h1:ZdErNrApSABdVXjA1RejD67R8SNRI6RKVfYgQDZtKtk=
|
||||
|
||||
@@ -54,7 +54,7 @@ type computeBladeAgent struct {
|
||||
|
||||
// NewComputeBladeAgent creates and initializes a new ComputeBladeAgent, including gRPC server setup and hardware interfaces.
|
||||
func NewComputeBladeAgent(ctx context.Context, config agent.ComputeBladeAgentConfig, agentInfo agent.ComputeBladeAgentInfo) (agent.ComputeBladeAgent, error) {
|
||||
blade, err := hal.NewCm4Hal(ctx, config.ComputeBladeHalOpts)
|
||||
blade, err := hal.NewHal(ctx, config.ComputeBladeHalOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -86,20 +86,31 @@ func (f *fanControllerLinear) GetFanSpeedPercent(temperature float64) uint8 {
|
||||
return f.overrideOpts.Percent
|
||||
}
|
||||
|
||||
if temperature <= f.config.Steps[0].Temperature {
|
||||
return f.config.Steps[0].Percent
|
||||
}
|
||||
if temperature >= f.config.Steps[1].Temperature {
|
||||
return f.config.Steps[1].Percent
|
||||
steps := f.config.Steps
|
||||
|
||||
// Below minimum temperature: use minimum fan speed
|
||||
if temperature <= steps[0].Temperature {
|
||||
return steps[0].Percent
|
||||
}
|
||||
|
||||
// Calculate slope
|
||||
slope := float64(f.config.Steps[1].Percent-f.config.Steps[0].Percent) / (f.config.Steps[1].Temperature - f.config.Steps[0].Temperature)
|
||||
// Above maximum temperature: use maximum fan speed
|
||||
lastIdx := len(steps) - 1
|
||||
if temperature >= steps[lastIdx].Temperature {
|
||||
return steps[lastIdx].Percent
|
||||
}
|
||||
|
||||
// Calculate speed
|
||||
speed := float64(f.config.Steps[0].Percent) + slope*(temperature-f.config.Steps[0].Temperature)
|
||||
// Find the bracket where steps[i].Temperature <= temperature < steps[i+1].Temperature
|
||||
for i := 0; i < lastIdx; i++ {
|
||||
if temperature >= steps[i].Temperature && temperature < steps[i+1].Temperature {
|
||||
// Linear interpolation between steps[i] and steps[i+1]
|
||||
slope := float64(steps[i+1].Percent-steps[i].Percent) / (steps[i+1].Temperature - steps[i].Temperature)
|
||||
speed := float64(steps[i].Percent) + slope*(temperature-steps[i].Temperature)
|
||||
return uint8(speed)
|
||||
}
|
||||
}
|
||||
|
||||
return uint8(speed)
|
||||
// Fallback (should not reach here due to above checks)
|
||||
return steps[lastIdx].Percent
|
||||
}
|
||||
|
||||
func (f *fanControllerLinear) IsAutomaticSpeed() bool {
|
||||
|
||||
@@ -45,6 +45,55 @@ func TestFanControllerLinear_GetFanSpeed(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestFanControllerLinear_GetFanSpeedMultipleSteps(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Typical 5-step fan curve configuration
|
||||
config := fancontroller.Config{
|
||||
Steps: []fancontroller.Step{
|
||||
{Temperature: 40, Percent: 30},
|
||||
{Temperature: 50, Percent: 50},
|
||||
{Temperature: 60, Percent: 70},
|
||||
{Temperature: 70, Percent: 90},
|
||||
{Temperature: 75, Percent: 100},
|
||||
},
|
||||
}
|
||||
|
||||
controller, err := fancontroller.NewLinearFanController(config)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create fan controller: %v", err)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
temperature float64
|
||||
expected uint8
|
||||
}{
|
||||
{"below minimum", 30, 30}, // Below 40°C: use minimum 30%
|
||||
{"at step 0", 40, 30}, // At 40°C: 30%
|
||||
{"between step 0-1", 45, 40}, // Midpoint 40-50°C: 40%
|
||||
{"at step 1", 50, 50}, // At 50°C: 50%
|
||||
{"between step 1-2", 55, 60}, // Midpoint 50-60°C: 60%
|
||||
{"at step 2", 60, 70}, // At 60°C: 70%
|
||||
{"between step 2-3", 65, 80}, // Midpoint 60-70°C: 80%
|
||||
{"at step 3", 70, 90}, // At 70°C: 90%
|
||||
{"between step 3-4", 72, 94}, // 70 + (100-90)*(72-70)/(75-70) = 90 + 4 = 94%
|
||||
{"at step 4", 75, 100}, // At 75°C: 100%
|
||||
{"above maximum", 80, 100}, // Above 75°C: use maximum 100%
|
||||
{"well above maximum", 90, 100}, // Well above: still 100%
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
expected := tc.expected
|
||||
temperature := tc.temperature
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
speed := controller.GetFanSpeedPercent(temperature)
|
||||
assert.Equal(t, expected, speed, "Temperature %.1f°C should yield %d%% fan speed", temperature, expected)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFanControllerLinear_GetFanSpeedWithOverride(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ type bcm2711 struct {
|
||||
fanUnit FanUnit
|
||||
}
|
||||
|
||||
func NewCm4Hal(ctx context.Context, opts ComputeBladeHalOpts) (ComputeBladeHal, error) {
|
||||
func newBcm2711Hal(ctx context.Context, opts ComputeBladeHalOpts) (ComputeBladeHal, error) {
|
||||
// /dev/gpiomem doesn't allow complex operations for PWM fan control or WS281x
|
||||
devmem, err := os.OpenFile("/dev/mem", os.O_RDWR|os.O_SYNC, os.ModePerm)
|
||||
if err != nil {
|
||||
|
||||
@@ -20,7 +20,7 @@ type SimulatedHal struct {
|
||||
isStealthMode bool
|
||||
}
|
||||
|
||||
func NewCm4Hal(_ context.Context, _ ComputeBladeHalOpts) (ComputeBladeHal, error) {
|
||||
func NewHal(_ context.Context, _ ComputeBladeHalOpts) (ComputeBladeHal, error) {
|
||||
logger := otelzap.L().Named("hal").Named("simulated-cm4")
|
||||
logger.Warn("Using simulated hal")
|
||||
|
||||
|
||||
653
pkg/hal/hal_bcm2712.go
Normal file
653
pkg/hal/hal_bcm2712.go
Normal file
@@ -0,0 +1,653 @@
|
||||
//go:build linux && !tinygo
|
||||
|
||||
package hal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/compute-blade-community/compute-blade-agent/pkg/hal/led"
|
||||
"github.com/compute-blade-community/compute-blade-agent/pkg/log"
|
||||
"github.com/warthog618/gpiod"
|
||||
"github.com/warthog618/gpiod/device/rpi"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
const (
|
||||
// RP1 southbridge is connected via PCIe on BCM2712.
|
||||
// The BAR base address is fixed by firmware at 0x1f00000000.
|
||||
rp1BarBase int64 = 0x1f00000000
|
||||
rp1GpioBase int64 = rp1BarBase + 0xd0000
|
||||
rp1Pwm0Base int64 = rp1BarBase + 0x98000
|
||||
rp1PageSize = 4096
|
||||
|
||||
// RP1 PWM input clock is 50 MHz (from device tree assigned-clock-rates)
|
||||
rp1PwmClockHz = 50_000_000
|
||||
|
||||
// RP1 GPIO register layout: each GPIO has 8 bytes (STATUS + CTRL)
|
||||
rp1GpioCtrlOffset = 0x04 // CTRL register offset within each GPIO's 8-byte block
|
||||
rp1GpioRegSize = 0x08
|
||||
rp1GpioFuncselMask = 0x1f // CTRL bits [4:0]
|
||||
|
||||
// GPIO function select values (confirmed via `pinctrl set/get` on CM5)
|
||||
// GPIO 12: funcsel 0 (a0) = PWM0_CHAN0 (fan PWM)
|
||||
// GPIO 18: funcsel 3 (a3) = PWM0_CHAN2 (WS281x LEDs)
|
||||
rp1Gpio12FuncselPwm = 0
|
||||
rp1Gpio18FuncselPwm = 3
|
||||
rp1GpioFuncselSio = 5 // Software IO (standard GPIO)
|
||||
rp1GpioFuncselNull = 0x1f // Null function (disabled)
|
||||
|
||||
// RP1 PWM register offsets (byte offsets, divide by 4 for []uint32 index)
|
||||
rp1PwmGlobalCtrl = 0x00
|
||||
rp1PwmFifoCtrl = 0x04
|
||||
rp1PwmFifoPush = 0x08
|
||||
rp1PwmFifoLevel = 0x0c
|
||||
|
||||
// Per-channel registers: base = 0x14 + channel * 0x10
|
||||
// From kernel pwm-rp1.c: CTRL(x)=0x14+x*16, RANGE(x)=0x18+x*16, DUTY(x)=0x20+x*16
|
||||
rp1PwmChanCtrlOff = 0x00 // relative to channel base
|
||||
rp1PwmChanRangeOff = 0x04
|
||||
rp1PwmChanPhaseOff = 0x08 // undocumented in kernel driver, possibly COUNT
|
||||
rp1PwmChanDutyOff = 0x0c
|
||||
rp1PwmChanSize = 0x10
|
||||
rp1PwmChanBase = 0x14 // first channel base offset (NOT 0x10)
|
||||
|
||||
// PWM global ctrl bits (from kernel pwm-rp1.c)
|
||||
rp1PwmGlobalChanEnBit = 0 // bits [3:0] = per-channel enable, BIT(x)
|
||||
rp1PwmGlobalSetUpdate = 31 // BIT(31) = global set_update trigger
|
||||
|
||||
// PWM channel ctrl bits
|
||||
rp1PwmChanCtrlModeBit = 0 // bits [1:0]
|
||||
rp1PwmChanCtrlInvertBit = 2
|
||||
rp1PwmChanCtrlUseFifoBit = 4
|
||||
rp1PwmChanCtrlFifoPopMaskBit = 7 // bits [8:7]
|
||||
|
||||
// PWM modes
|
||||
rp1PwmModeTrailingEdge = 0
|
||||
rp1PwmModeMarkSpace = 1
|
||||
rp1PwmModeSerializer = 3
|
||||
|
||||
// FIFO ctrl bits
|
||||
rp1PwmFifoFlushBit = 5
|
||||
|
||||
// Fan PWM: 25 kHz for Noctua fans
|
||||
// RANGE = 50 MHz / 25 kHz = 2000
|
||||
rp1FanPwmRange = rp1PwmClockHz / 25000
|
||||
|
||||
bcm2712SmartFanUnitDev = "/dev/ttyAMA4" // UART4 on BCM2712 (same GPIO 12/13 pins as UART5 on BCM2711)
|
||||
bcm2712ThermalZonePath = "/sys/class/thermal/thermal_zone0/temp"
|
||||
bcm2712DebounceInterval = 100 * time.Millisecond
|
||||
|
||||
// WS281x LED encoding for RP1 at 50MHz with RANGE=32 serializer mode.
|
||||
// Each WS281x data bit is encoded as 2 × 32-bit FIFO words (1280ns per bit ≈ 781kHz).
|
||||
// Channel 2 on GPIO 18 is used independently from fan PWM on channel 0.
|
||||
rp1Ws281xChan = 2 // PWM0 channel 2 on GPIO 18
|
||||
rp1Ws281xRange = 32 // full 32-bit word in serializer mode
|
||||
|
||||
// "0" bit: T0H=400ns (20 high bits), T0L=880ns (44 low bits)
|
||||
rp1Ws281xBit0Word0 uint32 = 0xFFFFF000
|
||||
rp1Ws281xBit0Word1 uint32 = 0x00000000
|
||||
// "1" bit: T1H=800ns (40 high bits), T1L=480ns (24 low bits)
|
||||
rp1Ws281xBit1Word0 uint32 = 0xFFFFFFFF
|
||||
rp1Ws281xBit1Word1 uint32 = 0xFF000000
|
||||
|
||||
// Reset: >50μs of low. At 640ns/word, 80 words = 51.2μs.
|
||||
rp1Ws281xResetWords = 80
|
||||
// Conservative FIFO depth assumption (BCM2835 has 16, RP1 may differ)
|
||||
rp1Ws281xFifoMax uint32 = 8
|
||||
// Safety timeout for FIFO operations
|
||||
rp1Ws281xTimeout = 5 * time.Millisecond
|
||||
)
|
||||
|
||||
// pwmChanRegIdx returns the []uint32 index for a per-channel register.
|
||||
func pwmChanRegIdx(channel, regOffset int) int {
|
||||
return (rp1PwmChanBase + channel*rp1PwmChanSize + regOffset) / 4
|
||||
}
|
||||
|
||||
type bcm2712 struct {
|
||||
opts ComputeBladeHalOpts
|
||||
|
||||
wrMutex sync.Mutex
|
||||
|
||||
currFanSpeed uint8
|
||||
|
||||
devmem *os.File
|
||||
gpioMem8 []uint8
|
||||
gpioMem []uint32
|
||||
pwmMem8 []uint8
|
||||
pwmMem []uint32
|
||||
|
||||
gpioChip0 *gpiod.Chip
|
||||
|
||||
// WS281x LED colors (top + edge)
|
||||
leds [2]led.Color
|
||||
|
||||
// Stealth mode output
|
||||
stealthModeLine *gpiod.Line
|
||||
|
||||
// Edge button input
|
||||
edgeButtonLine *gpiod.Line
|
||||
edgeButtonDebounceChan chan struct{}
|
||||
edgeButtonWatchChan chan struct{}
|
||||
|
||||
// PoE detection input
|
||||
poeLine *gpiod.Line
|
||||
|
||||
// Fan unit
|
||||
fanUnit FanUnit
|
||||
}
|
||||
|
||||
func newBcm2712Hal(ctx context.Context, opts ComputeBladeHalOpts) (ComputeBladeHal, error) {
|
||||
devmem, err := os.OpenFile("/dev/mem", os.O_RDWR|os.O_SYNC, os.ModePerm)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open /dev/mem: %w", err)
|
||||
}
|
||||
|
||||
gpioChip0, err := gpiod.NewChip("gpiochip0")
|
||||
if err != nil {
|
||||
_ = devmem.Close()
|
||||
return nil, fmt.Errorf("failed to open gpiochip0: %w", err)
|
||||
}
|
||||
|
||||
// Memory-map RP1 GPIO bank 0 (GPIOs 0-27)
|
||||
gpioMem, gpioMem8, err := mmap(devmem, rp1GpioBase, rp1PageSize)
|
||||
if err != nil {
|
||||
_ = gpioChip0.Close()
|
||||
_ = devmem.Close()
|
||||
return nil, fmt.Errorf("failed to mmap RP1 GPIO at 0x%x: %w", rp1GpioBase, err)
|
||||
}
|
||||
|
||||
// Memory-map RP1 PWM0
|
||||
pwmMem, pwmMem8, err := mmap(devmem, rp1Pwm0Base, rp1PageSize)
|
||||
if err != nil {
|
||||
_ = syscall.Munmap(gpioMem8)
|
||||
_ = gpioChip0.Close()
|
||||
_ = devmem.Close()
|
||||
return nil, fmt.Errorf("failed to mmap RP1 PWM0 at 0x%x: %w", rp1Pwm0Base, err)
|
||||
}
|
||||
|
||||
bcm := &bcm2712{
|
||||
devmem: devmem,
|
||||
gpioMem: gpioMem,
|
||||
gpioMem8: gpioMem8,
|
||||
pwmMem: pwmMem,
|
||||
pwmMem8: pwmMem8,
|
||||
gpioChip0: gpioChip0,
|
||||
opts: opts,
|
||||
edgeButtonDebounceChan: make(chan struct{}, 1),
|
||||
edgeButtonWatchChan: make(chan struct{}),
|
||||
}
|
||||
|
||||
computeModule.WithLabelValues("cm5").Set(1)
|
||||
|
||||
log.FromContext(ctx).Info("starting hal setup", zap.String("hal", "bcm2712"))
|
||||
if err := bcm.setup(ctx); err != nil {
|
||||
_ = bcm.Close()
|
||||
return nil, err
|
||||
}
|
||||
return bcm, nil
|
||||
}
|
||||
|
||||
func (bcm *bcm2712) Close() error {
|
||||
errs := errors.Join(
|
||||
bcm.closeFanUnit(),
|
||||
bcm.unmapMem(),
|
||||
bcm.closeDevmem(),
|
||||
bcm.closeGpio(),
|
||||
)
|
||||
return errs
|
||||
}
|
||||
|
||||
func (bcm *bcm2712) closeFanUnit() error {
|
||||
if bcm.fanUnit != nil {
|
||||
return bcm.fanUnit.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bcm *bcm2712) unmapMem() error {
|
||||
return errors.Join(
|
||||
munmapIfNonNil(bcm.gpioMem8),
|
||||
munmapIfNonNil(bcm.pwmMem8),
|
||||
)
|
||||
}
|
||||
|
||||
func munmapIfNonNil(mem []uint8) error {
|
||||
if mem != nil {
|
||||
return syscall.Munmap(mem)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bcm *bcm2712) closeDevmem() error {
|
||||
if bcm.devmem != nil {
|
||||
return bcm.devmem.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bcm *bcm2712) closeGpio() error {
|
||||
var errs []error
|
||||
if bcm.gpioChip0 != nil {
|
||||
errs = append(errs, bcm.gpioChip0.Close())
|
||||
}
|
||||
if bcm.poeLine != nil {
|
||||
errs = append(errs, bcm.poeLine.Close())
|
||||
}
|
||||
if bcm.stealthModeLine != nil {
|
||||
errs = append(errs, bcm.stealthModeLine.Close())
|
||||
}
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
// setGpioFuncsel sets the function select for a GPIO pin via direct register write.
|
||||
func (bcm *bcm2712) setGpioFuncsel(gpio int, funcsel uint32) {
|
||||
ctrlIdx := (gpio*rp1GpioRegSize + rp1GpioCtrlOffset) / 4
|
||||
ctrl := bcm.gpioMem[ctrlIdx]
|
||||
ctrl = (ctrl &^ uint32(rp1GpioFuncselMask)) | (funcsel & uint32(rp1GpioFuncselMask))
|
||||
bcm.gpioMem[ctrlIdx] = ctrl
|
||||
}
|
||||
|
||||
func (bcm *bcm2712) setup(ctx context.Context) error {
|
||||
var err error
|
||||
|
||||
// Register edge event handler for edge button (GPIO 20)
|
||||
bcm.edgeButtonLine, err = bcm.gpioChip0.RequestLine(
|
||||
rpi.GPIO20, gpiod.WithEventHandler(bcm.handleEdgeButtonEdge),
|
||||
gpiod.WithFallingEdge, gpiod.WithPullUp, gpiod.WithDebounce(50*time.Millisecond))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to request GPIO20 (edge button): %w", err)
|
||||
}
|
||||
|
||||
// Register input for PoE detection (GPIO 23)
|
||||
bcm.poeLine, err = bcm.gpioChip0.RequestLine(rpi.GPIO23, gpiod.AsInput, gpiod.WithPullUp)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to request GPIO23 (PoE detect): %w", err)
|
||||
}
|
||||
|
||||
// Register output for stealth mode (GPIO 21)
|
||||
bcm.stealthModeLine, err = bcm.gpioChip0.RequestLine(rpi.GPIO21, gpiod.AsOutput(1))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to request GPIO21 (stealth mode): %w", err)
|
||||
}
|
||||
|
||||
// Detect fan unit type
|
||||
log.FromContext(ctx).Info("detecting fan unit")
|
||||
detectCtx, cancel := context.WithTimeout(ctx, 3*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if smartFanUnitPresent, err := SmartFanUnitPresent(detectCtx, bcm2712SmartFanUnitDev); err == nil && smartFanUnitPresent {
|
||||
log.FromContext(ctx).Info("detected smart fan unit")
|
||||
bcm.fanUnit, err = NewSmartFanUnit(bcm2712SmartFanUnitDev)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create smart fan unit: %w", err)
|
||||
}
|
||||
} else {
|
||||
log.FromContext(ctx).WithError(err).Info("no smart fan unit detected, assuming standard fan unit")
|
||||
|
||||
// Set GPIO 12 to PWM0_CHAN0 function
|
||||
bcm.setGpioFuncsel(12, rp1Gpio12FuncselPwm)
|
||||
|
||||
// Initialize PWM0 channel 0 for fan control (25 kHz mark-space)
|
||||
bcm.initFanPwm()
|
||||
|
||||
bcm.fanUnit = &standardFanUnitBcm2711{
|
||||
GpioChip0: bcm.gpioChip0,
|
||||
DisableRpmReporting: !bcm.opts.RpmReportingStandardFanUnit,
|
||||
SetFanSpeedPwmFunc: func(speed uint8) error {
|
||||
bcm.setFanSpeedPWM(speed)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// initFanPwm configures PWM0 channel 0 in mark-space mode at 25 kHz.
|
||||
func (bcm *bcm2712) initFanPwm() {
|
||||
ch := 0
|
||||
|
||||
// Disable channel 0
|
||||
globalCtrl := bcm.pwmMem[rp1PwmGlobalCtrl/4]
|
||||
globalCtrl &^= (1 << (rp1PwmGlobalChanEnBit + ch))
|
||||
bcm.pwmMem[rp1PwmGlobalCtrl/4] = globalCtrl
|
||||
time.Sleep(10 * time.Microsecond)
|
||||
|
||||
// Configure channel 0: mark-space mode, no FIFO, no invert
|
||||
bcm.pwmMem[pwmChanRegIdx(ch, rp1PwmChanCtrlOff)] = uint32(rp1PwmModeMarkSpace) << rp1PwmChanCtrlModeBit
|
||||
time.Sleep(10 * time.Microsecond)
|
||||
|
||||
// Set range (period) for 25 kHz
|
||||
bcm.pwmMem[pwmChanRegIdx(ch, rp1PwmChanRangeOff)] = rp1FanPwmRange
|
||||
time.Sleep(10 * time.Microsecond)
|
||||
|
||||
// Set initial duty to 0 (fan off)
|
||||
bcm.pwmMem[pwmChanRegIdx(ch, rp1PwmChanDutyOff)] = 0
|
||||
time.Sleep(10 * time.Microsecond)
|
||||
|
||||
// Phase = 0
|
||||
bcm.pwmMem[pwmChanRegIdx(ch, rp1PwmChanPhaseOff)] = 0
|
||||
time.Sleep(10 * time.Microsecond)
|
||||
|
||||
// Trigger update for channel 0
|
||||
globalCtrl = bcm.pwmMem[rp1PwmGlobalCtrl/4]
|
||||
globalCtrl |= (1 << rp1PwmGlobalSetUpdate)
|
||||
bcm.pwmMem[rp1PwmGlobalCtrl/4] = globalCtrl
|
||||
time.Sleep(10 * time.Microsecond)
|
||||
|
||||
// Enable channel 0
|
||||
globalCtrl = bcm.pwmMem[rp1PwmGlobalCtrl/4]
|
||||
globalCtrl |= (1 << (rp1PwmGlobalChanEnBit + ch))
|
||||
bcm.pwmMem[rp1PwmGlobalCtrl/4] = globalCtrl
|
||||
time.Sleep(10 * time.Microsecond)
|
||||
}
|
||||
|
||||
func (bcm *bcm2712) Run(parentCtx context.Context) error {
|
||||
ctx, cancel := context.WithCancel(parentCtx)
|
||||
defer cancel()
|
||||
|
||||
group := errgroup.Group{}
|
||||
|
||||
group.Go(func() error {
|
||||
defer cancel()
|
||||
return bcm.fanUnit.Run(ctx)
|
||||
})
|
||||
|
||||
return group.Wait()
|
||||
}
|
||||
|
||||
func (bcm *bcm2712) handleEdgeButtonEdge(evt gpiod.LineEvent) {
|
||||
select {
|
||||
case bcm.edgeButtonDebounceChan <- struct{}{}:
|
||||
go func() {
|
||||
<-bcm.edgeButtonDebounceChan
|
||||
time.Sleep(bcm2712DebounceInterval)
|
||||
edgeButtonEventCount.Inc()
|
||||
close(bcm.edgeButtonWatchChan)
|
||||
bcm.edgeButtonWatchChan = make(chan struct{})
|
||||
}()
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (bcm *bcm2712) WaitForEdgeButtonPress(parentCtx context.Context) error {
|
||||
ctx, cancel := context.WithCancel(parentCtx)
|
||||
defer cancel()
|
||||
|
||||
fanUnitChan := make(chan struct{})
|
||||
go func() {
|
||||
err := bcm.fanUnit.WaitForButtonPress(ctx)
|
||||
if err != nil && err != context.Canceled {
|
||||
log.FromContext(ctx).WithError(err).Error("failed to wait for button press")
|
||||
} else {
|
||||
close(fanUnitChan)
|
||||
}
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-bcm.edgeButtonWatchChan:
|
||||
return nil
|
||||
case <-fanUnitChan:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (bcm *bcm2712) GetFanRPM() (float64, error) {
|
||||
rpm, err := bcm.fanUnit.FanSpeedRPM(context.TODO())
|
||||
return float64(rpm), err
|
||||
}
|
||||
|
||||
func (bcm *bcm2712) GetPowerStatus() (PowerStatus, error) {
|
||||
val, err := bcm.poeLine.Value()
|
||||
if err != nil {
|
||||
return PowerPoeOrUsbC, err
|
||||
}
|
||||
|
||||
if val > 0 {
|
||||
powerStatus.WithLabelValues(fmt.Sprint(PowerPoe802at)).Set(1)
|
||||
powerStatus.WithLabelValues(fmt.Sprint(PowerPoeOrUsbC)).Set(0)
|
||||
return PowerPoe802at, nil
|
||||
}
|
||||
powerStatus.WithLabelValues(fmt.Sprint(PowerPoe802at)).Set(0)
|
||||
powerStatus.WithLabelValues(fmt.Sprint(PowerPoeOrUsbC)).Set(1)
|
||||
return PowerPoeOrUsbC, nil
|
||||
}
|
||||
|
||||
// SetFanSpeed sets the fan speed in percent.
|
||||
func (bcm *bcm2712) SetFanSpeed(speed uint8) error {
|
||||
fanTargetPercent.Set(float64(speed))
|
||||
return bcm.fanUnit.SetFanSpeedPercent(context.TODO(), speed)
|
||||
}
|
||||
|
||||
// setFanSpeedPWM sets the fan PWM duty cycle using RP1 PWM0 channel 0 in mark-space mode.
|
||||
func (bcm *bcm2712) setFanSpeedPWM(speed uint8) {
|
||||
ch := 0
|
||||
|
||||
var duty uint32
|
||||
if speed == 0 {
|
||||
duty = 0
|
||||
} else if speed <= 100 {
|
||||
duty = uint32(float64(rp1FanPwmRange) * float64(speed) / 100.0)
|
||||
} else {
|
||||
duty = rp1FanPwmRange
|
||||
}
|
||||
|
||||
// Update duty cycle
|
||||
bcm.pwmMem[pwmChanRegIdx(ch, rp1PwmChanDutyOff)] = duty
|
||||
|
||||
// Trigger update
|
||||
globalCtrl := bcm.pwmMem[rp1PwmGlobalCtrl/4]
|
||||
globalCtrl |= (1 << rp1PwmGlobalSetUpdate)
|
||||
bcm.pwmMem[rp1PwmGlobalCtrl/4] = globalCtrl
|
||||
|
||||
bcm.currFanSpeed = speed
|
||||
}
|
||||
|
||||
func (bcm *bcm2712) SetStealthMode(enable bool) error {
|
||||
if enable {
|
||||
stealthModeEnabled.Set(1)
|
||||
return bcm.stealthModeLine.SetValue(1)
|
||||
}
|
||||
stealthModeEnabled.Set(0)
|
||||
return bcm.stealthModeLine.SetValue(0)
|
||||
}
|
||||
|
||||
func (bcm *bcm2712) StealthModeActive() bool {
|
||||
val, err := bcm.stealthModeLine.Value()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return val > 0
|
||||
}
|
||||
|
||||
// SetLed sets the WS281x LED color via RP1 PWM0 channel 2 serializer mode.
|
||||
// Unlike BCM2711 which shares PWM0 between fan and LEDs, RP1 has independent channels:
|
||||
// channel 0 (GPIO 12) handles fan PWM, channel 2 (GPIO 18) handles WS281x LEDs.
|
||||
func (bcm *bcm2712) SetLed(idx LedIndex, color led.Color) error {
|
||||
if idx >= 2 {
|
||||
return fmt.Errorf("invalid led index %d, supported: [0, 1]", idx)
|
||||
}
|
||||
|
||||
// Update the fan unit LED if this is the edge LED
|
||||
if idx == LedEdge {
|
||||
if err := bcm.fanUnit.SetLed(context.TODO(), color); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
bcm.leds[idx] = color
|
||||
|
||||
return bcm.updateLEDs()
|
||||
}
|
||||
|
||||
// updateLEDs sends WS281x data via RP1 PWM0 channel 2 serializer mode.
|
||||
// Uses the shared FIFO (channel 0 fan PWM uses mark-space mode without FIFO, so no conflict).
|
||||
// The RP1 PWM clock is fixed at 50MHz, so we use 2 FIFO words per WS281x data bit
|
||||
// at RANGE=32 to achieve ~1280ns per bit (within WS281x tolerance).
|
||||
func (bcm *bcm2712) updateLEDs() error {
|
||||
bcm.wrMutex.Lock()
|
||||
defer bcm.wrMutex.Unlock()
|
||||
|
||||
ledColorChangeEventCount.Inc()
|
||||
|
||||
ch := rp1Ws281xChan
|
||||
|
||||
// Build complete FIFO data stream
|
||||
data := bcm.buildWs281xStream()
|
||||
|
||||
// Set GPIO 18 to PWM0_CH2 function
|
||||
bcm.setGpioFuncsel(18, rp1Gpio18FuncselPwm)
|
||||
time.Sleep(10 * time.Microsecond)
|
||||
|
||||
// Disable channel 2
|
||||
globalCtrl := bcm.pwmMem[rp1PwmGlobalCtrl/4]
|
||||
globalCtrl &^= (1 << (rp1PwmGlobalChanEnBit + ch))
|
||||
bcm.pwmMem[rp1PwmGlobalCtrl/4] = globalCtrl
|
||||
time.Sleep(10 * time.Microsecond)
|
||||
|
||||
// Configure channel 2: serializer mode, use FIFO, SBIT=0 (low when idle)
|
||||
bcm.pwmMem[pwmChanRegIdx(ch, rp1PwmChanCtrlOff)] =
|
||||
(rp1PwmModeSerializer << rp1PwmChanCtrlModeBit) | (1 << rp1PwmChanCtrlUseFifoBit)
|
||||
bcm.pwmMem[pwmChanRegIdx(ch, rp1PwmChanRangeOff)] = rp1Ws281xRange
|
||||
bcm.pwmMem[pwmChanRegIdx(ch, rp1PwmChanPhaseOff)] = 0
|
||||
time.Sleep(10 * time.Microsecond)
|
||||
|
||||
// Flush FIFO
|
||||
bcm.pwmMem[rp1PwmFifoCtrl/4] = (1 << rp1PwmFifoFlushBit)
|
||||
time.Sleep(10 * time.Microsecond)
|
||||
|
||||
// Trigger update for channel 2
|
||||
globalCtrl = bcm.pwmMem[rp1PwmGlobalCtrl/4]
|
||||
globalCtrl |= (1 << rp1PwmGlobalSetUpdate)
|
||||
bcm.pwmMem[rp1PwmGlobalCtrl/4] = globalCtrl
|
||||
time.Sleep(10 * time.Microsecond)
|
||||
|
||||
// Pre-fill FIFO before enabling channel
|
||||
idx := 0
|
||||
for idx < len(data) && uint32(idx) < rp1Ws281xFifoMax {
|
||||
bcm.pwmMem[rp1PwmFifoPush/4] = data[idx]
|
||||
idx++
|
||||
}
|
||||
|
||||
// Lock OS thread for tight FIFO feeding
|
||||
runtime.LockOSThread()
|
||||
|
||||
// Enable channel 2
|
||||
globalCtrl = bcm.pwmMem[rp1PwmGlobalCtrl/4]
|
||||
globalCtrl |= (1 << (rp1PwmGlobalChanEnBit + ch))
|
||||
bcm.pwmMem[rp1PwmGlobalCtrl/4] = globalCtrl
|
||||
|
||||
// Push remaining data, polling FIFO level to avoid overflow
|
||||
fifoLevelReg := rp1PwmFifoLevel / 4
|
||||
fifoPushReg := rp1PwmFifoPush / 4
|
||||
deadline := time.Now().Add(rp1Ws281xTimeout)
|
||||
|
||||
for idx < len(data) {
|
||||
if time.Now().After(deadline) {
|
||||
break
|
||||
}
|
||||
if bcm.pwmMem[fifoLevelReg] < rp1Ws281xFifoMax {
|
||||
bcm.pwmMem[fifoPushReg] = data[idx]
|
||||
idx++
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for FIFO to drain
|
||||
for bcm.pwmMem[fifoLevelReg] > 0 {
|
||||
if time.Now().After(deadline) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
runtime.UnlockOSThread()
|
||||
|
||||
// Wait for last word to finish shifting out
|
||||
time.Sleep(200 * time.Microsecond)
|
||||
|
||||
// Disable channel 2
|
||||
globalCtrl = bcm.pwmMem[rp1PwmGlobalCtrl/4]
|
||||
globalCtrl &^= (1 << (rp1PwmGlobalChanEnBit + ch))
|
||||
bcm.pwmMem[rp1PwmGlobalCtrl/4] = globalCtrl
|
||||
|
||||
// Disconnect GPIO 18 from PWM to prevent residual noise on the data line
|
||||
bcm.setGpioFuncsel(18, rp1GpioFuncselNull)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// buildWs281xStream constructs the complete FIFO word stream for both WS281x LEDs.
|
||||
// Format: [reset padding] [top LED RGB] [edge LED RGB] [trailing zeros]
|
||||
func (bcm *bcm2712) buildWs281xStream() []uint32 {
|
||||
// Pre-allocate: 80 reset + 96 data (2 LEDs × 3 bytes × 16 words) + 2 trailing
|
||||
words := make([]uint32, 0, rp1Ws281xResetWords+96+2)
|
||||
|
||||
// Reset padding (>50μs of low signal)
|
||||
for i := 0; i < rp1Ws281xResetWords; i++ {
|
||||
words = append(words, 0)
|
||||
}
|
||||
|
||||
// Top LED (index 0) - RGB order (matches BCM2711 upstream)
|
||||
words = appendByteWs281x(words, bcm.leds[0].Red)
|
||||
words = appendByteWs281x(words, bcm.leds[0].Green)
|
||||
words = appendByteWs281x(words, bcm.leds[0].Blue)
|
||||
|
||||
// Edge LED (index 1)
|
||||
words = appendByteWs281x(words, bcm.leds[1].Red)
|
||||
words = appendByteWs281x(words, bcm.leds[1].Green)
|
||||
words = appendByteWs281x(words, bcm.leds[1].Blue)
|
||||
|
||||
// Trailing zeros for clean end-of-frame
|
||||
words = append(words, 0, 0)
|
||||
|
||||
return words
|
||||
}
|
||||
|
||||
// appendByteWs281x encodes one byte as 16 FIFO words (2 per data bit, MSB first) for RP1 WS281x.
|
||||
func appendByteWs281x(words []uint32, b uint8) []uint32 {
|
||||
for i := 7; i >= 0; i-- {
|
||||
if (b>>uint(i))&1 == 0 {
|
||||
words = append(words, rp1Ws281xBit0Word0, rp1Ws281xBit0Word1)
|
||||
} else {
|
||||
words = append(words, rp1Ws281xBit1Word0, rp1Ws281xBit1Word1)
|
||||
}
|
||||
}
|
||||
return words
|
||||
}
|
||||
|
||||
// GetTemperature returns the SoC temperature in degrees Celsius.
|
||||
func (bcm *bcm2712) GetTemperature() (float64, error) {
|
||||
f, err := os.Open(bcm2712ThermalZonePath)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
defer func() { _ = f.Close() }()
|
||||
|
||||
raw, err := io.ReadAll(f)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
cpuTemp, err := strconv.Atoi(strings.TrimSpace(string(raw)))
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
temp := float64(cpuTemp) / 1000.0
|
||||
socTemperature.Set(temp)
|
||||
|
||||
return temp, nil
|
||||
}
|
||||
178
pkg/hal/hal_rk3588.go
Normal file
178
pkg/hal/hal_rk3588.go
Normal file
@@ -0,0 +1,178 @@
|
||||
//go:build linux && !tinygo
|
||||
|
||||
package hal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/compute-blade-community/compute-blade-agent/pkg/hal/led"
|
||||
"github.com/compute-blade-community/compute-blade-agent/pkg/log"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
const (
|
||||
rk3588ThermalZonePath = "/sys/class/thermal/thermal_zone0/temp"
|
||||
rk3588PwmFanHwmonName = "pwmfan"
|
||||
)
|
||||
|
||||
// rk3588 implements the ComputeBladeHal interface for the Rockchip RK3588 (Radxa CM5).
|
||||
// Fan control uses the kernel's pwmfan driver via sysfs. GPIO-dependent features
|
||||
// (button, stealth hardware, PoE detection, LEDs, tachometer) are stubbed until the
|
||||
// Rockchip-to-B2B-connector pin mapping is determined.
|
||||
type rk3588 struct {
|
||||
opts ComputeBladeHalOpts
|
||||
pwmPath string // e.g. /sys/class/hwmon/hwmon8/pwm1
|
||||
pwmEnablePath string // e.g. /sys/class/hwmon/hwmon8/pwm1_enable
|
||||
stealthMode bool
|
||||
}
|
||||
|
||||
// Compile-time interface check
|
||||
var _ ComputeBladeHal = &rk3588{}
|
||||
|
||||
func newRk3588Hal(ctx context.Context, opts ComputeBladeHalOpts) (*rk3588, error) {
|
||||
logger := log.FromContext(ctx)
|
||||
|
||||
pwmPath, err := findHwmonPwm(rk3588PwmFanHwmonName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to find pwmfan hwmon device: %w", err)
|
||||
}
|
||||
|
||||
enablePath := pwmPath + "_enable"
|
||||
|
||||
// Set manual control mode (1 = manual PWM control)
|
||||
if err := os.WriteFile(enablePath, []byte("1"), 0644); err != nil {
|
||||
return nil, fmt.Errorf("failed to set pwm1_enable to manual mode: %w", err)
|
||||
}
|
||||
|
||||
computeModule.WithLabelValues("radxa-cm5").Set(1)
|
||||
|
||||
logger.Info("starting hal setup", zap.String("hal", "rk3588"))
|
||||
logger.Warn("GPIO pin mapping unknown for RK3588 B2B connector — button, stealth hardware, PoE detection, LEDs, and tachometer are stubbed")
|
||||
|
||||
return &rk3588{
|
||||
opts: opts,
|
||||
pwmPath: pwmPath,
|
||||
pwmEnablePath: enablePath,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (rk *rk3588) Run(ctx context.Context) error {
|
||||
fanUnit.WithLabelValues("sysfs").Set(1)
|
||||
<-ctx.Done()
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
func (rk *rk3588) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetFanSpeed sets the fan speed via sysfs pwm1 (0-100% mapped to 0-255).
|
||||
func (rk *rk3588) SetFanSpeed(speed uint8) error {
|
||||
fanTargetPercent.Set(float64(speed))
|
||||
|
||||
var pwmVal uint8
|
||||
if speed == 0 {
|
||||
pwmVal = 0
|
||||
} else if speed >= 100 {
|
||||
pwmVal = 255
|
||||
} else {
|
||||
pwmVal = uint8(float64(speed) * 255.0 / 100.0)
|
||||
}
|
||||
|
||||
return os.WriteFile(rk.pwmPath, []byte(strconv.Itoa(int(pwmVal))), 0644)
|
||||
}
|
||||
|
||||
// GetFanRPM returns 0 — no tachometer GPIO is mapped on the RK3588.
|
||||
func (rk *rk3588) GetFanRPM() (float64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// GetTemperature returns the SoC temperature in degrees Celsius.
|
||||
func (rk *rk3588) GetTemperature() (float64, error) {
|
||||
f, err := os.Open(rk3588ThermalZonePath)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
defer func() { _ = f.Close() }()
|
||||
|
||||
raw, err := io.ReadAll(f)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
cpuTemp, err := strconv.Atoi(strings.TrimSpace(string(raw)))
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
temp := float64(cpuTemp) / 1000.0
|
||||
socTemperature.Set(temp)
|
||||
|
||||
return temp, nil
|
||||
}
|
||||
|
||||
// SetStealthMode tracks stealth mode in software only (no GPIO mapped).
|
||||
func (rk *rk3588) SetStealthMode(enabled bool) error {
|
||||
rk.stealthMode = enabled
|
||||
if enabled {
|
||||
stealthModeEnabled.Set(1)
|
||||
} else {
|
||||
stealthModeEnabled.Set(0)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rk *rk3588) StealthModeActive() bool {
|
||||
return rk.stealthMode
|
||||
}
|
||||
|
||||
// SetLed is a no-op — WS281x LED GPIO pin mapping is unknown on RK3588.
|
||||
func (rk *rk3588) SetLed(idx LedIndex, color led.Color) error {
|
||||
ledColorChangeEventCount.Inc()
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPowerStatus returns PowerPoeOrUsbC as a safe default (no PoE detection GPIO mapped).
|
||||
func (rk *rk3588) GetPowerStatus() (PowerStatus, error) {
|
||||
powerStatus.WithLabelValues(fmt.Sprint(PowerPoe802at)).Set(0)
|
||||
powerStatus.WithLabelValues(fmt.Sprint(PowerPoeOrUsbC)).Set(1)
|
||||
return PowerPoeOrUsbC, nil
|
||||
}
|
||||
|
||||
// WaitForEdgeButtonPress blocks until context cancellation (no button GPIO mapped).
|
||||
func (rk *rk3588) WaitForEdgeButtonPress(ctx context.Context) error {
|
||||
<-ctx.Done()
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
// findHwmonPwm scans /sys/class/hwmon/hwmon*/name for a device matching the given name
|
||||
// and returns the path to its pwm1 file.
|
||||
func findHwmonPwm(name string) (string, error) {
|
||||
matches, err := filepath.Glob("/sys/class/hwmon/hwmon*/name")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to glob hwmon devices: %w", err)
|
||||
}
|
||||
|
||||
for _, namePath := range matches {
|
||||
raw, err := os.ReadFile(namePath)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if strings.TrimSpace(string(raw)) == name {
|
||||
dir := filepath.Dir(namePath)
|
||||
pwmPath := filepath.Join(dir, "pwm1")
|
||||
if _, err := os.Stat(pwmPath); err != nil {
|
||||
return "", fmt.Errorf("found %s hwmon at %s but pwm1 does not exist: %w", name, dir, err)
|
||||
}
|
||||
return pwmPath, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("no hwmon device found with name %q", name)
|
||||
}
|
||||
39
pkg/hal/platform_linux.go
Normal file
39
pkg/hal/platform_linux.go
Normal file
@@ -0,0 +1,39 @@
|
||||
//go:build linux && !tinygo
|
||||
|
||||
package hal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/compute-blade-community/compute-blade-agent/pkg/log"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
const deviceTreeCompatiblePath = "/sys/firmware/devicetree/base/compatible"
|
||||
|
||||
// NewHal creates the appropriate HAL implementation based on the detected platform.
|
||||
// It reads the device tree compatible string to determine whether the SoC is a
|
||||
// BCM2711 (CM4/Pi 4) or BCM2712 (CM5/Pi 5).
|
||||
func NewHal(ctx context.Context, opts ComputeBladeHalOpts) (ComputeBladeHal, error) {
|
||||
compatible, err := os.ReadFile(deviceTreeCompatiblePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read device tree compatible string: %w", err)
|
||||
}
|
||||
|
||||
compatStr := string(compatible)
|
||||
log.FromContext(ctx).Info("detected platform", zap.String("compatible", strings.ReplaceAll(compatStr, "\x00", ", ")))
|
||||
|
||||
switch {
|
||||
case strings.Contains(compatStr, "bcm2712"):
|
||||
return newBcm2712Hal(ctx, opts)
|
||||
case strings.Contains(compatStr, "bcm2711"):
|
||||
return newBcm2711Hal(ctx, opts)
|
||||
case strings.Contains(compatStr, "rockchip,rk3588"):
|
||||
return newRk3588Hal(ctx, opts)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported platform: %s", strings.ReplaceAll(compatStr, "\x00", ", "))
|
||||
}
|
||||
}
|
||||
115
scripts/find-tach-gpio.sh
Executable file
115
scripts/find-tach-gpio.sh
Executable file
@@ -0,0 +1,115 @@
|
||||
#!/bin/bash
|
||||
# find-tach-gpio.sh - Safely probe GPIOs to find fan tachometer signal
|
||||
# Run on Radxa node with fan at 100%: sudo ./find-tach-gpio.sh
|
||||
#
|
||||
# The tachometer generates 2 pulses per revolution. At 5000 RPM:
|
||||
# 5000 RPM / 60 = 83.33 RPS * 2 pulses = ~167 Hz
|
||||
# At 3000 RPM: ~100 Hz
|
||||
# We look for any GPIO showing periodic edge events.
|
||||
|
||||
set -e
|
||||
|
||||
PROBE_DURATION=1 # seconds to monitor each GPIO
|
||||
MIN_EVENTS=10 # minimum events to consider "active" (10 events in 1s = 600 RPM minimum)
|
||||
DELAY_BETWEEN=0.5 # seconds between probes to let system settle
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
echo "=== Fan Tachometer GPIO Finder ==="
|
||||
echo ""
|
||||
|
||||
# Check if running as root (needed for gpiomon)
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
echo -e "${RED}Please run as root (sudo)${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check fan speed
|
||||
FAN_PWM=$(cat /sys/class/hwmon/hwmon8/pwm1 2>/dev/null || echo "unknown")
|
||||
echo "Current fan PWM: $FAN_PWM (should be 255 for best detection)"
|
||||
if [ "$FAN_PWM" != "255" ]; then
|
||||
echo -e "${YELLOW}Setting fan to 100% for detection...${NC}"
|
||||
echo 255 > /sys/class/hwmon/hwmon8/pwm1
|
||||
sleep 2 # Let fan spin up
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Get list of gpiochips
|
||||
CHIPS=$(ls /dev/gpiochip* | sed 's|/dev/||')
|
||||
|
||||
echo "Scanning GPIO chips: $CHIPS"
|
||||
echo "Probe duration: ${PROBE_DURATION}s per line, minimum ${MIN_EVENTS} events to flag"
|
||||
echo ""
|
||||
|
||||
FOUND_CANDIDATES=""
|
||||
|
||||
for chip in $CHIPS; do
|
||||
# Get number of lines for this chip
|
||||
NUM_LINES=$(gpioinfo $chip 2>/dev/null | wc -l)
|
||||
NUM_LINES=$((NUM_LINES - 1)) # Subtract header line
|
||||
|
||||
if [ "$NUM_LINES" -le 0 ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
echo -e "${YELLOW}=== Scanning $chip ($NUM_LINES lines) ===${NC}"
|
||||
|
||||
for line in $(seq 0 $((NUM_LINES - 1))); do
|
||||
# Check if line is already in use
|
||||
LINE_INFO=$(gpioinfo $chip 2>/dev/null | grep "line *$line:" || true)
|
||||
if echo "$LINE_INFO" | grep -q "\[used\]"; then
|
||||
# Skip lines that are in use
|
||||
continue
|
||||
fi
|
||||
|
||||
# Probe the line
|
||||
printf " Line %2d: " "$line"
|
||||
|
||||
# Run gpiomon with timeout, count events
|
||||
EVENTS=$(timeout ${PROBE_DURATION}s gpiomon --num-events=100 $chip $line 2>&1 | wc -l || echo "0")
|
||||
|
||||
if [ "$EVENTS" -ge "$MIN_EVENTS" ]; then
|
||||
# Calculate approximate frequency
|
||||
FREQ=$((EVENTS / PROBE_DURATION))
|
||||
RPM_ESTIMATE=$((FREQ * 60 / 2)) # 2 pulses per revolution
|
||||
echo -e "${GREEN}ACTIVE! $EVENTS events (~${FREQ} Hz, ~${RPM_ESTIMATE} RPM)${NC}"
|
||||
FOUND_CANDIDATES="$FOUND_CANDIDATES\n $chip line $line: $EVENTS events (~${RPM_ESTIMATE} RPM)"
|
||||
elif [ "$EVENTS" -gt 0 ]; then
|
||||
echo "$EVENTS events (noise?)"
|
||||
else
|
||||
echo "no events"
|
||||
fi
|
||||
|
||||
# Small delay to let system settle
|
||||
sleep $DELAY_BETWEEN
|
||||
done
|
||||
echo ""
|
||||
done
|
||||
|
||||
echo "=== Scan Complete ==="
|
||||
if [ -n "$FOUND_CANDIDATES" ]; then
|
||||
echo -e "${GREEN}Candidate tachometer GPIOs found:${NC}"
|
||||
echo -e "$FOUND_CANDIDATES"
|
||||
echo ""
|
||||
echo "To verify, try monitoring the candidate with varying fan speeds:"
|
||||
echo " gpiomon --num-events=50 <chip> <line>"
|
||||
echo ""
|
||||
echo "Then add to device tree or HAL configuration."
|
||||
else
|
||||
echo -e "${YELLOW}No active GPIOs found.${NC}"
|
||||
echo "Possible reasons:"
|
||||
echo " - Fan tachometer not connected on this carrier board"
|
||||
echo " - Tachometer uses a different interface (I2C, ADC, etc.)"
|
||||
echo " - Fan doesn't have tachometer wire connected"
|
||||
fi
|
||||
|
||||
# Restore fan to auto if we changed it
|
||||
if [ "$FAN_PWM" != "255" ] && [ "$FAN_PWM" != "unknown" ]; then
|
||||
echo ""
|
||||
echo "Restoring fan PWM to $FAN_PWM"
|
||||
echo "$FAN_PWM" > /sys/class/hwmon/hwmon8/pwm1
|
||||
fi
|
||||
Reference in New Issue
Block a user