Refactor kafka to pure Go (franz-go), fix DBC stubs, update Dockerfile
This commit is contained in:
114
README.md
114
README.md
@@ -1,49 +1,85 @@
|
|||||||
# cloud-services
|
# Cloud Services
|
||||||
|
|
||||||
Go microservices for the vehicle cloud platform.
|
Refactored cloud microservices from project-ai.
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
```bash
|
|
||||||
# Install devbox (if needed)
|
|
||||||
curl -fsSL https://get.jetify.com/devbox | bash
|
|
||||||
|
|
||||||
# Enter dev environment
|
|
||||||
devbox shell
|
|
||||||
|
|
||||||
# Run a service locally
|
|
||||||
cd services/gateway
|
|
||||||
go run .
|
|
||||||
```
|
|
||||||
|
|
||||||
## Structure
|
## Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
services/ # Individual Go microservices
|
cloud-services/
|
||||||
shared/ # Shared Go modules
|
├── pkg/ # Shared Go packages
|
||||||
deploy/ # Kubernetes manifests (kustomize)
|
│ ├── kafka/ # Pure Go Kafka client (franz-go)
|
||||||
base/ # Base configs
|
│ ├── dbc/ # CAN database signal definitions
|
||||||
overlays/ # Environment-specific (development, etc.)
|
│ ├── can-go/ # CAN protocol library
|
||||||
|
│ └── ... # Other shared modules
|
||||||
|
├── services/
|
||||||
|
│ └── gateway/ # API gateway service
|
||||||
|
├── deploy/
|
||||||
|
│ ├── base/ # Base k8s manifests
|
||||||
|
│ └── overlays/ # Environment-specific configs
|
||||||
|
└── scripts/ # Build and utility scripts
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build all
|
||||||
|
go build ./...
|
||||||
|
|
||||||
|
# Build gateway
|
||||||
|
go build ./services/gateway
|
||||||
|
|
||||||
|
# Run tests
|
||||||
|
go test ./...
|
||||||
|
|
||||||
|
# Build Docker image
|
||||||
|
docker build -t gateway -f services/gateway/Dockerfile .
|
||||||
```
|
```
|
||||||
|
|
||||||
## Services
|
## Services
|
||||||
| Service | Description |
|
|
||||||
|---------|-------------|
|
|
||||||
| gateway | API gateway, routes requests |
|
|
||||||
| auth | Authentication (Keycloak integration) |
|
|
||||||
| ota | OTA update management |
|
|
||||||
| depot | Vehicle registration & management |
|
|
||||||
| attendant | Event processing |
|
|
||||||
| cargo | Data ingestion to storage |
|
|
||||||
| ditto | Digital twin state |
|
|
||||||
| manufacture | Manufacturing integration |
|
|
||||||
| aftersales | Aftersales/diagnostic services |
|
|
||||||
|
|
||||||
## Local Development
|
### Gateway
|
||||||
Services connect to:
|
WebSocket gateway for TRex, HMI, and Mobile connections. Handles auth, message routing to Kafka.
|
||||||
- PostgreSQL: `cloud-dev-rw.cnpg-system.svc:5432`
|
|
||||||
- MongoDB: `cloud-dev-svc.mongodb.svc:27017`
|
- Port 8077: HTTP/WebSocket
|
||||||
- Redis: `cloud-dev.redis.svc:6379`
|
- Port 11011: Health check
|
||||||
- Kafka: `cloud-dev-kafka-bootstrap.kafka.svc:9092`
|
|
||||||
- Keycloak: `https://keycloak.mini.cloud.fiskerinc.com`
|
## Development
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
- Go 1.25+
|
||||||
|
- Docker (for container builds)
|
||||||
|
|
||||||
|
### Module Structure
|
||||||
|
Uses Go workspaces (`go.work`) for local development:
|
||||||
|
- `./pkg` - shared packages
|
||||||
|
- `./pkg/can-go` - CAN protocol library
|
||||||
|
- `./services/gateway` - gateway service
|
||||||
|
|
||||||
|
### Generating DBC Code
|
||||||
|
CAN signal definitions are generated from DBC files. See `pkg/dbc/README.md`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/generate-dbc.sh /path/to/dbc/files
|
||||||
|
```
|
||||||
|
|
||||||
## Deployment
|
## Deployment
|
||||||
ArgoCD syncs from this repo. Push to main → auto-deploy to mini cluster.
|
|
||||||
|
Kubernetes manifests in `deploy/` use Kustomize overlays:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Development
|
||||||
|
kubectl apply -k deploy/overlays/development
|
||||||
|
|
||||||
|
# Or via ArgoCD
|
||||||
|
# See k8s-gitops-setup repo
|
||||||
|
```
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|----------|---------|-------------|
|
||||||
|
| `KAFKA_HOSTS` | `localhost:9092` | Kafka brokers |
|
||||||
|
| `REDIS_HOST` | `localhost` | Redis host |
|
||||||
|
| `REDIS_PORT` | `6379` | Redis port |
|
||||||
|
| `JWK_URL` | - | JWKS endpoint for JWT validation |
|
||||||
|
| `LOG_LEVEL` | `info` | Log level |
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ data:
|
|||||||
# Redis
|
# Redis
|
||||||
REDIS_HOST: cloud-dev.redis.svc.cluster.local
|
REDIS_HOST: cloud-dev.redis.svc.cluster.local
|
||||||
REDIS_PORT: "6379"
|
REDIS_PORT: "6379"
|
||||||
|
REDIS_PASSWORD: ""
|
||||||
REDIS_IDLETIMEOUT_MS: "3600000"
|
REDIS_IDLETIMEOUT_MS: "3600000"
|
||||||
REDIS_MAXIDLECONN: "10"
|
REDIS_MAXIDLECONN: "10"
|
||||||
REDIS_MAXACTIVECONN: "10"
|
REDIS_MAXACTIVECONN: "10"
|
||||||
@@ -38,6 +39,7 @@ data:
|
|||||||
# Auth (Keycloak)
|
# Auth (Keycloak)
|
||||||
OIDC_ISSUER: https://keycloak.mini.cloud.fiskerinc.com/realms/compute-auth
|
OIDC_ISSUER: https://keycloak.mini.cloud.fiskerinc.com/realms/compute-auth
|
||||||
OIDC_JWK_URL: https://keycloak.mini.cloud.fiskerinc.com/realms/compute-auth/protocol/openid-connect/certs
|
OIDC_JWK_URL: https://keycloak.mini.cloud.fiskerinc.com/realms/compute-auth/protocol/openid-connect/certs
|
||||||
|
JWK_URL: https://keycloak.mini.cloud.fiskerinc.com/realms/compute-auth/protocol/openid-connect/certs
|
||||||
|
|
||||||
# Vault
|
# Vault
|
||||||
VAULT_URL: http://vault.vault.svc.cluster.local:8200/v1
|
VAULT_URL: http://vault.vault.svc.cluster.local:8200/v1
|
||||||
|
|||||||
@@ -6,9 +6,7 @@ namespace: cloud-services
|
|||||||
resources:
|
resources:
|
||||||
- ../../base
|
- ../../base
|
||||||
- secrets.yaml
|
- secrets.yaml
|
||||||
# Services (uncomment as migrated)
|
- services/gateway/
|
||||||
# - services/gateway/
|
|
||||||
# - services/auth/
|
|
||||||
|
|
||||||
commonLabels:
|
commonLabels:
|
||||||
environment: development
|
environment: development
|
||||||
|
|||||||
62
deploy/overlays/development/services/gateway/deployment.yaml
Normal file
62
deploy/overlays/development/services/gateway/deployment.yaml
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: gateway
|
||||||
|
namespace: cloud-services
|
||||||
|
labels:
|
||||||
|
app: gateway
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: gateway
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: gateway
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: gateway
|
||||||
|
image: localhost:32000/gateway:latest
|
||||||
|
imagePullPolicy: Always
|
||||||
|
ports:
|
||||||
|
- containerPort: 8077
|
||||||
|
name: http
|
||||||
|
- containerPort: 11011
|
||||||
|
name: health
|
||||||
|
envFrom:
|
||||||
|
- configMapRef:
|
||||||
|
name: cloud-common-config
|
||||||
|
- secretRef:
|
||||||
|
name: cloud-db-credentials
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 100m
|
||||||
|
memory: 128Mi
|
||||||
|
limits:
|
||||||
|
memory: 256Mi
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /liveness
|
||||||
|
port: 11011
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 30
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /readiness
|
||||||
|
port: 11011
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 10
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: gateway
|
||||||
|
namespace: cloud-services
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: gateway
|
||||||
|
ports:
|
||||||
|
- port: 8077
|
||||||
|
targetPort: 8077
|
||||||
|
name: http
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
|
||||||
|
resources:
|
||||||
|
- deployment.yaml
|
||||||
3
go.work
3
go.work
@@ -1,6 +1,7 @@
|
|||||||
go 1.24
|
go 1.25
|
||||||
|
|
||||||
use (
|
use (
|
||||||
./pkg
|
./pkg
|
||||||
|
./pkg/can-go
|
||||||
./services/gateway
|
./services/gateway
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package adminroles
|
package adminroles
|
||||||
|
|
||||||
import "fiskerinc.com/modules/utils/envtool"
|
import "github.com/fiskerinc/cloud-services/pkg/utils/envtool"
|
||||||
|
|
||||||
// RoleID for groups
|
// RoleID for groups
|
||||||
type RoleID string
|
type RoleID string
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package adminroles
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"fiskerinc.com/modules/validator"
|
"github.com/fiskerinc/cloud-services/pkg/validator"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ package adminroles_test
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"fiskerinc.com/modules/adminroles"
|
"github.com/fiskerinc/cloud-services/pkg/adminroles"
|
||||||
"fiskerinc.com/modules/testhelper"
|
"github.com/fiskerinc/cloud-services/pkg/testhelper"
|
||||||
)
|
)
|
||||||
|
|
||||||
const testRole = "7bcdcdb2-3279-44bf-a998-771bab4b33e1"
|
const testRole = "7bcdcdb2-3279-44bf-a998-771bab4b33e1"
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ package americanlease
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"fiskerinc.com/modules/validator"
|
"github.com/fiskerinc/cloud-services/pkg/validator"
|
||||||
"fiskerinc.com/modules/vindecoder"
|
"github.com/fiskerinc/cloud-services/pkg/vindecoder"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"fiskerinc.com/modules/common"
|
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||||
"fiskerinc.com/modules/logger"
|
"github.com/fiskerinc/cloud-services/pkg/logger"
|
||||||
"fiskerinc.com/modules/utils/envtool"
|
"github.com/fiskerinc/cloud-services/pkg/utils/envtool"
|
||||||
"github.com/aws/aws-sdk-go/aws"
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
"github.com/aws/aws-sdk-go/aws/session"
|
"github.com/aws/aws-sdk-go/aws/session"
|
||||||
"github.com/aws/aws-sdk-go/service/cognitoidentityprovider"
|
"github.com/aws/aws-sdk-go/service/cognitoidentityprovider"
|
||||||
|
|||||||
@@ -7,14 +7,14 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"fiskerinc.com/modules/common"
|
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||||
"fiskerinc.com/modules/db/queries"
|
"github.com/fiskerinc/cloud-services/pkg/db/queries"
|
||||||
|
|
||||||
"fiskerinc.com/modules/httpclient"
|
"github.com/fiskerinc/cloud-services/pkg/httpclient"
|
||||||
"fiskerinc.com/modules/jwt"
|
"github.com/fiskerinc/cloud-services/pkg/jwt"
|
||||||
"fiskerinc.com/modules/logger"
|
"github.com/fiskerinc/cloud-services/pkg/logger"
|
||||||
"fiskerinc.com/modules/utils/envtool"
|
"github.com/fiskerinc/cloud-services/pkg/utils/envtool"
|
||||||
"fiskerinc.com/modules/utils"
|
"github.com/fiskerinc/cloud-services/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
var getUserURL string = envtool.GetEnv("AUTH_GET_USER", "https://dev-auth.fiskerdps.com/auth/me")
|
var getUserURL string = envtool.GetEnv("AUTH_GET_USER", "https://dev-auth.fiskerdps.com/auth/me")
|
||||||
|
|||||||
@@ -10,10 +10,10 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"fiskerinc.com/modules/httpclient"
|
"github.com/fiskerinc/cloud-services/pkg/httpclient"
|
||||||
"fiskerinc.com/modules/jwt"
|
"github.com/fiskerinc/cloud-services/pkg/jwt"
|
||||||
"fiskerinc.com/modules/logger"
|
"github.com/fiskerinc/cloud-services/pkg/logger"
|
||||||
"fiskerinc.com/modules/utils/envtool"
|
"github.com/fiskerinc/cloud-services/pkg/utils/envtool"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
auth "fiskerinc.com/modules/auth"
|
auth "github.com/fiskerinc/cloud-services/pkg/auth"
|
||||||
"fiskerinc.com/modules/httpclient"
|
"github.com/fiskerinc/cloud-services/pkg/httpclient"
|
||||||
"fiskerinc.com/modules/httpclient/mock"
|
"github.com/fiskerinc/cloud-services/pkg/httpclient/mock"
|
||||||
"fiskerinc.com/modules/testhelper"
|
"github.com/fiskerinc/cloud-services/pkg/testhelper"
|
||||||
)
|
)
|
||||||
|
|
||||||
const responseUserConsentJSON = `{"access_token":"eyJraWQiOiJqSXowUVRjc0tDVCtoeEd6MlMwK0NoUHlON3c4cmlQXC9sNm1xekFYUmw2bz0iLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIyZGQ2ZmVkOS1lNTgyLTQ1MWItYTkzYi01Yjk0MTBkZmJjNDMiLCJjb2duaXRvOmdyb3VwcyI6WyJ1cy13ZXN0LTJfQVd3akxYeW0yX0F6dXJlQUQiXSwidG9rZW5fdXNlIjoiYWNjZXNzIiwic2NvcGUiOiJodHRwczpcL1wvZmlza2VyaW5jLmNvbVwvb3RhdXBkYXRlLnJlYWQgaHR0cHM6XC9cL2Zpc2tlcmluYy5jb21cL290YXVwZGF0ZS5jcmVhdGUgb3BlbmlkIGVtYWlsIiwiYXV0aF90aW1lIjoxNjEzNjA3NTc2LCJpc3MiOiJodHRwczpcL1wvY29nbml0by1pZHAudXMtd2VzdC0yLmFtYXpvbmF3cy5jb21cL3VzLXdlc3QtMl9BV3dqTFh5bTIiLCJleHAiOjE2MTM2MTExNzYsImlhdCI6MTYxMzYwNzU3NiwidmVyc2lvbiI6MiwianRpIjoiYzUyNjI0YjItYmJkYi00N2RiLTllNTgtOGU5ZmU3Yjg1ODMxIiwiY2xpZW50X2lkIjoiN2NrMnRmb3FhdmM3MmM0NWhoN3RnZTQya2QiLCJ1c2VybmFtZSI6ImF6dXJlYWRfand1QGZpc2tlcmluYy5jb20ifQ.FvlES5AgjhymQKnHP41D2Ude0Ten6L8REBRXTyu5dyWGrG4vTfBGoxlkGE2-MEFc0s6uhbdST_E2Mc5QNlXG47ibK14tFl6kOqDd74TCfg5sWghb_nSjC-M769eUHQSQcs4L8jcnEt0bjqMmPtt8lZwu3VS7mkSRXD6_hX43rPLGUpMaz5RqKlfHX8YUyD6UnENW9Gg3zonPRsPWVtupc494B_pSZGuFs-jVzBDgb_SdrGt5wb3GazsNcB8KeAf0m0QoEiApsCYxKGUG9eQZw_CAUrhCj9mFT-xJuyvEp0t6B8HDHrdW4mIHblKqhZok1mPwCntJmOfyOs3niNaILg","id_token":"eyJraWQiOiJlUTNuZFJLaUVcL084VUZ5RHFsYjN0S1RzWG00SzVPMlc4NXd3VWkzT2tNZz0iLCJhbGciOiJSUzI1NiJ9.eyJhdF9oYXNoIjoiMHFmcmdyVlZfOW1XRWp5MVdOZDl5QSIsInN1YiI6IjJkZDZmZWQ5LWU1ODItNDUxYi1hOTNiLTViOTQxMGRmYmM0MyIsImNvZ25pdG86Z3JvdXBzIjpbInVzLXdlc3QtMl9BV3dqTFh5bTJfQXp1cmVBRCJdLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsImlzcyI6Imh0dHBzOlwvXC9jb2duaXRvLWlkcC51cy13ZXN0LTIuYW1hem9uYXdzLmNvbVwvdXMtd2VzdC0yX0FXd2pMWHltMiIsImNvZ25pdG86dXNlcm5hbWUiOiJhenVyZWFkX2p3dUBmaXNrZXJpbmMuY29tIiwiYXVkIjoiN2NrMnRmb3FhdmM3MmM0NWhoN3RnZTQya2QiLCJpZGVudGl0aWVzIjpbeyJ1c2VySWQiOiJqd3VAZmlza2VyaW5jLmNvbSIsInByb3ZpZGVyTmFtZSI6IkF6dXJlQUQiLCJwcm92aWRlclR5cGUiOiJTQU1MIiwiaXNzdWVyIjoiaHR0cHM6XC9cL3N0cy53aW5kb3dzLm5ldFwvNWFhNGI2NDAtYzlmYy00YTliLWIzYTMtZDRhN2QwMDhmYjVlXC8iLCJwcmltYXJ5IjoidHJ1ZSIsImRhdGVDcmVhdGVkIjoiMTYxMjkwMjQxMzM4MyJ9XSwidG9rZW5fdXNlIjoiaWQiLCJhdXRoX3RpbWUiOjE2MTM2MDc1NzYsImV4cCI6MTYxMzYxMTE3NiwiaWF0IjoxNjEzNjA3NTc2LCJlbWFpbCI6Imp3dUBmaXNrZXJpbmMuY29tIn0.NbEWEgX48Z-zz3gREEH44OpnvhoYDcm9RlVdqKVoSJ777g0A0LDpGwz7UGcqvZLeQLPsHaMyV8-sblLvKQvpsenJfq81XddVWCAqI55VCdbnouCphIDYOEPNbWs9ORdrXxciALTt1AAehsF0dTDG0V5fce5Vku2qZZbpELdq9r4CBJQXWtFiV8lUaZPEMNJbZVdh1KjwJSpeF8CtJGKUXIIm6tAYVVKc27YWgxe2fh3zhke5MUnGYrb98-RLmDUwpUQ4eBnXu9gtA-9qIpOumXkftogWpeNZ7Rc0tAI8ZvOmG8plFyYoRMrKuC4kECeUdrsRJlCv4ijpK_L7GwEL9Q","refresh_token":"eyJjdHkiOiJKV1QiLCJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiUlNBLU9BRVAifQ.LHo8ysGz7T3sJtwf8aHpWFzH8B84yDvfL4Q0YuRd0kfKSA51z4hKFPSLpo4PiFodJ-VPugJQWfSYXXpe4Tjd3bdTH-oYDJcJvRHIV3ZIID0EApt53lkxsFWV_9b33bltLYyJ7DnclQq1GnfgohDhD9F2CpnN3Xa-ntVmF9ntLe6wxZvk_zdBlbhIPwCc4FuPDIB_skNaciWCzU9LUfzvcfZJAR8KztM8ofDm3YJGZrRJltz6In78ZlN1sIlFuPSIRy56sg3yG3wMfe9Lrst0VacG_2fy6Ccg9VuLqD_xnzMmzjwMF9PGdnO5DlCblWwrHsDE6FkTuDy7ojnPJpPJlw.gzFbDwAmMKp-4eiE.AfY0IecXygmkDUkUGIIG7JmhBSzk4VD_sEAwuTeOufKD_duvNXFTQYNU_QvDc7M-9Vssbbb35dMMw3KLxW2IbC7fll8lNvHHMm1gkxlVxK1h5uRhmgt0q7tyMwLw1iKUDqOa177faHZJISN_gvfh-rlbNswswDGU061dyFh-w6Ck8SXoPnWfp9GxZJBgxzZ5uBV1D7_1bAghqWYNMsMUTSvOYyeWvVJHap-gjtGc491Vf97z6mh9PDBvIi734D90NbV5idZ11CCW7liI5L7kgRwuHZVxiu_NpkPED7dWcaBhOATur4r3P28U39JC5P5FD4JXlqyPl9FXVBkW049E1vdJrrkV3IbiqUMVXlkUeq6G87YUTdmt8qRPgiOc-G6g84RxSPQE55uojbuSSlON2CKZYmSmFVM0X7bBU42wP1wNP7Jq3LTjHcj4rOaN1ozffJxyGs54r7NP4D9u3nt2ozNkjk_DNK3UmxDPaQtZAtFO1d-T7UXv2BvzoCN2LGilzxVi04p9LcvoTDzI5GUY9OsjGUsdSZJvISylHAMMDi8nSxsBBSPD18fzV0tLhdjGM0-XljiM4bjZWNR4Nvraus33p8U4k5lmn2bx13JfHvDa3Zqf_aK57lam6Zf_6mvAK4I7A40WmiolJCxeEeDD54ljF0kAluT4sw6sxVY8It80A95TGFd0lm5e-tGrFKIoqRyPV5uwzzz3XT1HVPJda1ufdGhSUj8slsyqTUrnphh6JWbRfA9mrLdKQKuqM3xEslAwYZOhX6qOzADbo5WQMneTwn34QixMT4A6imaDBc6P4cOaLo7hNyS3e1h6SfwigEX9H42wkC3TWOiITakFq3tKkVwahMkdeds_uxloNoeicdGePjob6BfU8xq0IKxJh9UoeCsSX4KVtIrErHyYuoU-_ENZXYArSwfqorKgdjmQAa13NQjOiHzpgA5HngSCK1xy6zq9NvNA7hUe9O0gTqrFZDsYhRWSYEuOt5QpxJYalPGKIPXlsUJOURfR_J0iT52UxiOZIuXmpk-X8fgDhM_0fZm8GQ2GaIsf3nR49h7QnZRG9azTZV5q9Bs0bqPvP7wRL5xenByeIBsdwP6Bwaqd3n5BkFn-LE7eo6UPn_9o7Gx3g9VlN8pG7SIo_3a07L0yauJIO4ahL5aC07uBCLu3pJQW0ftlVpLdAA8gh3XPhfvOuH4XV7yU1fQhqGKich1hhHx2dFHyVr5mJPxc1VQvAyyAhxjvyQ2TTLfvSiYLOP1vFCVjUwb_RjF5tuh3ArH9IlXH9kIBVQbiSOWfgQ1PqD-go4jqDR_ie3aGc5Fm8Vd6lSh_2HW2GR0Ht0ASoCbl3C5roXCRkyTTaGl3nX6uwfg.thw4B0ug4OIZsZrDzCtJcQ","expires_in": 3600,"token_type": "Bearer"}`
|
const responseUserConsentJSON = `{"access_token":"eyJraWQiOiJqSXowUVRjc0tDVCtoeEd6MlMwK0NoUHlON3c4cmlQXC9sNm1xekFYUmw2bz0iLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIyZGQ2ZmVkOS1lNTgyLTQ1MWItYTkzYi01Yjk0MTBkZmJjNDMiLCJjb2duaXRvOmdyb3VwcyI6WyJ1cy13ZXN0LTJfQVd3akxYeW0yX0F6dXJlQUQiXSwidG9rZW5fdXNlIjoiYWNjZXNzIiwic2NvcGUiOiJodHRwczpcL1wvZmlza2VyaW5jLmNvbVwvb3RhdXBkYXRlLnJlYWQgaHR0cHM6XC9cL2Zpc2tlcmluYy5jb21cL290YXVwZGF0ZS5jcmVhdGUgb3BlbmlkIGVtYWlsIiwiYXV0aF90aW1lIjoxNjEzNjA3NTc2LCJpc3MiOiJodHRwczpcL1wvY29nbml0by1pZHAudXMtd2VzdC0yLmFtYXpvbmF3cy5jb21cL3VzLXdlc3QtMl9BV3dqTFh5bTIiLCJleHAiOjE2MTM2MTExNzYsImlhdCI6MTYxMzYwNzU3NiwidmVyc2lvbiI6MiwianRpIjoiYzUyNjI0YjItYmJkYi00N2RiLTllNTgtOGU5ZmU3Yjg1ODMxIiwiY2xpZW50X2lkIjoiN2NrMnRmb3FhdmM3MmM0NWhoN3RnZTQya2QiLCJ1c2VybmFtZSI6ImF6dXJlYWRfand1QGZpc2tlcmluYy5jb20ifQ.FvlES5AgjhymQKnHP41D2Ude0Ten6L8REBRXTyu5dyWGrG4vTfBGoxlkGE2-MEFc0s6uhbdST_E2Mc5QNlXG47ibK14tFl6kOqDd74TCfg5sWghb_nSjC-M769eUHQSQcs4L8jcnEt0bjqMmPtt8lZwu3VS7mkSRXD6_hX43rPLGUpMaz5RqKlfHX8YUyD6UnENW9Gg3zonPRsPWVtupc494B_pSZGuFs-jVzBDgb_SdrGt5wb3GazsNcB8KeAf0m0QoEiApsCYxKGUG9eQZw_CAUrhCj9mFT-xJuyvEp0t6B8HDHrdW4mIHblKqhZok1mPwCntJmOfyOs3niNaILg","id_token":"eyJraWQiOiJlUTNuZFJLaUVcL084VUZ5RHFsYjN0S1RzWG00SzVPMlc4NXd3VWkzT2tNZz0iLCJhbGciOiJSUzI1NiJ9.eyJhdF9oYXNoIjoiMHFmcmdyVlZfOW1XRWp5MVdOZDl5QSIsInN1YiI6IjJkZDZmZWQ5LWU1ODItNDUxYi1hOTNiLTViOTQxMGRmYmM0MyIsImNvZ25pdG86Z3JvdXBzIjpbInVzLXdlc3QtMl9BV3dqTFh5bTJfQXp1cmVBRCJdLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsImlzcyI6Imh0dHBzOlwvXC9jb2duaXRvLWlkcC51cy13ZXN0LTIuYW1hem9uYXdzLmNvbVwvdXMtd2VzdC0yX0FXd2pMWHltMiIsImNvZ25pdG86dXNlcm5hbWUiOiJhenVyZWFkX2p3dUBmaXNrZXJpbmMuY29tIiwiYXVkIjoiN2NrMnRmb3FhdmM3MmM0NWhoN3RnZTQya2QiLCJpZGVudGl0aWVzIjpbeyJ1c2VySWQiOiJqd3VAZmlza2VyaW5jLmNvbSIsInByb3ZpZGVyTmFtZSI6IkF6dXJlQUQiLCJwcm92aWRlclR5cGUiOiJTQU1MIiwiaXNzdWVyIjoiaHR0cHM6XC9cL3N0cy53aW5kb3dzLm5ldFwvNWFhNGI2NDAtYzlmYy00YTliLWIzYTMtZDRhN2QwMDhmYjVlXC8iLCJwcmltYXJ5IjoidHJ1ZSIsImRhdGVDcmVhdGVkIjoiMTYxMjkwMjQxMzM4MyJ9XSwidG9rZW5fdXNlIjoiaWQiLCJhdXRoX3RpbWUiOjE2MTM2MDc1NzYsImV4cCI6MTYxMzYxMTE3NiwiaWF0IjoxNjEzNjA3NTc2LCJlbWFpbCI6Imp3dUBmaXNrZXJpbmMuY29tIn0.NbEWEgX48Z-zz3gREEH44OpnvhoYDcm9RlVdqKVoSJ777g0A0LDpGwz7UGcqvZLeQLPsHaMyV8-sblLvKQvpsenJfq81XddVWCAqI55VCdbnouCphIDYOEPNbWs9ORdrXxciALTt1AAehsF0dTDG0V5fce5Vku2qZZbpELdq9r4CBJQXWtFiV8lUaZPEMNJbZVdh1KjwJSpeF8CtJGKUXIIm6tAYVVKc27YWgxe2fh3zhke5MUnGYrb98-RLmDUwpUQ4eBnXu9gtA-9qIpOumXkftogWpeNZ7Rc0tAI8ZvOmG8plFyYoRMrKuC4kECeUdrsRJlCv4ijpK_L7GwEL9Q","refresh_token":"eyJjdHkiOiJKV1QiLCJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiUlNBLU9BRVAifQ.LHo8ysGz7T3sJtwf8aHpWFzH8B84yDvfL4Q0YuRd0kfKSA51z4hKFPSLpo4PiFodJ-VPugJQWfSYXXpe4Tjd3bdTH-oYDJcJvRHIV3ZIID0EApt53lkxsFWV_9b33bltLYyJ7DnclQq1GnfgohDhD9F2CpnN3Xa-ntVmF9ntLe6wxZvk_zdBlbhIPwCc4FuPDIB_skNaciWCzU9LUfzvcfZJAR8KztM8ofDm3YJGZrRJltz6In78ZlN1sIlFuPSIRy56sg3yG3wMfe9Lrst0VacG_2fy6Ccg9VuLqD_xnzMmzjwMF9PGdnO5DlCblWwrHsDE6FkTuDy7ojnPJpPJlw.gzFbDwAmMKp-4eiE.AfY0IecXygmkDUkUGIIG7JmhBSzk4VD_sEAwuTeOufKD_duvNXFTQYNU_QvDc7M-9Vssbbb35dMMw3KLxW2IbC7fll8lNvHHMm1gkxlVxK1h5uRhmgt0q7tyMwLw1iKUDqOa177faHZJISN_gvfh-rlbNswswDGU061dyFh-w6Ck8SXoPnWfp9GxZJBgxzZ5uBV1D7_1bAghqWYNMsMUTSvOYyeWvVJHap-gjtGc491Vf97z6mh9PDBvIi734D90NbV5idZ11CCW7liI5L7kgRwuHZVxiu_NpkPED7dWcaBhOATur4r3P28U39JC5P5FD4JXlqyPl9FXVBkW049E1vdJrrkV3IbiqUMVXlkUeq6G87YUTdmt8qRPgiOc-G6g84RxSPQE55uojbuSSlON2CKZYmSmFVM0X7bBU42wP1wNP7Jq3LTjHcj4rOaN1ozffJxyGs54r7NP4D9u3nt2ozNkjk_DNK3UmxDPaQtZAtFO1d-T7UXv2BvzoCN2LGilzxVi04p9LcvoTDzI5GUY9OsjGUsdSZJvISylHAMMDi8nSxsBBSPD18fzV0tLhdjGM0-XljiM4bjZWNR4Nvraus33p8U4k5lmn2bx13JfHvDa3Zqf_aK57lam6Zf_6mvAK4I7A40WmiolJCxeEeDD54ljF0kAluT4sw6sxVY8It80A95TGFd0lm5e-tGrFKIoqRyPV5uwzzz3XT1HVPJda1ufdGhSUj8slsyqTUrnphh6JWbRfA9mrLdKQKuqM3xEslAwYZOhX6qOzADbo5WQMneTwn34QixMT4A6imaDBc6P4cOaLo7hNyS3e1h6SfwigEX9H42wkC3TWOiITakFq3tKkVwahMkdeds_uxloNoeicdGePjob6BfU8xq0IKxJh9UoeCsSX4KVtIrErHyYuoU-_ENZXYArSwfqorKgdjmQAa13NQjOiHzpgA5HngSCK1xy6zq9NvNA7hUe9O0gTqrFZDsYhRWSYEuOt5QpxJYalPGKIPXlsUJOURfR_J0iT52UxiOZIuXmpk-X8fgDhM_0fZm8GQ2GaIsf3nR49h7QnZRG9azTZV5q9Bs0bqPvP7wRL5xenByeIBsdwP6Bwaqd3n5BkFn-LE7eo6UPn_9o7Gx3g9VlN8pG7SIo_3a07L0yauJIO4ahL5aC07uBCLu3pJQW0ftlVpLdAA8gh3XPhfvOuH4XV7yU1fQhqGKich1hhHx2dFHyVr5mJPxc1VQvAyyAhxjvyQ2TTLfvSiYLOP1vFCVjUwb_RjF5tuh3ArH9IlXH9kIBVQbiSOWfgQ1PqD-go4jqDR_ie3aGc5Fm8Vd6lSh_2HW2GR0Ht0ASoCbl3C5roXCRkyTTaGl3nX6uwfg.thw4B0ug4OIZsZrDzCtJcQ","expires_in": 3600,"token_type": "Bearer"}`
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"fiskerinc.com/modules/logger"
|
"github.com/fiskerinc/cloud-services/pkg/logger"
|
||||||
"github.com/Azure/azure-storage-blob-go/azblob"
|
"github.com/Azure/azure-storage-blob-go/azblob"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|||||||
4
pkg/cache/apitokens.go
vendored
4
pkg/cache/apitokens.go
vendored
@@ -4,10 +4,10 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"fiskerinc.com/modules/common"
|
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"fiskerinc.com/modules/db/queries"
|
"github.com/fiskerinc/cloud-services/pkg/db/queries"
|
||||||
"github.com/ReneKroon/ttlcache/v2"
|
"github.com/ReneKroon/ttlcache/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
14
pkg/cache/apitokens_test.go
vendored
14
pkg/cache/apitokens_test.go
vendored
@@ -3,13 +3,13 @@ package cache_test
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"fiskerinc.com/modules/adminroles"
|
"github.com/fiskerinc/cloud-services/pkg/adminroles"
|
||||||
"fiskerinc.com/modules/cache"
|
"github.com/fiskerinc/cloud-services/pkg/cache"
|
||||||
"fiskerinc.com/modules/common"
|
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||||
"fiskerinc.com/modules/db"
|
"github.com/fiskerinc/cloud-services/pkg/db"
|
||||||
"fiskerinc.com/modules/db/queries"
|
"github.com/fiskerinc/cloud-services/pkg/db/queries"
|
||||||
"fiskerinc.com/modules/redis"
|
"github.com/fiskerinc/cloud-services/pkg/redis"
|
||||||
"fiskerinc.com/modules/testhelper"
|
"github.com/fiskerinc/cloud-services/pkg/testhelper"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestIntegration(t *testing.T) {
|
func TestIntegration(t *testing.T) {
|
||||||
|
|||||||
2
pkg/cache/car_dtcs.go
vendored
2
pkg/cache/car_dtcs.go
vendored
@@ -1,7 +1,7 @@
|
|||||||
package cache
|
package cache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fiskerinc.com/modules/common"
|
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CarDTCsCacheInterface interface {
|
type CarDTCsCacheInterface interface {
|
||||||
|
|||||||
4
pkg/cache/car_dtcs_test.go
vendored
4
pkg/cache/car_dtcs_test.go
vendored
@@ -3,8 +3,8 @@ package cache_test
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
m "fiskerinc.com/modules/common"
|
m "github.com/fiskerinc/cloud-services/pkg/common"
|
||||||
"fiskerinc.com/modules/cache"
|
"github.com/fiskerinc/cloud-services/pkg/cache"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|||||||
8
pkg/cache/digital_twin.go
vendored
8
pkg/cache/digital_twin.go
vendored
@@ -7,10 +7,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"fiskerinc.com/modules/dbc/state"
|
"github.com/fiskerinc/cloud-services/pkg/dbc/state"
|
||||||
"fiskerinc.com/modules/logger"
|
"github.com/fiskerinc/cloud-services/pkg/logger"
|
||||||
"fiskerinc.com/modules/redis"
|
"github.com/fiskerinc/cloud-services/pkg/redis"
|
||||||
"fiskerinc.com/modules/utils/querystring"
|
"github.com/fiskerinc/cloud-services/pkg/utils/querystring"
|
||||||
redigo "github.com/gomodule/redigo/redis"
|
redigo "github.com/gomodule/redigo/redis"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|||||||
8
pkg/cache/drivers.go
vendored
8
pkg/cache/drivers.go
vendored
@@ -1,10 +1,10 @@
|
|||||||
package cache
|
package cache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fiskerinc.com/modules/common"
|
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||||
"fiskerinc.com/modules/db/queries"
|
"github.com/fiskerinc/cloud-services/pkg/db/queries"
|
||||||
"fiskerinc.com/modules/logger"
|
"github.com/fiskerinc/cloud-services/pkg/logger"
|
||||||
"fiskerinc.com/modules/redis"
|
"github.com/fiskerinc/cloud-services/pkg/redis"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
12
pkg/cache/drivers_test.go
vendored
12
pkg/cache/drivers_test.go
vendored
@@ -4,12 +4,12 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"fiskerinc.com/modules/cache"
|
"github.com/fiskerinc/cloud-services/pkg/cache"
|
||||||
"fiskerinc.com/modules/db/queries"
|
"github.com/fiskerinc/cloud-services/pkg/db/queries"
|
||||||
"fiskerinc.com/modules/db/queries/mocks"
|
"github.com/fiskerinc/cloud-services/pkg/db/queries/mocks"
|
||||||
"fiskerinc.com/modules/redis"
|
"github.com/fiskerinc/cloud-services/pkg/redis"
|
||||||
"fiskerinc.com/modules/redis/tester"
|
"github.com/fiskerinc/cloud-services/pkg/redis/tester"
|
||||||
"fiskerinc.com/modules/testhelper"
|
"github.com/fiskerinc/cloud-services/pkg/testhelper"
|
||||||
)
|
)
|
||||||
|
|
||||||
var mockRedis redis.Client
|
var mockRedis redis.Client
|
||||||
|
|||||||
8
pkg/cache/fileids.go
vendored
8
pkg/cache/fileids.go
vendored
@@ -3,10 +3,10 @@ package cache
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
"fiskerinc.com/modules/common"
|
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||||
"fiskerinc.com/modules/db/queries"
|
"github.com/fiskerinc/cloud-services/pkg/db/queries"
|
||||||
"fiskerinc.com/modules/logger"
|
"github.com/fiskerinc/cloud-services/pkg/logger"
|
||||||
"fiskerinc.com/modules/redis"
|
"github.com/fiskerinc/cloud-services/pkg/redis"
|
||||||
|
|
||||||
r "github.com/gomodule/redigo/redis"
|
r "github.com/gomodule/redigo/redis"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|||||||
12
pkg/cache/fileids_test.go
vendored
12
pkg/cache/fileids_test.go
vendored
@@ -3,12 +3,12 @@ package cache_test
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"fiskerinc.com/modules/cache"
|
"github.com/fiskerinc/cloud-services/pkg/cache"
|
||||||
"fiskerinc.com/modules/common"
|
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||||
"fiskerinc.com/modules/db"
|
"github.com/fiskerinc/cloud-services/pkg/db"
|
||||||
"fiskerinc.com/modules/db/queries"
|
"github.com/fiskerinc/cloud-services/pkg/db/queries"
|
||||||
"fiskerinc.com/modules/db/queries/mocks"
|
"github.com/fiskerinc/cloud-services/pkg/db/queries/mocks"
|
||||||
"fiskerinc.com/modules/redis"
|
"github.com/fiskerinc/cloud-services/pkg/redis"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRetrieveFileEncryptionParams(t *testing.T) {
|
func TestRetrieveFileEncryptionParams(t *testing.T) {
|
||||||
|
|||||||
4
pkg/cache/filters.go
vendored
4
pkg/cache/filters.go
vendored
@@ -1,8 +1,8 @@
|
|||||||
package cache
|
package cache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fiskerinc.com/modules/common"
|
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||||
"fiskerinc.com/modules/redis"
|
"github.com/fiskerinc/cloud-services/pkg/redis"
|
||||||
)
|
)
|
||||||
|
|
||||||
func FillCarFilterOnline(redisCLI redis.Client, filter *common.CarSearch) error {
|
func FillCarFilterOnline(redisCLI redis.Client, filter *common.CarSearch) error {
|
||||||
|
|||||||
6
pkg/cache/filters_test.go
vendored
6
pkg/cache/filters_test.go
vendored
@@ -3,9 +3,9 @@ package cache_test
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"fiskerinc.com/modules/cache"
|
"github.com/fiskerinc/cloud-services/pkg/cache"
|
||||||
m "fiskerinc.com/modules/common"
|
m "github.com/fiskerinc/cloud-services/pkg/common"
|
||||||
"fiskerinc.com/modules/redis/tester"
|
"github.com/fiskerinc/cloud-services/pkg/redis/tester"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|||||||
2
pkg/cache/ringmap_test.go
vendored
2
pkg/cache/ringmap_test.go
vendored
@@ -5,7 +5,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"fiskerinc.com/modules/cache"
|
"github.com/fiskerinc/cloud-services/pkg/cache"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|||||||
10
pkg/cache/subscription_types.go
vendored
10
pkg/cache/subscription_types.go
vendored
@@ -3,11 +3,11 @@ package cache
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
"fiskerinc.com/modules/common"
|
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||||
"fiskerinc.com/modules/db/queries"
|
"github.com/fiskerinc/cloud-services/pkg/db/queries"
|
||||||
"fiskerinc.com/modules/duration"
|
"github.com/fiskerinc/cloud-services/pkg/duration"
|
||||||
"fiskerinc.com/modules/logger"
|
"github.com/fiskerinc/cloud-services/pkg/logger"
|
||||||
"fiskerinc.com/modules/redis"
|
"github.com/fiskerinc/cloud-services/pkg/redis"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|||||||
10
pkg/cache/subscription_types_test.go
vendored
10
pkg/cache/subscription_types_test.go
vendored
@@ -4,11 +4,11 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"fiskerinc.com/modules/cache"
|
"github.com/fiskerinc/cloud-services/pkg/cache"
|
||||||
"fiskerinc.com/modules/common"
|
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||||
"fiskerinc.com/modules/db/queries"
|
"github.com/fiskerinc/cloud-services/pkg/db/queries"
|
||||||
"fiskerinc.com/modules/redis"
|
"github.com/fiskerinc/cloud-services/pkg/redis"
|
||||||
"fiskerinc.com/modules/testhelper"
|
"github.com/fiskerinc/cloud-services/pkg/testhelper"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
12
pkg/cache/vehicle_config.go
vendored
12
pkg/cache/vehicle_config.go
vendored
@@ -3,13 +3,13 @@ package cache
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
"fiskerinc.com/modules/common"
|
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||||
"fiskerinc.com/modules/logger"
|
"github.com/fiskerinc/cloud-services/pkg/logger"
|
||||||
"fiskerinc.com/modules/mongo"
|
"github.com/fiskerinc/cloud-services/pkg/mongo"
|
||||||
"fiskerinc.com/modules/redis"
|
"github.com/fiskerinc/cloud-services/pkg/redis"
|
||||||
"fiskerinc.com/modules/utils/envtool"
|
"github.com/fiskerinc/cloud-services/pkg/utils/envtool"
|
||||||
|
|
||||||
"fiskerinc.com/modules/utils/elptr"
|
"github.com/fiskerinc/cloud-services/pkg/utils/elptr"
|
||||||
redigo "github.com/gomodule/redigo/redis"
|
redigo "github.com/gomodule/redigo/redis"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|||||||
12
pkg/cache/vehicle_config_test.go
vendored
12
pkg/cache/vehicle_config_test.go
vendored
@@ -5,12 +5,12 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"fiskerinc.com/modules/cache"
|
"github.com/fiskerinc/cloud-services/pkg/cache"
|
||||||
"fiskerinc.com/modules/common"
|
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||||
"fiskerinc.com/modules/mongo"
|
"github.com/fiskerinc/cloud-services/pkg/mongo"
|
||||||
"fiskerinc.com/modules/redis"
|
"github.com/fiskerinc/cloud-services/pkg/redis"
|
||||||
"fiskerinc.com/modules/testhelper"
|
"github.com/fiskerinc/cloud-services/pkg/testhelper"
|
||||||
"fiskerinc.com/modules/utils/elptr"
|
"github.com/fiskerinc/cloud-services/pkg/utils/elptr"
|
||||||
redigo "github.com/gomodule/redigo/redis"
|
redigo "github.com/gomodule/redigo/redis"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|||||||
10
pkg/cache/vehicle_state.go
vendored
10
pkg/cache/vehicle_state.go
vendored
@@ -6,11 +6,11 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"fiskerinc.com/modules/common"
|
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||||
dt "fiskerinc.com/modules/dbc/state"
|
dt "github.com/fiskerinc/cloud-services/pkg/dbc/state"
|
||||||
"fiskerinc.com/modules/logger"
|
"github.com/fiskerinc/cloud-services/pkg/logger"
|
||||||
"fiskerinc.com/modules/redis"
|
"github.com/fiskerinc/cloud-services/pkg/redis"
|
||||||
"fiskerinc.com/modules/utils/querystring"
|
"github.com/fiskerinc/cloud-services/pkg/utils/querystring"
|
||||||
|
|
||||||
redigo "github.com/gomodule/redigo/redis"
|
redigo "github.com/gomodule/redigo/redis"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|||||||
6
pkg/cache/vehicle_state_multi.go
vendored
6
pkg/cache/vehicle_state_multi.go
vendored
@@ -1,9 +1,9 @@
|
|||||||
package cache
|
package cache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fiskerinc.com/modules/common"
|
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||||
"fiskerinc.com/modules/logger"
|
"github.com/fiskerinc/cloud-services/pkg/logger"
|
||||||
"fiskerinc.com/modules/redis"
|
"github.com/fiskerinc/cloud-services/pkg/redis"
|
||||||
|
|
||||||
redigo "github.com/gomodule/redigo/redis"
|
redigo "github.com/gomodule/redigo/redis"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|||||||
8
pkg/cache/vehicle_state_test.go
vendored
8
pkg/cache/vehicle_state_test.go
vendored
@@ -5,10 +5,10 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"fiskerinc.com/modules/cache"
|
"github.com/fiskerinc/cloud-services/pkg/cache"
|
||||||
"fiskerinc.com/modules/common"
|
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||||
"fiskerinc.com/modules/redis"
|
"github.com/fiskerinc/cloud-services/pkg/redis"
|
||||||
"fiskerinc.com/modules/redis/tester"
|
"github.com/fiskerinc/cloud-services/pkg/redis/tester"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
4
pkg/cache/vehicles.go
vendored
4
pkg/cache/vehicles.go
vendored
@@ -2,8 +2,8 @@ package cache
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fiskerinc.com/modules/common"
|
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||||
orm "fiskerinc.com/modules/db/queries"
|
orm "github.com/fiskerinc/cloud-services/pkg/db/queries"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/ReneKroon/ttlcache/v2"
|
"github.com/ReneKroon/ttlcache/v2"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|||||||
8
pkg/cache/verify.go
vendored
8
pkg/cache/verify.go
vendored
@@ -1,10 +1,10 @@
|
|||||||
package cache
|
package cache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fiskerinc.com/modules/common"
|
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||||
"fiskerinc.com/modules/db/queries"
|
"github.com/fiskerinc/cloud-services/pkg/db/queries"
|
||||||
"fiskerinc.com/modules/logger"
|
"github.com/fiskerinc/cloud-services/pkg/logger"
|
||||||
"fiskerinc.com/modules/redis"
|
"github.com/fiskerinc/cloud-services/pkg/redis"
|
||||||
|
|
||||||
redigo "github.com/gomodule/redigo/redis"
|
redigo "github.com/gomodule/redigo/redis"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|||||||
12
pkg/cache/verify_test.go
vendored
12
pkg/cache/verify_test.go
vendored
@@ -3,12 +3,12 @@ package cache_test
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"fiskerinc.com/modules/cache"
|
"github.com/fiskerinc/cloud-services/pkg/cache"
|
||||||
"fiskerinc.com/modules/common"
|
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||||
"fiskerinc.com/modules/db/queries/mocks"
|
"github.com/fiskerinc/cloud-services/pkg/db/queries/mocks"
|
||||||
"fiskerinc.com/modules/redis"
|
"github.com/fiskerinc/cloud-services/pkg/redis"
|
||||||
"fiskerinc.com/modules/redis/tester"
|
"github.com/fiskerinc/cloud-services/pkg/redis/tester"
|
||||||
"fiskerinc.com/modules/testhelper"
|
"github.com/fiskerinc/cloud-services/pkg/testhelper"
|
||||||
|
|
||||||
redigo "github.com/gomodule/redigo/redis"
|
redigo "github.com/gomodule/redigo/redis"
|
||||||
)
|
)
|
||||||
|
|||||||
8
pkg/cache/vins.go
vendored
8
pkg/cache/vins.go
vendored
@@ -3,10 +3,10 @@ package cache
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"fiskerinc.com/modules/common"
|
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||||
"fiskerinc.com/modules/db/queries"
|
"github.com/fiskerinc/cloud-services/pkg/db/queries"
|
||||||
"fiskerinc.com/modules/logger"
|
"github.com/fiskerinc/cloud-services/pkg/logger"
|
||||||
"fiskerinc.com/modules/redis"
|
"github.com/fiskerinc/cloud-services/pkg/redis"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RetrieveVINs retrieves VINs from redis or from DB based on driver ID and proceeds to cache VINs
|
// RetrieveVINs retrieves VINs from redis or from DB based on driver ID and proceeds to cache VINs
|
||||||
|
|||||||
6
pkg/cache/vins_test.go
vendored
6
pkg/cache/vins_test.go
vendored
@@ -4,9 +4,9 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"fiskerinc.com/modules/cache"
|
"github.com/fiskerinc/cloud-services/pkg/cache"
|
||||||
"fiskerinc.com/modules/redis"
|
"github.com/fiskerinc/cloud-services/pkg/redis"
|
||||||
"fiskerinc.com/modules/testhelper"
|
"github.com/fiskerinc/cloud-services/pkg/testhelper"
|
||||||
)
|
)
|
||||||
|
|
||||||
type mockRedisCacheVINs struct {
|
type mockRedisCacheVINs struct {
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"fiskerinc.com/modules/dbc/state"
|
"github.com/fiskerinc/cloud-services/pkg/dbc/state"
|
||||||
"fiskerinc.com/modules/logger"
|
"github.com/fiskerinc/cloud-services/pkg/logger"
|
||||||
"fiskerinc.com/modules/redis"
|
"github.com/fiskerinc/cloud-services/pkg/redis"
|
||||||
"fiskerinc.com/modules/utils/querystring"
|
"github.com/fiskerinc/cloud-services/pkg/utils/querystring"
|
||||||
redigo "github.com/gomodule/redigo/redis"
|
redigo "github.com/gomodule/redigo/redis"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package cachev2
|
package cachev2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fiskerinc.com/modules/common"
|
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||||
"fiskerinc.com/modules/db/queries"
|
"github.com/fiskerinc/cloud-services/pkg/db/queries"
|
||||||
"fiskerinc.com/modules/logger"
|
"github.com/fiskerinc/cloud-services/pkg/logger"
|
||||||
redis "fiskerinc.com/modules/redisv2"
|
redis "github.com/fiskerinc/cloud-services/pkg/redisv2"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
cache "fiskerinc.com/modules/cachev2"
|
cache "github.com/fiskerinc/cloud-services/pkg/cachev2"
|
||||||
"fiskerinc.com/modules/db/queries"
|
"github.com/fiskerinc/cloud-services/pkg/db/queries"
|
||||||
"fiskerinc.com/modules/db/queries/mocks"
|
"github.com/fiskerinc/cloud-services/pkg/db/queries/mocks"
|
||||||
"fiskerinc.com/modules/redis/tester"
|
"github.com/fiskerinc/cloud-services/pkg/redis/tester"
|
||||||
redis "fiskerinc.com/modules/redisv2"
|
redis "github.com/fiskerinc/cloud-services/pkg/redisv2"
|
||||||
"fiskerinc.com/modules/testhelper"
|
"github.com/fiskerinc/cloud-services/pkg/testhelper"
|
||||||
)
|
)
|
||||||
|
|
||||||
var mockRedis redis.Client
|
var mockRedis redis.Client
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ package cachev2
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
"fiskerinc.com/modules/common"
|
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||||
"fiskerinc.com/modules/logger"
|
"github.com/fiskerinc/cloud-services/pkg/logger"
|
||||||
"fiskerinc.com/modules/mongo"
|
"github.com/fiskerinc/cloud-services/pkg/mongo"
|
||||||
"fiskerinc.com/modules/redis"
|
"github.com/fiskerinc/cloud-services/pkg/redis"
|
||||||
"fiskerinc.com/modules/utils/envtool"
|
"github.com/fiskerinc/cloud-services/pkg/utils/envtool"
|
||||||
|
|
||||||
"fiskerinc.com/modules/utils/elptr"
|
"github.com/fiskerinc/cloud-services/pkg/utils/elptr"
|
||||||
redigo "github.com/gomodule/redigo/redis"
|
redigo "github.com/gomodule/redigo/redis"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
cache "fiskerinc.com/modules/cachev2"
|
cache "github.com/fiskerinc/cloud-services/pkg/cachev2"
|
||||||
"fiskerinc.com/modules/common"
|
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||||
"fiskerinc.com/modules/mongo"
|
"github.com/fiskerinc/cloud-services/pkg/mongo"
|
||||||
"fiskerinc.com/modules/redis"
|
"github.com/fiskerinc/cloud-services/pkg/redis"
|
||||||
"fiskerinc.com/modules/testhelper"
|
"github.com/fiskerinc/cloud-services/pkg/testhelper"
|
||||||
"fiskerinc.com/modules/utils/elptr"
|
"github.com/fiskerinc/cloud-services/pkg/utils/elptr"
|
||||||
redigo "github.com/gomodule/redigo/redis"
|
redigo "github.com/gomodule/redigo/redis"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"fiskerinc.com/modules/common"
|
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||||
dt "fiskerinc.com/modules/dbc/state"
|
dt "github.com/fiskerinc/cloud-services/pkg/dbc/state"
|
||||||
"fiskerinc.com/modules/logger"
|
"github.com/fiskerinc/cloud-services/pkg/logger"
|
||||||
redis "fiskerinc.com/modules/redisv2"
|
redis "github.com/fiskerinc/cloud-services/pkg/redisv2"
|
||||||
"fiskerinc.com/modules/utils/querystring"
|
"github.com/fiskerinc/cloud-services/pkg/utils/querystring"
|
||||||
redispkg "github.com/redis/go-redis/v9"
|
redispkg "github.com/redis/go-redis/v9"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ package cachev2
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"fiskerinc.com/modules/common"
|
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||||
"fiskerinc.com/modules/logger"
|
"github.com/fiskerinc/cloud-services/pkg/logger"
|
||||||
redis "fiskerinc.com/modules/redisv2"
|
redis "github.com/fiskerinc/cloud-services/pkg/redisv2"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"fiskerinc.com/modules/db/queries"
|
"github.com/fiskerinc/cloud-services/pkg/db/queries"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetVINListDigitalTwin(vins []string, redisClient *redis.Connection) (digitalTwins map[string]common.CarState, errorList []error) {
|
func GetVINListDigitalTwin(vins []string, redisClient *redis.Connection) (digitalTwins map[string]common.CarState, errorList []error) {
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
cache "fiskerinc.com/modules/cachev2"
|
cache "github.com/fiskerinc/cloud-services/pkg/cachev2"
|
||||||
"fiskerinc.com/modules/common"
|
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||||
redis "fiskerinc.com/modules/redisv2"
|
redis "github.com/fiskerinc/cloud-services/pkg/redisv2"
|
||||||
"github.com/go-redis/redismock/v9"
|
"github.com/go-redis/redismock/v9"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"fiskerinc.com/modules/common"
|
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||||
redis "fiskerinc.com/modules/redisv2"
|
redis "github.com/fiskerinc/cloud-services/pkg/redisv2"
|
||||||
"fiskerinc.com/modules/utils/elptr"
|
"github.com/fiskerinc/cloud-services/pkg/utils/elptr"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetTowManDigitalTwin(vin string, redisClient *redis.Connection)(tdt common.TowmanDigitalTwin, err error){
|
func GetTowManDigitalTwin(vin string, redisClient *redis.Connection)(tdt common.TowmanDigitalTwin, err error){
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ package cachev2
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"fiskerinc.com/modules/common"
|
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||||
"fiskerinc.com/modules/db/queries"
|
"github.com/fiskerinc/cloud-services/pkg/db/queries"
|
||||||
redis "fiskerinc.com/modules/redisv2"
|
redis "github.com/fiskerinc/cloud-services/pkg/redisv2"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ package cachev2_test
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"fiskerinc.com/modules/cache"
|
"github.com/fiskerinc/cloud-services/pkg/cache"
|
||||||
"fiskerinc.com/modules/common"
|
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||||
"fiskerinc.com/modules/db/queries/mocks"
|
"github.com/fiskerinc/cloud-services/pkg/db/queries/mocks"
|
||||||
"fiskerinc.com/modules/redis"
|
"github.com/fiskerinc/cloud-services/pkg/redis"
|
||||||
"fiskerinc.com/modules/redis/tester"
|
"github.com/fiskerinc/cloud-services/pkg/redis/tester"
|
||||||
"fiskerinc.com/modules/testhelper"
|
"github.com/fiskerinc/cloud-services/pkg/testhelper"
|
||||||
|
|
||||||
redigo "github.com/gomodule/redigo/redis"
|
redigo "github.com/gomodule/redigo/redis"
|
||||||
)
|
)
|
||||||
|
|||||||
3
pkg/can-go/.gitignore
vendored
Normal file
3
pkg/can-go/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# generated DBC files
|
||||||
|
*.dbc.go
|
||||||
|
*.dbc
|
||||||
129
pkg/can-go/CODE_OF_CONDUCT.md
Normal file
129
pkg/can-go/CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
We as members, contributors, and leaders pledge to make participation in our
|
||||||
|
community a harassment-free experience for everyone, regardless of age, body
|
||||||
|
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||||
|
identity and expression, level of experience, education, socio-economic status,
|
||||||
|
nationality, personal appearance, race, religion, or sexual identity
|
||||||
|
and orientation.
|
||||||
|
|
||||||
|
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||||
|
diverse, inclusive, and healthy community.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to a positive environment for our
|
||||||
|
community include:
|
||||||
|
|
||||||
|
- Demonstrating empathy and kindness toward other people
|
||||||
|
- Being respectful of differing opinions, viewpoints, and experiences
|
||||||
|
- Giving and gracefully accepting constructive feedback
|
||||||
|
- Accepting responsibility and apologizing to those affected by our mistakes,
|
||||||
|
and learning from the experience
|
||||||
|
- Focusing on what is best not just for us as individuals, but for the
|
||||||
|
overall community
|
||||||
|
|
||||||
|
Examples of unacceptable behavior include:
|
||||||
|
|
||||||
|
- The use of sexualized language or imagery, and sexual attention or
|
||||||
|
advances of any kind
|
||||||
|
- Trolling, insulting or derogatory comments, and personal or political attacks
|
||||||
|
- Public or private harassment
|
||||||
|
- Publishing others' private information, such as a physical or email
|
||||||
|
address, without their explicit permission
|
||||||
|
- Other conduct which could reasonably be considered inappropriate in a
|
||||||
|
professional setting
|
||||||
|
|
||||||
|
## Enforcement Responsibilities
|
||||||
|
|
||||||
|
Community leaders are responsible for clarifying and enforcing our standards of
|
||||||
|
acceptable behavior and will take appropriate and fair corrective action in
|
||||||
|
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||||
|
or harmful.
|
||||||
|
|
||||||
|
Community leaders have the right and responsibility to remove, edit, or reject
|
||||||
|
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||||
|
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||||
|
decisions when appropriate.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies within all community spaces, and also applies when
|
||||||
|
an individual is officially representing the community in public spaces.
|
||||||
|
Examples of representing our community include using an official e-mail address,
|
||||||
|
posting via an official social media account, or acting as an appointed
|
||||||
|
representative at an online or offline event.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
|
reported to the community leaders responsible for enforcement at
|
||||||
|
<open-source@einride.tech>.
|
||||||
|
|
||||||
|
All complaints will be reviewed and investigated promptly and fairly.
|
||||||
|
|
||||||
|
All community leaders are obligated to respect the privacy and security of the
|
||||||
|
reporter of any incident.
|
||||||
|
|
||||||
|
## Enforcement Guidelines
|
||||||
|
|
||||||
|
Community leaders will follow these Community Impact Guidelines in determining
|
||||||
|
the consequences for any action they deem in violation of this Code of Conduct:
|
||||||
|
|
||||||
|
### 1. Correction
|
||||||
|
|
||||||
|
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||||
|
unprofessional or unwelcome in the community.
|
||||||
|
|
||||||
|
**Consequence**: A private, written warning from community leaders, providing
|
||||||
|
clarity around the nature of the violation and an explanation of why the
|
||||||
|
behavior was inappropriate. A public apology may be requested.
|
||||||
|
|
||||||
|
### 2. Warning
|
||||||
|
|
||||||
|
**Community Impact**: A violation through a single incident or series
|
||||||
|
of actions.
|
||||||
|
|
||||||
|
**Consequence**: A warning with consequences for continued behavior. No
|
||||||
|
interaction with the people involved, including unsolicited interaction with
|
||||||
|
those enforcing the Code of Conduct, for a specified period of time. This
|
||||||
|
includes avoiding interactions in community spaces as well as external channels
|
||||||
|
like social media. Violating these terms may lead to a temporary or
|
||||||
|
permanent ban.
|
||||||
|
|
||||||
|
### 3. Temporary Ban
|
||||||
|
|
||||||
|
**Community Impact**: A serious violation of community standards, including
|
||||||
|
sustained inappropriate behavior.
|
||||||
|
|
||||||
|
**Consequence**: A temporary ban from any sort of interaction or public
|
||||||
|
communication with the community for a specified period of time. No public or
|
||||||
|
private interaction with the people involved, including unsolicited interaction
|
||||||
|
with those enforcing the Code of Conduct, is allowed during this period.
|
||||||
|
Violating these terms may lead to a permanent ban.
|
||||||
|
|
||||||
|
### 4. Permanent Ban
|
||||||
|
|
||||||
|
**Community Impact**: Demonstrating a pattern of violation of community
|
||||||
|
standards, including sustained inappropriate behavior, harassment of an
|
||||||
|
individual, or aggression toward or disparagement of classes of individuals.
|
||||||
|
|
||||||
|
**Consequence**: A permanent ban from any sort of public interaction within
|
||||||
|
the community.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||||
|
version 2.0, available at
|
||||||
|
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||||
|
|
||||||
|
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||||
|
enforcement ladder](https://github.com/mozilla/diversity).
|
||||||
|
|
||||||
|
[homepage]: https://www.contributor-covenant.org
|
||||||
|
|
||||||
|
For answers to common questions about this code of conduct, see the FAQ at
|
||||||
|
https://www.contributor-covenant.org/faq. Translations are available at
|
||||||
|
https://www.contributor-covenant.org/translations.
|
||||||
21
pkg/can-go/LICENSE
Normal file
21
pkg/can-go/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2019 Einride AB
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
70
pkg/can-go/Makefile
Normal file
70
pkg/can-go/Makefile
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
SHELL := /bin/bash
|
||||||
|
|
||||||
|
all: \
|
||||||
|
commitlint \
|
||||||
|
stringer-generate \
|
||||||
|
mockgen-generate \
|
||||||
|
testdata \
|
||||||
|
go-lint \
|
||||||
|
go-review \
|
||||||
|
go-test \
|
||||||
|
go-mod-tidy \
|
||||||
|
git-verify-nodiff
|
||||||
|
|
||||||
|
include tools/commitlint/rules.mk
|
||||||
|
include tools/git-verify-nodiff/rules.mk
|
||||||
|
include tools/golangci-lint/rules.mk
|
||||||
|
include tools/goreview/rules.mk
|
||||||
|
include tools/semantic-release/rules.mk
|
||||||
|
include tools/stringer/rules.mk
|
||||||
|
|
||||||
|
.PHONY: clean
|
||||||
|
clean:
|
||||||
|
$(info [$@] removing build files...)
|
||||||
|
@rm -rf tools/*/*/ build
|
||||||
|
|
||||||
|
.PHONY: mockgen-generate
|
||||||
|
mockgen-generate: \
|
||||||
|
internal/gen/mock/mockcanrunner/mocks.go \
|
||||||
|
internal/gen/mock/mockclock/mocks.go \
|
||||||
|
internal/gen/mock/mocksocketcan/mocks.go
|
||||||
|
|
||||||
|
internal/gen/mock/mockcanrunner/mocks.go: pkg/canrunner/run.go go.mod
|
||||||
|
go run github.com/golang/mock/mockgen \
|
||||||
|
-destination $@ -package mockcanrunner github.com/Fisker-Inc/project-ai-can-go/pkg/canrunner \
|
||||||
|
Node,TransmittedMessage,ReceivedMessage,FrameTransmitter,FrameReceiver
|
||||||
|
|
||||||
|
internal/gen/mock/mockclock/mocks.go: internal/clock/clock.go go.mod
|
||||||
|
go run github.com/golang/mock/mockgen \
|
||||||
|
-destination $@ -package mockclock github.com/Fisker-Inc/project-ai-can-go/internal/clock \
|
||||||
|
Clock,Ticker
|
||||||
|
|
||||||
|
internal/gen/mock/mocksocketcan/mocks.go: pkg/socketcan/fileconn.go go.mod
|
||||||
|
go run github.com/golang/mock/mockgen \
|
||||||
|
-destination $@ -package mocksocketcan -source $<
|
||||||
|
|
||||||
|
.PHONY: stringer-generate
|
||||||
|
stringer-generate: \
|
||||||
|
pkg/descriptor/sendtype_string.go \
|
||||||
|
pkg/socketcan/errorclass_string.go \
|
||||||
|
pkg/socketcan/protocolviolationerrorlocation_string.go \
|
||||||
|
pkg/socketcan/protocolviolationerror_string.go \
|
||||||
|
pkg/socketcan/controllererror_string.go \
|
||||||
|
pkg/socketcan/transceivererror_string.go
|
||||||
|
|
||||||
|
%_string.go: %.go $(stringer)
|
||||||
|
go generate $<
|
||||||
|
|
||||||
|
.PHONY: testdata
|
||||||
|
testdata:
|
||||||
|
go run cmd/cantool/main.go generate testdata/dbc testdata/gen/go
|
||||||
|
|
||||||
|
.PHONY: go-test
|
||||||
|
go-test:
|
||||||
|
$(info [$@] running Go tests...)
|
||||||
|
@mkdir -p build/coverage
|
||||||
|
@go test -short -race -coverprofile=build/coverage/$@.txt -covermode=atomic ./...
|
||||||
|
|
||||||
|
.PHONY: go-mod-tidy
|
||||||
|
go-mod-tidy:
|
||||||
|
go mod tidy -v
|
||||||
168
pkg/can-go/README.md
Normal file
168
pkg/can-go/README.md
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
# :electric_plug: CAN Go
|
||||||
|
|
||||||
|
[![PkgGoDev][pkg-badge]][pkg]
|
||||||
|
[![GoReportCard][report-badge]][report]
|
||||||
|
[![Codecov][codecov-badge]][codecov]
|
||||||
|
|
||||||
|
[pkg-badge]: https://pkg.go.dev/badge/github.com/Fisker-Inc/project-ai-can-go
|
||||||
|
[pkg]: https://pkg.go.dev/github.com/Fisker-Inc/project-ai-can-go
|
||||||
|
[report-badge]: https://goreportcard.com/badge/github.com/Fisker-Inc/project-ai-can-go
|
||||||
|
[report]: https://goreportcard.com/report/github.com/Fisker-Inc/project-ai-can-go
|
||||||
|
[codecov-badge]: https://codecov.io/gh/einride/can-go/branch/master/graph/badge.svg
|
||||||
|
[codecov]: https://codecov.io/gh/einride/can-go
|
||||||
|
|
||||||
|
CAN toolkit for Go programmers.
|
||||||
|
|
||||||
|
can-go makes use of the Linux SocketCAN abstraction for CAN communication.
|
||||||
|
(See the [SocketCAN][socketcan] documentation for more details).
|
||||||
|
|
||||||
|
[socketcan]: https://www.kernel.org/doc/Documentation/networking/can.txt
|
||||||
|
|
||||||
|
## Modifications to the Original Repo
|
||||||
|
Original repo: `https://github.com/jshiv/can-go/tree/623b1140d54a845026249a5bbbaf23f44c614173`.
|
||||||
|
|
||||||
|
Comment out the value descriptor generation code within `can-go/internal/generate/file.go`, lines 128-141. This code creates compile errors due to duplicate values within the `.dbc` file provided.
|
||||||
|
|
||||||
|
## DBC Go Code Library Generation
|
||||||
|
1. Place `.dbc` file into `orig/` folder.
|
||||||
|
2. Run `scripts/main.go`:
|
||||||
|
```
|
||||||
|
go run scripts/main.go
|
||||||
|
```
|
||||||
|
This will translate all Chinese variable values within the file to English. Any non-alphanumeric characters are also removed. **NOTE** The default read file is labelled `orig/121-N60AB_ADASBUS_Matrix_CANFD_V2.3.dbc` and the default write file is labelled `dbc/n60.dbc`.
|
||||||
|
|
||||||
|
3. There will still be leftover Chinese characters in the `.dbc` file, I chose to just remove them.
|
||||||
|
4. Generate Go code from cleaned `.dbc` file:
|
||||||
|
```
|
||||||
|
can-go> go run cmd/cantool/main.go generate data/dbc dbc
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Decoding CAN messages
|
||||||
|
|
||||||
|
Decoding CAN messages from byte arrays can be done using `can.Payload`
|
||||||
|
|
||||||
|
```go
|
||||||
|
func main() {
|
||||||
|
// DBC file
|
||||||
|
var dbcFile = []byte(`
|
||||||
|
VERSION ""
|
||||||
|
NS_ :
|
||||||
|
BS_:
|
||||||
|
BU_: DBG DRIVER IO MOTOR SENSOR
|
||||||
|
|
||||||
|
BO_ 1530 DisconnectState: 14 MOTOR
|
||||||
|
SG_ LockCountRearRight : 91|20@0+ (1,0) [0|1048575] "" IO
|
||||||
|
SG_ DisconnectStateRearRight : 95|4@0+ (1,0) [0|5] "" IO
|
||||||
|
SG_ CurrentRearRight : 79|16@0+ (1,0) [0|65535] "" IO
|
||||||
|
SG_ DisconnectStateRearRightTarget : 64|1@0+ (1,0) [0|1] "" IO
|
||||||
|
SG_ TargetSpeedRearRight : 63|15@0+ (0.125,-2048) [-2048|2047.875] "rad/s" IO
|
||||||
|
SG_ LockCountRearLeft : 35|20@0+ (1,0) [0|1048575] "" IO
|
||||||
|
SG_ DisconnectStateRearLeft : 39|4@0+ (1,0) [0|5] "" IO
|
||||||
|
SG_ CurrentRearLeft : 23|16@0+ (1,0) [0|65535] "" IO
|
||||||
|
SG_ DisconnectStateRearLeftTarget : 8|1@0+ (1,0) [0|1] "" IO
|
||||||
|
SG_ TargetSpeedRearLeft : 7|15@0+ (0.125,-2048) [-2048|2047.875] "rad/s" IO
|
||||||
|
|
||||||
|
VAL_ 1530 DisconnectStateRearRight 0 "Undefined" 1 "Locked" 2 "Unlocked" 3 "Locking" 4 "Unlocking" 5 "Faulted" ;
|
||||||
|
VAL_ 1530 DisconnectStateRearLeft 0 "Undefined" 1 "Locked" 2 "Unlocked" 3 "Locking" 4 "Unlocking" 5 "Faulted" ;
|
||||||
|
`)
|
||||||
|
|
||||||
|
// Create payload from hex string
|
||||||
|
byteStringHex := "8000000420061880000005200600"
|
||||||
|
p, _ := can.PayloadFromHex(byteStringHex)
|
||||||
|
|
||||||
|
// Load example dbc file
|
||||||
|
c, _ := generate.Compile("test.dbc", dbcFile)
|
||||||
|
db := *c.Database
|
||||||
|
|
||||||
|
// Decode message frame ID 1530
|
||||||
|
message, _ := db.Message(uint32(1530))
|
||||||
|
decodedSignals := message.Decode(&p)
|
||||||
|
for _, signal := range decodedSignals {
|
||||||
|
fmt.Printf("Signal: %s, Value: %f, Description: %s\n", signal.Signal.Name, signal.Value, signal.Description)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
Signal: TargetSpeedRearLeft, Value: 0.000000, Description:
|
||||||
|
Signal: DisconnectStateRearLeftTarget, Value: 0.000000, Description:
|
||||||
|
Signal: CurrentRearLeft, Value: 4.000000, Description:
|
||||||
|
Signal: LockCountRearLeft, Value: 1560.000000, Description:
|
||||||
|
Signal: DisconnectStateRearLeft, Value: 2.000000, Description: Unlocked
|
||||||
|
Signal: TargetSpeedRearRight, Value: 0.000000, Description:
|
||||||
|
Signal: DisconnectStateRearRightTarget, Value: 0.000000, Description:
|
||||||
|
Signal: CurrentRearRight, Value: 5.000000, Description:
|
||||||
|
Signal: LockCountRearRight, Value: 1536.000000, Description:
|
||||||
|
Signal: DisconnectStateRearRight, Value: 2.000000, Description: Unlocked
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Receiving CAN frames
|
||||||
|
|
||||||
|
Receiving CAN frames from a socketcan interface.
|
||||||
|
|
||||||
|
```go
|
||||||
|
func main() {
|
||||||
|
// Error handling omitted to keep example simple
|
||||||
|
conn, _ := socketcan.DialContext(context.Background(), "can", "can0")
|
||||||
|
|
||||||
|
recv := socketcan.NewReceiver(conn)
|
||||||
|
for recv.Receive() {
|
||||||
|
frame := recv.Frame()
|
||||||
|
fmt.Println(frame.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sending CAN frames/messages
|
||||||
|
|
||||||
|
Sending CAN frames to a socketcan interface.
|
||||||
|
|
||||||
|
```go
|
||||||
|
func main() {
|
||||||
|
// Error handling omitted to keep example simple
|
||||||
|
|
||||||
|
conn, _ := socketcan.DialContext(context.Background(), "can", "can0")
|
||||||
|
|
||||||
|
frame := can.Frame{}
|
||||||
|
tx := socketcan.NewTransmitter(conn)
|
||||||
|
_ = tx.TransmitFrame(context.Background(), frame)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Generating Go code from a DBC file
|
||||||
|
|
||||||
|
It is possible to generate Go code from a `.dbc` file.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ go run github.com/Fisker-Inc/project-ai-can-go/cmd/cantool generate <dbc file root folder> <output folder>
|
||||||
|
```
|
||||||
|
|
||||||
|
In order to generate Go code that makes sense, we currently perform some
|
||||||
|
validations when parsing the DBC file so there may need to be some changes
|
||||||
|
on the DBC file to make it work
|
||||||
|
|
||||||
|
After generating Go code we can marshal a message to a frame:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// import etruckcan "github.com/myproject/myrepo/gen"
|
||||||
|
|
||||||
|
auxMsg := etruckcan.NewAuxiliary().SetHeadLights(etruckcan.Auxiliary_HeadLights_LowBeam)
|
||||||
|
frame := auxMsg.Frame()
|
||||||
|
```
|
||||||
|
|
||||||
|
Or unmarshal a frame to a message:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// import etruckcan "github.com/myproject/myrepo/gen"
|
||||||
|
|
||||||
|
// Error handling omitted for simplicity
|
||||||
|
_ := recv.Receive()
|
||||||
|
frame := recv.Frame()
|
||||||
|
|
||||||
|
var auxMsg *etruckcan.Auxiliary
|
||||||
|
_ = auxMsg.UnmarshalFrame(frame)
|
||||||
|
|
||||||
|
```
|
||||||
4
pkg/can-go/can.go
Normal file
4
pkg/can-go/can.go
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
// Package can provides primitives for working with CAN.
|
||||||
|
//
|
||||||
|
// See: https://en.wikipedia.org/wiki/CAN_bus
|
||||||
|
package can // import "github.com/fiskerinc/cloud-services/pkg/can-go"
|
||||||
212
pkg/can-go/cmd/cantool/main.go
Normal file
212
pkg/can-go/cmd/cantool/main.go
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"text/scanner"
|
||||||
|
|
||||||
|
"github.com/fiskerinc/cloud-services/pkg/can-go/internal/generate"
|
||||||
|
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/dbc"
|
||||||
|
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/dbc/analysis"
|
||||||
|
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/dbc/analysis/passes/definitiontypeorder"
|
||||||
|
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/dbc/analysis/passes/intervals"
|
||||||
|
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/dbc/analysis/passes/lineendings"
|
||||||
|
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/dbc/analysis/passes/messagenames"
|
||||||
|
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/dbc/analysis/passes/multiplexedsignals"
|
||||||
|
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/dbc/analysis/passes/newsymbols"
|
||||||
|
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/dbc/analysis/passes/nodereferences"
|
||||||
|
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/dbc/analysis/passes/noreservedsignals"
|
||||||
|
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/dbc/analysis/passes/requireddefinitions"
|
||||||
|
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/dbc/analysis/passes/signalbounds"
|
||||||
|
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/dbc/analysis/passes/signalnames"
|
||||||
|
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/dbc/analysis/passes/singletondefinitions"
|
||||||
|
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/dbc/analysis/passes/siunits"
|
||||||
|
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/dbc/analysis/passes/uniquenodenames"
|
||||||
|
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/dbc/analysis/passes/uniquesignalnames"
|
||||||
|
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/dbc/analysis/passes/unitsuffixes"
|
||||||
|
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/dbc/analysis/passes/valuedescriptions"
|
||||||
|
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/dbc/analysis/passes/version"
|
||||||
|
"github.com/fatih/color"
|
||||||
|
"gopkg.in/alecthomas/kingpin.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
app := kingpin.New("cantool", "CAN tool for Go programmers")
|
||||||
|
generateCommand(app)
|
||||||
|
lintCommand(app)
|
||||||
|
kingpin.MustParse(app.Parse(os.Args[1:]))
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateCommand(app *kingpin.Application) {
|
||||||
|
command := app.Command("generate", "generate CAN messages")
|
||||||
|
inputDir := command.
|
||||||
|
Arg("input-dir", "input directory").
|
||||||
|
Required().
|
||||||
|
ExistingDir()
|
||||||
|
outputDir := command.
|
||||||
|
Arg("output-dir", "output directory").
|
||||||
|
Required().
|
||||||
|
String()
|
||||||
|
command.Action(func(c *kingpin.ParseContext) error {
|
||||||
|
return filepath.Walk(*inputDir, func(p string, i os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if i.IsDir() || filepath.Ext(p) != ".dbc" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
relPath, err := filepath.Rel(*inputDir, p)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
outputFile := relPath + ".go"
|
||||||
|
outputPath := filepath.Join(*outputDir, outputFile)
|
||||||
|
return genGo(p, outputPath)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func lintCommand(app *kingpin.Application) {
|
||||||
|
command := app.Command("lint", "lint DBC files")
|
||||||
|
fileOrDir := command.
|
||||||
|
Arg("file-or-dir", "DBC file or directory").
|
||||||
|
Required().
|
||||||
|
ExistingFileOrDir()
|
||||||
|
command.Action(func(context *kingpin.ParseContext) error {
|
||||||
|
filesToLint, err := resolveFileOrDirectory(*fileOrDir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var hasFailed bool
|
||||||
|
for _, lintFile := range filesToLint {
|
||||||
|
f, err := os.Open(lintFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
source, err := ioutil.ReadAll(f)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p := dbc.NewParser(f.Name(), source)
|
||||||
|
if err := p.Parse(); err != nil {
|
||||||
|
printError(source, err.Position(), err.Reason(), "parse")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, a := range analyzers() {
|
||||||
|
pass := &analysis.Pass{
|
||||||
|
Analyzer: a,
|
||||||
|
File: p.File(),
|
||||||
|
}
|
||||||
|
if err := a.Run(pass); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
hasFailed = hasFailed || len(pass.Diagnostics) > 0
|
||||||
|
for _, d := range pass.Diagnostics {
|
||||||
|
printError(source, d.Pos, d.Message, a.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if hasFailed {
|
||||||
|
return errors.New("one or more lint errors")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func analyzers() []*analysis.Analyzer {
|
||||||
|
return []*analysis.Analyzer{
|
||||||
|
// TODO: Re-evaluate if we want boolprefix.Analyzer(), since it creates a lot of churn in vendor schemas
|
||||||
|
definitiontypeorder.Analyzer(),
|
||||||
|
intervals.Analyzer(),
|
||||||
|
lineendings.Analyzer(),
|
||||||
|
messagenames.Analyzer(),
|
||||||
|
multiplexedsignals.Analyzer(),
|
||||||
|
newsymbols.Analyzer(),
|
||||||
|
nodereferences.Analyzer(),
|
||||||
|
noreservedsignals.Analyzer(),
|
||||||
|
requireddefinitions.Analyzer(),
|
||||||
|
signalbounds.Analyzer(),
|
||||||
|
signalnames.Analyzer(),
|
||||||
|
singletondefinitions.Analyzer(),
|
||||||
|
siunits.Analyzer(),
|
||||||
|
uniquenodenames.Analyzer(),
|
||||||
|
uniquesignalnames.Analyzer(),
|
||||||
|
unitsuffixes.Analyzer(),
|
||||||
|
valuedescriptions.Analyzer(),
|
||||||
|
version.Analyzer(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func genGo(inputFile, outputFile string) error {
|
||||||
|
if err := os.MkdirAll(filepath.Dir(outputFile), 0o755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
input, err := ioutil.ReadFile(inputFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
result, err := generate.Compile(inputFile, input)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, warning := range result.Warnings {
|
||||||
|
return warning
|
||||||
|
}
|
||||||
|
output, err := generate.Database(result.Hash, result.Database)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := ioutil.WriteFile(outputFile, output, 0o600); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Println("hash:", result.Hash)
|
||||||
|
fmt.Println("version:", result.Database.Version)
|
||||||
|
fmt.Println("wrote:", outputFile)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveFileOrDirectory(fileOrDirectory string) ([]string, error) {
|
||||||
|
fileInfo, err := os.Stat(fileOrDirectory)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !fileInfo.IsDir() {
|
||||||
|
return []string{fileOrDirectory}, nil
|
||||||
|
}
|
||||||
|
var files []string
|
||||||
|
if err := filepath.Walk(fileOrDirectory, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if !info.IsDir() && filepath.Ext(path) == ".dbc" {
|
||||||
|
files = append(files, path)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return files, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func printError(source []byte, pos scanner.Position, msg, name string) {
|
||||||
|
fmt.Printf("\n%s: %s (%s)\n", pos, color.RedString("%s", msg), name)
|
||||||
|
fmt.Printf("%s\n", getSourceLine(source, pos))
|
||||||
|
fmt.Printf("%s\n", caretAtPosition(pos))
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSourceLine(source []byte, pos scanner.Position) []byte {
|
||||||
|
lineStart := pos.Offset
|
||||||
|
for lineStart > 0 && source[lineStart-1] != '\n' {
|
||||||
|
lineStart--
|
||||||
|
}
|
||||||
|
lineEnd := pos.Offset
|
||||||
|
for lineEnd < len(source) && source[lineEnd] != '\n' {
|
||||||
|
lineEnd++
|
||||||
|
}
|
||||||
|
return source[lineStart:lineEnd]
|
||||||
|
}
|
||||||
|
|
||||||
|
func caretAtPosition(pos scanner.Position) string {
|
||||||
|
return strings.Repeat(" ", pos.Column-1) + color.YellowString("^")
|
||||||
|
}
|
||||||
309
pkg/can-go/data.go
Normal file
309
pkg/can-go/data.go
Normal file
@@ -0,0 +1,309 @@
|
|||||||
|
package can
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/fiskerinc/cloud-services/pkg/can-go/internal/reinterpret"
|
||||||
|
)
|
||||||
|
|
||||||
|
const MaxDataLength = 8
|
||||||
|
|
||||||
|
// Data holds the data in a CAN frame.
|
||||||
|
//
|
||||||
|
// Layout
|
||||||
|
//
|
||||||
|
// Individual bits in the data are numbered according to the following scheme:
|
||||||
|
//
|
||||||
|
// BIT
|
||||||
|
// NUMBER
|
||||||
|
// +------+------+------+------+------+------+------+------+
|
||||||
|
// | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|
||||||
|
// BYTE +------+------+------+------+------+------+------+------+
|
||||||
|
// NUMBER
|
||||||
|
// +-----+ +------+------+------+------+------+------+------+------+
|
||||||
|
// | 0 | | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|
||||||
|
// +-----+ +------+------+------+------+------+------+------+------+
|
||||||
|
// | 1 | | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 |
|
||||||
|
// +-----+ +------+------+------+------+------+------+------+------+
|
||||||
|
// | 2 | | 23 | 22 | 21 | 20 | 19 | 18 | 17 | 16 |
|
||||||
|
// +-----+ +------+------+------+------+------+------+------+------+
|
||||||
|
// | 3 | | 31 | 30 | 29 | 28 | 27 | 26 | 25 | 24 |
|
||||||
|
// +-----+ +------+------+------+------+------+------+------+------+
|
||||||
|
// | 4 | | 39 | 38 | 37 | 36 | 35 | 34 | 33 | 32 |
|
||||||
|
// +-----+ +------+------+------+------+------+------+------+------+
|
||||||
|
// | 5 | | 47 | 46 | 45 | 44 | 43 | 42 | 41 | 40 |
|
||||||
|
// +-----+ +------+------+------+------+------+------+------+------+
|
||||||
|
// | 6 | | 55 | 54 | 53 | 52 | 51 | 50 | 49 | 48 |
|
||||||
|
// +-----+ +------+------+------+------+------+------+------+------+
|
||||||
|
// | 7 | | 63 | 62 | 61 | 60 | 59 | 58 | 57 | 56 |
|
||||||
|
// +-----+ +------+------+------+------+------+------+------+------+
|
||||||
|
//
|
||||||
|
// Bit ranges can be manipulated using little-endian and big-endian bit ordering.
|
||||||
|
//
|
||||||
|
// Little-endian bit ranges
|
||||||
|
//
|
||||||
|
// Example range of length 32 starting at bit 29:
|
||||||
|
//
|
||||||
|
// BIT
|
||||||
|
// NUMBER
|
||||||
|
// +------+------+------+------+------+------+------+------+
|
||||||
|
// | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|
||||||
|
// BYTE +------+------+------+------+------+------+------+------+
|
||||||
|
// NUMBER
|
||||||
|
// +-----+ +------+------+------+------+------+------+------+------+
|
||||||
|
// | 0 | | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|
||||||
|
// +-----+ +------+------+------+------+------+------+------+------+
|
||||||
|
// | 1 | | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 |
|
||||||
|
// +-----+ +------+------+------+------+------+------+------+------+
|
||||||
|
// | 2 | | 23 | 22 | 21 | 20 | 19 | 18 | 17 | 16 |
|
||||||
|
// +-----+ +------+------+------+------+------+------+------+------+
|
||||||
|
// | 3 | | <-------------LSb | 28 | 27 | 26 | 25 | 24 |
|
||||||
|
// +-----+ +------+------+------+------+------+------+------+------+
|
||||||
|
// | 4 | | <-------------------------------------------------- |
|
||||||
|
// +-----+ +------+------+------+------+------+------+------+------+
|
||||||
|
// | 5 | | <-------------------------------------------------- |
|
||||||
|
// +-----+ +------+------+------+------+------+------+------+------+
|
||||||
|
// | 6 | | <-------------------------------------------------- |
|
||||||
|
// +-----+ +------+------+------+------+------+------+------+------+
|
||||||
|
// | 7 | | 63 | 62 | 61 | <-MSb--------------------------- |
|
||||||
|
// +-----+ +------+------+------+------+------+------+------+------+
|
||||||
|
//
|
||||||
|
// Big-endian bit ranges
|
||||||
|
//
|
||||||
|
// Example range of length 32 starting at bit 29:
|
||||||
|
//
|
||||||
|
// BIT
|
||||||
|
// NUMBER
|
||||||
|
// +------+------+------+------+------+------+------+------+
|
||||||
|
// | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|
||||||
|
// BYTE +------+------+------+------+------+------+------+------+
|
||||||
|
// NUMBER
|
||||||
|
// +-----+ +------+------+------+------+------+------+------+------+
|
||||||
|
// | 0 | | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|
||||||
|
// +-----+ +------+------+------+------+------+------+------+------+
|
||||||
|
// | 1 | | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 |
|
||||||
|
// +-----+ +------+------+------+------+------+------+------+------+
|
||||||
|
// | 2 | | 23 | 22 | 21 | 20 | 19 | 18 | 17 | 16 |
|
||||||
|
// +-----+ +------+------+------+------+------+------+------+------+
|
||||||
|
// | 3 | | 31 | 30 | <-MSb--------------------------------- |
|
||||||
|
// +-----+ +------+------+------+------+------+------+------+------+
|
||||||
|
// | 4 | | <-------------------------------------------------- |
|
||||||
|
// +-----+ +------+------+------+------+------+------+------+------+
|
||||||
|
// | 5 | | <-------------------------------------------------- |
|
||||||
|
// +-----+ +------+------+------+------+------+------+------+------+
|
||||||
|
// | 6 | | <-------------------------------------------------- |
|
||||||
|
// +-----+ +------+------+------+------+------+------+------+------+
|
||||||
|
// | 7 | | <------LSb | 61 | 60 | 59 | 58 | 57 | 56 |
|
||||||
|
// +-----+ +------+------+------+------+------+------+------+------+
|
||||||
|
type Data [MaxDataLength]byte
|
||||||
|
|
||||||
|
// UnsignedBitsLittleEndian returns the little-endian bit range [start, start+length) as an unsigned value.
|
||||||
|
func (d *Data) UnsignedBitsLittleEndian(start, length uint8) uint64 {
|
||||||
|
// pack bits into one continuous value
|
||||||
|
packed := d.PackLittleEndian()
|
||||||
|
// lsb index in the packed value is the start bit
|
||||||
|
lsbIndex := start
|
||||||
|
// shift away lower bits
|
||||||
|
shifted := packed >> lsbIndex
|
||||||
|
// mask away higher bits
|
||||||
|
masked := shifted & ((1 << length) - 1)
|
||||||
|
// done
|
||||||
|
return masked
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnsignedBitsBigEndian returns the big-endian bit range [start, start+length) as an unsigned value.
|
||||||
|
func (d *Data) UnsignedBitsBigEndian(start, length uint8) uint64 {
|
||||||
|
// pack bits into one continuous value
|
||||||
|
packed := d.PackBigEndian()
|
||||||
|
// calculate msb index in the packed value
|
||||||
|
msbIndex := invertEndian(start)
|
||||||
|
// calculate lsb index in the packed value
|
||||||
|
lsbIndex := msbIndex - length + 1
|
||||||
|
// shift away lower bits
|
||||||
|
shifted := packed >> lsbIndex
|
||||||
|
// mask away higher bits
|
||||||
|
masked := shifted & ((1 << length) - 1)
|
||||||
|
// done
|
||||||
|
return masked
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignedBitsLittleEndian returns little-endian bit range [start, start+length) as a signed value.
|
||||||
|
func (d *Data) SignedBitsLittleEndian(start, length uint8) int64 {
|
||||||
|
unsigned := d.UnsignedBitsLittleEndian(start, length)
|
||||||
|
return reinterpret.AsSigned(unsigned, length)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignedBitsBigEndian returns little-endian bit range [start, start+length) as a signed value.
|
||||||
|
func (d *Data) SignedBitsBigEndian(start, length uint8) int64 {
|
||||||
|
unsigned := d.UnsignedBitsBigEndian(start, length)
|
||||||
|
return reinterpret.AsSigned(unsigned, length)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUnsignedBitsBigEndian sets the little-endian bit range [start, start+length) to the provided unsigned value.
|
||||||
|
func (d *Data) SetUnsignedBitsLittleEndian(start, length uint8, value uint64) {
|
||||||
|
// pack bits into one continuous value
|
||||||
|
packed := d.PackLittleEndian()
|
||||||
|
// lsb index in the packed value is the start bit
|
||||||
|
lsbIndex := start
|
||||||
|
// calculate bit mask for zeroing the bit range to set
|
||||||
|
unsetMask := ^uint64(((1 << length) - 1) << lsbIndex)
|
||||||
|
// calculate bit mask for setting the new value
|
||||||
|
setMask := value << lsbIndex
|
||||||
|
// calculate the new packed value
|
||||||
|
newPacked := packed&unsetMask | setMask
|
||||||
|
// unpack the new packed value into the data
|
||||||
|
d.UnpackLittleEndian(newPacked)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUnsignedBitsBigEndian sets the big-endian bit range [start, start+length) to the provided unsigned value.
|
||||||
|
func (d *Data) SetUnsignedBitsBigEndian(start, length uint8, value uint64) {
|
||||||
|
// pack bits into one continuous value
|
||||||
|
packed := d.PackBigEndian()
|
||||||
|
// calculate msb index in the packed value
|
||||||
|
msbIndex := invertEndian(start)
|
||||||
|
// calculate lsb index in the packed value
|
||||||
|
lsbIndex := msbIndex - length + 1
|
||||||
|
// calculate bit mask for zeroing the bit range to set
|
||||||
|
unsetMask := ^uint64(((1 << length) - 1) << lsbIndex)
|
||||||
|
// calculate bit mask for setting the new value
|
||||||
|
setMask := value << lsbIndex
|
||||||
|
// calculate the new packed value
|
||||||
|
newPacked := packed&unsetMask | setMask
|
||||||
|
// unpack the new packed value into the data
|
||||||
|
d.UnpackBigEndian(newPacked)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetSignedBitsLittleEndian sets the little-endian bit range [start, start+length) to the provided signed value.
|
||||||
|
func (d *Data) SetSignedBitsLittleEndian(start, length uint8, value int64) {
|
||||||
|
d.SetUnsignedBitsLittleEndian(start, length, reinterpret.AsUnsigned(value, length))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetSignedBitsBigEndian sets the big-endian bit range [start, start+length) to the provided signed value.
|
||||||
|
func (d *Data) SetSignedBitsBigEndian(start, length uint8, value int64) {
|
||||||
|
d.SetUnsignedBitsBigEndian(start, length, reinterpret.AsUnsigned(value, length))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bit returns the value of the i:th bit in the data as a bool.
|
||||||
|
func (d *Data) Bit(i uint8) bool {
|
||||||
|
if i > 63 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// calculate which byte the bit belongs to
|
||||||
|
byteIndex := i / 8
|
||||||
|
// calculate bit mask for extracting the bit
|
||||||
|
bitMask := uint8(1 << (i % 8))
|
||||||
|
// mocks the bit
|
||||||
|
bit := d[byteIndex]&bitMask > 0
|
||||||
|
// done
|
||||||
|
return bit
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBit sets the value of the i:th bit in the data.
|
||||||
|
func (d *Data) SetBit(i uint8, value bool) {
|
||||||
|
if i > 63 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
byteIndex := i / 8
|
||||||
|
bitIndex := i % 8
|
||||||
|
if value {
|
||||||
|
d[byteIndex] |= uint8(1 << bitIndex)
|
||||||
|
} else {
|
||||||
|
d[byteIndex] &= ^uint8(1 << bitIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PackLittleEndian packs the data into a contiguous uint64 value for little-endian signals.
|
||||||
|
func (d *Data) PackLittleEndian() uint64 {
|
||||||
|
var packed uint64
|
||||||
|
packed |= uint64(d[0]) << (0 * 8)
|
||||||
|
packed |= uint64(d[1]) << (1 * 8)
|
||||||
|
packed |= uint64(d[2]) << (2 * 8)
|
||||||
|
packed |= uint64(d[3]) << (3 * 8)
|
||||||
|
packed |= uint64(d[4]) << (4 * 8)
|
||||||
|
packed |= uint64(d[5]) << (5 * 8)
|
||||||
|
packed |= uint64(d[6]) << (6 * 8)
|
||||||
|
packed |= uint64(d[7]) << (7 * 8)
|
||||||
|
return packed
|
||||||
|
}
|
||||||
|
|
||||||
|
// PackBigEndian packs the data into a contiguous uint64 value for big-endian signals.
|
||||||
|
func (d *Data) PackBigEndian() uint64 {
|
||||||
|
var packed uint64
|
||||||
|
packed |= uint64(d[0]) << (7 * 8)
|
||||||
|
packed |= uint64(d[1]) << (6 * 8)
|
||||||
|
packed |= uint64(d[2]) << (5 * 8)
|
||||||
|
packed |= uint64(d[3]) << (4 * 8)
|
||||||
|
packed |= uint64(d[4]) << (3 * 8)
|
||||||
|
packed |= uint64(d[5]) << (2 * 8)
|
||||||
|
packed |= uint64(d[6]) << (1 * 8)
|
||||||
|
packed |= uint64(d[7]) << (0 * 8)
|
||||||
|
return packed
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnpackLittleEndian sets the value of d.Bytes by unpacking the provided value as sequential little-endian bits.
|
||||||
|
func (d *Data) UnpackLittleEndian(packed uint64) {
|
||||||
|
d[0] = uint8(packed >> (0 * 8))
|
||||||
|
d[1] = uint8(packed >> (1 * 8))
|
||||||
|
d[2] = uint8(packed >> (2 * 8))
|
||||||
|
d[3] = uint8(packed >> (3 * 8))
|
||||||
|
d[4] = uint8(packed >> (4 * 8))
|
||||||
|
d[5] = uint8(packed >> (5 * 8))
|
||||||
|
d[6] = uint8(packed >> (6 * 8))
|
||||||
|
d[7] = uint8(packed >> (7 * 8))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnpackBigEndian sets the value of d.Bytes by unpacking the provided value as sequential big-endian bits.
|
||||||
|
func (d *Data) UnpackBigEndian(packed uint64) {
|
||||||
|
d[0] = uint8(packed >> (7 * 8))
|
||||||
|
d[1] = uint8(packed >> (6 * 8))
|
||||||
|
d[2] = uint8(packed >> (5 * 8))
|
||||||
|
d[3] = uint8(packed >> (4 * 8))
|
||||||
|
d[4] = uint8(packed >> (3 * 8))
|
||||||
|
d[5] = uint8(packed >> (2 * 8))
|
||||||
|
d[6] = uint8(packed >> (1 * 8))
|
||||||
|
d[7] = uint8(packed >> (0 * 8))
|
||||||
|
}
|
||||||
|
|
||||||
|
// invertEndian converts from big-endian to little-endian bit indexing and vice versa.
|
||||||
|
func invertEndian(i uint8) uint8 {
|
||||||
|
row := i / 8
|
||||||
|
col := i % 8
|
||||||
|
oppositeRow := 7 - row
|
||||||
|
bitIndex := (oppositeRow * 8) + col
|
||||||
|
return bitIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckBitRangeLittleEndian checks that a little-endian bit range fits in the data.
|
||||||
|
func CheckBitRangeLittleEndian(frameLength, rangeStart, rangeLength uint8) error {
|
||||||
|
lsbIndex := rangeStart
|
||||||
|
msbIndex := rangeStart + rangeLength - 1
|
||||||
|
upperBound := frameLength * 8
|
||||||
|
if msbIndex >= upperBound {
|
||||||
|
return fmt.Errorf("bit range out of bounds [0, %v): [%v, %v]", upperBound, lsbIndex, msbIndex)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckBitRangeBigEndian checks that a big-endian bit range fits in the data.
|
||||||
|
func CheckBitRangeBigEndian(frameLength, rangeStart, rangeLength uint8) error {
|
||||||
|
upperBound := frameLength * 8
|
||||||
|
if rangeStart >= upperBound {
|
||||||
|
return fmt.Errorf("bit range starts out of bounds [0, %v): %v", upperBound, rangeStart)
|
||||||
|
}
|
||||||
|
msbIndex := invertEndian(rangeStart)
|
||||||
|
lsbIndex := msbIndex - rangeLength + 1
|
||||||
|
end := invertEndian(lsbIndex)
|
||||||
|
if end >= upperBound {
|
||||||
|
return fmt.Errorf("bit range ends out of bounds [0, %v): %v", upperBound, end)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckValue checks that a value fits in a number of bits.
|
||||||
|
func CheckValue(value uint64, bits uint8) error {
|
||||||
|
upperBound := uint64(1 << bits)
|
||||||
|
if value >= upperBound {
|
||||||
|
return fmt.Errorf("value out of bounds [0, %v): %v", upperBound, value)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
347
pkg/can-go/data_test.go
Normal file
347
pkg/can-go/data_test.go
Normal file
@@ -0,0 +1,347 @@
|
|||||||
|
package can
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"testing/quick"
|
||||||
|
|
||||||
|
"gotest.tools/v3/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestData_Bit(t *testing.T) {
|
||||||
|
for i, tt := range []struct {
|
||||||
|
data Data
|
||||||
|
bits []struct {
|
||||||
|
i uint8
|
||||||
|
bit bool
|
||||||
|
}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
data: Data{0x01, 0x23},
|
||||||
|
bits: []struct {
|
||||||
|
i uint8
|
||||||
|
bit bool
|
||||||
|
}{
|
||||||
|
// nibble 1: 0x1
|
||||||
|
{bit: true, i: 0},
|
||||||
|
{bit: false, i: 1},
|
||||||
|
{bit: false, i: 2},
|
||||||
|
{bit: false, i: 3},
|
||||||
|
// nibble 2: 0x0
|
||||||
|
{bit: false, i: 4},
|
||||||
|
{bit: false, i: 5},
|
||||||
|
{bit: false, i: 6},
|
||||||
|
{bit: false, i: 7},
|
||||||
|
// nibble 3: 0x3
|
||||||
|
{bit: true, i: 8},
|
||||||
|
{bit: true, i: 9},
|
||||||
|
{bit: false, i: 10},
|
||||||
|
{bit: false, i: 11},
|
||||||
|
// nibble 4: 0x2
|
||||||
|
{bit: false, i: 12},
|
||||||
|
{bit: true, i: 13},
|
||||||
|
{bit: false, i: 14},
|
||||||
|
{bit: false, i: 15},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
i, tt := i, tt
|
||||||
|
t.Run("Get", func(t *testing.T) {
|
||||||
|
i, tt := i, tt
|
||||||
|
for j, ttBit := range tt.bits {
|
||||||
|
j, ttBit := j, ttBit
|
||||||
|
t.Run(fmt.Sprintf("tt=%v,bit=%v", i, j), func(t *testing.T) {
|
||||||
|
bit := tt.data.Bit(ttBit.i)
|
||||||
|
assert.Equal(t, ttBit.bit, bit)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("Set", func(t *testing.T) {
|
||||||
|
i, tt := i, tt
|
||||||
|
t.Run(fmt.Sprintf("data=%v", i), func(t *testing.T) {
|
||||||
|
var data Data
|
||||||
|
for _, tBit := range tt.bits {
|
||||||
|
data.SetBit(tBit.i, tBit.bit)
|
||||||
|
}
|
||||||
|
assert.DeepEqual(t, tt.data, data)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestData_Property_SetGetBit(t *testing.T) {
|
||||||
|
f := func(_ Data, _ uint8, bit bool) bool {
|
||||||
|
return bit
|
||||||
|
}
|
||||||
|
g := func(data Data, i uint8, bit bool) bool {
|
||||||
|
i %= 64
|
||||||
|
data.SetBit(i, bit)
|
||||||
|
return data.Bit(i)
|
||||||
|
}
|
||||||
|
assert.NilError(t, quick.CheckEqual(f, g, nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestData_LittleEndian(t *testing.T) {
|
||||||
|
for i, tt := range []struct {
|
||||||
|
data Data
|
||||||
|
signals []struct {
|
||||||
|
start uint8
|
||||||
|
length uint8
|
||||||
|
unsigned uint64
|
||||||
|
signed int64
|
||||||
|
}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
data: Data{0x80, 0x01},
|
||||||
|
signals: []struct {
|
||||||
|
start uint8
|
||||||
|
length uint8
|
||||||
|
unsigned uint64
|
||||||
|
signed int64
|
||||||
|
}{
|
||||||
|
{start: 7, length: 2, unsigned: 0x3, signed: -1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: Data{0x01, 0x02, 0x03},
|
||||||
|
signals: []struct {
|
||||||
|
start uint8
|
||||||
|
length uint8
|
||||||
|
unsigned uint64
|
||||||
|
signed int64
|
||||||
|
}{
|
||||||
|
{start: 0, length: 24, unsigned: 0x030201, signed: 197121},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: Data{0x40, 0x23, 0x01, 0x12},
|
||||||
|
signals: []struct {
|
||||||
|
start uint8
|
||||||
|
length uint8
|
||||||
|
unsigned uint64
|
||||||
|
signed int64
|
||||||
|
}{
|
||||||
|
{start: 24, length: 8, unsigned: 0x12, signed: 18},
|
||||||
|
{start: 8, length: 8, unsigned: 0x23, signed: 35},
|
||||||
|
{start: 4, length: 16, unsigned: 0x1234, signed: 4660},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
i, tt := i, tt
|
||||||
|
t.Run(fmt.Sprintf("UnsignedBits:%v", i), func(t *testing.T) {
|
||||||
|
for j, signal := range tt.signals {
|
||||||
|
j, signal := j, signal
|
||||||
|
t.Run(fmt.Sprintf("signal:%v", j), func(t *testing.T) {
|
||||||
|
actual := tt.data.UnsignedBitsLittleEndian(signal.start, signal.length)
|
||||||
|
assert.Equal(t, signal.unsigned, actual)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run(fmt.Sprintf("SignedBits:%v", i), func(t *testing.T) {
|
||||||
|
for j, signal := range tt.signals {
|
||||||
|
j, signal := j, signal
|
||||||
|
t.Run(fmt.Sprintf("signal:%v", j), func(t *testing.T) {
|
||||||
|
actual := tt.data.SignedBitsLittleEndian(signal.start, signal.length)
|
||||||
|
assert.Equal(t, signal.signed, actual)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run(fmt.Sprintf("SetUnsignedBits:%v", i), func(t *testing.T) {
|
||||||
|
var data Data
|
||||||
|
for j, signal := range tt.signals {
|
||||||
|
j, signal := j, signal
|
||||||
|
t.Run(fmt.Sprintf("data:%v", j), func(t *testing.T) {
|
||||||
|
data.SetUnsignedBitsLittleEndian(signal.start, signal.length, signal.unsigned)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
assert.DeepEqual(t, tt.data, data)
|
||||||
|
})
|
||||||
|
t.Run(fmt.Sprintf("SetSignedBits:%v", i), func(t *testing.T) {
|
||||||
|
var data Data
|
||||||
|
for j, signal := range tt.signals {
|
||||||
|
j, signal := j, signal
|
||||||
|
t.Run(fmt.Sprintf("data:%v", j), func(t *testing.T) {
|
||||||
|
data.SetSignedBitsLittleEndian(signal.start, signal.length, signal.signed)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
assert.DeepEqual(t, tt.data, data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestData_BigEndian(t *testing.T) {
|
||||||
|
for i, tt := range []struct {
|
||||||
|
data Data
|
||||||
|
signals []struct {
|
||||||
|
start uint8
|
||||||
|
length uint8
|
||||||
|
unsigned uint64
|
||||||
|
signed int64
|
||||||
|
}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
data: Data{0x3f, 0xf7, 0x0d, 0xc4, 0x0c, 0x93, 0xff, 0xff},
|
||||||
|
signals: []struct {
|
||||||
|
start uint8
|
||||||
|
length uint8
|
||||||
|
unsigned uint64
|
||||||
|
signed int64
|
||||||
|
}{
|
||||||
|
{start: 7, length: 3, unsigned: 0x1, signed: 1},
|
||||||
|
{start: 4, length: 1, unsigned: 0x1, signed: -1},
|
||||||
|
{start: 55, length: 16, unsigned: 0xffff, signed: -1},
|
||||||
|
{start: 39, length: 16, unsigned: 0xc93, signed: 3219},
|
||||||
|
{start: 23, length: 16, unsigned: 0xdc4, signed: 3524},
|
||||||
|
{start: 3, length: 12, unsigned: 0xff7, signed: -9},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: Data{0x3f, 0xe4, 0x0e, 0xb6, 0x0c, 0xba, 0x00, 0x05},
|
||||||
|
signals: []struct {
|
||||||
|
start uint8
|
||||||
|
length uint8
|
||||||
|
unsigned uint64
|
||||||
|
signed int64
|
||||||
|
}{
|
||||||
|
{start: 7, length: 3, unsigned: 0x1, signed: 1},
|
||||||
|
{start: 4, length: 1, unsigned: 0x1, signed: -1},
|
||||||
|
{start: 55, length: 16, unsigned: 0x5, signed: 5},
|
||||||
|
{start: 39, length: 16, unsigned: 0xcba, signed: 3258},
|
||||||
|
{start: 23, length: 16, unsigned: 0xeb6, signed: 3766},
|
||||||
|
{start: 3, length: 12, unsigned: 0xfe4, signed: -28},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: Data{0x30, 0x53, 0x23, 0xe5, 0x0e, 0x11, 0xff, 0xff},
|
||||||
|
signals: []struct {
|
||||||
|
start uint8
|
||||||
|
length uint8
|
||||||
|
unsigned uint64
|
||||||
|
signed int64
|
||||||
|
}{
|
||||||
|
{start: 7, length: 3, unsigned: 0x1, signed: 1},
|
||||||
|
{start: 4, length: 1, unsigned: 0x1, signed: -1},
|
||||||
|
{start: 55, length: 16, unsigned: 0xffff, signed: -1},
|
||||||
|
{start: 39, length: 16, unsigned: 0xe11, signed: 3601},
|
||||||
|
{start: 23, length: 16, unsigned: 0x23e5, signed: 9189},
|
||||||
|
{start: 3, length: 12, unsigned: 0x53, signed: 83},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
i, tt := i, tt
|
||||||
|
t.Run(fmt.Sprintf("UnsignedBits:%v", i), func(t *testing.T) {
|
||||||
|
for j, signal := range tt.signals {
|
||||||
|
j, signal := j, signal
|
||||||
|
t.Run(fmt.Sprintf("signal:%v", j), func(t *testing.T) {
|
||||||
|
actual := tt.data.UnsignedBitsBigEndian(signal.start, signal.length)
|
||||||
|
assert.Equal(t, signal.unsigned, actual)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run(fmt.Sprintf("SignedBits:%v", i), func(t *testing.T) {
|
||||||
|
for j, signal := range tt.signals {
|
||||||
|
j, signal := j, signal
|
||||||
|
t.Run(fmt.Sprintf("signal:%v", j), func(t *testing.T) {
|
||||||
|
actual := tt.data.SignedBitsBigEndian(signal.start, signal.length)
|
||||||
|
assert.Equal(t, signal.signed, actual)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run(fmt.Sprintf("SetUnsignedBits:%v", i), func(t *testing.T) {
|
||||||
|
var data Data
|
||||||
|
for j, signal := range tt.signals {
|
||||||
|
j, signal := j, signal
|
||||||
|
t.Run(fmt.Sprintf("data:%v", j), func(t *testing.T) {
|
||||||
|
data.SetUnsignedBitsBigEndian(signal.start, signal.length, signal.unsigned)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
assert.DeepEqual(t, tt.data, data)
|
||||||
|
})
|
||||||
|
t.Run(fmt.Sprintf("SetSignedBits:%v", i), func(t *testing.T) {
|
||||||
|
var data Data
|
||||||
|
for j, signal := range tt.signals {
|
||||||
|
j, signal := j, signal
|
||||||
|
t.Run(fmt.Sprintf("data:%v", j), func(t *testing.T) {
|
||||||
|
data.SetSignedBitsBigEndian(signal.start, signal.length, signal.signed)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
assert.DeepEqual(t, tt.data, data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInvertEndian_Property_Idempotent(t *testing.T) {
|
||||||
|
for i := uint8(0); i < 64; i++ {
|
||||||
|
assert.Equal(t, i, invertEndian(invertEndian(i)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPackUnpackBigEndian(t *testing.T) {
|
||||||
|
f := func(data Data) Data {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
g := func(data Data) Data {
|
||||||
|
data.UnpackBigEndian(data.PackBigEndian())
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
assert.NilError(t, quick.CheckEqual(f, g, nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPackUnpackLittleEndian(t *testing.T) {
|
||||||
|
f := func(data Data) Data {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
g := func(data Data) Data {
|
||||||
|
data.UnpackLittleEndian(data.PackLittleEndian())
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
assert.NilError(t, quick.CheckEqual(f, g, nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestData_CheckBitRange(t *testing.T) {
|
||||||
|
// example case that big-endian signals and little-endian signals use different indexing
|
||||||
|
assert.NilError(t, CheckBitRangeBigEndian(8, 55, 16))
|
||||||
|
assert.ErrorContains(t, CheckBitRangeLittleEndian(8, 55, 16), "bit range out of bounds")
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkData_UnpackLittleEndian(b *testing.B) {
|
||||||
|
var data Data
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
data.UnpackLittleEndian(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkData_UnpackBigEndian(b *testing.B) {
|
||||||
|
var data Data
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
data.UnpackBigEndian(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkData_PackBigEndian(b *testing.B) {
|
||||||
|
var data Data
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = data.PackBigEndian()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkData_PackLittleEndian(b *testing.B) {
|
||||||
|
var data Data
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = data.PackLittleEndian()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkData_UnsignedBitsBigEndian(b *testing.B) {
|
||||||
|
var data Data
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = data.UnsignedBitsBigEndian(0, 16)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkData_UnsignedBitsLittleEndian(b *testing.B) {
|
||||||
|
var data Data
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = data.UnsignedBitsLittleEndian(0, 16)
|
||||||
|
}
|
||||||
|
}
|
||||||
135
pkg/can-go/frame.go
Normal file
135
pkg/can-go/frame.go
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
package can
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
idBits = 11
|
||||||
|
extendedIDBits = 29
|
||||||
|
)
|
||||||
|
|
||||||
|
// CAN format constants.
|
||||||
|
const (
|
||||||
|
MaxID = 0x7ff
|
||||||
|
MaxExtendedID = 0x1fffffff
|
||||||
|
)
|
||||||
|
|
||||||
|
// Frame represents a CAN frame.
|
||||||
|
//
|
||||||
|
// A Frame is intentionally designed to fit into 16 bytes on common architectures
|
||||||
|
// and is therefore amenable to pass-by-value and judicious copying.
|
||||||
|
type Frame struct {
|
||||||
|
// ID is the CAN ID
|
||||||
|
ID uint32
|
||||||
|
// Length is the number of bytes of data in the frame.
|
||||||
|
Length uint16
|
||||||
|
// Data is the frame data.
|
||||||
|
Data Data
|
||||||
|
// IsRemote is true for remote frames.
|
||||||
|
IsRemote bool
|
||||||
|
// IsExtended is true for extended frames, i.e. frames with 29-bit IDs.
|
||||||
|
IsExtended bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate returns an error if the Frame is not a valid CAN frame.
|
||||||
|
func (f *Frame) Validate() error {
|
||||||
|
// Validate: ID
|
||||||
|
if f.IsExtended && f.ID > MaxExtendedID {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"invalid extended CAN id: %v does not fit in %v bits",
|
||||||
|
f.ID,
|
||||||
|
extendedIDBits,
|
||||||
|
)
|
||||||
|
} else if !f.IsExtended && f.ID > MaxID {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"invalid standard CAN id: %v does not fit in %v bits",
|
||||||
|
f.ID,
|
||||||
|
idBits,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// Validate: Data
|
||||||
|
if f.Length > MaxDataLength {
|
||||||
|
return fmt.Errorf("invalid data length: %v", f.Length)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns an ASCII representation the CAN frame.
|
||||||
|
//
|
||||||
|
// Format:
|
||||||
|
//
|
||||||
|
// ([0-9A-F]{3}|[0-9A-F]{3})#(R[0-8]?|[0-9A-F]{0,16})
|
||||||
|
//
|
||||||
|
// The format is compatible with the candump(1) log file format.
|
||||||
|
func (f Frame) String() string {
|
||||||
|
var id string
|
||||||
|
if f.IsExtended {
|
||||||
|
id = fmt.Sprintf("%08X", f.ID)
|
||||||
|
} else {
|
||||||
|
id = fmt.Sprintf("%03X", f.ID)
|
||||||
|
}
|
||||||
|
if f.IsRemote && f.Length == 0 {
|
||||||
|
return id + "#R"
|
||||||
|
} else if f.IsRemote {
|
||||||
|
return id + "#R" + strconv.Itoa(int(f.Length))
|
||||||
|
}
|
||||||
|
return id + "#" + strings.ToUpper(hex.EncodeToString(f.Data[:f.Length]))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalString sets *f using the provided ASCII representation of a Frame.
|
||||||
|
func (f *Frame) UnmarshalString(s string) error {
|
||||||
|
// Split split into parts
|
||||||
|
parts := strings.Split(s, "#")
|
||||||
|
if len(parts) != 2 {
|
||||||
|
return fmt.Errorf("invalid frame format: %v", s)
|
||||||
|
}
|
||||||
|
idPart, dataPart := parts[0], parts[1]
|
||||||
|
var frame Frame
|
||||||
|
// Parse: IsExtended
|
||||||
|
if len(idPart) != 3 && len(idPart) != 8 {
|
||||||
|
return fmt.Errorf("invalid ID length: %v", s)
|
||||||
|
}
|
||||||
|
frame.IsExtended = len(idPart) == 8
|
||||||
|
// Parse: ID
|
||||||
|
id, err := strconv.ParseUint(idPart, 16, 32)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid frame ID: %v", s)
|
||||||
|
}
|
||||||
|
frame.ID = uint32(id)
|
||||||
|
if len(dataPart) == 0 {
|
||||||
|
*f = frame
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Parse: IsRemote
|
||||||
|
if dataPart[0] == 'R' {
|
||||||
|
frame.IsRemote = true
|
||||||
|
if len(dataPart) > 2 {
|
||||||
|
return fmt.Errorf("invalid remote length: %v", s)
|
||||||
|
} else if len(dataPart) == 2 {
|
||||||
|
dataLength, err := strconv.Atoi(dataPart[1:2])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid remote length: %v: %w", s, err)
|
||||||
|
}
|
||||||
|
frame.Length = uint16(dataLength)
|
||||||
|
}
|
||||||
|
*f = frame
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Parse: Length
|
||||||
|
if len(dataPart) > 16 || len(dataPart)%2 != 0 {
|
||||||
|
return fmt.Errorf("invalid data length: %v", s)
|
||||||
|
}
|
||||||
|
frame.Length = uint16(len(dataPart) / 2)
|
||||||
|
// Parse: Data
|
||||||
|
decodedData, err := hex.DecodeString(dataPart)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid data: %v: %w", s, err)
|
||||||
|
}
|
||||||
|
copy(frame.Data[:], decodedData)
|
||||||
|
*f = frame
|
||||||
|
return nil
|
||||||
|
}
|
||||||
95
pkg/can-go/frame_json.go
Normal file
95
pkg/can-go/frame_json.go
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
package can
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type jsonFrame struct {
|
||||||
|
ID uint32 `json:"id"`
|
||||||
|
Data *string `json:"data"`
|
||||||
|
Length *uint16 `json:"length"`
|
||||||
|
Extended *bool `json:"extended"`
|
||||||
|
Remote *bool `json:"remote"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSON returns the JSON-encoding of f, using hex-encoding for the data.
|
||||||
|
//
|
||||||
|
// Examples:
|
||||||
|
//
|
||||||
|
// {"id":32,"data":"0102030405060708"}
|
||||||
|
// {"id":32,"extended":true,"remote":true,"length":4}
|
||||||
|
func (f Frame) JSON() string {
|
||||||
|
switch {
|
||||||
|
case f.IsRemote && f.IsExtended:
|
||||||
|
return `{"id":` + strconv.Itoa(int(f.ID)) +
|
||||||
|
`,"extended":true,"remote":true,"length":` +
|
||||||
|
strconv.Itoa(int(f.Length)) + `}`
|
||||||
|
case f.IsRemote:
|
||||||
|
return `{"id":` + strconv.Itoa(int(f.ID)) +
|
||||||
|
`,"remote":true,"length":` +
|
||||||
|
strconv.Itoa(int(f.Length)) + `}`
|
||||||
|
case f.IsExtended && f.Length == 0:
|
||||||
|
return `{"id":` + strconv.Itoa(int(f.ID)) + `,"extended":true}`
|
||||||
|
case f.IsExtended:
|
||||||
|
return `{"id":` + strconv.Itoa(int(f.ID)) +
|
||||||
|
`,"data":"` + hex.EncodeToString(f.Data[:f.Length]) + `"` +
|
||||||
|
`,"extended":true}`
|
||||||
|
case f.Length == 0:
|
||||||
|
return `{"id":` + strconv.Itoa(int(f.ID)) + `}`
|
||||||
|
default:
|
||||||
|
return `{"id":` + strconv.Itoa(int(f.ID)) +
|
||||||
|
`,"data":"` + hex.EncodeToString(f.Data[:f.Length]) + `"}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON returns the JSON-encoding of f, using hex-encoding for the data.
|
||||||
|
//
|
||||||
|
// See JSON for an example of the JSON schema.
|
||||||
|
func (f Frame) MarshalJSON() ([]byte, error) {
|
||||||
|
return []byte(f.JSON()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON sets *f using the provided JSON-encoded values.
|
||||||
|
//
|
||||||
|
// See MarshalJSON for an example of the expected JSON schema.
|
||||||
|
//
|
||||||
|
// The result should be checked with Validate to guard against invalid JSON data.
|
||||||
|
func (f *Frame) UnmarshalJSON(jsonData []byte) error {
|
||||||
|
jf := jsonFrame{}
|
||||||
|
if err := json.Unmarshal(jsonData, &jf); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if jf.Data != nil {
|
||||||
|
data, err := hex.DecodeString(*jf.Data)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to hex-decode CAN data: %v: %w", string(jsonData), err)
|
||||||
|
}
|
||||||
|
f.Data = Data{}
|
||||||
|
copy(f.Data[:], data)
|
||||||
|
f.Length = uint16(len(data))
|
||||||
|
} else {
|
||||||
|
f.Data = Data{}
|
||||||
|
f.Length = 0
|
||||||
|
}
|
||||||
|
f.ID = jf.ID
|
||||||
|
if jf.Remote != nil {
|
||||||
|
f.IsRemote = *jf.Remote
|
||||||
|
} else {
|
||||||
|
f.IsRemote = false
|
||||||
|
}
|
||||||
|
if f.IsRemote {
|
||||||
|
if jf.Length == nil {
|
||||||
|
return fmt.Errorf("missing length field for remote JSON frame: %v", string(jsonData))
|
||||||
|
}
|
||||||
|
f.Length = *jf.Length
|
||||||
|
}
|
||||||
|
if jf.Extended != nil {
|
||||||
|
f.IsExtended = *jf.Extended
|
||||||
|
} else {
|
||||||
|
f.IsExtended = false
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
126
pkg/can-go/frame_json_test.go
Normal file
126
pkg/can-go/frame_json_test.go
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
package can
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"testing/quick"
|
||||||
|
|
||||||
|
"gotest.tools/v3/assert"
|
||||||
|
is "gotest.tools/v3/assert/cmp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFrame_JSON(t *testing.T) {
|
||||||
|
for _, tt := range []struct {
|
||||||
|
jsonFrame string
|
||||||
|
frame Frame
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
// Standard frame
|
||||||
|
jsonFrame: `{"id":42,"data":"00010203"}`,
|
||||||
|
frame: Frame{
|
||||||
|
ID: 42,
|
||||||
|
Length: 4,
|
||||||
|
Data: Data{0x00, 0x01, 0x02, 0x03},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Standard frame, no data
|
||||||
|
jsonFrame: `{"id":42}`,
|
||||||
|
frame: Frame{ID: 42},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Standard remote frame
|
||||||
|
jsonFrame: `{"id":42,"remote":true,"length":4}`,
|
||||||
|
frame: Frame{
|
||||||
|
ID: 42,
|
||||||
|
IsRemote: true,
|
||||||
|
Length: 4,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Extended frame
|
||||||
|
jsonFrame: `{"id":42,"data":"0001020304050607","extended":true}`,
|
||||||
|
frame: Frame{
|
||||||
|
ID: 42,
|
||||||
|
IsExtended: true,
|
||||||
|
Length: 8,
|
||||||
|
Data: Data{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Extended frame, no data
|
||||||
|
jsonFrame: `{"id":42,"extended":true}`,
|
||||||
|
frame: Frame{ID: 42, IsExtended: true},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Extended remote frame
|
||||||
|
jsonFrame: `{"id":42,"extended":true,"remote":true,"length":8}`,
|
||||||
|
frame: Frame{
|
||||||
|
ID: 42,
|
||||||
|
IsExtended: true,
|
||||||
|
IsRemote: true,
|
||||||
|
Length: 8,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
tt := tt
|
||||||
|
t.Run(fmt.Sprintf("JSON|frame=%v", tt.frame), func(t *testing.T) {
|
||||||
|
assert.Check(t, is.Equal(tt.jsonFrame, tt.frame.JSON()))
|
||||||
|
})
|
||||||
|
t.Run(fmt.Sprintf("UnmarshalJSON|frame=%v", tt.frame), func(t *testing.T) {
|
||||||
|
var frame Frame
|
||||||
|
if err := json.Unmarshal([]byte(tt.jsonFrame), &frame); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
assert.Check(t, is.DeepEqual(tt.frame, frame))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFrame_UnmarshalJSON_Invalid(t *testing.T) {
|
||||||
|
var f Frame
|
||||||
|
t.Run("invalid JSON", func(t *testing.T) {
|
||||||
|
data := `foobar`
|
||||||
|
assert.Check(t, f.UnmarshalJSON([]uint8(data)) != nil)
|
||||||
|
})
|
||||||
|
t.Run("invalid payload", func(t *testing.T) {
|
||||||
|
data := `{"id":1,"data":"foobar","extended":false,"remote":false}`
|
||||||
|
assert.Check(t, f.UnmarshalJSON([]uint8(data)) != nil)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Frame) Generate(rand *rand.Rand, size int) reflect.Value {
|
||||||
|
f := Frame{
|
||||||
|
IsExtended: rand.Intn(2) == 0,
|
||||||
|
IsRemote: rand.Intn(2) == 0,
|
||||||
|
}
|
||||||
|
if f.IsExtended {
|
||||||
|
f.ID = rand.Uint32() & MaxExtendedID
|
||||||
|
} else {
|
||||||
|
f.ID = rand.Uint32() & MaxID
|
||||||
|
}
|
||||||
|
f.Length = uint16(rand.Intn(9))
|
||||||
|
if !f.IsRemote {
|
||||||
|
_, _ = rand.Read(f.Data[:f.Length])
|
||||||
|
}
|
||||||
|
return reflect.ValueOf(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPropertyFrame_MarshalUnmarshalJSON(t *testing.T) {
|
||||||
|
f := func(f Frame) Frame {
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
g := func(f Frame) Frame {
|
||||||
|
f2 := Frame{}
|
||||||
|
if err := json.Unmarshal([]uint8(f.JSON()), &f2); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return f2
|
||||||
|
}
|
||||||
|
if err := quick.CheckEqual(f, g, nil); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
86
pkg/can-go/frame_string_test.go
Normal file
86
pkg/can-go/frame_string_test.go
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
package can
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gotest.tools/v3/assert"
|
||||||
|
is "gotest.tools/v3/assert/cmp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFrame_String(t *testing.T) {
|
||||||
|
for _, tt := range []struct {
|
||||||
|
frame Frame
|
||||||
|
str string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
frame: Frame{
|
||||||
|
ID: 0x62e,
|
||||||
|
Length: 2,
|
||||||
|
Data: Data{0x10, 0x44},
|
||||||
|
},
|
||||||
|
str: "62E#1044",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
frame: Frame{
|
||||||
|
ID: 0x410,
|
||||||
|
IsRemote: true,
|
||||||
|
Length: 3,
|
||||||
|
},
|
||||||
|
str: "410#R3",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
frame: Frame{
|
||||||
|
ID: 0xd2,
|
||||||
|
Length: 2,
|
||||||
|
Data: Data{0xf0, 0x31},
|
||||||
|
},
|
||||||
|
str: "0D2#F031",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
frame: Frame{ID: 0xee},
|
||||||
|
str: "0EE#",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
frame: Frame{ID: 0},
|
||||||
|
str: "000#",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
frame: Frame{ID: 0, IsExtended: true},
|
||||||
|
str: "00000000#",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
frame: Frame{ID: 0x1234abcd, IsExtended: true},
|
||||||
|
str: "1234ABCD#",
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
tt := tt
|
||||||
|
t.Run(fmt.Sprintf("String|frame=%v,str=%v", tt.frame, tt.str), func(t *testing.T) {
|
||||||
|
assert.Check(t, is.Equal(tt.str, tt.frame.String()))
|
||||||
|
})
|
||||||
|
t.Run(fmt.Sprintf("UnmarshalString|frame=%v,str=%v", tt.frame, tt.str), func(t *testing.T) {
|
||||||
|
var actual Frame
|
||||||
|
if err := actual.UnmarshalString(tt.str); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
assert.Check(t, is.DeepEqual(actual, tt.frame))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseFrame_Errors(t *testing.T) {
|
||||||
|
for _, tt := range []string{
|
||||||
|
"foo", // invalid
|
||||||
|
"foo#", // invalid ID
|
||||||
|
"0D23#F031", // invalid ID length
|
||||||
|
"62E#104400000000000000", // invalid data length
|
||||||
|
} {
|
||||||
|
tt := tt
|
||||||
|
t.Run(fmt.Sprintf("str=%v", tt), func(t *testing.T) {
|
||||||
|
var frame Frame
|
||||||
|
err := frame.UnmarshalString(tt)
|
||||||
|
assert.ErrorContains(t, err, "invalid")
|
||||||
|
assert.Check(t, is.DeepEqual(Frame{}, frame))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
27
pkg/can-go/frame_test.go
Normal file
27
pkg/can-go/frame_test.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package can
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"gotest.tools/v3/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
// If this mocks ever starts failing, the documentation needs to be updated
|
||||||
|
// to prefer pass-by-pointer over pass-by-value.
|
||||||
|
func TestFrame_Size(t *testing.T) {
|
||||||
|
assert.Assert(t, unsafe.Sizeof(Frame{}) <= 16, "Frame size is <= 16 bytes")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFrame_Validate_Error(t *testing.T) {
|
||||||
|
for _, tt := range []Frame{
|
||||||
|
{ID: MaxID + 1},
|
||||||
|
{ID: MaxExtendedID + 1, IsExtended: true},
|
||||||
|
} {
|
||||||
|
tt := tt
|
||||||
|
t.Run(fmt.Sprintf("%v", tt), func(t *testing.T) {
|
||||||
|
assert.Check(t, tt.Validate() != nil, "should return validation error")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
29
pkg/can-go/go.mod
Normal file
29
pkg/can-go/go.mod
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
module github.com/fiskerinc/cloud-services/pkg/can-go
|
||||||
|
|
||||||
|
go 1.24.0
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
|
||||||
|
github.com/fatih/color v1.15.0
|
||||||
|
github.com/golang/mock v1.7.0-rc.1
|
||||||
|
github.com/shurcooL/go-goon v0.0.0-20210110234559-7585751d9a17
|
||||||
|
go.uber.org/goleak v1.3.0
|
||||||
|
golang.org/x/net v0.47.0
|
||||||
|
golang.org/x/sync v0.18.0
|
||||||
|
golang.org/x/sys v0.38.0
|
||||||
|
golang.org/x/tools v0.38.0
|
||||||
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6
|
||||||
|
gotest.tools/v3 v3.5.1
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
|
||||||
|
github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15 // indirect
|
||||||
|
github.com/google/go-cmp v0.7.0 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
|
github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636 // indirect
|
||||||
|
github.com/stretchr/testify v1.10.0 // indirect
|
||||||
|
golang.org/x/mod v0.29.0 // indirect
|
||||||
|
)
|
||||||
45
pkg/can-go/go.sum
Normal file
45
pkg/can-go/go.sum
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
|
||||||
|
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
|
github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15 h1:AUNCr9CiJuwrRYS3XieqF+Z9B9gNxo/eANAJCF2eiN4=
|
||||||
|
github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||||
|
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
|
||||||
|
github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U=
|
||||||
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||||
|
github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636 h1:aSISeOcal5irEhJd1M+IrApc0PdcN7e7Aj4yuEnOrfQ=
|
||||||
|
github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
|
||||||
|
github.com/shurcooL/go-goon v0.0.0-20210110234559-7585751d9a17 h1:lRAUE0dIvigSSFAmaM2dfg7OH8T+a8zJ5smEh09a/GI=
|
||||||
|
github.com/shurcooL/go-goon v0.0.0-20210110234559-7585751d9a17/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
|
||||||
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
|
||||||
27
pkg/can-go/internal/clock/clock.go
Normal file
27
pkg/can-go/internal/clock/clock.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
// Package clock provides primitives for mocking time.
|
||||||
|
package clock
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Clock provides capabilities from the time standard library package.
|
||||||
|
type Clock interface {
|
||||||
|
// After waits for the duration to elapse and then sends the current time on the returned channel.
|
||||||
|
After(duration time.Duration) <-chan time.Time
|
||||||
|
|
||||||
|
// NewTicker returns a new Ticker.
|
||||||
|
NewTicker(d time.Duration) Ticker
|
||||||
|
|
||||||
|
// Now returns the current local time.
|
||||||
|
Now() time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ticker wraps the time.Ticker class.
|
||||||
|
type Ticker interface {
|
||||||
|
// C returns the channel on which the ticks are delivered.
|
||||||
|
C() <-chan time.Time
|
||||||
|
|
||||||
|
// Stop the Ticker.
|
||||||
|
Stop()
|
||||||
|
}
|
||||||
34
pkg/can-go/internal/clock/system.go
Normal file
34
pkg/can-go/internal/clock/system.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package clock
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// System returns a Clock implementation that delegate to the time package.
|
||||||
|
func System() Clock {
|
||||||
|
return &systemClock{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type systemClock struct{}
|
||||||
|
|
||||||
|
var _ Clock = &systemClock{}
|
||||||
|
|
||||||
|
func (c systemClock) After(d time.Duration) <-chan time.Time {
|
||||||
|
return time.After(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c systemClock) NewTicker(d time.Duration) Ticker {
|
||||||
|
return &systemTicker{Ticker: *time.NewTicker(d)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c systemClock) Now() time.Time {
|
||||||
|
return time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
type systemTicker struct {
|
||||||
|
time.Ticker
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t systemTicker) C() <-chan time.Time {
|
||||||
|
return t.Ticker.C
|
||||||
|
}
|
||||||
529
pkg/can-go/internal/gen/mock/mockcanrunner/mocks.go
Normal file
529
pkg/can-go/internal/gen/mock/mockcanrunner/mocks.go
Normal file
@@ -0,0 +1,529 @@
|
|||||||
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
|
// Source: github.com/fiskerinc/cloud-services/pkg/can-go/pkg/canrunner (interfaces: Node,TransmittedMessage,ReceivedMessage,FrameTransmitter,FrameReceiver)
|
||||||
|
|
||||||
|
// Package mockcanrunner is a generated GoMock package.
|
||||||
|
package mockcanrunner
|
||||||
|
|
||||||
|
import (
|
||||||
|
context "context"
|
||||||
|
can "github.com/fiskerinc/cloud-services/pkg/can-go"
|
||||||
|
canrunner "github.com/fiskerinc/cloud-services/pkg/can-go/pkg/canrunner"
|
||||||
|
descriptor "github.com/fiskerinc/cloud-services/pkg/can-go/pkg/descriptor"
|
||||||
|
gomock "github.com/golang/mock/gomock"
|
||||||
|
net "net"
|
||||||
|
reflect "reflect"
|
||||||
|
time "time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockNode is a mock of Node interface.
|
||||||
|
type MockNode struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockNodeMockRecorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockNodeMockRecorder is the mock recorder for MockNode.
|
||||||
|
type MockNodeMockRecorder struct {
|
||||||
|
mock *MockNode
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockNode creates a new mock instance.
|
||||||
|
func NewMockNode(ctrl *gomock.Controller) *MockNode {
|
||||||
|
mock := &MockNode{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockNodeMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
|
func (m *MockNode) EXPECT() *MockNodeMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect mocks base method.
|
||||||
|
func (m *MockNode) Connect() (net.Conn, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Connect")
|
||||||
|
ret0, _ := ret[0].(net.Conn)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect indicates an expected call of Connect.
|
||||||
|
func (mr *MockNodeMockRecorder) Connect() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Connect", reflect.TypeOf((*MockNode)(nil).Connect))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Descriptor mocks base method.
|
||||||
|
func (m *MockNode) Descriptor() *descriptor.Node {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Descriptor")
|
||||||
|
ret0, _ := ret[0].(*descriptor.Node)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Descriptor indicates an expected call of Descriptor.
|
||||||
|
func (mr *MockNodeMockRecorder) Descriptor() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Descriptor", reflect.TypeOf((*MockNode)(nil).Descriptor))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lock mocks base method.
|
||||||
|
func (m *MockNode) Lock() {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
m.ctrl.Call(m, "Lock")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lock indicates an expected call of Lock.
|
||||||
|
func (mr *MockNodeMockRecorder) Lock() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Lock", reflect.TypeOf((*MockNode)(nil).Lock))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReceivedMessage mocks base method.
|
||||||
|
func (m *MockNode) ReceivedMessage(arg0 uint32) (canrunner.ReceivedMessage, bool) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "ReceivedMessage", arg0)
|
||||||
|
ret0, _ := ret[0].(canrunner.ReceivedMessage)
|
||||||
|
ret1, _ := ret[1].(bool)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReceivedMessage indicates an expected call of ReceivedMessage.
|
||||||
|
func (mr *MockNodeMockRecorder) ReceivedMessage(arg0 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReceivedMessage", reflect.TypeOf((*MockNode)(nil).ReceivedMessage), arg0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TransmittedMessages mocks base method.
|
||||||
|
func (m *MockNode) TransmittedMessages() []canrunner.TransmittedMessage {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "TransmittedMessages")
|
||||||
|
ret0, _ := ret[0].([]canrunner.TransmittedMessage)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// TransmittedMessages indicates an expected call of TransmittedMessages.
|
||||||
|
func (mr *MockNodeMockRecorder) TransmittedMessages() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TransmittedMessages", reflect.TypeOf((*MockNode)(nil).TransmittedMessages))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlock mocks base method.
|
||||||
|
func (m *MockNode) Unlock() {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
m.ctrl.Call(m, "Unlock")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlock indicates an expected call of Unlock.
|
||||||
|
func (mr *MockNodeMockRecorder) Unlock() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Unlock", reflect.TypeOf((*MockNode)(nil).Unlock))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockTransmittedMessage is a mock of TransmittedMessage interface.
|
||||||
|
type MockTransmittedMessage struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockTransmittedMessageMockRecorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockTransmittedMessageMockRecorder is the mock recorder for MockTransmittedMessage.
|
||||||
|
type MockTransmittedMessageMockRecorder struct {
|
||||||
|
mock *MockTransmittedMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockTransmittedMessage creates a new mock instance.
|
||||||
|
func NewMockTransmittedMessage(ctrl *gomock.Controller) *MockTransmittedMessage {
|
||||||
|
mock := &MockTransmittedMessage{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockTransmittedMessageMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
|
func (m *MockTransmittedMessage) EXPECT() *MockTransmittedMessageMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// BeforeTransmitHook mocks base method.
|
||||||
|
func (m *MockTransmittedMessage) BeforeTransmitHook() func(context.Context) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "BeforeTransmitHook")
|
||||||
|
ret0, _ := ret[0].(func(context.Context) error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// BeforeTransmitHook indicates an expected call of BeforeTransmitHook.
|
||||||
|
func (mr *MockTransmittedMessageMockRecorder) BeforeTransmitHook() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BeforeTransmitHook", reflect.TypeOf((*MockTransmittedMessage)(nil).BeforeTransmitHook))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Descriptor mocks base method.
|
||||||
|
func (m *MockTransmittedMessage) Descriptor() *descriptor.Message {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Descriptor")
|
||||||
|
ret0, _ := ret[0].(*descriptor.Message)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Descriptor indicates an expected call of Descriptor.
|
||||||
|
func (mr *MockTransmittedMessageMockRecorder) Descriptor() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Descriptor", reflect.TypeOf((*MockTransmittedMessage)(nil).Descriptor))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Frame mocks base method.
|
||||||
|
func (m *MockTransmittedMessage) Frame() can.Frame {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Frame")
|
||||||
|
ret0, _ := ret[0].(can.Frame)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Frame indicates an expected call of Frame.
|
||||||
|
func (mr *MockTransmittedMessageMockRecorder) Frame() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Frame", reflect.TypeOf((*MockTransmittedMessage)(nil).Frame))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsCyclicTransmissionEnabled mocks base method.
|
||||||
|
func (m *MockTransmittedMessage) IsCyclicTransmissionEnabled() bool {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "IsCyclicTransmissionEnabled")
|
||||||
|
ret0, _ := ret[0].(bool)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsCyclicTransmissionEnabled indicates an expected call of IsCyclicTransmissionEnabled.
|
||||||
|
func (mr *MockTransmittedMessageMockRecorder) IsCyclicTransmissionEnabled() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsCyclicTransmissionEnabled", reflect.TypeOf((*MockTransmittedMessage)(nil).IsCyclicTransmissionEnabled))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalFrame mocks base method.
|
||||||
|
func (m *MockTransmittedMessage) MarshalFrame() (can.Frame, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "MarshalFrame")
|
||||||
|
ret0, _ := ret[0].(can.Frame)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalFrame indicates an expected call of MarshalFrame.
|
||||||
|
func (mr *MockTransmittedMessageMockRecorder) MarshalFrame() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MarshalFrame", reflect.TypeOf((*MockTransmittedMessage)(nil).MarshalFrame))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset mocks base method.
|
||||||
|
func (m *MockTransmittedMessage) Reset() {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
m.ctrl.Call(m, "Reset")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset indicates an expected call of Reset.
|
||||||
|
func (mr *MockTransmittedMessageMockRecorder) Reset() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Reset", reflect.TypeOf((*MockTransmittedMessage)(nil).Reset))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTransmitTime mocks base method.
|
||||||
|
func (m *MockTransmittedMessage) SetTransmitTime(arg0 time.Time) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
m.ctrl.Call(m, "SetTransmitTime", arg0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTransmitTime indicates an expected call of SetTransmitTime.
|
||||||
|
func (mr *MockTransmittedMessageMockRecorder) SetTransmitTime(arg0 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetTransmitTime", reflect.TypeOf((*MockTransmittedMessage)(nil).SetTransmitTime), arg0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// String mocks base method.
|
||||||
|
func (m *MockTransmittedMessage) String() string {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "String")
|
||||||
|
ret0, _ := ret[0].(string)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// String indicates an expected call of String.
|
||||||
|
func (mr *MockTransmittedMessageMockRecorder) String() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "String", reflect.TypeOf((*MockTransmittedMessage)(nil).String))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TransmitEventChan mocks base method.
|
||||||
|
func (m *MockTransmittedMessage) TransmitEventChan() <-chan struct{} {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "TransmitEventChan")
|
||||||
|
ret0, _ := ret[0].(<-chan struct{})
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// TransmitEventChan indicates an expected call of TransmitEventChan.
|
||||||
|
func (mr *MockTransmittedMessageMockRecorder) TransmitEventChan() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TransmitEventChan", reflect.TypeOf((*MockTransmittedMessage)(nil).TransmitEventChan))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalFrame mocks base method.
|
||||||
|
func (m *MockTransmittedMessage) UnmarshalFrame(arg0 can.Frame) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "UnmarshalFrame", arg0)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalFrame indicates an expected call of UnmarshalFrame.
|
||||||
|
func (mr *MockTransmittedMessageMockRecorder) UnmarshalFrame(arg0 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnmarshalFrame", reflect.TypeOf((*MockTransmittedMessage)(nil).UnmarshalFrame), arg0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WakeUpChan mocks base method.
|
||||||
|
func (m *MockTransmittedMessage) WakeUpChan() <-chan struct{} {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "WakeUpChan")
|
||||||
|
ret0, _ := ret[0].(<-chan struct{})
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// WakeUpChan indicates an expected call of WakeUpChan.
|
||||||
|
func (mr *MockTransmittedMessageMockRecorder) WakeUpChan() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WakeUpChan", reflect.TypeOf((*MockTransmittedMessage)(nil).WakeUpChan))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockReceivedMessage is a mock of ReceivedMessage interface.
|
||||||
|
type MockReceivedMessage struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockReceivedMessageMockRecorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockReceivedMessageMockRecorder is the mock recorder for MockReceivedMessage.
|
||||||
|
type MockReceivedMessageMockRecorder struct {
|
||||||
|
mock *MockReceivedMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockReceivedMessage creates a new mock instance.
|
||||||
|
func NewMockReceivedMessage(ctrl *gomock.Controller) *MockReceivedMessage {
|
||||||
|
mock := &MockReceivedMessage{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockReceivedMessageMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
|
func (m *MockReceivedMessage) EXPECT() *MockReceivedMessageMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// AfterReceiveHook mocks base method.
|
||||||
|
func (m *MockReceivedMessage) AfterReceiveHook() func(context.Context) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "AfterReceiveHook")
|
||||||
|
ret0, _ := ret[0].(func(context.Context) error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// AfterReceiveHook indicates an expected call of AfterReceiveHook.
|
||||||
|
func (mr *MockReceivedMessageMockRecorder) AfterReceiveHook() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AfterReceiveHook", reflect.TypeOf((*MockReceivedMessage)(nil).AfterReceiveHook))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Descriptor mocks base method.
|
||||||
|
func (m *MockReceivedMessage) Descriptor() *descriptor.Message {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Descriptor")
|
||||||
|
ret0, _ := ret[0].(*descriptor.Message)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Descriptor indicates an expected call of Descriptor.
|
||||||
|
func (mr *MockReceivedMessageMockRecorder) Descriptor() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Descriptor", reflect.TypeOf((*MockReceivedMessage)(nil).Descriptor))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Frame mocks base method.
|
||||||
|
func (m *MockReceivedMessage) Frame() can.Frame {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Frame")
|
||||||
|
ret0, _ := ret[0].(can.Frame)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Frame indicates an expected call of Frame.
|
||||||
|
func (mr *MockReceivedMessageMockRecorder) Frame() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Frame", reflect.TypeOf((*MockReceivedMessage)(nil).Frame))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalFrame mocks base method.
|
||||||
|
func (m *MockReceivedMessage) MarshalFrame() (can.Frame, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "MarshalFrame")
|
||||||
|
ret0, _ := ret[0].(can.Frame)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalFrame indicates an expected call of MarshalFrame.
|
||||||
|
func (mr *MockReceivedMessageMockRecorder) MarshalFrame() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MarshalFrame", reflect.TypeOf((*MockReceivedMessage)(nil).MarshalFrame))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset mocks base method.
|
||||||
|
func (m *MockReceivedMessage) Reset() {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
m.ctrl.Call(m, "Reset")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset indicates an expected call of Reset.
|
||||||
|
func (mr *MockReceivedMessageMockRecorder) Reset() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Reset", reflect.TypeOf((*MockReceivedMessage)(nil).Reset))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetReceiveTime mocks base method.
|
||||||
|
func (m *MockReceivedMessage) SetReceiveTime(arg0 time.Time) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
m.ctrl.Call(m, "SetReceiveTime", arg0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetReceiveTime indicates an expected call of SetReceiveTime.
|
||||||
|
func (mr *MockReceivedMessageMockRecorder) SetReceiveTime(arg0 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetReceiveTime", reflect.TypeOf((*MockReceivedMessage)(nil).SetReceiveTime), arg0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// String mocks base method.
|
||||||
|
func (m *MockReceivedMessage) String() string {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "String")
|
||||||
|
ret0, _ := ret[0].(string)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// String indicates an expected call of String.
|
||||||
|
func (mr *MockReceivedMessageMockRecorder) String() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "String", reflect.TypeOf((*MockReceivedMessage)(nil).String))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalFrame mocks base method.
|
||||||
|
func (m *MockReceivedMessage) UnmarshalFrame(arg0 can.Frame) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "UnmarshalFrame", arg0)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalFrame indicates an expected call of UnmarshalFrame.
|
||||||
|
func (mr *MockReceivedMessageMockRecorder) UnmarshalFrame(arg0 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnmarshalFrame", reflect.TypeOf((*MockReceivedMessage)(nil).UnmarshalFrame), arg0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockFrameTransmitter is a mock of FrameTransmitter interface.
|
||||||
|
type MockFrameTransmitter struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockFrameTransmitterMockRecorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockFrameTransmitterMockRecorder is the mock recorder for MockFrameTransmitter.
|
||||||
|
type MockFrameTransmitterMockRecorder struct {
|
||||||
|
mock *MockFrameTransmitter
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockFrameTransmitter creates a new mock instance.
|
||||||
|
func NewMockFrameTransmitter(ctrl *gomock.Controller) *MockFrameTransmitter {
|
||||||
|
mock := &MockFrameTransmitter{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockFrameTransmitterMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
|
func (m *MockFrameTransmitter) EXPECT() *MockFrameTransmitterMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// TransmitFrame mocks base method.
|
||||||
|
func (m *MockFrameTransmitter) TransmitFrame(arg0 context.Context, arg1 can.Frame) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "TransmitFrame", arg0, arg1)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// TransmitFrame indicates an expected call of TransmitFrame.
|
||||||
|
func (mr *MockFrameTransmitterMockRecorder) TransmitFrame(arg0, arg1 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TransmitFrame", reflect.TypeOf((*MockFrameTransmitter)(nil).TransmitFrame), arg0, arg1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockFrameReceiver is a mock of FrameReceiver interface.
|
||||||
|
type MockFrameReceiver struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockFrameReceiverMockRecorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockFrameReceiverMockRecorder is the mock recorder for MockFrameReceiver.
|
||||||
|
type MockFrameReceiverMockRecorder struct {
|
||||||
|
mock *MockFrameReceiver
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockFrameReceiver creates a new mock instance.
|
||||||
|
func NewMockFrameReceiver(ctrl *gomock.Controller) *MockFrameReceiver {
|
||||||
|
mock := &MockFrameReceiver{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockFrameReceiverMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
|
func (m *MockFrameReceiver) EXPECT() *MockFrameReceiverMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err mocks base method.
|
||||||
|
func (m *MockFrameReceiver) Err() error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Err")
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err indicates an expected call of Err.
|
||||||
|
func (mr *MockFrameReceiverMockRecorder) Err() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Err", reflect.TypeOf((*MockFrameReceiver)(nil).Err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Frame mocks base method.
|
||||||
|
func (m *MockFrameReceiver) Frame() can.Frame {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Frame")
|
||||||
|
ret0, _ := ret[0].(can.Frame)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Frame indicates an expected call of Frame.
|
||||||
|
func (mr *MockFrameReceiverMockRecorder) Frame() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Frame", reflect.TypeOf((*MockFrameReceiver)(nil).Frame))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Receive mocks base method.
|
||||||
|
func (m *MockFrameReceiver) Receive() bool {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Receive")
|
||||||
|
ret0, _ := ret[0].(bool)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Receive indicates an expected call of Receive.
|
||||||
|
func (mr *MockFrameReceiverMockRecorder) Receive() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Receive", reflect.TypeOf((*MockFrameReceiver)(nil).Receive))
|
||||||
|
}
|
||||||
126
pkg/can-go/internal/gen/mock/mockclock/mocks.go
Normal file
126
pkg/can-go/internal/gen/mock/mockclock/mocks.go
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
|
// Source: github.com/fiskerinc/cloud-services/pkg/can-go/internal/clock (interfaces: Clock,Ticker)
|
||||||
|
|
||||||
|
// Package mockclock is a generated GoMock package.
|
||||||
|
package mockclock
|
||||||
|
|
||||||
|
import (
|
||||||
|
clock "github.com/fiskerinc/cloud-services/pkg/can-go/internal/clock"
|
||||||
|
gomock "github.com/golang/mock/gomock"
|
||||||
|
reflect "reflect"
|
||||||
|
time "time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockClock is a mock of Clock interface.
|
||||||
|
type MockClock struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockClockMockRecorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockClockMockRecorder is the mock recorder for MockClock.
|
||||||
|
type MockClockMockRecorder struct {
|
||||||
|
mock *MockClock
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockClock creates a new mock instance.
|
||||||
|
func NewMockClock(ctrl *gomock.Controller) *MockClock {
|
||||||
|
mock := &MockClock{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockClockMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
|
func (m *MockClock) EXPECT() *MockClockMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// After mocks base method.
|
||||||
|
func (m *MockClock) After(arg0 time.Duration) <-chan time.Time {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "After", arg0)
|
||||||
|
ret0, _ := ret[0].(<-chan time.Time)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// After indicates an expected call of After.
|
||||||
|
func (mr *MockClockMockRecorder) After(arg0 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "After", reflect.TypeOf((*MockClock)(nil).After), arg0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTicker mocks base method.
|
||||||
|
func (m *MockClock) NewTicker(arg0 time.Duration) clock.Ticker {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "NewTicker", arg0)
|
||||||
|
ret0, _ := ret[0].(clock.Ticker)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTicker indicates an expected call of NewTicker.
|
||||||
|
func (mr *MockClockMockRecorder) NewTicker(arg0 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewTicker", reflect.TypeOf((*MockClock)(nil).NewTicker), arg0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now mocks base method.
|
||||||
|
func (m *MockClock) Now() time.Time {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Now")
|
||||||
|
ret0, _ := ret[0].(time.Time)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now indicates an expected call of Now.
|
||||||
|
func (mr *MockClockMockRecorder) Now() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Now", reflect.TypeOf((*MockClock)(nil).Now))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockTicker is a mock of Ticker interface.
|
||||||
|
type MockTicker struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockTickerMockRecorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockTickerMockRecorder is the mock recorder for MockTicker.
|
||||||
|
type MockTickerMockRecorder struct {
|
||||||
|
mock *MockTicker
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockTicker creates a new mock instance.
|
||||||
|
func NewMockTicker(ctrl *gomock.Controller) *MockTicker {
|
||||||
|
mock := &MockTicker{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockTickerMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
|
func (m *MockTicker) EXPECT() *MockTickerMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// C mocks base method.
|
||||||
|
func (m *MockTicker) C() <-chan time.Time {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "C")
|
||||||
|
ret0, _ := ret[0].(<-chan time.Time)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// C indicates an expected call of C.
|
||||||
|
func (mr *MockTickerMockRecorder) C() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "C", reflect.TypeOf((*MockTicker)(nil).C))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop mocks base method.
|
||||||
|
func (m *MockTicker) Stop() {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
m.ctrl.Call(m, "Stop")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop indicates an expected call of Stop.
|
||||||
|
func (mr *MockTickerMockRecorder) Stop() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Stop", reflect.TypeOf((*MockTicker)(nil).Stop))
|
||||||
|
}
|
||||||
120
pkg/can-go/internal/gen/mock/mocksocketcan/mocks.go
Normal file
120
pkg/can-go/internal/gen/mock/mocksocketcan/mocks.go
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
|
// Source: pkg/socketcan/fileconn.go
|
||||||
|
|
||||||
|
// Package mocksocketcan is a generated GoMock package.
|
||||||
|
package mocksocketcan
|
||||||
|
|
||||||
|
import (
|
||||||
|
gomock "github.com/golang/mock/gomock"
|
||||||
|
reflect "reflect"
|
||||||
|
time "time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Mockfile is a mock of file interface.
|
||||||
|
type Mockfile struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockfileMockRecorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockfileMockRecorder is the mock recorder for Mockfile.
|
||||||
|
type MockfileMockRecorder struct {
|
||||||
|
mock *Mockfile
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockfile creates a new mock instance.
|
||||||
|
func NewMockfile(ctrl *gomock.Controller) *Mockfile {
|
||||||
|
mock := &Mockfile{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockfileMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
|
func (m *Mockfile) EXPECT() *MockfileMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read mocks base method.
|
||||||
|
func (m *Mockfile) Read(arg0 []byte) (int, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Read", arg0)
|
||||||
|
ret0, _ := ret[0].(int)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read indicates an expected call of Read.
|
||||||
|
func (mr *MockfileMockRecorder) Read(arg0 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Read", reflect.TypeOf((*Mockfile)(nil).Read), arg0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write mocks base method.
|
||||||
|
func (m *Mockfile) Write(arg0 []byte) (int, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Write", arg0)
|
||||||
|
ret0, _ := ret[0].(int)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write indicates an expected call of Write.
|
||||||
|
func (mr *MockfileMockRecorder) Write(arg0 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Write", reflect.TypeOf((*Mockfile)(nil).Write), arg0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDeadline mocks base method.
|
||||||
|
func (m *Mockfile) SetDeadline(arg0 time.Time) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "SetDeadline", arg0)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDeadline indicates an expected call of SetDeadline.
|
||||||
|
func (mr *MockfileMockRecorder) SetDeadline(arg0 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDeadline", reflect.TypeOf((*Mockfile)(nil).SetDeadline), arg0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetReadDeadline mocks base method.
|
||||||
|
func (m *Mockfile) SetReadDeadline(arg0 time.Time) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "SetReadDeadline", arg0)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetReadDeadline indicates an expected call of SetReadDeadline.
|
||||||
|
func (mr *MockfileMockRecorder) SetReadDeadline(arg0 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetReadDeadline", reflect.TypeOf((*Mockfile)(nil).SetReadDeadline), arg0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetWriteDeadline mocks base method.
|
||||||
|
func (m *Mockfile) SetWriteDeadline(arg0 time.Time) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "SetWriteDeadline", arg0)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetWriteDeadline indicates an expected call of SetWriteDeadline.
|
||||||
|
func (mr *MockfileMockRecorder) SetWriteDeadline(arg0 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetWriteDeadline", reflect.TypeOf((*Mockfile)(nil).SetWriteDeadline), arg0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close mocks base method.
|
||||||
|
func (m *Mockfile) Close() error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Close")
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close indicates an expected call of Close.
|
||||||
|
func (mr *MockfileMockRecorder) Close() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*Mockfile)(nil).Close))
|
||||||
|
}
|
||||||
231
pkg/can-go/internal/generate/compile.go
Normal file
231
pkg/can-go/internal/generate/compile.go
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
package generate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"sort"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/dbc"
|
||||||
|
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/descriptor"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CompileResult struct {
|
||||||
|
Hash string
|
||||||
|
Database *descriptor.Database
|
||||||
|
Warnings []error
|
||||||
|
}
|
||||||
|
|
||||||
|
func Compile(sourceFile string, data []byte) (result *CompileResult, err error) {
|
||||||
|
p := dbc.NewParser(sourceFile, data)
|
||||||
|
if err := p.Parse(); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse DBC source file: %w", err)
|
||||||
|
}
|
||||||
|
defs := p.Defs()
|
||||||
|
c := &compiler{
|
||||||
|
db: &descriptor.Database{SourceFile: sourceFile, ECUs: make(map[string]bool)},
|
||||||
|
defs: defs,
|
||||||
|
}
|
||||||
|
c.collectDescriptors()
|
||||||
|
c.addMetadata()
|
||||||
|
c.sortDescriptors()
|
||||||
|
|
||||||
|
hash := ComputeHash(data)
|
||||||
|
return &CompileResult{Hash: hash, Database: c.db, Warnings: c.warnings}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type compileError struct {
|
||||||
|
def dbc.Def
|
||||||
|
reason string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *compileError) Error() string {
|
||||||
|
return fmt.Sprintf("failed to compile: %v (%v)", e.reason, e.def)
|
||||||
|
}
|
||||||
|
|
||||||
|
type compiler struct {
|
||||||
|
db *descriptor.Database
|
||||||
|
defs []dbc.Def
|
||||||
|
warnings []error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *compiler) addWarning(warning error) {
|
||||||
|
c.warnings = append(c.warnings, warning)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *compiler) collectDescriptors() {
|
||||||
|
// find ECU names from messages
|
||||||
|
re := regexp.MustCompile(`^[0-9A-Za-z]+`)
|
||||||
|
|
||||||
|
for _, def := range c.defs {
|
||||||
|
switch def := def.(type) {
|
||||||
|
case *dbc.VersionDef:
|
||||||
|
c.db.Version = def.Version
|
||||||
|
case *dbc.MessageDef:
|
||||||
|
if def.MessageID == dbc.IndependentSignalsMessageID {
|
||||||
|
continue // don't compile
|
||||||
|
}
|
||||||
|
|
||||||
|
// add ECU names to set
|
||||||
|
ecu := re.FindString(string(def.Name))
|
||||||
|
if ecu != "" {
|
||||||
|
c.db.ECUs[ecu] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
message := &descriptor.Message{
|
||||||
|
Name: string(def.Name),
|
||||||
|
ID: def.MessageID.ToCAN(),
|
||||||
|
IsExtended: def.MessageID.IsExtended(),
|
||||||
|
Length: uint16(def.Size),
|
||||||
|
SenderNode: string(def.Transmitter),
|
||||||
|
}
|
||||||
|
for _, signalDef := range def.Signals {
|
||||||
|
signal := &descriptor.Signal{
|
||||||
|
Name: string(signalDef.Name),
|
||||||
|
IsBigEndian: signalDef.IsBigEndian,
|
||||||
|
IsSigned: signalDef.IsSigned,
|
||||||
|
IsMultiplexer: signalDef.IsMultiplexerSwitch,
|
||||||
|
IsMultiplexed: signalDef.IsMultiplexed,
|
||||||
|
MultiplexerValue: uint(signalDef.MultiplexerSwitch),
|
||||||
|
Start: uint16(signalDef.StartBit),
|
||||||
|
Length: uint16(signalDef.Size),
|
||||||
|
Scale: signalDef.Factor,
|
||||||
|
Offset: signalDef.Offset,
|
||||||
|
Min: signalDef.Minimum,
|
||||||
|
Max: signalDef.Maximum,
|
||||||
|
Unit: signalDef.Unit,
|
||||||
|
}
|
||||||
|
for _, receiver := range signalDef.Receivers {
|
||||||
|
signal.ReceiverNodes = append(signal.ReceiverNodes, string(receiver))
|
||||||
|
}
|
||||||
|
message.Signals = append(message.Signals, signal)
|
||||||
|
}
|
||||||
|
c.db.Messages[message.ID] = message
|
||||||
|
case *dbc.NodesDef:
|
||||||
|
for _, node := range def.NodeNames {
|
||||||
|
c.db.Nodes = append(c.db.Nodes, &descriptor.Node{Name: string(node)})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *compiler) addMetadata() {
|
||||||
|
for _, def := range c.defs {
|
||||||
|
switch def := def.(type) {
|
||||||
|
case *dbc.CommentDef:
|
||||||
|
switch def.ObjectType {
|
||||||
|
case dbc.ObjectTypeMessage:
|
||||||
|
if def.MessageID == dbc.IndependentSignalsMessageID {
|
||||||
|
continue // don't compile
|
||||||
|
}
|
||||||
|
message, ok := c.db.Message(def.MessageID.ToCAN())
|
||||||
|
if !ok {
|
||||||
|
c.addWarning(&compileError{def: def, reason: "no declared message"})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
message.Description = def.Comment
|
||||||
|
case dbc.ObjectTypeSignal:
|
||||||
|
if def.MessageID == dbc.IndependentSignalsMessageID {
|
||||||
|
continue // don't compile
|
||||||
|
}
|
||||||
|
signal, ok := c.db.Signal(def.MessageID.ToCAN(), string(def.SignalName))
|
||||||
|
if !ok {
|
||||||
|
c.addWarning(&compileError{def: def, reason: "no declared signal"})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
signal.Description = def.Comment
|
||||||
|
case dbc.ObjectTypeNetworkNode:
|
||||||
|
node, ok := c.db.Node(string(def.NodeName))
|
||||||
|
if !ok {
|
||||||
|
c.addWarning(&compileError{def: def, reason: "no declared node"})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
node.Description = def.Comment
|
||||||
|
}
|
||||||
|
case *dbc.ValueDescriptionsDef:
|
||||||
|
if def.MessageID == dbc.IndependentSignalsMessageID {
|
||||||
|
continue // don't compile
|
||||||
|
}
|
||||||
|
if def.ObjectType != dbc.ObjectTypeSignal {
|
||||||
|
continue // don't compile
|
||||||
|
}
|
||||||
|
signal, ok := c.db.Signal(def.MessageID.ToCAN(), string(def.SignalName))
|
||||||
|
if !ok {
|
||||||
|
c.addWarning(&compileError{def: def, reason: "no declared signal"})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, valueDescription := range def.ValueDescriptions {
|
||||||
|
signal.ValueDescriptions = append(signal.ValueDescriptions, &descriptor.ValueDescription{
|
||||||
|
Description: valueDescription.Description,
|
||||||
|
Value: int(valueDescription.Value),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
case *dbc.AttributeValueForObjectDef:
|
||||||
|
switch def.ObjectType {
|
||||||
|
case dbc.ObjectTypeMessage:
|
||||||
|
msg, ok := c.db.Message(def.MessageID.ToCAN())
|
||||||
|
if !ok {
|
||||||
|
c.addWarning(&compileError{def: def, reason: "no declared message"})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch def.AttributeName {
|
||||||
|
case "GenMsgSendType":
|
||||||
|
if err := msg.SendType.UnmarshalString(def.StringValue); err != nil {
|
||||||
|
c.addWarning(&compileError{def: def, reason: err.Error()})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
case "GenMsgCycleTime":
|
||||||
|
msg.CycleTime = time.Duration(def.IntValue) * time.Millisecond
|
||||||
|
case "GenMsgDelayTime":
|
||||||
|
msg.DelayTime = time.Duration(def.IntValue) * time.Millisecond
|
||||||
|
}
|
||||||
|
case dbc.ObjectTypeSignal:
|
||||||
|
sig, ok := c.db.Signal(def.MessageID.ToCAN(), string(def.SignalName))
|
||||||
|
if !ok {
|
||||||
|
c.addWarning(&compileError{def: def, reason: "no declared signal"})
|
||||||
|
}
|
||||||
|
if def.AttributeName == "GenSigStartValue" {
|
||||||
|
sig.DefaultValue = int(def.IntValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *compiler) sortDescriptors() {
|
||||||
|
// Sort nodes by name
|
||||||
|
sort.Slice(c.db.Nodes, func(i, j int) bool {
|
||||||
|
return c.db.Nodes[i].Name < c.db.Nodes[j].Name
|
||||||
|
})
|
||||||
|
// Sort messages by ID
|
||||||
|
// sort.Slice(c.db.Messages, func(i, j int) bool {
|
||||||
|
// return c.db.Messages[i].ID < c.db.Messages[j].ID
|
||||||
|
// })
|
||||||
|
for _, m := range c.db.Messages {
|
||||||
|
if m == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
m := m
|
||||||
|
// Sort signals by start (and multiplexer value)
|
||||||
|
sort.Slice(m.Signals, func(j, k int) bool {
|
||||||
|
if m.Signals[j].MultiplexerValue < m.Signals[k].MultiplexerValue {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return m.Signals[j].Start < m.Signals[k].Start
|
||||||
|
})
|
||||||
|
// Sort value descriptions by value
|
||||||
|
for _, s := range m.Signals {
|
||||||
|
s := s
|
||||||
|
sort.Slice(s.ValueDescriptions, func(k, l int) bool {
|
||||||
|
return s.ValueDescriptions[k].Value < s.ValueDescriptions[l].Value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ComputeHash(input []byte) string {
|
||||||
|
h := sha256.New()
|
||||||
|
h.Write(input)
|
||||||
|
return fmt.Sprintf("%x", h.Sum(nil))
|
||||||
|
}
|
||||||
306
pkg/can-go/internal/generate/compile_test.go
Normal file
306
pkg/can-go/internal/generate/compile_test.go
Normal file
@@ -0,0 +1,306 @@
|
|||||||
|
package generate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/descriptor"
|
||||||
|
examplecan "github.com/fiskerinc/cloud-services/pkg/can-go/testdata/gen/go/example"
|
||||||
|
"gotest.tools/v3/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCompile_ExampleDBC(t *testing.T) {
|
||||||
|
finish := runTestInDir(t, "../..")
|
||||||
|
defer finish()
|
||||||
|
const exampleDBCFile = "testdata/dbc/example/example.dbc"
|
||||||
|
exampleDatabase := &descriptor.Database{
|
||||||
|
SourceFile: exampleDBCFile,
|
||||||
|
Version: "",
|
||||||
|
Nodes: []*descriptor.Node{
|
||||||
|
{
|
||||||
|
Name: "DBG",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "DRIVER",
|
||||||
|
Description: "The driver controller driving the car",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "IO",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "MOTOR",
|
||||||
|
Description: "The motor controller of the car",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "SENSOR",
|
||||||
|
Description: "The sensor controller of the car",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
exampleDatabase.Messages[1] = &descriptor.Message{
|
||||||
|
ID: 1,
|
||||||
|
Name: "EmptyMessage",
|
||||||
|
SenderNode: "DBG",
|
||||||
|
}
|
||||||
|
|
||||||
|
exampleDatabase.Messages[100] = &descriptor.Message{
|
||||||
|
ID: 100,
|
||||||
|
Name: "DriverHeartbeat",
|
||||||
|
Length: 1,
|
||||||
|
SenderNode: "DRIVER",
|
||||||
|
Description: "Sync message used to synchronize the controllers",
|
||||||
|
SendType: descriptor.SendTypeCyclic,
|
||||||
|
CycleTime: time.Second,
|
||||||
|
Signals: []*descriptor.Signal{
|
||||||
|
{
|
||||||
|
Name: "Command",
|
||||||
|
Start: 0,
|
||||||
|
Length: 8,
|
||||||
|
Scale: 1,
|
||||||
|
ReceiverNodes: []string{"SENSOR", "MOTOR"},
|
||||||
|
ValueDescriptions: []*descriptor.ValueDescription{
|
||||||
|
{Value: 0, Description: "None"},
|
||||||
|
{Value: 1, Description: "Sync"},
|
||||||
|
{Value: 2, Description: "Reboot"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
exampleDatabase.Messages[101] = &descriptor.Message{
|
||||||
|
ID: 101,
|
||||||
|
Name: "MotorCommand",
|
||||||
|
Length: 1,
|
||||||
|
SenderNode: "DRIVER",
|
||||||
|
SendType: descriptor.SendTypeCyclic,
|
||||||
|
CycleTime: 100 * time.Millisecond,
|
||||||
|
Signals: []*descriptor.Signal{
|
||||||
|
{
|
||||||
|
Name: "Steer",
|
||||||
|
Start: 0,
|
||||||
|
Length: 4,
|
||||||
|
IsSigned: true,
|
||||||
|
Scale: 1,
|
||||||
|
Offset: -5,
|
||||||
|
Min: -5,
|
||||||
|
Max: 5,
|
||||||
|
ReceiverNodes: []string{"MOTOR"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Drive",
|
||||||
|
Start: 4,
|
||||||
|
Length: 4,
|
||||||
|
Scale: 1,
|
||||||
|
Max: 9,
|
||||||
|
ReceiverNodes: []string{"MOTOR"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
exampleDatabase.Messages[200] = &descriptor.Message{
|
||||||
|
ID: 200,
|
||||||
|
Name: "SensorSonars",
|
||||||
|
Length: 8,
|
||||||
|
SenderNode: "SENSOR",
|
||||||
|
SendType: descriptor.SendTypeCyclic,
|
||||||
|
CycleTime: 100 * time.Millisecond,
|
||||||
|
Signals: []*descriptor.Signal{
|
||||||
|
{
|
||||||
|
Name: "Mux",
|
||||||
|
IsMultiplexer: true,
|
||||||
|
Start: 0,
|
||||||
|
Length: 4,
|
||||||
|
Scale: 1,
|
||||||
|
ReceiverNodes: []string{"DRIVER", "IO"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "ErrCount",
|
||||||
|
Start: 4,
|
||||||
|
Length: 12,
|
||||||
|
Scale: 1,
|
||||||
|
ReceiverNodes: []string{"DRIVER", "IO"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Left",
|
||||||
|
IsMultiplexed: true,
|
||||||
|
MultiplexerValue: 0,
|
||||||
|
Start: 16,
|
||||||
|
Length: 12,
|
||||||
|
Scale: 0.1,
|
||||||
|
ReceiverNodes: []string{"DRIVER", "IO"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "NoFiltLeft",
|
||||||
|
IsMultiplexed: true,
|
||||||
|
MultiplexerValue: 1,
|
||||||
|
Start: 16,
|
||||||
|
Length: 12,
|
||||||
|
Scale: 0.1,
|
||||||
|
ReceiverNodes: []string{"DBG"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Middle",
|
||||||
|
IsMultiplexed: true,
|
||||||
|
MultiplexerValue: 0,
|
||||||
|
Start: 28,
|
||||||
|
Length: 12,
|
||||||
|
Scale: 0.1,
|
||||||
|
ReceiverNodes: []string{"DRIVER", "IO"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "NoFiltMiddle",
|
||||||
|
IsMultiplexed: true,
|
||||||
|
MultiplexerValue: 1,
|
||||||
|
Start: 28,
|
||||||
|
Length: 12,
|
||||||
|
Scale: 0.1,
|
||||||
|
ReceiverNodes: []string{"DBG"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Right",
|
||||||
|
IsMultiplexed: true,
|
||||||
|
MultiplexerValue: 0,
|
||||||
|
Start: 40,
|
||||||
|
Length: 12,
|
||||||
|
Scale: 0.1,
|
||||||
|
ReceiverNodes: []string{"DRIVER", "IO"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "NoFiltRight",
|
||||||
|
IsMultiplexed: true,
|
||||||
|
MultiplexerValue: 1,
|
||||||
|
Start: 40,
|
||||||
|
Length: 12,
|
||||||
|
Scale: 0.1,
|
||||||
|
ReceiverNodes: []string{"DBG"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Rear",
|
||||||
|
IsMultiplexed: true,
|
||||||
|
MultiplexerValue: 0,
|
||||||
|
Start: 52,
|
||||||
|
Length: 12,
|
||||||
|
Scale: 0.1,
|
||||||
|
ReceiverNodes: []string{"DRIVER", "IO"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "NoFiltRear",
|
||||||
|
IsMultiplexed: true,
|
||||||
|
MultiplexerValue: 1,
|
||||||
|
Start: 52,
|
||||||
|
Length: 12,
|
||||||
|
Scale: 0.1,
|
||||||
|
ReceiverNodes: []string{"DBG"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
exampleDatabase.Messages[400] = &descriptor.Message{
|
||||||
|
ID: 400,
|
||||||
|
Name: "MotorStatus",
|
||||||
|
Length: 3,
|
||||||
|
SenderNode: "MOTOR",
|
||||||
|
SendType: descriptor.SendTypeCyclic,
|
||||||
|
CycleTime: 100 * time.Millisecond,
|
||||||
|
Signals: []*descriptor.Signal{
|
||||||
|
{
|
||||||
|
Name: "WheelError",
|
||||||
|
Start: 0,
|
||||||
|
Length: 1,
|
||||||
|
Scale: 1,
|
||||||
|
ReceiverNodes: []string{"DRIVER", "IO"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "SpeedKph",
|
||||||
|
Start: 8,
|
||||||
|
Length: 16,
|
||||||
|
Scale: 0.001,
|
||||||
|
Unit: "km/h",
|
||||||
|
ReceiverNodes: []string{"DRIVER", "IO"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
exampleDatabase.Messages[500] = &descriptor.Message{
|
||||||
|
ID: 500,
|
||||||
|
Name: "IODebug",
|
||||||
|
Length: 6,
|
||||||
|
SenderNode: "IO",
|
||||||
|
SendType: descriptor.SendTypeEvent,
|
||||||
|
Signals: []*descriptor.Signal{
|
||||||
|
{
|
||||||
|
Name: "TestUnsigned",
|
||||||
|
Start: 0,
|
||||||
|
Length: 8,
|
||||||
|
Scale: 1,
|
||||||
|
ReceiverNodes: []string{"DBG"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "TestEnum",
|
||||||
|
Start: 8,
|
||||||
|
Length: 6,
|
||||||
|
Scale: 1,
|
||||||
|
ReceiverNodes: []string{"DBG"},
|
||||||
|
DefaultValue: int(examplecan.IODebug_TestEnum_Two),
|
||||||
|
ValueDescriptions: []*descriptor.ValueDescription{
|
||||||
|
{Value: 1, Description: "One"},
|
||||||
|
{Value: 2, Description: "Two"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "TestSigned",
|
||||||
|
Start: 16,
|
||||||
|
Length: 8,
|
||||||
|
IsSigned: true,
|
||||||
|
Scale: 1,
|
||||||
|
ReceiverNodes: []string{"DBG"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "TestFloat",
|
||||||
|
Start: 24,
|
||||||
|
Length: 8,
|
||||||
|
Scale: 0.5,
|
||||||
|
ReceiverNodes: []string{"DBG"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "TestBoolEnum",
|
||||||
|
Start: 32,
|
||||||
|
Length: 1,
|
||||||
|
Scale: 1,
|
||||||
|
ReceiverNodes: []string{"DBG"},
|
||||||
|
ValueDescriptions: []*descriptor.ValueDescription{
|
||||||
|
{Value: 0, Description: "Zero"},
|
||||||
|
{Value: 1, Description: "One"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "TestScaledEnum",
|
||||||
|
Start: 40,
|
||||||
|
Length: 2,
|
||||||
|
Scale: 2,
|
||||||
|
Min: 0,
|
||||||
|
Max: 6,
|
||||||
|
ReceiverNodes: []string{"DBG"},
|
||||||
|
ValueDescriptions: []*descriptor.ValueDescription{
|
||||||
|
{Value: 0, Description: "Zero"},
|
||||||
|
{Value: 1, Description: "Two"},
|
||||||
|
{Value: 2, Description: "Four"},
|
||||||
|
{Value: 3, Description: "Six"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
input, err := ioutil.ReadFile(exampleDBCFile)
|
||||||
|
assert.NilError(t, err)
|
||||||
|
result, err := Compile(exampleDBCFile, input)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(result.Warnings) > 0 {
|
||||||
|
t.Fatal(result.Warnings)
|
||||||
|
}
|
||||||
|
assert.DeepEqual(t, exampleDatabase, result.Database)
|
||||||
|
}
|
||||||
338
pkg/can-go/internal/generate/example_test.go
Normal file
338
pkg/can-go/internal/generate/example_test.go
Normal file
@@ -0,0 +1,338 @@
|
|||||||
|
package generate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
can "github.com/fiskerinc/cloud-services/pkg/can-go"
|
||||||
|
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/generated"
|
||||||
|
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/socketcan"
|
||||||
|
examplecan "github.com/fiskerinc/cloud-services/pkg/can-go/testdata/gen/go/example"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
"gotest.tools/v3/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestExampleDatabase_MarshalUnmarshal(t *testing.T) {
|
||||||
|
for _, tt := range []struct {
|
||||||
|
name string
|
||||||
|
m can.Message
|
||||||
|
f can.Frame
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "IODebug",
|
||||||
|
m: examplecan.NewIODebug().
|
||||||
|
SetTestUnsigned(5).
|
||||||
|
SetTestEnum(examplecan.IODebug_TestEnum_Two).
|
||||||
|
SetTestSigned(-42).
|
||||||
|
SetTestFloat(61.5).
|
||||||
|
SetTestBoolEnum(examplecan.IODebug_TestBoolEnum_One).
|
||||||
|
SetRawTestScaledEnum(examplecan.IODebug_TestScaledEnum_Four),
|
||||||
|
f: can.Frame{
|
||||||
|
ID: 500,
|
||||||
|
Length: 6,
|
||||||
|
Data: can.Data{5, 2, 214, 123, 1, 2},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "MotorStatus1",
|
||||||
|
m: examplecan.NewMotorStatus().
|
||||||
|
SetSpeedKph(0.423).
|
||||||
|
SetWheelError(true),
|
||||||
|
f: can.Frame{
|
||||||
|
ID: 400,
|
||||||
|
Length: 3,
|
||||||
|
Data: can.Data{0x1, 0xa7, 0x1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "MotorStatus2",
|
||||||
|
m: examplecan.NewMotorStatus().
|
||||||
|
SetSpeedKph(12),
|
||||||
|
f: can.Frame{
|
||||||
|
ID: 400,
|
||||||
|
Length: 3,
|
||||||
|
Data: can.Data{0x00, 0xe0, 0x2e},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
tt := tt
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
f, err := tt.m.MarshalFrame()
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.Equal(t, tt.f, f)
|
||||||
|
// allocate new message of same type as tt.m
|
||||||
|
msg := reflect.New(reflect.ValueOf(tt.m).Elem().Type()).Interface().(generated.Message)
|
||||||
|
assert.NilError(t, msg.UnmarshalFrame(f))
|
||||||
|
assert.Assert(t, reflect.DeepEqual(tt.m, msg))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExampleDatabase_UnmarshalFrame_Error(t *testing.T) {
|
||||||
|
for _, tt := range []struct {
|
||||||
|
name string
|
||||||
|
f can.Frame
|
||||||
|
m generated.Message
|
||||||
|
err string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "wrong ID",
|
||||||
|
f: can.Frame{ID: 11, Length: 8},
|
||||||
|
m: examplecan.NewSensorSonars(),
|
||||||
|
err: "unmarshal SensorSonars: expects ID 200 (got 00B#0000000000000000 with ID 11)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "wrong length",
|
||||||
|
f: can.Frame{ID: 200, Length: 4},
|
||||||
|
m: examplecan.NewSensorSonars(),
|
||||||
|
err: "unmarshal SensorSonars: expects length 8 (got 0C8#00000000 with length 4)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "remote frame",
|
||||||
|
f: can.Frame{ID: 200, Length: 8, IsRemote: true},
|
||||||
|
m: examplecan.NewSensorSonars(),
|
||||||
|
err: "unmarshal SensorSonars: expects non-remote frame (got remote frame 0C8#R8)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "extended ID",
|
||||||
|
f: can.Frame{ID: 200, Length: 8, IsExtended: true},
|
||||||
|
m: examplecan.NewSensorSonars(),
|
||||||
|
err: "unmarshal SensorSonars: expects standard ID (got 000000C8#0000000000000000 with extended ID)",
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
tt := tt
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
assert.Equal(t, tt.err, tt.m.UnmarshalFrame(tt.f).Error())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExampleDatabase_TestEnum_String(t *testing.T) {
|
||||||
|
assert.Equal(t, "One", examplecan.IODebug_TestEnum_One.String())
|
||||||
|
assert.Equal(t, "Two", examplecan.IODebug_TestEnum_Two.String())
|
||||||
|
assert.Equal(t, "IODebug_TestEnum(3)", examplecan.IODebug_TestEnum(3).String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExampleDatabase_Message_String(t *testing.T) {
|
||||||
|
const expected = "{WheelError: true, SpeedKph: 42km/h}"
|
||||||
|
msg := examplecan.NewMotorStatus().
|
||||||
|
SetSpeedKph(42).
|
||||||
|
SetWheelError(true)
|
||||||
|
assert.Equal(t, expected, msg.String())
|
||||||
|
assert.Equal(t, expected, fmt.Sprintf("%v", msg))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExampleDatabase_OutOfBoundsValue(t *testing.T) {
|
||||||
|
const expected = examplecan.IODebug_TestEnum(63)
|
||||||
|
actual := examplecan.NewIODebug().SetTestEnum(255).TestEnum()
|
||||||
|
assert.Equal(t, expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExampleDatabase_MultiplexedSignals(t *testing.T) {
|
||||||
|
// Given a message with multiplexed signals
|
||||||
|
msg := examplecan.NewSensorSonars().
|
||||||
|
SetErrCount(1).
|
||||||
|
SetMux(1).
|
||||||
|
SetLeft(20).
|
||||||
|
SetMiddle(30).
|
||||||
|
SetRight(40).
|
||||||
|
SetRear(50).
|
||||||
|
SetNoFiltLeft(60).
|
||||||
|
SetNoFiltMiddle(70).
|
||||||
|
SetNoFiltRight(80).
|
||||||
|
SetNoFiltRear(90)
|
||||||
|
for _, tt := range []struct {
|
||||||
|
expectedMux uint8
|
||||||
|
expectedErrCount uint16
|
||||||
|
expectedLeft float64
|
||||||
|
expectedMiddle float64
|
||||||
|
expectedRight float64
|
||||||
|
expectedRear float64
|
||||||
|
expectedNoFiltLeft float64
|
||||||
|
expectedNoFiltMiddle float64
|
||||||
|
expectedNoFiltRight float64
|
||||||
|
expectedNoFiltRear float64
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
expectedMux: 0,
|
||||||
|
expectedErrCount: 1,
|
||||||
|
expectedLeft: 20,
|
||||||
|
expectedMiddle: 30,
|
||||||
|
expectedRight: 40,
|
||||||
|
expectedRear: 50,
|
||||||
|
expectedNoFiltLeft: 0,
|
||||||
|
expectedNoFiltMiddle: 0,
|
||||||
|
expectedNoFiltRight: 0,
|
||||||
|
expectedNoFiltRear: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
expectedMux: 1,
|
||||||
|
expectedErrCount: 1,
|
||||||
|
expectedLeft: 0,
|
||||||
|
expectedMiddle: 0,
|
||||||
|
expectedRight: 0,
|
||||||
|
expectedRear: 0,
|
||||||
|
expectedNoFiltLeft: 60,
|
||||||
|
expectedNoFiltMiddle: 70,
|
||||||
|
expectedNoFiltRight: 80,
|
||||||
|
expectedNoFiltRear: 90,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
tt := tt
|
||||||
|
t.Run(fmt.Sprintf("mux=%v", tt.expectedMux), func(t *testing.T) {
|
||||||
|
unmarshal1 := examplecan.NewSensorSonars()
|
||||||
|
// When the multiplexer signal is 0 and we marshal the message
|
||||||
|
// to a CAN frame
|
||||||
|
msg.SetMux(tt.expectedMux)
|
||||||
|
f1, err := msg.MarshalFrame()
|
||||||
|
assert.NilError(t, err)
|
||||||
|
// When we unmarshal the CAN frame back to a message
|
||||||
|
assert.NilError(t, unmarshal1.UnmarshalFrame(f1))
|
||||||
|
// Then only the multiplexed signals with multiplexer value 0
|
||||||
|
// should be unmarshaled
|
||||||
|
assert.Equal(t, tt.expectedMux, unmarshal1.Mux(), "Mux")
|
||||||
|
assert.Equal(t, tt.expectedErrCount, unmarshal1.ErrCount(), "ErrCount")
|
||||||
|
assert.Equal(t, tt.expectedLeft, unmarshal1.Left(), "Left")
|
||||||
|
assert.Equal(t, tt.expectedMiddle, unmarshal1.Middle(), "Middle")
|
||||||
|
assert.Equal(t, tt.expectedRight, unmarshal1.Right(), "Right")
|
||||||
|
assert.Equal(t, tt.expectedRear, unmarshal1.Rear(), "Rear")
|
||||||
|
assert.Equal(t, tt.expectedNoFiltLeft, unmarshal1.NoFiltLeft(), "NoFiltLeft")
|
||||||
|
assert.Equal(t, tt.expectedNoFiltMiddle, unmarshal1.NoFiltMiddle(), "NoFiltMiddle")
|
||||||
|
assert.Equal(t, tt.expectedNoFiltRight, unmarshal1.NoFiltRight(), "NoFiltRight")
|
||||||
|
assert.Equal(t, tt.expectedNoFiltRear, unmarshal1.NoFiltRear(), "NoFiltRear")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExampleDatabase_CopyFrom(t *testing.T) {
|
||||||
|
// Given: an original message
|
||||||
|
from := examplecan.NewIODebug().
|
||||||
|
SetRawTestScaledEnum(examplecan.IODebug_TestScaledEnum_Four).
|
||||||
|
SetTestBoolEnum(true).
|
||||||
|
SetTestFloat(0.1).
|
||||||
|
SetTestSigned(-10).
|
||||||
|
SetTestUnsigned(10)
|
||||||
|
// When: another message copies from the original message
|
||||||
|
to := examplecan.NewIODebug().CopyFrom(from)
|
||||||
|
// Then:
|
||||||
|
// all fields should be equal...
|
||||||
|
assert.Equal(t, from.String(), to.String())
|
||||||
|
assert.Equal(t, from.TestScaledEnum(), to.TestScaledEnum())
|
||||||
|
assert.Equal(t, from.TestBoolEnum(), to.TestBoolEnum())
|
||||||
|
assert.Equal(t, from.TestFloat(), to.TestFloat())
|
||||||
|
assert.Equal(t, from.TestSigned(), to.TestSigned())
|
||||||
|
assert.Equal(t, from.TestUnsigned(), to.TestUnsigned())
|
||||||
|
// ...and changes to the original should not affect the new message
|
||||||
|
from.SetTestUnsigned(100)
|
||||||
|
assert.Equal(t, uint8(10), to.TestUnsigned())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExample_Nodes(t *testing.T) {
|
||||||
|
const testTimeout = 2 * time.Second
|
||||||
|
requireVCAN0(t)
|
||||||
|
// given a DRIVER node and a MOTOR node
|
||||||
|
motor := examplecan.NewMOTOR("can", "vcan0")
|
||||||
|
driver := examplecan.NewDRIVER("can", "vcan0")
|
||||||
|
// when starting them
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
|
||||||
|
g, ctx := errgroup.WithContext(ctx)
|
||||||
|
g.Go(func() error {
|
||||||
|
return motor.Run(ctx)
|
||||||
|
})
|
||||||
|
g.Go(func() error {
|
||||||
|
return driver.Run(ctx)
|
||||||
|
})
|
||||||
|
// and the MOTOR node is configured to send a speed report
|
||||||
|
const expectedSpeedKph = 42
|
||||||
|
motor.Lock()
|
||||||
|
motor.Tx().MotorStatus().SetSpeedKph(expectedSpeedKph)
|
||||||
|
motor.Tx().MotorStatus().SetCyclicTransmissionEnabled(true)
|
||||||
|
motor.Unlock()
|
||||||
|
// and the DRIVER node is configured to send a steering command
|
||||||
|
const expectedSteer = -4
|
||||||
|
driver.Lock()
|
||||||
|
driver.Tx().MotorCommand().SetSteer(expectedSteer)
|
||||||
|
driver.Tx().MotorCommand().SetCyclicTransmissionEnabled(true)
|
||||||
|
driver.Unlock()
|
||||||
|
// and the MOTOR node is listening for the steering command
|
||||||
|
expectedSteerReceivedChan := make(chan struct{})
|
||||||
|
motor.Lock()
|
||||||
|
motor.Rx().MotorCommand().SetAfterReceiveHook(func(context.Context) error {
|
||||||
|
motor.Lock()
|
||||||
|
if motor.Rx().MotorCommand().Steer() == expectedSteer {
|
||||||
|
close(expectedSteerReceivedChan)
|
||||||
|
motor.Rx().MotorCommand().SetAfterReceiveHook(func(context.Context) error { return nil })
|
||||||
|
}
|
||||||
|
motor.Unlock()
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
motor.Unlock()
|
||||||
|
// and the DRIVER node is listening for the speed report
|
||||||
|
expectedSpeedReceivedChan := make(chan struct{})
|
||||||
|
driver.Lock()
|
||||||
|
driver.Rx().MotorStatus().SetAfterReceiveHook(func(context.Context) error {
|
||||||
|
driver.Lock()
|
||||||
|
if driver.Rx().MotorStatus().SpeedKph() == expectedSpeedKph {
|
||||||
|
close(expectedSpeedReceivedChan)
|
||||||
|
driver.Rx().MotorStatus().SetAfterReceiveHook(func(context.Context) error { return nil })
|
||||||
|
}
|
||||||
|
driver.Unlock()
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
driver.Unlock()
|
||||||
|
// then the steer command transmitted by DRIVER should be received by MOTOR
|
||||||
|
select {
|
||||||
|
case <-expectedSteerReceivedChan:
|
||||||
|
case <-ctx.Done():
|
||||||
|
t.Fatalf("expected steer not received: %v", expectedSteer)
|
||||||
|
}
|
||||||
|
// and the speed report transmitted by MOTOR should be received by DRIVER
|
||||||
|
select {
|
||||||
|
case <-expectedSpeedReceivedChan:
|
||||||
|
case <-ctx.Done():
|
||||||
|
t.Fatalf("expected speed not received: %v", expectedSpeedKph)
|
||||||
|
}
|
||||||
|
cancel()
|
||||||
|
assert.NilError(t, g.Wait())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExample_Node_NoEmptyMessages(t *testing.T) {
|
||||||
|
const testTimeout = 2 * time.Second
|
||||||
|
requireVCAN0(t)
|
||||||
|
// given a DRIVER node and a MOTOR node
|
||||||
|
motor := examplecan.NewMOTOR("can", "vcan0")
|
||||||
|
// when starting them
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
|
||||||
|
handler := func(ctx context.Context) error {
|
||||||
|
motor.Lock()
|
||||||
|
motor.Tx().MotorStatus().SetSpeedKph(100).SetWheelError(true)
|
||||||
|
motor.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
motor.Tx().MotorStatus().SetBeforeTransmitHook(handler)
|
||||||
|
motor.Tx().MotorStatus().SetCyclicTransmissionEnabled(true)
|
||||||
|
c, err := socketcan.Dial("can", "vcan0")
|
||||||
|
r := socketcan.NewReceiver(c)
|
||||||
|
assert.NilError(t, err)
|
||||||
|
g := errgroup.Group{}
|
||||||
|
g.Go(func() error {
|
||||||
|
return motor.Run(ctx)
|
||||||
|
})
|
||||||
|
assert.Assert(t, r.Receive())
|
||||||
|
assert.Equal(t, examplecan.NewMotorStatus().SetSpeedKph(100).SetWheelError(true).Frame(), r.Frame())
|
||||||
|
cancel()
|
||||||
|
assert.NilError(t, g.Wait())
|
||||||
|
}
|
||||||
|
|
||||||
|
func requireVCAN0(t *testing.T) {
|
||||||
|
t.Helper()
|
||||||
|
if _, err := net.InterfaceByName("vcan0"); err != nil {
|
||||||
|
t.Skip("interface vcan0 does not exist")
|
||||||
|
}
|
||||||
|
}
|
||||||
976
pkg/can-go/internal/generate/file.go
Normal file
976
pkg/can-go/internal/generate/file.go
Normal file
@@ -0,0 +1,976 @@
|
|||||||
|
package generate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"go/format"
|
||||||
|
"go/types"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/descriptor"
|
||||||
|
"github.com/shurcooL/go-goon"
|
||||||
|
)
|
||||||
|
|
||||||
|
type File struct {
|
||||||
|
buf bytes.Buffer
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFile() *File {
|
||||||
|
f := &File{}
|
||||||
|
f.buf.Grow(1e5) // 100K
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *File) Write(p []byte) (int, error) {
|
||||||
|
if f.err != nil {
|
||||||
|
return 0, f.err
|
||||||
|
}
|
||||||
|
n, err := f.buf.Write(p)
|
||||||
|
f.err = err
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *File) P(v ...interface{}) {
|
||||||
|
for _, x := range v {
|
||||||
|
_, _ = fmt.Fprint(f, x)
|
||||||
|
}
|
||||||
|
_, _ = fmt.Fprintln(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *File) Dump(v interface{}) {
|
||||||
|
_, _ = goon.Fdump(f, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *File) Content() ([]byte, error) {
|
||||||
|
if f.err != nil {
|
||||||
|
return nil, fmt.Errorf("file content: %w", f.err)
|
||||||
|
}
|
||||||
|
formatted, err := format.Source(f.buf.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("file content: %s: %w", f.buf.String(), err)
|
||||||
|
}
|
||||||
|
return formatted, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Database(h string, d *descriptor.Database) ([]byte, error) {
|
||||||
|
f := NewFile()
|
||||||
|
Package(f, d)
|
||||||
|
Imports(f)
|
||||||
|
Version(f, h, d.Version)
|
||||||
|
ListECUs(f, d)
|
||||||
|
|
||||||
|
for _, m := range d.Messages {
|
||||||
|
if m == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
MessageType(f, m)
|
||||||
|
for _, s := range m.Signals {
|
||||||
|
if hasCustomType(s) {
|
||||||
|
SignalCustomType(f, m, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MarshalFrame(f, m)
|
||||||
|
UnmarshalFrame(f, m)
|
||||||
|
}
|
||||||
|
if hasSendType(d) { // only code-generate nodes for schemas with send types specified
|
||||||
|
for _, n := range d.Nodes {
|
||||||
|
Node(f, d, n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Descriptors(f, d)
|
||||||
|
return f.Content()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Package(f *File, d *descriptor.Database) {
|
||||||
|
packageName := strings.TrimSuffix(path.Base(d.SourceFile), path.Ext(d.SourceFile)) + "can"
|
||||||
|
f.P("// Package ", packageName, " provides primitives for encoding and decoding ", d.Name(), " CAN messages.")
|
||||||
|
f.P("//")
|
||||||
|
f.P("// Source: ", d.SourceFile)
|
||||||
|
f.P("package ", packageName)
|
||||||
|
f.P()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Imports(f *File) {
|
||||||
|
f.P("import (")
|
||||||
|
f.P(`"context"`)
|
||||||
|
f.P(`"fmt"`)
|
||||||
|
f.P(`"net"`)
|
||||||
|
f.P(`"net/http"`)
|
||||||
|
f.P(`"sync"`)
|
||||||
|
f.P(`"time"`)
|
||||||
|
f.P()
|
||||||
|
f.P(`"github.com/fiskerinc/cloud-services/pkg/can-go"`)
|
||||||
|
f.P(`"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/socketcan"`)
|
||||||
|
f.P(`"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/candebug"`)
|
||||||
|
f.P(`"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/canrunner"`)
|
||||||
|
f.P(`"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/descriptor"`)
|
||||||
|
f.P(`"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/generated"`)
|
||||||
|
f.P(`"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/cantext"`)
|
||||||
|
f.P(")")
|
||||||
|
f.P()
|
||||||
|
// we could use goimports for this, but it significantly slows down code generation
|
||||||
|
f.P("// prevent unused imports")
|
||||||
|
f.P("var (")
|
||||||
|
f.P("_ = context.Background")
|
||||||
|
f.P("_ = fmt.Print")
|
||||||
|
f.P("_ = net.Dial")
|
||||||
|
f.P("_ = http.Error")
|
||||||
|
f.P("_ = sync.Mutex{}")
|
||||||
|
f.P("_ = time.Now")
|
||||||
|
f.P("_ = socketcan.Dial")
|
||||||
|
f.P("_ = candebug.ServeMessagesHTTP")
|
||||||
|
f.P("_ = canrunner.Run")
|
||||||
|
f.P(")")
|
||||||
|
f.P()
|
||||||
|
f.P("// Generated code. DO NOT EDIT.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Version(f *File, h string, v string) {
|
||||||
|
f.P()
|
||||||
|
f.P("// Hash used as versioning control for DBC")
|
||||||
|
f.P(`const Hash string = "`, h, `"`)
|
||||||
|
f.P("// Version is the version listed in the DBC")
|
||||||
|
f.P(`const Version string = "`, v, `"`)
|
||||||
|
f.P()
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListECUs(f *File, d *descriptor.Database) {
|
||||||
|
f.P()
|
||||||
|
f.P("// ECUs parsed from DBC")
|
||||||
|
|
||||||
|
ecuList := "var ECUs = []string{"
|
||||||
|
i := 0
|
||||||
|
for ecu := range d.ECUs {
|
||||||
|
ecuList += fmt.Sprintf(`"%s"`, ecu)
|
||||||
|
i++
|
||||||
|
if i != len(d.ECUs) {
|
||||||
|
ecuList += ", "
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ecuList += "}"
|
||||||
|
f.P(ecuList)
|
||||||
|
f.P()
|
||||||
|
}
|
||||||
|
|
||||||
|
func SignalCustomType(f *File, m *descriptor.Message, s *descriptor.Signal) {
|
||||||
|
f.P("// ", signalType(m, s), " models the ", s.Name, " signal of the ", m.Name, " message.")
|
||||||
|
f.P("type ", signalType(m, s), " ", signalPrimitiveType(s))
|
||||||
|
f.P()
|
||||||
|
|
||||||
|
// dtaylor@fiskerinc.com EDIT
|
||||||
|
|
||||||
|
// f.P("// Value descriptions for the ", s.Name, " signal of the ", m.Name, " message.")
|
||||||
|
// f.P("const (")
|
||||||
|
// for _, vd := range s.ValueDescriptions {
|
||||||
|
// switch {
|
||||||
|
// case s.Length == 1 && vd.Value == 1:
|
||||||
|
// f.P(signalType(m, s), "_", vd.Description, " ", signalType(m, s), " = true")
|
||||||
|
// case s.Length == 1 && vd.Value == 0:
|
||||||
|
// f.P(signalType(m, s), "_", vd.Description, " ", signalType(m, s), " = false")
|
||||||
|
// default:
|
||||||
|
// f.P(signalType(m, s), "_", vd.Description, " ", signalType(m, s), " = ", vd.Value)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// f.P(")")
|
||||||
|
// f.P()
|
||||||
|
|
||||||
|
f.P("func (v ", signalType(m, s), ") String() string {")
|
||||||
|
if s.Length == 1 {
|
||||||
|
f.P("switch bool(v) {")
|
||||||
|
for _, vd := range s.ValueDescriptions {
|
||||||
|
if vd.Value == 1 {
|
||||||
|
f.P("case true:")
|
||||||
|
} else {
|
||||||
|
f.P("case false:")
|
||||||
|
}
|
||||||
|
f.P(`return "`, vd.Description, `"`)
|
||||||
|
}
|
||||||
|
f.P("}")
|
||||||
|
f.P(`return fmt.Sprintf("`, signalType(m, s), `(%t)", v)`)
|
||||||
|
} else {
|
||||||
|
f.P("switch v {")
|
||||||
|
for _, vd := range s.ValueDescriptions {
|
||||||
|
f.P("case ", vd.Value, ":")
|
||||||
|
f.P(`return "`, vd.Description, `"`)
|
||||||
|
}
|
||||||
|
f.P("default:")
|
||||||
|
f.P(`return fmt.Sprintf("`, signalType(m, s), `(%d)", v)`)
|
||||||
|
f.P("}")
|
||||||
|
}
|
||||||
|
f.P("}")
|
||||||
|
}
|
||||||
|
|
||||||
|
func MessageType(f *File, m *descriptor.Message) {
|
||||||
|
f.P("// ", messageReaderInterface(m), " provides read access to a ", m.Name, " message.")
|
||||||
|
f.P("type ", messageReaderInterface(m), " interface {")
|
||||||
|
for _, s := range m.Signals {
|
||||||
|
if hasPhysicalRepresentation(s) {
|
||||||
|
f.P("// ", s.Name, " returns the physical value of the ", s.Name, " signal.")
|
||||||
|
f.P(s.Name, "() float64")
|
||||||
|
if len(s.ValueDescriptions) > 0 {
|
||||||
|
f.P()
|
||||||
|
f.P("// ", s.Name, " returns the raw (encoded) value of the ", s.Name, " signal.")
|
||||||
|
f.P("Raw", s.Name, "() ", signalType(m, s))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
f.P("// ", s.Name, " returns the value of the ", s.Name, " signal.")
|
||||||
|
f.P(s.Name, "()", signalType(m, s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
f.P("// ", messageWriterInterface(m), " provides write access to a ", m.Name, " message.")
|
||||||
|
f.P("type ", messageWriterInterface(m), " interface {")
|
||||||
|
f.P("// CopyFrom copies all values from ", messageReaderInterface(m), ".")
|
||||||
|
f.P("CopyFrom(", messageReaderInterface(m), ") *", messageStruct(m))
|
||||||
|
for _, s := range m.Signals {
|
||||||
|
if hasPhysicalRepresentation(s) {
|
||||||
|
f.P("// Set", s.Name, " sets the physical value of the ", s.Name, " signal.")
|
||||||
|
f.P("Set", s.Name, "(float64) *", messageStruct(m))
|
||||||
|
if len(s.ValueDescriptions) > 0 {
|
||||||
|
f.P()
|
||||||
|
f.P("// SetRaw", s.Name, " sets the raw (encoded) value of the ", s.Name, " signal.")
|
||||||
|
f.P("SetRaw", s.Name, "(", signalType(m, s), ") *", messageStruct(m))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
f.P("// Set", s.Name, " sets the value of the ", s.Name, " signal.")
|
||||||
|
f.P("Set", s.Name, "(", signalType(m, s), ") *", messageStruct(m))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
f.P("type ", messageStruct(m), " struct {")
|
||||||
|
for _, s := range m.Signals {
|
||||||
|
f.P(signalField(s), " ", signalType(m, s))
|
||||||
|
}
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
f.P("func New", messageStruct(m), "() *", messageStruct(m), " {")
|
||||||
|
f.P("m := &", messageStruct(m), "{}")
|
||||||
|
f.P("m.Reset()")
|
||||||
|
f.P("return m")
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
f.P("func (m *", messageStruct(m), ") Reset() {")
|
||||||
|
for _, s := range m.Signals {
|
||||||
|
switch {
|
||||||
|
case s.Length == 1 && s.DefaultValue == 1:
|
||||||
|
f.P("m.", signalField(s), " = true")
|
||||||
|
case s.Length == 1:
|
||||||
|
f.P("m.", signalField(s), " = false")
|
||||||
|
default:
|
||||||
|
f.P("m.", signalField(s), " = ", s.DefaultValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
f.P("func (m *", messageStruct(m), ") CopyFrom(o ", messageReaderInterface(m), ") *", messageStruct(m), "{")
|
||||||
|
for _, s := range m.Signals {
|
||||||
|
if hasPhysicalRepresentation(s) {
|
||||||
|
f.P("m.Set", s.Name, "(o.", s.Name, "())")
|
||||||
|
} else {
|
||||||
|
f.P("m.", signalField(s), " = o.", s.Name, "()")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.P("return m")
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
f.P("// Descriptor returns the ", m.Name, " descriptor.")
|
||||||
|
f.P("func (m *", messageStruct(m), ") Descriptor() *descriptor.Message {")
|
||||||
|
f.P("return ", messageDescriptor(m), ".Message")
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
f.P("// String returns a compact string representation of the message.")
|
||||||
|
f.P("func(m *", messageStruct(m), ") String() string {")
|
||||||
|
f.P("return cantext.MessageString(m)")
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
for _, s := range m.Signals {
|
||||||
|
if !hasPhysicalRepresentation(s) {
|
||||||
|
f.P("func (m *", messageStruct(m), ") ", s.Name, "() ", signalType(m, s), " {")
|
||||||
|
f.P("return m.", signalField(s))
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
f.P("func (m *", messageStruct(m), ") Set", s.Name, "(v ", signalType(m, s), ") *", messageStruct(m), " {")
|
||||||
|
if s.Length == 1 {
|
||||||
|
f.P("m.", signalField(s), " = v")
|
||||||
|
} else {
|
||||||
|
f.P(
|
||||||
|
"m.", signalField(s), " = ", signalType(m, s), "(",
|
||||||
|
signalDescriptor(m, s), ".SaturatedCast", signalSuperType(s), "(",
|
||||||
|
signalPrimitiveSuperType(s), "(v)))",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
f.P("return m")
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
f.P("func (m *", messageStruct(m), ") ", s.Name, "() float64 {")
|
||||||
|
f.P("return ", signalDescriptor(m, s), ".ToPhysical(float64(m.", signalField(s), "))")
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
f.P("func (m *", messageStruct(m), ") Set", s.Name, "(v float64) *", messageStruct(m), " {")
|
||||||
|
f.P("m.", signalField(s), " = ", signalType(m, s), "(", signalDescriptor(m, s), ".FromPhysical(v))")
|
||||||
|
f.P("return m")
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
if len(s.ValueDescriptions) > 0 {
|
||||||
|
f.P("func (m *", messageStruct(m), ") Raw", s.Name, "() ", signalType(m, s), " {")
|
||||||
|
f.P("return m.", signalField(s))
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
f.P("func (m *", messageStruct(m), ") SetRaw", s.Name, "(v ", signalType(m, s), ") *", messageStruct(m), "{")
|
||||||
|
f.P(
|
||||||
|
"m.", signalField(s), " = ", signalType(m, s), "(",
|
||||||
|
signalDescriptor(m, s), ".SaturatedCast", signalSuperType(s), "(",
|
||||||
|
signalPrimitiveSuperType(s), "(v)))",
|
||||||
|
)
|
||||||
|
f.P("return m")
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Descriptors(f *File, d *descriptor.Database) {
|
||||||
|
f.P("// Nodes returns the ", d.Name(), " node descriptors.")
|
||||||
|
f.P("func Nodes() *NodesDescriptor {")
|
||||||
|
f.P("return nd")
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
f.P("// NodesDescriptor contains all ", d.Name(), " node descriptors.")
|
||||||
|
f.P("type NodesDescriptor struct{")
|
||||||
|
for _, n := range d.Nodes {
|
||||||
|
f.P(n.Name, " *descriptor.Node")
|
||||||
|
}
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
f.P("// Messages returns the ", d.Name(), " message descriptors.")
|
||||||
|
f.P("func Messages() *MessagesDescriptor {")
|
||||||
|
f.P("return md")
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
f.P("// MessagesDescriptor contains all ", d.Name(), " message descriptors.")
|
||||||
|
f.P("type MessagesDescriptor struct{")
|
||||||
|
for _, m := range d.Messages {
|
||||||
|
if m == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
f.P(m.Name, " *", m.Name, "Descriptor")
|
||||||
|
}
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
f.P("// UnmarshalFrame unmarshals the provided ", d.Name(), " CAN frame.")
|
||||||
|
f.P("func (md *MessagesDescriptor) UnmarshalFrame(f can.Frame) (generated.Message, error) {")
|
||||||
|
f.P("switch f.ID {")
|
||||||
|
for _, m := range d.Messages {
|
||||||
|
if m == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
f.P("case md.", m.Name, ".ID:")
|
||||||
|
f.P("var msg ", messageStruct(m))
|
||||||
|
f.P("if err := msg.UnmarshalFrame(f); err != nil {")
|
||||||
|
f.P(`return nil, fmt.Errorf("unmarshal `, d.Name(), ` frame: %w", err)`)
|
||||||
|
f.P("}")
|
||||||
|
f.P("return &msg, nil")
|
||||||
|
}
|
||||||
|
f.P("default:")
|
||||||
|
f.P(`return nil, fmt.Errorf("unmarshal `, d.Name(), ` frame: ID not in database: %d", f.ID)`)
|
||||||
|
f.P("}")
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
for _, m := range d.Messages {
|
||||||
|
if m == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
f.P("type ", m.Name, "Descriptor struct{")
|
||||||
|
f.P("*descriptor.Message")
|
||||||
|
for _, s := range m.Signals {
|
||||||
|
f.P(s.Name, " *descriptor.Signal")
|
||||||
|
}
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
}
|
||||||
|
f.P("// Database returns the ", d.Name(), " database descriptor.")
|
||||||
|
f.P("func (md *MessagesDescriptor) Database() *descriptor.Database {")
|
||||||
|
f.P("return d")
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
f.P("var nd = &NodesDescriptor{")
|
||||||
|
for ni, n := range d.Nodes {
|
||||||
|
f.P(n.Name, ": d.Nodes[", ni, "],")
|
||||||
|
}
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
f.P("var md = &MessagesDescriptor{")
|
||||||
|
for mi, m := range d.Messages {
|
||||||
|
if m == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
f.P(m.Name, ": &", m.Name, "Descriptor{")
|
||||||
|
f.P("Message: d.Messages[", mi, "],")
|
||||||
|
for si, s := range m.Signals {
|
||||||
|
f.P(s.Name, ": d.Messages[", mi, "].Signals[", si, "],")
|
||||||
|
}
|
||||||
|
f.P("},")
|
||||||
|
}
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
f.P("var d = ")
|
||||||
|
f.Dump(d)
|
||||||
|
f.P()
|
||||||
|
}
|
||||||
|
|
||||||
|
func MarshalFrame(f *File, m *descriptor.Message) {
|
||||||
|
f.P("// Frame returns a CAN frame representing the message.")
|
||||||
|
f.P("func (m *", messageStruct(m), ") Frame() can.Frame {")
|
||||||
|
f.P("md := ", messageDescriptor(m))
|
||||||
|
f.P("f := can.Frame{ID: md.ID, IsExtended: md.IsExtended, Length: md.Length}")
|
||||||
|
for _, s := range m.Signals {
|
||||||
|
if s.IsMultiplexed {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
f.P(
|
||||||
|
"md.", s.Name, ".Marshal", signalSuperType(s),
|
||||||
|
"(&f.Data, ", signalPrimitiveSuperType(s), "(m.", signalField(s), "))",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if mux, ok := m.MultiplexerSignal(); ok {
|
||||||
|
for _, s := range m.Signals {
|
||||||
|
if !s.IsMultiplexed {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
f.P("if m.", signalField(mux), " == ", s.MultiplexerValue, " {")
|
||||||
|
f.P(
|
||||||
|
"md.", s.Name, ".Marshal", signalSuperType(s), "(&f.Data, ", signalPrimitiveSuperType(s),
|
||||||
|
"(m.", signalField(s), "))",
|
||||||
|
)
|
||||||
|
f.P("}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.P("return f")
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
f.P("// MarshalFrame encodes the message as a CAN frame.")
|
||||||
|
f.P("func (m *", messageStruct(m), ") MarshalFrame() (can.Frame, error) {")
|
||||||
|
f.P("return m.Frame(), nil")
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
}
|
||||||
|
|
||||||
|
func UnmarshalFrame(f *File, m *descriptor.Message) {
|
||||||
|
f.P("// UnmarshalFrame decodes the message from a CAN frame.")
|
||||||
|
f.P("func (m *", messageStruct(m), ") UnmarshalFrame(f can.Frame) error {")
|
||||||
|
f.P("md := ", messageDescriptor(m))
|
||||||
|
// generate frame checks
|
||||||
|
id := func(isExtended bool) string {
|
||||||
|
if isExtended {
|
||||||
|
return "extended ID"
|
||||||
|
}
|
||||||
|
return "standard ID"
|
||||||
|
}
|
||||||
|
f.P("switch {")
|
||||||
|
f.P("case f.ID != md.ID:")
|
||||||
|
f.P(`return fmt.Errorf(`)
|
||||||
|
f.P(`"unmarshal `, m.Name, `: expects ID `, m.ID, ` (got %s with ID %d)", f.String(), f.ID,`)
|
||||||
|
f.P(`)`)
|
||||||
|
f.P("case f.Length != md.Length:")
|
||||||
|
f.P(`return fmt.Errorf(`)
|
||||||
|
f.P(`"unmarshal `, m.Name, `: expects length `, m.Length, ` (got %s with length %d)", f.String(), f.Length,`)
|
||||||
|
f.P(`)`)
|
||||||
|
f.P("case f.IsRemote:")
|
||||||
|
f.P(`return fmt.Errorf(`)
|
||||||
|
f.P(`"unmarshal `, m.Name, `: expects non-remote frame (got remote frame %s)", f.String(),`)
|
||||||
|
f.P(`)`)
|
||||||
|
f.P("case f.IsExtended != md.IsExtended:")
|
||||||
|
f.P(`return fmt.Errorf(`)
|
||||||
|
f.P(`"unmarshal `, m.Name, `: expects `, id(m.IsExtended), ` (got %s with `, id(!m.IsExtended), `)", f.String(),`)
|
||||||
|
f.P(`)`)
|
||||||
|
f.P("}")
|
||||||
|
if len(m.Signals) == 0 {
|
||||||
|
f.P("return nil")
|
||||||
|
f.P("}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// generate non-multiplexed signal unmarshaling
|
||||||
|
for _, s := range m.Signals {
|
||||||
|
if s.IsMultiplexed {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
f.P("m.", signalField(s), " = ", signalType(m, s), "(md.", s.Name, ".Unmarshal", signalSuperType(s), "(f.Data))")
|
||||||
|
}
|
||||||
|
// generate multiplexed signal unmarshaling
|
||||||
|
if mux, ok := m.MultiplexerSignal(); ok {
|
||||||
|
for _, s := range m.Signals {
|
||||||
|
if !s.IsMultiplexed {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
f.P("if m.", signalField(mux), " == ", s.MultiplexerValue, " {")
|
||||||
|
f.P("m.", signalField(s), " = ", signalType(m, s), "(md.", s.Name, ".Unmarshal", signalSuperType(s), "(f.Data))")
|
||||||
|
f.P("}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.P("return nil")
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Node(f *File, d *descriptor.Database, n *descriptor.Node) {
|
||||||
|
rxMessages := collectRxMessages(d, n)
|
||||||
|
txMessages := collectTxMessages(d, n)
|
||||||
|
f.P("type ", nodeInterface(n), " interface {")
|
||||||
|
f.P("sync.Locker")
|
||||||
|
f.P("Tx() ", txGroupInterface(n))
|
||||||
|
f.P("Rx() ", rxGroupInterface(n))
|
||||||
|
f.P("Run(ctx context.Context) error")
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
f.P("type ", rxGroupInterface(n), " interface {")
|
||||||
|
f.P("http.Handler // for debugging")
|
||||||
|
for _, m := range rxMessages {
|
||||||
|
f.P(m.Name, "() ", rxMessageInterface(n, m))
|
||||||
|
}
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
f.P("type ", txGroupInterface(n), " interface {")
|
||||||
|
f.P("http.Handler // for debugging")
|
||||||
|
for _, m := range txMessages {
|
||||||
|
f.P(m.Name, "() ", txMessageInterface(n, m))
|
||||||
|
}
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
for _, m := range rxMessages {
|
||||||
|
f.P("type ", rxMessageInterface(n, m), " interface {")
|
||||||
|
f.P(messageReaderInterface(m))
|
||||||
|
f.P("ReceiveTime() time.Time")
|
||||||
|
f.P("SetAfterReceiveHook(h func(context.Context) error)")
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
}
|
||||||
|
for _, m := range txMessages {
|
||||||
|
f.P("type ", txMessageInterface(n, m), " interface {")
|
||||||
|
f.P(messageReaderInterface(m))
|
||||||
|
f.P(messageWriterInterface(m))
|
||||||
|
f.P("TransmitTime() time.Time")
|
||||||
|
f.P("Transmit(ctx context.Context) error")
|
||||||
|
f.P("SetBeforeTransmitHook(h func(context.Context) error)")
|
||||||
|
if m.SendType == descriptor.SendTypeCyclic {
|
||||||
|
f.P("// SetCyclicTransmissionEnabled enables/disables cyclic transmission.")
|
||||||
|
f.P("SetCyclicTransmissionEnabled(bool)")
|
||||||
|
f.P("// IsCyclicTransmissionEnabled returns whether cyclic transmission is enabled/disabled.")
|
||||||
|
f.P("IsCyclicTransmissionEnabled() bool")
|
||||||
|
}
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
}
|
||||||
|
f.P("type ", nodeStruct(n), " struct {")
|
||||||
|
f.P("sync.Mutex // protects all node state")
|
||||||
|
f.P("network string")
|
||||||
|
f.P("address string")
|
||||||
|
f.P("rx ", rxGroupStruct(n))
|
||||||
|
f.P("tx ", txGroupStruct(n))
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
f.P("var _ ", nodeInterface(n), " = &", nodeStruct(n), "{}")
|
||||||
|
f.P("var _ canrunner.Node = &", nodeStruct(n), "{}")
|
||||||
|
f.P()
|
||||||
|
f.P("func New", nodeInterface(n), "(network, address string) ", nodeInterface(n), " {")
|
||||||
|
f.P("n := &", nodeStruct(n), "{network: network, address: address}")
|
||||||
|
f.P("n.rx.parentMutex = &n.Mutex")
|
||||||
|
f.P("n.tx.parentMutex = &n.Mutex")
|
||||||
|
for _, m := range rxMessages {
|
||||||
|
f.P("n.rx.", messageField(m), ".init()")
|
||||||
|
f.P("n.rx.", messageField(m), ".Reset()")
|
||||||
|
}
|
||||||
|
for _, m := range txMessages {
|
||||||
|
f.P("n.tx.", messageField(m), ".init()")
|
||||||
|
f.P("n.tx.", messageField(m), ".Reset()")
|
||||||
|
}
|
||||||
|
f.P("return n")
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
f.P("func (n *", nodeStruct(n), ") Run(ctx context.Context) error {")
|
||||||
|
f.P("return canrunner.Run(ctx, n)")
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
f.P("func (n *", nodeStruct(n), ") Rx() ", rxGroupInterface(n), " {")
|
||||||
|
f.P("return &n.rx")
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
f.P("func (n *", nodeStruct(n), ") Tx() ", txGroupInterface(n), " {")
|
||||||
|
f.P("return &n.tx")
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
f.P("type ", rxGroupStruct(n), " struct {")
|
||||||
|
f.P("parentMutex *sync.Mutex")
|
||||||
|
for _, m := range rxMessages {
|
||||||
|
f.P(messageField(m), " ", rxMessageStruct(n, m))
|
||||||
|
}
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
f.P("var _ ", rxGroupInterface(n), " = &", rxGroupStruct(n), "{}")
|
||||||
|
f.P()
|
||||||
|
f.P("func (rx *", rxGroupStruct(n), ") ServeHTTP(w http.ResponseWriter, r *http.Request) {")
|
||||||
|
f.P("rx.parentMutex.Lock()")
|
||||||
|
f.P("defer rx.parentMutex.Unlock()")
|
||||||
|
f.P("candebug.ServeMessagesHTTP(w, r, []generated.Message{")
|
||||||
|
for _, m := range rxMessages {
|
||||||
|
f.P("&rx.", messageField(m), ",")
|
||||||
|
}
|
||||||
|
f.P("})")
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
for _, m := range rxMessages {
|
||||||
|
f.P("func (rx *", rxGroupStruct(n), ") ", m.Name, "() ", rxMessageInterface(n, m), " {")
|
||||||
|
f.P("return &rx.", messageField(m))
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
}
|
||||||
|
f.P()
|
||||||
|
f.P("type ", txGroupStruct(n), " struct {")
|
||||||
|
f.P("parentMutex *sync.Mutex")
|
||||||
|
for _, m := range txMessages {
|
||||||
|
f.P(messageField(m), " ", txMessageStruct(n, m))
|
||||||
|
}
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
f.P("var _ ", txGroupInterface(n), " = &", txGroupStruct(n), "{}")
|
||||||
|
f.P()
|
||||||
|
f.P("func (tx *", txGroupStruct(n), ") ServeHTTP(w http.ResponseWriter, r *http.Request) {")
|
||||||
|
f.P("tx.parentMutex.Lock()")
|
||||||
|
f.P("defer tx.parentMutex.Unlock()")
|
||||||
|
f.P("candebug.ServeMessagesHTTP(w, r, []generated.Message{")
|
||||||
|
for _, m := range txMessages {
|
||||||
|
f.P("&tx.", messageField(m), ",")
|
||||||
|
}
|
||||||
|
f.P("})")
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
for _, m := range txMessages {
|
||||||
|
f.P("func (tx *", txGroupStruct(n), ") ", m.Name, "() ", txMessageInterface(n, m), " {")
|
||||||
|
f.P("return &tx.", messageField(m))
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
}
|
||||||
|
f.P()
|
||||||
|
f.P("func (n *", nodeStruct(n), ") Descriptor() *descriptor.Node {")
|
||||||
|
f.P("return ", nodeDescriptor(n))
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
f.P("func (n *", nodeStruct(n), ") Connect() (net.Conn, error) {")
|
||||||
|
f.P("return socketcan.Dial(n.network, n.address)")
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
f.P("func (n *", nodeStruct(n), ") ReceivedMessage(id uint32) (canrunner.ReceivedMessage, bool) {")
|
||||||
|
f.P("switch id {")
|
||||||
|
for _, m := range rxMessages {
|
||||||
|
f.P("case ", m.ID, ":")
|
||||||
|
f.P("return &n.rx.", messageField(m), ", true")
|
||||||
|
}
|
||||||
|
f.P("default:")
|
||||||
|
f.P("return nil, false")
|
||||||
|
f.P("}")
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
f.P("func (n *", nodeStruct(n), ") TransmittedMessages() []canrunner.TransmittedMessage {")
|
||||||
|
f.P("return []canrunner.TransmittedMessage{")
|
||||||
|
for _, m := range txMessages {
|
||||||
|
f.P("&n.tx.", messageField(m), ",")
|
||||||
|
}
|
||||||
|
f.P("}")
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
for _, m := range rxMessages {
|
||||||
|
f.P("type ", rxMessageStruct(n, m), " struct {")
|
||||||
|
f.P(messageStruct(m))
|
||||||
|
f.P("receiveTime time.Time")
|
||||||
|
f.P("afterReceiveHook func(context.Context) error")
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
f.P("func (m *", rxMessageStruct(n, m), ") init() {")
|
||||||
|
f.P("m.afterReceiveHook = func(context.Context) error { return nil }")
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
f.P("func (m *", rxMessageStruct(n, m), ") SetAfterReceiveHook(h func(context.Context) error) {")
|
||||||
|
f.P("m.afterReceiveHook = h")
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
f.P("func (m *", rxMessageStruct(n, m), ") AfterReceiveHook() func(context.Context) error {")
|
||||||
|
f.P("return m.afterReceiveHook")
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
f.P("func (m *", rxMessageStruct(n, m), ") ReceiveTime() time.Time {")
|
||||||
|
f.P("return m.receiveTime")
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
f.P("func (m *", rxMessageStruct(n, m), ") SetReceiveTime(t time.Time) {")
|
||||||
|
f.P("m.receiveTime = t")
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
f.P("var _ canrunner.ReceivedMessage = &", rxMessageStruct(n, m), "{}")
|
||||||
|
f.P()
|
||||||
|
}
|
||||||
|
for _, m := range txMessages {
|
||||||
|
f.P("type ", txMessageStruct(n, m), " struct {")
|
||||||
|
f.P(messageStruct(m))
|
||||||
|
f.P("transmitTime time.Time")
|
||||||
|
f.P("beforeTransmitHook func(context.Context) error")
|
||||||
|
f.P("isCyclicEnabled bool")
|
||||||
|
f.P("wakeUpChan chan struct{}")
|
||||||
|
f.P("transmitEventChan chan struct{}")
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
f.P("var _ ", txMessageInterface(n, m), " = &", txMessageStruct(n, m), "{}")
|
||||||
|
f.P("var _ canrunner.TransmittedMessage = &", txMessageStruct(n, m), "{}")
|
||||||
|
f.P()
|
||||||
|
f.P("func (m *", txMessageStruct(n, m), ") init() {")
|
||||||
|
f.P("m.beforeTransmitHook = func(context.Context) error { return nil }")
|
||||||
|
f.P("m.wakeUpChan = make(chan struct{}, 1)")
|
||||||
|
f.P("m.transmitEventChan = make(chan struct{})")
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
f.P("func (m *", txMessageStruct(n, m), ") SetBeforeTransmitHook(h func(context.Context) error) {")
|
||||||
|
f.P("m.beforeTransmitHook = h")
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
f.P("func (m *", txMessageStruct(n, m), ") BeforeTransmitHook() func(context.Context) error {")
|
||||||
|
f.P("return m.beforeTransmitHook")
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
f.P("func (m *", txMessageStruct(n, m), ") TransmitTime() time.Time {")
|
||||||
|
f.P("return m.transmitTime")
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
f.P("func (m *", txMessageStruct(n, m), ") SetTransmitTime(t time.Time) {")
|
||||||
|
f.P("m.transmitTime = t")
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
f.P("func (m *", txMessageStruct(n, m), ") IsCyclicTransmissionEnabled() bool {")
|
||||||
|
f.P("return m.isCyclicEnabled")
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
f.P("func (m *", txMessageStruct(n, m), ") SetCyclicTransmissionEnabled(b bool) {")
|
||||||
|
f.P("m.isCyclicEnabled = b")
|
||||||
|
f.P("select {")
|
||||||
|
f.P("case m.wakeUpChan <-struct{}{}:")
|
||||||
|
f.P("default:")
|
||||||
|
f.P("}")
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
f.P("func (m *", txMessageStruct(n, m), ") WakeUpChan() <-chan struct{} {")
|
||||||
|
f.P("return m.wakeUpChan")
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
f.P("func (m *", txMessageStruct(n, m), ") Transmit(ctx context.Context) error {")
|
||||||
|
f.P("select {")
|
||||||
|
f.P("case m.transmitEventChan <- struct{}{}:")
|
||||||
|
f.P("return nil")
|
||||||
|
f.P("case <-ctx.Done():")
|
||||||
|
f.P(`return fmt.Errorf("event-triggered transmit of `, m.Name, `: %w", ctx.Err())`)
|
||||||
|
f.P("}")
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
f.P("func (m *", txMessageStruct(n, m), ") TransmitEventChan() <-chan struct{} {")
|
||||||
|
f.P("return m.transmitEventChan")
|
||||||
|
f.P("}")
|
||||||
|
f.P()
|
||||||
|
f.P("var _ canrunner.TransmittedMessage = &", txMessageStruct(n, m), "{}")
|
||||||
|
f.P()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func txGroupInterface(n *descriptor.Node) string {
|
||||||
|
return n.Name + "_Tx"
|
||||||
|
}
|
||||||
|
|
||||||
|
func txGroupStruct(n *descriptor.Node) string {
|
||||||
|
return "xxx_" + n.Name + "_Tx"
|
||||||
|
}
|
||||||
|
|
||||||
|
func rxGroupInterface(n *descriptor.Node) string {
|
||||||
|
return n.Name + "_Rx"
|
||||||
|
}
|
||||||
|
|
||||||
|
func rxGroupStruct(n *descriptor.Node) string {
|
||||||
|
return "xxx_" + n.Name + "_Rx"
|
||||||
|
}
|
||||||
|
|
||||||
|
func rxMessageInterface(n *descriptor.Node, m *descriptor.Message) string {
|
||||||
|
return n.Name + "_Rx_" + m.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func rxMessageStruct(n *descriptor.Node, m *descriptor.Message) string {
|
||||||
|
return "xxx_" + n.Name + "_Rx_" + m.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func txMessageInterface(n *descriptor.Node, m *descriptor.Message) string {
|
||||||
|
return n.Name + "_Tx_" + m.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func txMessageStruct(n *descriptor.Node, m *descriptor.Message) string {
|
||||||
|
return "xxx_" + n.Name + "_Tx_" + m.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectTxMessages(d *descriptor.Database, n *descriptor.Node) []*descriptor.Message {
|
||||||
|
tx := make([]*descriptor.Message, 0, len(d.Messages))
|
||||||
|
for _, m := range d.Messages {
|
||||||
|
if m == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.SenderNode == n.Name && m.SendType != descriptor.SendTypeNone {
|
||||||
|
tx = append(tx, m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tx
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectRxMessages(d *descriptor.Database, n *descriptor.Node) []*descriptor.Message {
|
||||||
|
rx := make([]*descriptor.Message, 0, len(d.Messages))
|
||||||
|
Loop:
|
||||||
|
for _, m := range d.Messages {
|
||||||
|
if m == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range m.Signals {
|
||||||
|
for _, node := range s.ReceiverNodes {
|
||||||
|
if node != n.Name {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
rx = append(rx, m)
|
||||||
|
continue Loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rx
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasPhysicalRepresentation(s *descriptor.Signal) bool {
|
||||||
|
hasScale := s.Scale != 0 && s.Scale != 1
|
||||||
|
hasOffset := s.Offset != 0
|
||||||
|
hasRange := s.Min != 0 || s.Max != 0
|
||||||
|
var hasConstrainedRange bool
|
||||||
|
if s.IsSigned {
|
||||||
|
hasConstrainedRange = s.Min > float64(s.MinSigned()) || s.Max < float64(s.MaxSigned())
|
||||||
|
} else {
|
||||||
|
hasConstrainedRange = s.Min > 0 || s.Max < float64(s.MaxUnsigned())
|
||||||
|
}
|
||||||
|
return hasScale || hasOffset || hasRange && hasConstrainedRange
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasCustomType(s *descriptor.Signal) bool {
|
||||||
|
return len(s.ValueDescriptions) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasSendType(d *descriptor.Database) bool {
|
||||||
|
for _, m := range d.Messages {
|
||||||
|
if m == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.SendType != descriptor.SendTypeNone {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func signalType(m *descriptor.Message, s *descriptor.Signal) string {
|
||||||
|
if hasCustomType(s) {
|
||||||
|
return m.Name + "_" + s.Name
|
||||||
|
}
|
||||||
|
return signalPrimitiveType(s).String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func signalPrimitiveType(s *descriptor.Signal) types.Type {
|
||||||
|
var t types.BasicKind
|
||||||
|
switch {
|
||||||
|
case s.Length == 1:
|
||||||
|
t = types.Bool
|
||||||
|
case s.Length <= 8 && s.IsSigned:
|
||||||
|
t = types.Int8
|
||||||
|
case s.Length <= 8:
|
||||||
|
t = types.Uint8
|
||||||
|
case s.Length <= 16 && s.IsSigned:
|
||||||
|
t = types.Int16
|
||||||
|
case s.Length <= 16:
|
||||||
|
t = types.Uint16
|
||||||
|
case s.Length <= 32 && s.IsSigned:
|
||||||
|
t = types.Int32
|
||||||
|
case s.Length <= 32:
|
||||||
|
t = types.Uint32
|
||||||
|
case s.Length <= 64 && s.IsSigned:
|
||||||
|
t = types.Int64
|
||||||
|
default:
|
||||||
|
t = types.Uint64
|
||||||
|
}
|
||||||
|
return types.Typ[t]
|
||||||
|
}
|
||||||
|
|
||||||
|
func signalPrimitiveSuperType(s *descriptor.Signal) types.Type {
|
||||||
|
var t types.BasicKind
|
||||||
|
switch {
|
||||||
|
case s.Length == 1:
|
||||||
|
t = types.Bool
|
||||||
|
case s.IsSigned:
|
||||||
|
t = types.Int64
|
||||||
|
default:
|
||||||
|
t = types.Uint64
|
||||||
|
}
|
||||||
|
return types.Typ[t]
|
||||||
|
}
|
||||||
|
|
||||||
|
func signalSuperType(s *descriptor.Signal) string {
|
||||||
|
switch {
|
||||||
|
case s.Length == 1:
|
||||||
|
return "Bool"
|
||||||
|
case s.IsSigned:
|
||||||
|
return "Signed"
|
||||||
|
default:
|
||||||
|
return "Unsigned"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func nodeInterface(n *descriptor.Node) string {
|
||||||
|
return n.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func nodeStruct(n *descriptor.Node) string {
|
||||||
|
return "xxx_" + n.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func messageStruct(m *descriptor.Message) string {
|
||||||
|
return m.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func messageReaderInterface(m *descriptor.Message) string {
|
||||||
|
return m.Name + "Reader"
|
||||||
|
}
|
||||||
|
|
||||||
|
func messageWriterInterface(m *descriptor.Message) string {
|
||||||
|
return m.Name + "Writer"
|
||||||
|
}
|
||||||
|
|
||||||
|
func messageField(m *descriptor.Message) string {
|
||||||
|
return "xxx_" + m.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func signalField(s *descriptor.Signal) string {
|
||||||
|
return "xxx_" + s.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func nodeDescriptor(n *descriptor.Node) string {
|
||||||
|
return "Nodes()." + n.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func messageDescriptor(m *descriptor.Message) string {
|
||||||
|
return "Messages()." + m.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func signalDescriptor(m *descriptor.Message, s *descriptor.Signal) string {
|
||||||
|
return messageDescriptor(m) + "." + s.Name
|
||||||
|
}
|
||||||
18
pkg/can-go/internal/generate/file_test.go
Normal file
18
pkg/can-go/internal/generate/file_test.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package generate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gotest.tools/v3/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func runTestInDir(t *testing.T, dir string) func() {
|
||||||
|
// change working directory to project root
|
||||||
|
wd, err := os.Getwd()
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.NilError(t, os.Chdir(dir))
|
||||||
|
return func() {
|
||||||
|
assert.NilError(t, os.Chdir(wd))
|
||||||
|
}
|
||||||
|
}
|
||||||
17
pkg/can-go/internal/identifiers/case.go
Normal file
17
pkg/can-go/internal/identifiers/case.go
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package identifiers
|
||||||
|
|
||||||
|
import "unicode"
|
||||||
|
|
||||||
|
func IsCamelCase(s string) bool {
|
||||||
|
i := 0
|
||||||
|
for _, r := range s {
|
||||||
|
if unicode.IsDigit(r) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if i == 0 && !unicode.IsUpper(r) || !IsAlphaChar(r) && !IsNumChar(r) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
18
pkg/can-go/internal/identifiers/case_test.go
Normal file
18
pkg/can-go/internal/identifiers/case_test.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package identifiers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gotest.tools/v3/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIsCamelCase(t *testing.T) {
|
||||||
|
assert.Assert(t, IsCamelCase("SOC"))
|
||||||
|
assert.Assert(t, IsCamelCase("Camel"))
|
||||||
|
assert.Assert(t, IsCamelCase("CamelCase"))
|
||||||
|
assert.Assert(t, IsCamelCase("111CamelCaseNr"))
|
||||||
|
assert.Assert(t, !IsCamelCase("camelCase"))
|
||||||
|
assert.Assert(t, !IsCamelCase("snake_case"))
|
||||||
|
assert.Assert(t, !IsCamelCase("kebab-case"))
|
||||||
|
assert.Assert(t, !IsCamelCase("111camelCaseNr"))
|
||||||
|
}
|
||||||
9
pkg/can-go/internal/identifiers/char.go
Normal file
9
pkg/can-go/internal/identifiers/char.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package identifiers
|
||||||
|
|
||||||
|
func IsAlphaChar(r rune) bool {
|
||||||
|
return ('A' <= r && r <= 'Z') || ('a' <= r && r <= 'z')
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsNumChar(r rune) bool {
|
||||||
|
return '0' <= r && r <= '9'
|
||||||
|
}
|
||||||
23
pkg/can-go/internal/identifiers/char_test.go
Normal file
23
pkg/can-go/internal/identifiers/char_test.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package identifiers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gotest.tools/v3/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIsAlphaChar(t *testing.T) {
|
||||||
|
assert.Assert(t, IsAlphaChar('b'))
|
||||||
|
assert.Assert(t, IsAlphaChar('C'))
|
||||||
|
assert.Assert(t, !IsAlphaChar('Ö'))
|
||||||
|
assert.Assert(t, !IsAlphaChar('_'))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsNumChar(t *testing.T) {
|
||||||
|
assert.Assert(t, IsNumChar('0'))
|
||||||
|
assert.Assert(t, IsNumChar('1'))
|
||||||
|
assert.Assert(t, IsNumChar('2'))
|
||||||
|
assert.Assert(t, IsNumChar('9'))
|
||||||
|
assert.Assert(t, !IsNumChar('/'))
|
||||||
|
assert.Assert(t, !IsNumChar('a'))
|
||||||
|
}
|
||||||
50
pkg/can-go/internal/reinterpret/reinterpret.go
Normal file
50
pkg/can-go/internal/reinterpret/reinterpret.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
// Package reinterpret provides primitives for reinterpreting arbitrary-length values as signed or unsigned.
|
||||||
|
package reinterpret
|
||||||
|
|
||||||
|
// AsSigned reinterprets the provided unsigned value as a signed value.
|
||||||
|
func AsSigned(unsigned uint64, bits uint8) int64 {
|
||||||
|
switch bits {
|
||||||
|
case 8:
|
||||||
|
return int64(int8(uint8(unsigned)))
|
||||||
|
case 16:
|
||||||
|
return int64(int16(uint16(unsigned)))
|
||||||
|
case 32:
|
||||||
|
return int64(int32(uint32(unsigned)))
|
||||||
|
case 64:
|
||||||
|
return int64(unsigned)
|
||||||
|
default:
|
||||||
|
// calculate bit mask for sign bit
|
||||||
|
signBitMask := uint64(1 << (bits - 1))
|
||||||
|
// check if sign bit is set
|
||||||
|
isNegative := unsigned&signBitMask > 0
|
||||||
|
if !isNegative {
|
||||||
|
// sign bit not set means we can reinterpret the value as-is
|
||||||
|
return int64(unsigned)
|
||||||
|
}
|
||||||
|
// calculate bit mask for extracting value bits (all bits except the sign bit)
|
||||||
|
valueBitMask := signBitMask - 1
|
||||||
|
// calculate two's complement of the value bits
|
||||||
|
value := ((^unsigned) & valueBitMask) + 1
|
||||||
|
// result is the negative value of the two's complement
|
||||||
|
return -1 * int64(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsUnsigned reinterprets the provided signed value as an unsigned value.
|
||||||
|
func AsUnsigned(signed int64, bits uint8) uint64 {
|
||||||
|
switch bits {
|
||||||
|
case 8:
|
||||||
|
return uint64(uint8(int8(signed)))
|
||||||
|
case 16:
|
||||||
|
return uint64(uint16(int16(signed)))
|
||||||
|
case 32:
|
||||||
|
return uint64(uint32(int32(signed)))
|
||||||
|
case 64:
|
||||||
|
return uint64(signed)
|
||||||
|
default:
|
||||||
|
// calculate bit mask for extracting relevant bits
|
||||||
|
valueBitMask := uint64(1<<bits) - 1
|
||||||
|
// extract relevant bits
|
||||||
|
return uint64(signed) & valueBitMask
|
||||||
|
}
|
||||||
|
}
|
||||||
68
pkg/can-go/internal/reinterpret/reinterpret_test.go
Normal file
68
pkg/can-go/internal/reinterpret/reinterpret_test.go
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
package reinterpret
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gotest.tools/v3/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestReinterpretSign(t *testing.T) {
|
||||||
|
for _, tt := range []struct {
|
||||||
|
unsigned uint64
|
||||||
|
length uint8
|
||||||
|
signed int64
|
||||||
|
}{
|
||||||
|
// -1, byte aligned
|
||||||
|
{unsigned: 0xf, length: 4, signed: -1},
|
||||||
|
{unsigned: 0xff, length: 8, signed: -1},
|
||||||
|
{unsigned: 0xfff, length: 12, signed: -1},
|
||||||
|
{unsigned: 0xffff, length: 16, signed: -1},
|
||||||
|
{unsigned: 0xfffff, length: 20, signed: -1},
|
||||||
|
{unsigned: 0xffffff, length: 24, signed: -1},
|
||||||
|
{unsigned: 0xfffffff, length: 28, signed: -1},
|
||||||
|
{unsigned: 0xffffffff, length: 32, signed: -1},
|
||||||
|
{unsigned: 0xfffffffff, length: 36, signed: -1},
|
||||||
|
{unsigned: 0xffffffffff, length: 40, signed: -1},
|
||||||
|
{unsigned: 0xfffffffffff, length: 44, signed: -1},
|
||||||
|
{unsigned: 0xffffffffffff, length: 48, signed: -1},
|
||||||
|
{unsigned: 0xfffffffffffff, length: 52, signed: -1},
|
||||||
|
{unsigned: 0xffffffffffffff, length: 56, signed: -1},
|
||||||
|
{unsigned: 0xfffffffffffffff, length: 60, signed: -1},
|
||||||
|
{unsigned: 0xffffffffffffffff, length: 64, signed: -1},
|
||||||
|
// 3 bits
|
||||||
|
{unsigned: 0x0, length: 3, signed: 0},
|
||||||
|
{unsigned: 0x1, length: 3, signed: 1},
|
||||||
|
{unsigned: 0x2, length: 3, signed: 2},
|
||||||
|
{unsigned: 0x3, length: 3, signed: 3},
|
||||||
|
{unsigned: 0x4, length: 3, signed: -4},
|
||||||
|
{unsigned: 0x5, length: 3, signed: -3},
|
||||||
|
{unsigned: 0x6, length: 3, signed: -2},
|
||||||
|
{unsigned: 0x7, length: 3, signed: -1},
|
||||||
|
// 4 bits
|
||||||
|
{unsigned: 0x0, length: 4, signed: 0},
|
||||||
|
{unsigned: 0x1, length: 4, signed: 1},
|
||||||
|
{unsigned: 0x2, length: 4, signed: 2},
|
||||||
|
{unsigned: 0x3, length: 4, signed: 3},
|
||||||
|
{unsigned: 0x4, length: 4, signed: 4},
|
||||||
|
{unsigned: 0x5, length: 4, signed: 5},
|
||||||
|
{unsigned: 0x6, length: 4, signed: 6},
|
||||||
|
{unsigned: 0x7, length: 4, signed: 7},
|
||||||
|
{unsigned: 0x8, length: 4, signed: -8},
|
||||||
|
{unsigned: 0x9, length: 4, signed: -7},
|
||||||
|
{unsigned: 0xa, length: 4, signed: -6},
|
||||||
|
{unsigned: 0xb, length: 4, signed: -5},
|
||||||
|
{unsigned: 0xc, length: 4, signed: -4},
|
||||||
|
{unsigned: 0xd, length: 4, signed: -3},
|
||||||
|
{unsigned: 0xe, length: 4, signed: -2},
|
||||||
|
{unsigned: 0xf, length: 4, signed: -1},
|
||||||
|
} {
|
||||||
|
tt := tt
|
||||||
|
t.Run(fmt.Sprintf("%+v", tt), func(t *testing.T) {
|
||||||
|
assert.Equal(t, tt.signed, AsSigned(tt.unsigned, tt.length))
|
||||||
|
assert.Equal(t, tt.unsigned, AsUnsigned(tt.signed, tt.length))
|
||||||
|
assert.Equal(t, tt.signed, AsSigned(AsUnsigned(tt.signed, tt.length), tt.length))
|
||||||
|
assert.Equal(t, tt.unsigned, AsUnsigned(AsSigned(tt.unsigned, tt.length), tt.length))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
17
pkg/can-go/message.go
Normal file
17
pkg/can-go/message.go
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package can
|
||||||
|
|
||||||
|
// Message is anything that can marshal and unmarshal itself to/from a CAN frame.
|
||||||
|
type Message interface {
|
||||||
|
FrameMarshaler
|
||||||
|
FrameUnmarshaler
|
||||||
|
}
|
||||||
|
|
||||||
|
// FrameMarshaler can marshal itself to a CAN frame.
|
||||||
|
type FrameMarshaler interface {
|
||||||
|
MarshalFrame() (Frame, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FrameUnmarshaler can unmarshal itself from a CAN frame.
|
||||||
|
type FrameUnmarshaler interface {
|
||||||
|
UnmarshalFrame(Frame) error
|
||||||
|
}
|
||||||
332
pkg/can-go/payload.go
Normal file
332
pkg/can-go/payload.go
Normal file
@@ -0,0 +1,332 @@
|
|||||||
|
package can
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"math/big"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Data holds the data in a CAN frame.
|
||||||
|
//
|
||||||
|
// Layout
|
||||||
|
//
|
||||||
|
// Individual bits in the data are numbered according to the following scheme:
|
||||||
|
//
|
||||||
|
// BIT
|
||||||
|
// NUMBER
|
||||||
|
// +------+------+------+------+------+------+------+------+
|
||||||
|
// | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|
||||||
|
// BYTE +------+------+------+------+------+------+------+------+
|
||||||
|
// NUMBER
|
||||||
|
// +-----+ +------+------+------+------+------+------+------+------+
|
||||||
|
// | 0 | | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|
||||||
|
// +-----+ +------+------+------+------+------+------+------+------+
|
||||||
|
// | 1 | | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 |
|
||||||
|
// +-----+ +------+------+------+------+------+------+------+------+
|
||||||
|
// | 2 | | 23 | 22 | 21 | 20 | 19 | 18 | 17 | 16 |
|
||||||
|
// +-----+ +------+------+------+------+------+------+------+------+
|
||||||
|
// | 3 | | 31 | 30 | 29 | 28 | 27 | 26 | 25 | 24 |
|
||||||
|
// +-----+ +------+------+------+------+------+------+------+------+
|
||||||
|
// | 4 | | 39 | 38 | 37 | 36 | 35 | 34 | 33 | 32 |
|
||||||
|
// +-----+ +------+------+------+------+------+------+------+------+
|
||||||
|
// | 5 | | 47 | 46 | 45 | 44 | 43 | 42 | 41 | 40 |
|
||||||
|
// +-----+ +------+------+------+------+------+------+------+------+
|
||||||
|
// | 6 | | 55 | 54 | 53 | 52 | 51 | 50 | 49 | 48 |
|
||||||
|
// +-----+ +------+------+------+------+------+------+------+------+
|
||||||
|
// | 7 | | 63 | 62 | 61 | 60 | 59 | 58 | 57 | 56 |
|
||||||
|
// +-----+ +------+------+------+------+------+------+------+------+
|
||||||
|
//
|
||||||
|
// Bit ranges can be manipulated using little-endian and big-endian bit ordering.
|
||||||
|
//
|
||||||
|
// Little-endian bit ranges
|
||||||
|
//
|
||||||
|
// Example range of length 32 starting at bit 29:
|
||||||
|
//
|
||||||
|
// BIT
|
||||||
|
// NUMBER
|
||||||
|
// +------+------+------+------+------+------+------+------+
|
||||||
|
// | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|
||||||
|
// BYTE +------+------+------+------+------+------+------+------+
|
||||||
|
// NUMBER
|
||||||
|
// +-----+ +------+------+------+------+------+------+------+------+
|
||||||
|
// | 0 | | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|
||||||
|
// +-----+ +------+------+------+------+------+------+------+------+
|
||||||
|
// | 1 | | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 |
|
||||||
|
// +-----+ +------+------+------+------+------+------+------+------+
|
||||||
|
// | 2 | | 23 | 22 | 21 | 20 | 19 | 18 | 17 | 16 |
|
||||||
|
// +-----+ +------+------+------+------+------+------+------+------+
|
||||||
|
// | 3 | | <-------------LSb | 28 | 27 | 26 | 25 | 24 |
|
||||||
|
// +-----+ +------+------+------+------+------+------+------+------+
|
||||||
|
// | 4 | | <-------------------------------------------------- |
|
||||||
|
// +-----+ +------+------+------+------+------+------+------+------+
|
||||||
|
// | 5 | | <-------------------------------------------------- |
|
||||||
|
// +-----+ +------+------+------+------+------+------+------+------+
|
||||||
|
// | 6 | | <-------------------------------------------------- |
|
||||||
|
// +-----+ +------+------+------+------+------+------+------+------+
|
||||||
|
// | 7 | | 63 | 62 | 61 | <-MSb--------------------------- |
|
||||||
|
// +-----+ +------+------+------+------+------+------+------+------+
|
||||||
|
//
|
||||||
|
// Big-endian bit ranges
|
||||||
|
//
|
||||||
|
// Example range of length 32 starting at bit 29:
|
||||||
|
//
|
||||||
|
// BIT
|
||||||
|
// NUMBER
|
||||||
|
// +------+------+------+------+------+------+------+------+
|
||||||
|
// | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|
||||||
|
// BYTE +------+------+------+------+------+------+------+------+
|
||||||
|
// NUMBER
|
||||||
|
// +-----+ +------+------+------+------+------+------+------+------+
|
||||||
|
// | 0 | | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|
||||||
|
// +-----+ +------+------+------+------+------+------+------+------+
|
||||||
|
// | 1 | | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 |
|
||||||
|
// +-----+ +------+------+------+------+------+------+------+------+
|
||||||
|
// | 2 | | 23 | 22 | 21 | 20 | 19 | 18 | 17 | 16 |
|
||||||
|
// +-----+ +------+------+------+------+------+------+------+------+
|
||||||
|
// | 3 | | 31 | 30 | <-MSb--------------------------------- |
|
||||||
|
// +-----+ +------+------+------+------+------+------+------+------+
|
||||||
|
// | 4 | | <-------------------------------------------------- |
|
||||||
|
// +-----+ +------+------+------+------+------+------+------+------+
|
||||||
|
// | 5 | | <-------------------------------------------------- |
|
||||||
|
// +-----+ +------+------+------+------+------+------+------+------+
|
||||||
|
// | 6 | | <-------------------------------------------------- |
|
||||||
|
// +-----+ +------+------+------+------+------+------+------+------+
|
||||||
|
// | 7 | | <------LSb | 61 | 60 | 59 | 58 | 57 | 56 |
|
||||||
|
// +-----+ +------+------+------+------+------+------+------+------+
|
||||||
|
|
||||||
|
type Payload struct {
|
||||||
|
// Binary data
|
||||||
|
Data []byte
|
||||||
|
|
||||||
|
// Packed little endian
|
||||||
|
PackedLittleEndian *big.Int
|
||||||
|
|
||||||
|
// Packed big endian
|
||||||
|
PackedBigEndian *big.Int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hex returns the hexadecimal representation of the byte array in a Payload.
|
||||||
|
func (p *Payload) Hex() string {
|
||||||
|
h := hex.EncodeToString(p.Data)
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
// PayloadFromHex generates a Payload from a hexadecimal string.
|
||||||
|
func PayloadFromHex(hexString string) (Payload, error) {
|
||||||
|
b, err := hex.DecodeString(hexString)
|
||||||
|
var p Payload
|
||||||
|
if err != nil {
|
||||||
|
return p, err
|
||||||
|
}
|
||||||
|
p = Payload{Data: b}
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnsignedBitsLittleEndian returns the little-endian bit range [start, start+length) as an unsigned value.
|
||||||
|
func (p *Payload) UnsignedBitsLittleEndian(start, length uint16) uint64 {
|
||||||
|
// pack bits into one continuous value
|
||||||
|
packed := p.PackLittleEndian()
|
||||||
|
// lsb index in the packed value is the start bit
|
||||||
|
lsbIndex := uint(start)
|
||||||
|
// shift away lower bits
|
||||||
|
shifted := packed.Rsh(packed, lsbIndex)
|
||||||
|
// mask away higher bits
|
||||||
|
masked := shifted.And(shifted, big.NewInt((1<<length)-1))
|
||||||
|
// done
|
||||||
|
return masked.Uint64()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnsignedBitsBigEndian returns the big-endian bit range [start, start+length) as an unsigned value.
|
||||||
|
func (p *Payload) UnsignedBitsBigEndian(start, length uint16) uint64 {
|
||||||
|
// pack bits into one continuous value
|
||||||
|
packed := p.PackBigEndian()
|
||||||
|
// calculate msb index in the packed value
|
||||||
|
msbIndex := p.invertEndian(start)
|
||||||
|
// calculate lsb index in the packed value
|
||||||
|
lsbIndex := uint(msbIndex - length + 1)
|
||||||
|
// shift away lower bits
|
||||||
|
shifted := packed.Rsh(packed, lsbIndex)
|
||||||
|
// mask away higher bits
|
||||||
|
masked := shifted.And(shifted, big.NewInt((1<<length)-1))
|
||||||
|
// done
|
||||||
|
return masked.Uint64()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignedBitsLittleEndian returns little-endian bit range [start, start+length) as a signed value.
|
||||||
|
func (p *Payload) SignedBitsLittleEndian(start, length uint16) int64 {
|
||||||
|
unsigned := p.UnsignedBitsLittleEndian(start, length)
|
||||||
|
return AsSigned(unsigned, length)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignedBitsBigEndian returns little-endian bit range [start, start+length) as a signed value.
|
||||||
|
func (p *Payload) SignedBitsBigEndian(start, length uint16) int64 {
|
||||||
|
unsigned := p.UnsignedBitsBigEndian(start, length)
|
||||||
|
return AsSigned(unsigned, length)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Implement SetUnsignedBitsLittleEndian for Payload.
|
||||||
|
// SetUnsignedBitsLittleEndian sets the little-endian bit range [start, start+length) to the provided unsigned value.
|
||||||
|
// func (d *Data) SetUnsignedBitsLittleEndian(start, length uint8, value uint64) {
|
||||||
|
// // pack bits into one continuous value
|
||||||
|
// packed := d.PackLittleEndian()
|
||||||
|
// // lsb index in the packed value is the start bit
|
||||||
|
// lsbIndex := start
|
||||||
|
// // calculate bit mask for zeroing the bit range to set
|
||||||
|
// unsetMask := ^uint64(((1 << length) - 1) << lsbIndex)
|
||||||
|
// // calculate bit mask for setting the new value
|
||||||
|
// setMask := value << lsbIndex
|
||||||
|
// // calculate the new packed value
|
||||||
|
// newPacked := packed&unsetMask | setMask
|
||||||
|
// // unpack the new packed value into the data
|
||||||
|
// d.UnpackLittleEndian(newPacked)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// TODO: Implement SetUnsignedBitsBigEndian for Payload.
|
||||||
|
// SetUnsignedBitsBigEndian sets the big-endian bit range [start, start+length) to the provided unsigned value.
|
||||||
|
// func (d *Data) SetUnsignedBitsBigEndian(start, length uint8, value uint64) {
|
||||||
|
// // pack bits into one continuous value
|
||||||
|
// packed := d.PackBigEndian()
|
||||||
|
// // calculate msb index in the packed value
|
||||||
|
// msbIndex := invertEndian(start)
|
||||||
|
// // calculate lsb index in the packed value
|
||||||
|
// lsbIndex := msbIndex - length + 1
|
||||||
|
// // calculate bit mask for zeroing the bit range to set
|
||||||
|
// unsetMask := ^uint64(((1 << length) - 1) << lsbIndex)
|
||||||
|
// // calculate bit mask for setting the new value
|
||||||
|
// setMask := value << lsbIndex
|
||||||
|
// // calculate the new packed value
|
||||||
|
// newPacked := packed&unsetMask | setMask
|
||||||
|
// // unpack the new packed value into the data
|
||||||
|
// d.UnpackBigEndian(newPacked)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// TODO: Implement SetSignedBitsLittleEndian for Payload.
|
||||||
|
// SetSignedBitsLittleEndian sets the little-endian bit range [start, start+length) to the provided signed value.
|
||||||
|
// func (d *Data) SetSignedBitsLittleEndian(start, length uint8, value int64) {
|
||||||
|
// d.SetUnsignedBitsLittleEndian(start, length, reinterpret.AsUnsigned(value, length))
|
||||||
|
// }
|
||||||
|
|
||||||
|
// TODO: Implement SetSignedBitsBigEndian for Payload.
|
||||||
|
// SetSignedBitsBigEndian sets the big-endian bit range [start, start+length) to the provided signed value.
|
||||||
|
// func (d *Data) SetSignedBitsBigEndian(start, length uint8, value int64) {
|
||||||
|
// d.SetUnsignedBitsBigEndian(start, length, reinterpret.AsUnsigned(value, length))
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Bit returns the value of the i:th bit in the data as a bool.
|
||||||
|
func (p *Payload) Bit(i uint16) bool {
|
||||||
|
if int(i) > 8*len(p.Data)-1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// calculate which byte the bit belongs to
|
||||||
|
byteIndex := i / 8
|
||||||
|
// calculate bit mask for extracting the bit
|
||||||
|
bitMask := uint8(1 << (i % 8))
|
||||||
|
// mocks the bit
|
||||||
|
bit := p.Data[byteIndex]&bitMask > 0
|
||||||
|
// done
|
||||||
|
return bit
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBit sets the value of the i:th bit in the data.
|
||||||
|
func (p *Payload) SetBit(i uint16, value bool) {
|
||||||
|
if int(i) > 8*len(p.Data)-1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
byteIndex := i / 8
|
||||||
|
bitIndex := i % 8
|
||||||
|
if value {
|
||||||
|
p.Data[byteIndex] |= uint8(1 << bitIndex)
|
||||||
|
} else {
|
||||||
|
p.Data[byteIndex] &= ^uint8(1 << bitIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PackLittleEndian packs the byte array into a continuous little endian big.Int.
|
||||||
|
func (p *Payload) PackLittleEndian() *big.Int {
|
||||||
|
if p.PackedLittleEndian == nil {
|
||||||
|
packed := new(big.Int).SetBytes(reverse(p.Data))
|
||||||
|
p.PackedLittleEndian = packed
|
||||||
|
}
|
||||||
|
return new(big.Int).Set(p.PackedLittleEndian)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reverse byte array for little endian signals.
|
||||||
|
func reverse(data []byte) []byte {
|
||||||
|
reversedArray := make([]byte, len(data))
|
||||||
|
for i, j := 0, len(data)-1; i < j; i, j = i+1, j-1 {
|
||||||
|
reversedArray[i], reversedArray[j] = data[j], data[i]
|
||||||
|
}
|
||||||
|
return reversedArray
|
||||||
|
}
|
||||||
|
|
||||||
|
// PackBigEndian packs the byte array into a continuous big endian big.Int.
|
||||||
|
func (p *Payload) PackBigEndian() *big.Int {
|
||||||
|
if p.PackedBigEndian == nil {
|
||||||
|
packed := new(big.Int).SetBytes(p.Data)
|
||||||
|
p.PackedBigEndian = packed
|
||||||
|
}
|
||||||
|
return new(big.Int).Set(p.PackedBigEndian)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Implement UnpackLittleEndian for Payload.
|
||||||
|
// UnpackLittleEndian sets the value of d.Bytes by unpacking the provided value as sequential little-endian bits.
|
||||||
|
// func (d *Data) UnpackLittleEndian(packed uint64) {
|
||||||
|
// d[0] = uint8(packed >> (0 * 8))
|
||||||
|
// d[1] = uint8(packed >> (1 * 8))
|
||||||
|
// d[2] = uint8(packed >> (2 * 8))
|
||||||
|
// d[3] = uint8(packed >> (3 * 8))
|
||||||
|
// d[4] = uint8(packed >> (4 * 8))
|
||||||
|
// d[5] = uint8(packed >> (5 * 8))
|
||||||
|
// d[6] = uint8(packed >> (6 * 8))
|
||||||
|
// d[7] = uint8(packed >> (7 * 8))
|
||||||
|
// }
|
||||||
|
|
||||||
|
// TODO: Implement UnpackBigEndian for Payload.
|
||||||
|
// UnpackBigEndian sets the value of d.Bytes by unpacking the provided value as sequential big-endian bits.
|
||||||
|
// func (d *Data) UnpackBigEndian(packed uint64) {
|
||||||
|
// d[0] = uint8(packed >> (7 * 8))
|
||||||
|
// d[1] = uint8(packed >> (6 * 8))
|
||||||
|
// d[2] = uint8(packed >> (5 * 8))
|
||||||
|
// d[3] = uint8(packed >> (4 * 8))
|
||||||
|
// d[4] = uint8(packed >> (3 * 8))
|
||||||
|
// d[5] = uint8(packed >> (2 * 8))
|
||||||
|
// d[6] = uint8(packed >> (1 * 8))
|
||||||
|
// d[7] = uint8(packed >> (0 * 8))
|
||||||
|
// }
|
||||||
|
|
||||||
|
// invertEndian converts from big-endian to little-endian bit indexing and vice versa.
|
||||||
|
func (p *Payload) invertEndian(i uint16) uint16 {
|
||||||
|
row := i / 8
|
||||||
|
col := i % 8
|
||||||
|
oppositeRow := uint16(len(p.Data)) - row - 1
|
||||||
|
bitIndex := (oppositeRow * 8) + col
|
||||||
|
return bitIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsSigned reinterprets the provided unsigned value as a signed value.
|
||||||
|
func AsSigned(unsigned uint64, bits uint16) int64 {
|
||||||
|
switch bits {
|
||||||
|
case 8:
|
||||||
|
return int64(int8(uint8(unsigned)))
|
||||||
|
case 16:
|
||||||
|
return int64(int16(uint16(unsigned)))
|
||||||
|
case 32:
|
||||||
|
return int64(int32(uint32(unsigned)))
|
||||||
|
case 64:
|
||||||
|
return int64(unsigned)
|
||||||
|
default:
|
||||||
|
// calculate bit mask for sign bit
|
||||||
|
signBitMask := uint64(1 << (bits - 1))
|
||||||
|
// check if sign bit is set
|
||||||
|
isNegative := unsigned&signBitMask > 0
|
||||||
|
if !isNegative {
|
||||||
|
// sign bit not set means we can reinterpret the value as-is
|
||||||
|
return int64(unsigned)
|
||||||
|
}
|
||||||
|
// calculate bit mask for extracting value bits (all bits except the sign bit)
|
||||||
|
valueBitMask := signBitMask - 1
|
||||||
|
// calculate two's complement of the value bits
|
||||||
|
value := ((^unsigned) & valueBitMask) + 1
|
||||||
|
// result is the negative value of the two's complement
|
||||||
|
return -1 * int64(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
99
pkg/can-go/payload_test.go
Normal file
99
pkg/can-go/payload_test.go
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
package can
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type signals struct {
|
||||||
|
start uint16
|
||||||
|
length uint16
|
||||||
|
unsigned uint64
|
||||||
|
signed int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPackLittleEndian(t *testing.T) {
|
||||||
|
// 302064448
|
||||||
|
// 10010000000010010001101000000
|
||||||
|
data := []byte{0x40, 0x23, 0x01, 0x12}
|
||||||
|
payload := Payload{Data: data}
|
||||||
|
|
||||||
|
dataLittleEndian := payload.PackLittleEndian()
|
||||||
|
fmt.Println(dataLittleEndian)
|
||||||
|
fmt.Printf("%b\n", dataLittleEndian)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPackBigEndian(t *testing.T) {
|
||||||
|
// 4621538819433299968
|
||||||
|
// 100000000100011000000010001001000000000000000000000000000000000
|
||||||
|
data := []byte{0x40, 0x23, 0x01, 0x12}
|
||||||
|
payload := Payload{Data: data}
|
||||||
|
|
||||||
|
dataBigEndian := payload.PackBigEndian()
|
||||||
|
fmt.Println(dataBigEndian)
|
||||||
|
fmt.Printf("%b\n", dataBigEndian)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnsignedLittleEndian(t *testing.T) {
|
||||||
|
// 18
|
||||||
|
data := []byte{0x40, 0x23, 0x01, 0x12}
|
||||||
|
payload := Payload{Data: data}
|
||||||
|
signal := signals{start: 24, length: 8, unsigned: 0x12, signed: 18}
|
||||||
|
fmt.Println(payload.UnsignedBitsLittleEndian(signal.start, signal.length))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnsignedBigEndian(t *testing.T) {
|
||||||
|
// 3219
|
||||||
|
data := []byte{0x3f, 0xf7, 0x0d, 0xc4, 0x0c, 0x93, 0xff, 0xff}
|
||||||
|
payload := Payload{Data: data}
|
||||||
|
signal := signals{start: 39, length: 16, unsigned: 0xc93, signed: 3219}
|
||||||
|
fmt.Println(payload.UnsignedBitsBigEndian(signal.start, signal.length))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSignedLittleEndian(t *testing.T) {
|
||||||
|
// -1
|
||||||
|
data := []byte{0x80, 0x01}
|
||||||
|
payload := Payload{Data: data}
|
||||||
|
signal := signals{start: 7, length: 2, unsigned: 0x3, signed: -1}
|
||||||
|
fmt.Println(payload.SignedBitsLittleEndian(signal.start, signal.length))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSignedBigEndian(t *testing.T) {
|
||||||
|
// -9
|
||||||
|
data := []byte{0x3f, 0xf7, 0x0d, 0xc4, 0x0c, 0x93, 0xff, 0xff}
|
||||||
|
payload := Payload{Data: data}
|
||||||
|
signal := signals{start: 3, length: 12, unsigned: 0xff7, signed: -9}
|
||||||
|
fmt.Println(payload.SignedBitsBigEndian(signal.start, signal.length))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark4BytesPayload_PackLittleEndian(b *testing.B) {
|
||||||
|
data := []byte{0x40, 0x23, 0x01, 0x12}
|
||||||
|
payload := Payload{Data: data}
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = payload.PackLittleEndian()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark4BytesPayload_PackBigEndian(b *testing.B) {
|
||||||
|
data := []byte{0x40, 0x23, 0x01, 0x12}
|
||||||
|
payload := Payload{Data: data}
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = payload.PackBigEndian()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark4BytesPayload_UnsignedBitsLittleEndian(b *testing.B) {
|
||||||
|
data := []byte{0x40, 0x23, 0x01, 0x12}
|
||||||
|
payload := Payload{Data: data}
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = payload.UnsignedBitsLittleEndian(0, 16)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark4BytesPayload_UnsignedBitsBigEndian(b *testing.B) {
|
||||||
|
data := []byte{0x40, 0x23, 0x01, 0x12}
|
||||||
|
payload := Payload{Data: data}
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = payload.UnsignedBitsBigEndian(0, 16)
|
||||||
|
}
|
||||||
|
}
|
||||||
98
pkg/can-go/pkg/candebug/http.go
Normal file
98
pkg/can-go/pkg/candebug/http.go
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
package candebug
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"net/http"
|
||||||
|
"path"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/cantext"
|
||||||
|
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/descriptor"
|
||||||
|
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/generated"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ServeMessagesHTTP(w http.ResponseWriter, r *http.Request, msgs []generated.Message) {
|
||||||
|
base := path.Base(r.URL.Path)
|
||||||
|
// if path ends with a message name, serve only that message
|
||||||
|
for _, m := range msgs {
|
||||||
|
if m.Descriptor().Name == base {
|
||||||
|
serveMessagesHTTP(w, r, []generated.Message{m})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
serveMessagesHTTP(w, r, msgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func serveMessagesHTTP(w http.ResponseWriter, _ *http.Request, msgs []generated.Message) {
|
||||||
|
var buf []byte
|
||||||
|
for i, m := range msgs {
|
||||||
|
buf = appendMessage(buf, m)
|
||||||
|
if i != len(msgs)-1 {
|
||||||
|
buf = append(buf, "\n\n\n"...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
_, _ = w.Write(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendMessage(buf []byte, m generated.Message) []byte {
|
||||||
|
name := m.Descriptor().Name
|
||||||
|
sep := append(bytes.Repeat([]byte{'='}, len(name)), '\n')
|
||||||
|
buf = append(buf, name...)
|
||||||
|
buf = append(buf, '\n')
|
||||||
|
buf = append(buf, sep...)
|
||||||
|
buf = cantext.AppendID(buf, m.Descriptor())
|
||||||
|
buf = append(buf, '\n')
|
||||||
|
buf = cantext.AppendSender(buf, m.Descriptor())
|
||||||
|
buf = append(buf, '\n')
|
||||||
|
buf = cantext.AppendSendType(buf, m.Descriptor())
|
||||||
|
buf = append(buf, '\n')
|
||||||
|
if m.Descriptor().SendType == descriptor.SendTypeCyclic {
|
||||||
|
if enabler, ok := m.(interface{ IsCyclicTransmissionEnabled() bool }); ok {
|
||||||
|
buf = append(buf, "Enabled: "...)
|
||||||
|
buf = strconv.AppendBool(buf, enabler.IsCyclicTransmissionEnabled())
|
||||||
|
buf = append(buf, '\n')
|
||||||
|
}
|
||||||
|
buf = cantext.AppendCycleTime(buf, m.Descriptor())
|
||||||
|
buf = append(buf, '\n')
|
||||||
|
}
|
||||||
|
if m.Descriptor().DelayTime != 0 {
|
||||||
|
buf = cantext.AppendDelayTime(buf, m.Descriptor())
|
||||||
|
buf = append(buf, '\n')
|
||||||
|
}
|
||||||
|
buf = append(buf, sep...)
|
||||||
|
if timer, ok := m.(interface{ ReceiveTime() time.Time }); ok {
|
||||||
|
buf = append(buf, "Received: "...)
|
||||||
|
buf = appendTime(buf, timer.ReceiveTime())
|
||||||
|
buf = append(buf, '\n')
|
||||||
|
buf = append(buf, sep...)
|
||||||
|
}
|
||||||
|
if timer, ok := m.(interface{ TransmitTime() time.Time }); ok {
|
||||||
|
buf = append(buf, "Transmitted: "...)
|
||||||
|
buf = appendTime(buf, timer.TransmitTime())
|
||||||
|
buf = append(buf, '\n')
|
||||||
|
buf = append(buf, sep...)
|
||||||
|
}
|
||||||
|
f := m.Frame()
|
||||||
|
for i, s := range m.Descriptor().Signals {
|
||||||
|
buf = cantext.AppendSignal(buf, s, f.Data)
|
||||||
|
if i < len(m.Descriptor().Signals)-1 {
|
||||||
|
buf = append(buf, '\n')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendTime(buf []byte, t time.Time) []byte {
|
||||||
|
if t.IsZero() {
|
||||||
|
buf = append(buf, "never"...)
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
buf = append(buf, time.Since(t).String()...)
|
||||||
|
buf = append(buf, " ago ("...)
|
||||||
|
buf = t.AppendFormat(buf, "15:04:05.000000000")
|
||||||
|
buf = append(buf, ")"...)
|
||||||
|
return buf
|
||||||
|
}
|
||||||
142
pkg/can-go/pkg/candebug/http_test.go
Normal file
142
pkg/can-go/pkg/candebug/http_test.go
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
package candebug
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
can "github.com/fiskerinc/cloud-services/pkg/can-go"
|
||||||
|
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/descriptor"
|
||||||
|
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/generated"
|
||||||
|
"gotest.tools/v3/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestServeMessagesHTTP_Single(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ServeMessagesHTTP(w, r, []generated.Message{
|
||||||
|
&testMessage{
|
||||||
|
frame: can.Frame{ID: 100, Length: 1},
|
||||||
|
descriptor: newDriverHeartbeatDescriptor(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
res, err := http.Get(ts.URL)
|
||||||
|
assert.NilError(t, err)
|
||||||
|
response, err := ioutil.ReadAll(res.Body)
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.NilError(t, res.Body.Close())
|
||||||
|
const expected = `
|
||||||
|
DriverHeartbeat
|
||||||
|
===============
|
||||||
|
ID: 100 (0x64)
|
||||||
|
Sender: DRIVER
|
||||||
|
SendType: Cyclic
|
||||||
|
CycleTime: 100ms
|
||||||
|
DelayTime: 2s
|
||||||
|
===============
|
||||||
|
Command: 0 (0x0) None
|
||||||
|
`
|
||||||
|
assert.Equal(t, strings.TrimSpace(expected), string(response))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServeMessagesHTTP_Multi(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ServeMessagesHTTP(w, r, []generated.Message{
|
||||||
|
&testMessage{
|
||||||
|
frame: can.Frame{ID: 100, Length: 1},
|
||||||
|
descriptor: newDriverHeartbeatDescriptor(),
|
||||||
|
},
|
||||||
|
&testMessage{
|
||||||
|
frame: can.Frame{ID: 100, Length: 1, Data: can.Data{0x01}},
|
||||||
|
descriptor: newDriverHeartbeatDescriptor(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
res, err := http.Get(ts.URL)
|
||||||
|
assert.NilError(t, err)
|
||||||
|
response, err := ioutil.ReadAll(res.Body)
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.NilError(t, res.Body.Close())
|
||||||
|
const expected = `
|
||||||
|
DriverHeartbeat
|
||||||
|
===============
|
||||||
|
ID: 100 (0x64)
|
||||||
|
Sender: DRIVER
|
||||||
|
SendType: Cyclic
|
||||||
|
CycleTime: 100ms
|
||||||
|
DelayTime: 2s
|
||||||
|
===============
|
||||||
|
Command: 0 (0x0) None
|
||||||
|
|
||||||
|
|
||||||
|
DriverHeartbeat
|
||||||
|
===============
|
||||||
|
ID: 100 (0x64)
|
||||||
|
Sender: DRIVER
|
||||||
|
SendType: Cyclic
|
||||||
|
CycleTime: 100ms
|
||||||
|
DelayTime: 2s
|
||||||
|
===============
|
||||||
|
Command: 1 (0x1) Sync
|
||||||
|
`
|
||||||
|
assert.Equal(t, strings.TrimSpace(expected), string(response))
|
||||||
|
}
|
||||||
|
|
||||||
|
type testMessage struct {
|
||||||
|
frame can.Frame
|
||||||
|
descriptor *descriptor.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *testMessage) Frame() can.Frame {
|
||||||
|
return m.frame
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *testMessage) Descriptor() *descriptor.Message {
|
||||||
|
return m.descriptor
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *testMessage) MarshalFrame() (can.Frame, error) {
|
||||||
|
panic("should not be called")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (testMessage) Reset() {
|
||||||
|
panic("should not be called")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (testMessage) String() string {
|
||||||
|
panic("should not be called")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (testMessage) UnmarshalFrame(can.Frame) error {
|
||||||
|
panic("should not be called")
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDriverHeartbeatDescriptor() *descriptor.Message {
|
||||||
|
return &descriptor.Message{
|
||||||
|
Name: "DriverHeartbeat",
|
||||||
|
SenderNode: "DRIVER",
|
||||||
|
ID: 100,
|
||||||
|
Length: 1,
|
||||||
|
Description: "Sync message used to synchronize the controllers",
|
||||||
|
SendType: descriptor.SendTypeCyclic,
|
||||||
|
CycleTime: 100 * time.Millisecond,
|
||||||
|
DelayTime: 2 * time.Second,
|
||||||
|
Signals: []*descriptor.Signal{
|
||||||
|
{
|
||||||
|
Name: "Command",
|
||||||
|
Start: 0,
|
||||||
|
Length: 8,
|
||||||
|
Scale: 1,
|
||||||
|
ValueDescriptions: []*descriptor.ValueDescription{
|
||||||
|
{Value: 0, Description: "None"},
|
||||||
|
{Value: 1, Description: "Sync"},
|
||||||
|
{Value: 2, Description: "Reboot"},
|
||||||
|
},
|
||||||
|
ReceiverNodes: []string{"SENSOR", "MOTOR"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
105
pkg/can-go/pkg/canjson/encode.go
Normal file
105
pkg/can-go/pkg/canjson/encode.go
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
package canjson
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
can "github.com/fiskerinc/cloud-services/pkg/can-go"
|
||||||
|
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/descriptor"
|
||||||
|
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/generated"
|
||||||
|
)
|
||||||
|
|
||||||
|
// preAllocatedBytesPerSignal is an estimate of how many bytes each signal needs.
|
||||||
|
const preAllocatedBytesPerSignal = 40
|
||||||
|
|
||||||
|
// Marshal a CAN message to JSON.
|
||||||
|
func Marshal(m generated.Message) ([]byte, error) {
|
||||||
|
f := m.Frame()
|
||||||
|
bytes := make([]byte, 0, len(m.Descriptor().Signals)*preAllocatedBytesPerSignal)
|
||||||
|
bytes = append(bytes, '{')
|
||||||
|
for i, s := range m.Descriptor().Signals {
|
||||||
|
s := s
|
||||||
|
bytes = append(bytes, '"')
|
||||||
|
bytes = append(bytes, s.Name...)
|
||||||
|
bytes = append(bytes, `":`...)
|
||||||
|
sig := &signal{}
|
||||||
|
sig.set(s, f)
|
||||||
|
jsonSig, err := json.Marshal(sig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("marshal json: %w", err)
|
||||||
|
}
|
||||||
|
bytes = append(bytes, jsonSig...)
|
||||||
|
if i < len(m.Descriptor().Signals)-1 {
|
||||||
|
bytes = append(bytes, ',')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bytes = append(bytes, '}')
|
||||||
|
return bytes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type signal struct {
|
||||||
|
Raw json.Number
|
||||||
|
Physical json.Number
|
||||||
|
Unit string `json:",omitempty"`
|
||||||
|
Description string `json:",omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *signal) set(desc *descriptor.Signal, f can.Frame) {
|
||||||
|
switch {
|
||||||
|
case desc.Length == 1: // bool
|
||||||
|
s.setBoolValue(desc.UnmarshalBool(f.Data), desc)
|
||||||
|
case desc.IsSigned: // signed
|
||||||
|
s.setSignedValue(desc.UnmarshalSigned(f.Data), desc)
|
||||||
|
default: // unsigned
|
||||||
|
s.setUnsignedValue(desc.UnmarshalUnsigned(f.Data), desc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *signal) setUnsignedValue(value uint64, desc *descriptor.Signal) {
|
||||||
|
s.Raw = uintToJSON(value)
|
||||||
|
s.Physical = floatToJSON(desc.ToPhysical(float64(value)))
|
||||||
|
s.Unit = desc.Unit
|
||||||
|
if value, ok := desc.ValueDescription(int(value)); ok {
|
||||||
|
s.Description = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *signal) setSignedValue(value int64, desc *descriptor.Signal) {
|
||||||
|
s.Raw = intToJSON(value)
|
||||||
|
s.Physical = floatToJSON(desc.ToPhysical(float64(value)))
|
||||||
|
s.Unit = desc.Unit
|
||||||
|
if value, ok := desc.ValueDescription(int(value)); ok {
|
||||||
|
s.Description = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *signal) setBoolValue(value bool, desc *descriptor.Signal) {
|
||||||
|
if value {
|
||||||
|
s.Raw = "1"
|
||||||
|
s.Physical = floatToJSON(desc.ToPhysical(1))
|
||||||
|
} else {
|
||||||
|
s.Raw = "0"
|
||||||
|
s.Physical = floatToJSON(desc.ToPhysical(0))
|
||||||
|
}
|
||||||
|
s.Unit = desc.Unit
|
||||||
|
intValue := 0
|
||||||
|
if value {
|
||||||
|
intValue = 1
|
||||||
|
}
|
||||||
|
if value, ok := desc.ValueDescription(intValue); ok {
|
||||||
|
s.Description = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func floatToJSON(f float64) json.Number {
|
||||||
|
return json.Number(strconv.FormatFloat(f, 'f', -1, 64))
|
||||||
|
}
|
||||||
|
|
||||||
|
func intToJSON(i int64) json.Number {
|
||||||
|
return json.Number(strconv.Itoa(int(i)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func uintToJSON(i uint64) json.Number {
|
||||||
|
return json.Number(strconv.Itoa(int(i)))
|
||||||
|
}
|
||||||
19
pkg/can-go/pkg/canjson/encode_test.go
Normal file
19
pkg/can-go/pkg/canjson/encode_test.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package canjson
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
examplecan "github.com/fiskerinc/cloud-services/pkg/can-go/testdata/gen/go/example"
|
||||||
|
"gotest.tools/v3/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMarshal(t *testing.T) {
|
||||||
|
driverHeartbeat := examplecan.NewDriverHeartbeat().SetCommand(examplecan.DriverHeartbeat_Command_Reboot)
|
||||||
|
js, err := Marshal(driverHeartbeat)
|
||||||
|
assert.NilError(t, err)
|
||||||
|
expected := strings.TrimSpace(`
|
||||||
|
{"Command":{"Raw":2,"Physical":2,"Description":"Reboot"}}
|
||||||
|
`)
|
||||||
|
assert.Equal(t, expected, string(js))
|
||||||
|
}
|
||||||
204
pkg/can-go/pkg/canrunner/run.go
Normal file
204
pkg/can-go/pkg/canrunner/run.go
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
package canrunner
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
can "github.com/fiskerinc/cloud-services/pkg/can-go"
|
||||||
|
"github.com/fiskerinc/cloud-services/pkg/can-go/internal/clock"
|
||||||
|
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/descriptor"
|
||||||
|
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/generated"
|
||||||
|
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/socketcan"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
)
|
||||||
|
|
||||||
|
// defaultSendTimeout is the send timeout used for messages without a cycle time.
|
||||||
|
const defaultSendTimeout = time.Second
|
||||||
|
|
||||||
|
// Node is an interface for a CAN node to be run by the runner.
|
||||||
|
type Node interface {
|
||||||
|
sync.Locker
|
||||||
|
Connect() (net.Conn, error)
|
||||||
|
Descriptor() *descriptor.Node
|
||||||
|
TransmittedMessages() []TransmittedMessage
|
||||||
|
ReceivedMessage(id uint32) (ReceivedMessage, bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TransmittedMessage is an interface for a message to be transmitted by the runner.
|
||||||
|
type TransmittedMessage interface {
|
||||||
|
generated.Message
|
||||||
|
// SetTransmitTime sets the time the message was last transmitted.
|
||||||
|
SetTransmitTime(time.Time)
|
||||||
|
// IsCyclicTransmissionEnabled returns true when cyclic transmission is enabled.
|
||||||
|
IsCyclicTransmissionEnabled() bool
|
||||||
|
// WakeUpChan returns a channel for waking up and checking if cyclic transmission is enabled.
|
||||||
|
WakeUpChan() <-chan struct{}
|
||||||
|
// TransmitEventChan returns channel for event-based transmission of the message.
|
||||||
|
TransmitEventChan() <-chan struct{}
|
||||||
|
// BeforeTransmitHook returns a function to be called before the message is transmitted.
|
||||||
|
//
|
||||||
|
// If the hook returns an error, the transmitter halt.
|
||||||
|
BeforeTransmitHook() func(context.Context) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReceivedMessage is an interface for a message to be received by the runner.
|
||||||
|
type ReceivedMessage interface {
|
||||||
|
generated.Message
|
||||||
|
// SetReceiveTime sets the time the message was last received.
|
||||||
|
SetReceiveTime(time.Time)
|
||||||
|
// AfterReceiveHook returns a function to be called after the message has been received.
|
||||||
|
//
|
||||||
|
// If the hook returns an error, the receiver will halt.
|
||||||
|
AfterReceiveHook() func(context.Context) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// FrameTransmitter is an interface for the the CAN frame transmitter used by the runner.
|
||||||
|
type FrameTransmitter interface {
|
||||||
|
TransmitFrame(context.Context, can.Frame) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// FrameReceiver is an interface for the CAN frame receiver used by the runner.
|
||||||
|
type FrameReceiver interface {
|
||||||
|
Receive() bool
|
||||||
|
Frame() can.Frame
|
||||||
|
Err() error
|
||||||
|
}
|
||||||
|
|
||||||
|
func Run(ctx context.Context, n Node) error {
|
||||||
|
conn, err := n.Connect()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("run %s node: %w", n.Descriptor().Name, err)
|
||||||
|
}
|
||||||
|
g, ctx := errgroup.WithContext(ctx)
|
||||||
|
g.Go(func() error {
|
||||||
|
<-ctx.Done()
|
||||||
|
return conn.Close()
|
||||||
|
})
|
||||||
|
g.Go(func() error {
|
||||||
|
rx := socketcan.NewReceiver(conn)
|
||||||
|
return RunMessageReceiver(ctx, rx, n, clock.System())
|
||||||
|
})
|
||||||
|
for _, m := range n.TransmittedMessages() {
|
||||||
|
m := m
|
||||||
|
g.Go(func() error {
|
||||||
|
tx := socketcan.NewTransmitter(conn)
|
||||||
|
return RunMessageTransmitter(ctx, tx, n, m, clock.System())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if err := g.Wait(); err != nil {
|
||||||
|
if strings.Contains(err.Error(), "closed") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("run %s node: %w", n.Descriptor().Name, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func RunMessageReceiver(ctx context.Context, rx FrameReceiver, n Node, c clock.Clock) error {
|
||||||
|
for rx.Receive() {
|
||||||
|
f := rx.Frame()
|
||||||
|
m, ok := n.ReceivedMessage(f.ID)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
n.Lock()
|
||||||
|
hook := m.AfterReceiveHook()
|
||||||
|
m.SetReceiveTime(c.Now())
|
||||||
|
err := m.UnmarshalFrame(f)
|
||||||
|
n.Unlock()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("receiver: %w", err)
|
||||||
|
}
|
||||||
|
if err := hook(ctx); err != nil {
|
||||||
|
return fmt.Errorf("receiver: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := rx.Err(); err != nil {
|
||||||
|
return fmt.Errorf("receiver: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func RunMessageTransmitter(
|
||||||
|
ctx context.Context,
|
||||||
|
tx FrameTransmitter,
|
||||||
|
l sync.Locker,
|
||||||
|
m TransmittedMessage,
|
||||||
|
c clock.Clock,
|
||||||
|
) error {
|
||||||
|
sendTimeout := m.Descriptor().CycleTime
|
||||||
|
if sendTimeout == 0 {
|
||||||
|
sendTimeout = defaultSendTimeout
|
||||||
|
}
|
||||||
|
var cyclicTransmissionTicker *time.Ticker
|
||||||
|
var cyclicTransmissionTickChan <-chan time.Time
|
||||||
|
enableCyclicTransmission := func() {
|
||||||
|
isCyclic := m.Descriptor().SendType == descriptor.SendTypeCyclic
|
||||||
|
hasCycleTime := m.Descriptor().CycleTime > 0
|
||||||
|
if !isCyclic || !hasCycleTime || cyclicTransmissionTicker != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cyclicTransmissionTicker = time.NewTicker(m.Descriptor().CycleTime)
|
||||||
|
cyclicTransmissionTickChan = cyclicTransmissionTicker.C
|
||||||
|
}
|
||||||
|
disableCyclicTransmission := func() {
|
||||||
|
if cyclicTransmissionTicker == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cyclicTransmissionTicker.Stop()
|
||||||
|
cyclicTransmissionTicker = nil
|
||||||
|
}
|
||||||
|
setCyclicTransmission := func() {
|
||||||
|
l.Lock()
|
||||||
|
isCyclicTransmissionEnabled := m.IsCyclicTransmissionEnabled()
|
||||||
|
l.Unlock()
|
||||||
|
if isCyclicTransmissionEnabled {
|
||||||
|
enableCyclicTransmission()
|
||||||
|
} else {
|
||||||
|
disableCyclicTransmission()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
transmit := func() error {
|
||||||
|
l.Lock()
|
||||||
|
hook := m.BeforeTransmitHook()
|
||||||
|
m.SetTransmitTime(c.Now())
|
||||||
|
l.Unlock()
|
||||||
|
if err := hook(ctx); err != nil {
|
||||||
|
return fmt.Errorf("%s transmitter: %w", m.Descriptor().Name, err)
|
||||||
|
}
|
||||||
|
l.Lock()
|
||||||
|
f := m.Frame()
|
||||||
|
l.Unlock()
|
||||||
|
ctx, cancel := context.WithTimeout(ctx, sendTimeout)
|
||||||
|
err := tx.TransmitFrame(ctx, f)
|
||||||
|
cancel()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%s transmitter: %w", m.Descriptor().Name, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ctxDone := ctx.Done()
|
||||||
|
transmitEventChan := m.TransmitEventChan()
|
||||||
|
setCyclicTransmission()
|
||||||
|
wakeUpChan := m.WakeUpChan()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctxDone:
|
||||||
|
return nil
|
||||||
|
case <-wakeUpChan:
|
||||||
|
setCyclicTransmission()
|
||||||
|
case <-transmitEventChan:
|
||||||
|
if err := transmit(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case <-cyclicTransmissionTickChan:
|
||||||
|
if err := transmit(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
125
pkg/can-go/pkg/canrunner/run_test.go
Normal file
125
pkg/can-go/pkg/canrunner/run_test.go
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
package canrunner_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
can "github.com/fiskerinc/cloud-services/pkg/can-go"
|
||||||
|
"github.com/fiskerinc/cloud-services/pkg/can-go/internal/gen/mock/mockcanrunner"
|
||||||
|
"github.com/fiskerinc/cloud-services/pkg/can-go/internal/gen/mock/mockclock"
|
||||||
|
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/canrunner"
|
||||||
|
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/descriptor"
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
"gotest.tools/v3/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRunMessageReceiver_NoMessages(t *testing.T) {
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
rx := mockcanrunner.NewMockFrameReceiver(ctrl)
|
||||||
|
node := mockcanrunner.NewMockNode(ctrl)
|
||||||
|
clock := mockclock.NewMockClock(ctrl)
|
||||||
|
ctx := context.Background()
|
||||||
|
// when the first receive fails
|
||||||
|
rx.EXPECT().Receive().Return(false)
|
||||||
|
rx.EXPECT().Err().Return(os.ErrClosed)
|
||||||
|
// then an error is returned
|
||||||
|
assert.Assert(t, errors.Is(canrunner.RunMessageReceiver(ctx, rx, node, clock), os.ErrClosed))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunMessageReceiver_ReceiveMessage(t *testing.T) {
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
rx := mockcanrunner.NewMockFrameReceiver(ctrl)
|
||||||
|
node := mockcanrunner.NewMockNode(ctrl)
|
||||||
|
clock := mockclock.NewMockClock(ctrl)
|
||||||
|
msg := mockcanrunner.NewMockReceivedMessage(ctrl)
|
||||||
|
ctx := context.Background()
|
||||||
|
// when the first receive succeeds
|
||||||
|
frame := can.Frame{ID: 42}
|
||||||
|
rx.EXPECT().Receive().Return(true)
|
||||||
|
rx.EXPECT().Frame().Return(frame)
|
||||||
|
// then the receiver should do a message lookup
|
||||||
|
node.EXPECT().ReceivedMessage(frame.ID).Return(msg, true)
|
||||||
|
// and the node should be locked
|
||||||
|
node.EXPECT().Lock()
|
||||||
|
// and the message should be queried for a hook with the same context
|
||||||
|
afterReceiveHook := func(c context.Context) error {
|
||||||
|
assert.DeepEqual(t, ctx, c)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
msg.EXPECT().AfterReceiveHook().Return(afterReceiveHook)
|
||||||
|
// and the receive time should be set
|
||||||
|
now := time.Unix(0, 1)
|
||||||
|
clock.EXPECT().Now().Return(now)
|
||||||
|
msg.EXPECT().SetReceiveTime(now)
|
||||||
|
// and the message should be called to unmarshal the frame
|
||||||
|
msg.EXPECT().UnmarshalFrame(frame)
|
||||||
|
// and the node should be unlocked
|
||||||
|
node.EXPECT().Unlock()
|
||||||
|
// when the next receive fails
|
||||||
|
rx.EXPECT().Receive().Return(false)
|
||||||
|
rx.EXPECT().Err().Return(nil)
|
||||||
|
// then the receiver should return
|
||||||
|
assert.NilError(t, canrunner.RunMessageReceiver(ctx, rx, node, clock))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunMessageTransmitter_TransmitEventMessage(t *testing.T) {
|
||||||
|
t.Skip() // TODO: fix deadlock flakynes.
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
tx := mockcanrunner.NewMockFrameTransmitter(ctrl)
|
||||||
|
node := mockcanrunner.NewMockNode(ctrl)
|
||||||
|
msg := mockcanrunner.NewMockTransmittedMessage(ctrl)
|
||||||
|
clock := mockclock.NewMockClock(ctrl)
|
||||||
|
desc := &descriptor.Message{
|
||||||
|
Name: "TestMessage",
|
||||||
|
SendType: descriptor.SendTypeEvent,
|
||||||
|
}
|
||||||
|
transmitEventChan := make(chan struct{})
|
||||||
|
wakeUpChan := make(chan struct{})
|
||||||
|
ctx := context.Background()
|
||||||
|
msg.EXPECT().Descriptor().AnyTimes().Return(desc)
|
||||||
|
msg.EXPECT().TransmitEventChan().Return(transmitEventChan)
|
||||||
|
msg.EXPECT().WakeUpChan().Return(wakeUpChan)
|
||||||
|
// given a running transmitter
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
var g errgroup.Group
|
||||||
|
g.Go(func() error {
|
||||||
|
return canrunner.RunMessageTransmitter(ctx, tx, node, msg, clock)
|
||||||
|
})
|
||||||
|
// then message should be queried for if it has cyclic transmission enabled
|
||||||
|
node.EXPECT().Lock()
|
||||||
|
msg.EXPECT().IsCyclicTransmissionEnabled()
|
||||||
|
node.EXPECT().Unlock()
|
||||||
|
// then the node should be locked
|
||||||
|
node.EXPECT().Lock()
|
||||||
|
// and the time should be queried
|
||||||
|
now := time.Unix(0, 1)
|
||||||
|
clock.EXPECT().Now().Return(now)
|
||||||
|
// and the transmit hook should be queried with the same context
|
||||||
|
hook := func(c context.Context) error {
|
||||||
|
assert.Equal(t, ctx, c)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
msg.EXPECT().BeforeTransmitHook().Return(hook)
|
||||||
|
// and the message should be marshaled to a CAN frame
|
||||||
|
frame := can.Frame{ID: 42}
|
||||||
|
// and the transmit time should be set
|
||||||
|
msg.EXPECT().SetTransmitTime(now)
|
||||||
|
// and the node should be unlocked
|
||||||
|
node.EXPECT().Unlock()
|
||||||
|
node.EXPECT().Lock()
|
||||||
|
msg.EXPECT().Frame().Return(frame)
|
||||||
|
node.EXPECT().Unlock()
|
||||||
|
// and the CAN frame should be transmitted
|
||||||
|
tx.EXPECT().TransmitFrame(gomock.Any(), frame)
|
||||||
|
// when the transmitter receives a transmit event
|
||||||
|
transmitEventChan <- struct{}{}
|
||||||
|
cancel()
|
||||||
|
assert.NilError(t, g.Wait())
|
||||||
|
}
|
||||||
127
pkg/can-go/pkg/cantext/encode.go
Normal file
127
pkg/can-go/pkg/cantext/encode.go
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
package cantext
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
can "github.com/fiskerinc/cloud-services/pkg/can-go"
|
||||||
|
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/descriptor"
|
||||||
|
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/generated"
|
||||||
|
)
|
||||||
|
|
||||||
|
// preAllocatedBytesPerSignal is an estimate of how many bytes each signal needs.
|
||||||
|
const preAllocatedBytesPerSignal = 40
|
||||||
|
|
||||||
|
func MessageString(m generated.Message) string {
|
||||||
|
return string(MarshalCompact(m))
|
||||||
|
}
|
||||||
|
|
||||||
|
func MarshalCompact(m generated.Message) []byte {
|
||||||
|
f := m.Frame()
|
||||||
|
buf := make([]byte, 0, len(m.Descriptor().Signals)*preAllocatedBytesPerSignal)
|
||||||
|
buf = append(buf, "{"...)
|
||||||
|
for i, s := range m.Descriptor().Signals {
|
||||||
|
buf = AppendSignalCompact(buf, s, f.Data)
|
||||||
|
if i != len(m.Descriptor().Signals)-1 {
|
||||||
|
buf = append(buf, ", "...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf = append(buf, "}"...)
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
func Marshal(m generated.Message) []byte {
|
||||||
|
f := m.Frame()
|
||||||
|
// allocate space for one "extra" signal to account for message header
|
||||||
|
buf := make([]byte, 0, (len(m.Descriptor().Signals)+1)*preAllocatedBytesPerSignal)
|
||||||
|
buf = append(buf, m.Descriptor().Name...)
|
||||||
|
for _, s := range m.Descriptor().Signals {
|
||||||
|
buf = append(buf, "\n\t"...)
|
||||||
|
buf = AppendSignal(buf, s, f.Data)
|
||||||
|
}
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
func AppendSignal(buf []byte, s *descriptor.Signal, d can.Data) []byte {
|
||||||
|
buf = append(buf, s.Name...)
|
||||||
|
buf = append(buf, ": "...)
|
||||||
|
switch {
|
||||||
|
case s.Length == 1: // bool
|
||||||
|
val := s.UnmarshalBool(d)
|
||||||
|
buf = strconv.AppendBool(buf, val)
|
||||||
|
case s.IsSigned: // signed
|
||||||
|
buf = strconv.AppendFloat(buf, s.UnmarshalPhysical(d), 'g', -1, 64)
|
||||||
|
buf = append(buf, s.Unit...)
|
||||||
|
buf = append(buf, " ("...)
|
||||||
|
buf = append(buf, "0x"...)
|
||||||
|
buf = strconv.AppendUint(buf, uint64(s.UnmarshalSigned(d)), 16)
|
||||||
|
buf = append(buf, ')')
|
||||||
|
default: // unsigned
|
||||||
|
buf = strconv.AppendFloat(buf, s.UnmarshalPhysical(d), 'g', -1, 64)
|
||||||
|
buf = append(buf, s.Unit...)
|
||||||
|
buf = append(buf, " ("...)
|
||||||
|
buf = append(buf, "0x"...)
|
||||||
|
buf = strconv.AppendUint(buf, s.UnmarshalUnsigned(d), 16)
|
||||||
|
buf = append(buf, ")"...)
|
||||||
|
}
|
||||||
|
if vd, ok := s.UnmarshalValueDescription(d); ok {
|
||||||
|
buf = append(buf, ' ')
|
||||||
|
buf = append(buf, vd...)
|
||||||
|
}
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
func AppendSignalCompact(buf []byte, s *descriptor.Signal, d can.Data) []byte {
|
||||||
|
buf = append(buf, s.Name...)
|
||||||
|
buf = append(buf, ": "...)
|
||||||
|
valueDescription, hasValueDescription := s.UnmarshalValueDescription(d)
|
||||||
|
switch {
|
||||||
|
case hasValueDescription:
|
||||||
|
buf = append(buf, valueDescription...)
|
||||||
|
case s.Length == 1: // bool
|
||||||
|
val := s.UnmarshalBool(d)
|
||||||
|
buf = strconv.AppendBool(buf, val)
|
||||||
|
case s.IsSigned: // signed
|
||||||
|
buf = strconv.AppendFloat(buf, s.UnmarshalPhysical(d), 'g', -1, 64)
|
||||||
|
buf = append(buf, s.Unit...)
|
||||||
|
default: // unsigned
|
||||||
|
buf = strconv.AppendFloat(buf, s.UnmarshalPhysical(d), 'g', -1, 64)
|
||||||
|
buf = append(buf, s.Unit...)
|
||||||
|
}
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
func AppendID(buf []byte, m *descriptor.Message) []byte {
|
||||||
|
buf = append(buf, "ID: "...)
|
||||||
|
buf = strconv.AppendUint(buf, uint64(m.ID), 10)
|
||||||
|
buf = append(buf, " (0x"...)
|
||||||
|
buf = strconv.AppendUint(buf, uint64(m.ID), 16)
|
||||||
|
buf = append(buf, ")"...)
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
func AppendSender(buf []byte, m *descriptor.Message) []byte {
|
||||||
|
return appendAttributeString(buf, "Sender", m.SenderNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func AppendSendType(buf []byte, m *descriptor.Message) []byte {
|
||||||
|
return appendAttributeString(buf, "SendType", m.SendType.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func AppendCycleTime(buf []byte, m *descriptor.Message) []byte {
|
||||||
|
return appendAttributeString(buf, "CycleTime", m.CycleTime.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func AppendDelayTime(buf []byte, m *descriptor.Message) []byte {
|
||||||
|
return appendAttributeString(buf, "DelayTime", m.DelayTime.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func AppendFrame(buf []byte, f can.Frame) []byte {
|
||||||
|
return appendAttributeString(buf, "Frame", f.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendAttributeString(buf []byte, name string, s string) []byte {
|
||||||
|
buf = append(buf, name...)
|
||||||
|
buf = append(buf, ": "...)
|
||||||
|
buf = append(buf, s...)
|
||||||
|
return buf
|
||||||
|
}
|
||||||
234
pkg/can-go/pkg/cantext/encode_test.go
Normal file
234
pkg/can-go/pkg/cantext/encode_test.go
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
package cantext
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
can "github.com/fiskerinc/cloud-services/pkg/can-go"
|
||||||
|
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/descriptor"
|
||||||
|
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/generated"
|
||||||
|
"gotest.tools/v3/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMarshal(t *testing.T) {
|
||||||
|
for _, tt := range []struct {
|
||||||
|
name string
|
||||||
|
msg generated.Message
|
||||||
|
expected string
|
||||||
|
expectedCompact string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "with enum",
|
||||||
|
msg: &testMessage{
|
||||||
|
frame: can.Frame{ID: 100, Length: 1, Data: can.Data{2}},
|
||||||
|
descriptor: newDriverHeartbeatDescriptor(),
|
||||||
|
},
|
||||||
|
expected: `
|
||||||
|
DriverHeartbeat
|
||||||
|
Command: 2 (0x2) Reboot
|
||||||
|
`,
|
||||||
|
expectedCompact: `{Command: Reboot}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with unit",
|
||||||
|
msg: &testMessage{
|
||||||
|
frame: can.Frame{ID: 100, Length: 3, Data: can.Data{1, 0x7b}},
|
||||||
|
descriptor: newMotorStatusDescriptor(),
|
||||||
|
},
|
||||||
|
expected: `
|
||||||
|
MotorStatus
|
||||||
|
WheelError: true
|
||||||
|
SpeedKph: 0.123km/h (0x7b)
|
||||||
|
`,
|
||||||
|
expectedCompact: `{WheelError: true, SpeedKph: 0.123km/h}`,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
tt := tt
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Run("standard", func(t *testing.T) {
|
||||||
|
txt := Marshal(tt.msg)
|
||||||
|
assert.Equal(t, strings.TrimSpace(tt.expected), string(txt))
|
||||||
|
})
|
||||||
|
t.Run("compact", func(t *testing.T) {
|
||||||
|
txt := MarshalCompact(tt.msg)
|
||||||
|
assert.Equal(t, strings.TrimSpace(tt.expectedCompact), string(txt))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppendID(t *testing.T) {
|
||||||
|
const expected = "ID: 100 (0x64)"
|
||||||
|
actual := string(AppendID([]byte{}, newDriverHeartbeatDescriptor()))
|
||||||
|
assert.Equal(t, expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppendSender(t *testing.T) {
|
||||||
|
const expected = "Sender: DRIVER"
|
||||||
|
actual := string(AppendSender([]byte{}, newDriverHeartbeatDescriptor()))
|
||||||
|
assert.Equal(t, expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppendSendType(t *testing.T) {
|
||||||
|
const expected = "SendType: Cyclic"
|
||||||
|
actual := string(AppendSendType([]byte{}, newDriverHeartbeatDescriptor()))
|
||||||
|
assert.Equal(t, expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppendCycleTime(t *testing.T) {
|
||||||
|
const expected = "CycleTime: 100ms"
|
||||||
|
actual := string(AppendCycleTime([]byte{}, newDriverHeartbeatDescriptor()))
|
||||||
|
assert.Equal(t, expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppendDelayTime(t *testing.T) {
|
||||||
|
const expected = "DelayTime: 2s"
|
||||||
|
actual := string(AppendDelayTime([]byte{}, newDriverHeartbeatDescriptor()))
|
||||||
|
assert.Equal(t, expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppendFrame(t *testing.T) {
|
||||||
|
const expected = "Frame: 042#123456"
|
||||||
|
actual := string(AppendFrame([]byte{}, can.Frame{ID: 0x42, Length: 3, Data: can.Data{0x12, 0x34, 0x56}}))
|
||||||
|
assert.Equal(t, expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDriverHeartbeatDescriptor() *descriptor.Message {
|
||||||
|
return &descriptor.Message{
|
||||||
|
Name: (string)("DriverHeartbeat"),
|
||||||
|
ID: (uint32)(100),
|
||||||
|
IsExtended: (bool)(false),
|
||||||
|
Length: (uint16)(1),
|
||||||
|
Description: (string)("Sync message used to synchronize the controllers"),
|
||||||
|
SendType: descriptor.SendTypeCyclic,
|
||||||
|
CycleTime: 100 * time.Millisecond,
|
||||||
|
DelayTime: 2 * time.Second,
|
||||||
|
Signals: []*descriptor.Signal{
|
||||||
|
{
|
||||||
|
Name: (string)("Command"),
|
||||||
|
Start: (uint16)(0),
|
||||||
|
Length: (uint16)(8),
|
||||||
|
IsBigEndian: (bool)(false),
|
||||||
|
IsSigned: (bool)(false),
|
||||||
|
IsMultiplexer: (bool)(false),
|
||||||
|
IsMultiplexed: (bool)(false),
|
||||||
|
MultiplexerValue: (uint)(0),
|
||||||
|
Offset: (float64)(0),
|
||||||
|
Scale: (float64)(1),
|
||||||
|
Min: (float64)(0),
|
||||||
|
Max: (float64)(0),
|
||||||
|
Unit: (string)(""),
|
||||||
|
Description: (string)(""),
|
||||||
|
ValueDescriptions: []*descriptor.ValueDescription{
|
||||||
|
{
|
||||||
|
Value: (int)(0),
|
||||||
|
Description: (string)("None"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: (int)(1),
|
||||||
|
Description: (string)("Sync"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: (int)(2),
|
||||||
|
Description: (string)("Reboot"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ReceiverNodes: []string{
|
||||||
|
(string)("SENSOR"),
|
||||||
|
(string)("MOTOR"),
|
||||||
|
},
|
||||||
|
DefaultValue: (int)(0),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SenderNode: (string)("DRIVER"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMotorStatusDescriptor() *descriptor.Message {
|
||||||
|
return &descriptor.Message{
|
||||||
|
Name: (string)("MotorStatus"),
|
||||||
|
ID: (uint32)(400),
|
||||||
|
IsExtended: (bool)(false),
|
||||||
|
Length: (uint16)(3),
|
||||||
|
Description: (string)(""),
|
||||||
|
Signals: []*descriptor.Signal{
|
||||||
|
{
|
||||||
|
Name: (string)("WheelError"),
|
||||||
|
Start: (uint16)(0),
|
||||||
|
Length: (uint16)(1),
|
||||||
|
IsBigEndian: (bool)(false),
|
||||||
|
IsSigned: (bool)(false),
|
||||||
|
IsMultiplexer: (bool)(false),
|
||||||
|
IsMultiplexed: (bool)(false),
|
||||||
|
MultiplexerValue: (uint)(0),
|
||||||
|
Offset: (float64)(0),
|
||||||
|
Scale: (float64)(1),
|
||||||
|
Min: (float64)(0),
|
||||||
|
Max: (float64)(0),
|
||||||
|
Unit: (string)(""),
|
||||||
|
Description: (string)(""),
|
||||||
|
ValueDescriptions: ([]*descriptor.ValueDescription)(nil),
|
||||||
|
ReceiverNodes: []string{
|
||||||
|
(string)("DRIVER"),
|
||||||
|
(string)("IO"),
|
||||||
|
},
|
||||||
|
DefaultValue: (int)(0),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: (string)("SpeedKph"),
|
||||||
|
Start: (uint16)(8),
|
||||||
|
Length: (uint16)(16),
|
||||||
|
IsBigEndian: (bool)(false),
|
||||||
|
IsSigned: (bool)(false),
|
||||||
|
IsMultiplexer: (bool)(false),
|
||||||
|
IsMultiplexed: (bool)(false),
|
||||||
|
MultiplexerValue: (uint)(0),
|
||||||
|
Offset: (float64)(0),
|
||||||
|
Scale: (float64)(0.001),
|
||||||
|
Min: (float64)(0),
|
||||||
|
Max: (float64)(0),
|
||||||
|
Unit: (string)("km/h"),
|
||||||
|
Description: (string)(""),
|
||||||
|
ValueDescriptions: ([]*descriptor.ValueDescription)(nil),
|
||||||
|
ReceiverNodes: []string{
|
||||||
|
(string)("DRIVER"),
|
||||||
|
(string)("IO"),
|
||||||
|
},
|
||||||
|
DefaultValue: (int)(0),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SenderNode: (string)("MOTOR"),
|
||||||
|
CycleTime: (time.Duration)(100000000),
|
||||||
|
DelayTime: (time.Duration)(0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type testMessage struct {
|
||||||
|
frame can.Frame
|
||||||
|
descriptor *descriptor.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *testMessage) Frame() can.Frame {
|
||||||
|
return m.frame
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *testMessage) Descriptor() *descriptor.Message {
|
||||||
|
return m.descriptor
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *testMessage) MarshalFrame() (can.Frame, error) {
|
||||||
|
panic("should not be called")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (testMessage) Reset() {
|
||||||
|
panic("should not be called")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (testMessage) String() string {
|
||||||
|
panic("should not be called")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (testMessage) UnmarshalFrame(can.Frame) error {
|
||||||
|
panic("should not be called")
|
||||||
|
}
|
||||||
26
pkg/can-go/pkg/dbc/accesstype.go
Normal file
26
pkg/can-go/pkg/dbc/accesstype.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package dbc
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// AccessType represents the access type of an environment variable.
|
||||||
|
type AccessType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
AccessTypeUnrestricted AccessType = "DUMMY_NODE_VECTOR0"
|
||||||
|
AccessTypeRead AccessType = "DUMMY_NODE_VECTOR1"
|
||||||
|
AccessTypeWrite AccessType = "DUMMY_NODE_VECTOR2"
|
||||||
|
AccessTypeReadWrite AccessType = "DUMMY_NODE_VECTOR3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Validate returns an error for invalid access types.
|
||||||
|
func (a AccessType) Validate() error {
|
||||||
|
switch a {
|
||||||
|
case AccessTypeUnrestricted:
|
||||||
|
case AccessTypeRead:
|
||||||
|
case AccessTypeWrite:
|
||||||
|
case AccessTypeReadWrite:
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("invalid access type: %v", a)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
22
pkg/can-go/pkg/dbc/accesstype_test.go
Normal file
22
pkg/can-go/pkg/dbc/accesstype_test.go
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package dbc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gotest.tools/v3/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccessType_Validate(t *testing.T) {
|
||||||
|
for _, tt := range []AccessType{
|
||||||
|
AccessTypeUnrestricted,
|
||||||
|
AccessTypeRead,
|
||||||
|
AccessTypeWrite,
|
||||||
|
AccessTypeReadWrite,
|
||||||
|
} {
|
||||||
|
assert.NilError(t, tt.Validate())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccessType_Validate_Error(t *testing.T) {
|
||||||
|
assert.ErrorContains(t, AccessType("foo").Validate(), "invalid access type")
|
||||||
|
}
|
||||||
53
pkg/can-go/pkg/dbc/analysis/analysis.go
Normal file
53
pkg/can-go/pkg/dbc/analysis/analysis.go
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
package analysis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"text/scanner"
|
||||||
|
|
||||||
|
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/dbc"
|
||||||
|
)
|
||||||
|
|
||||||
|
// An Analyzer describes an analysis function and its options.
|
||||||
|
type Analyzer struct {
|
||||||
|
// Name of the analyzer.
|
||||||
|
Name string
|
||||||
|
|
||||||
|
// Doc is the documentation for the analyzer.
|
||||||
|
Doc string
|
||||||
|
|
||||||
|
// Run the analyzer.
|
||||||
|
Run func(*Pass) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Title is the part before the first "\n\n" of the documentation.
|
||||||
|
func (a *Analyzer) Title() string {
|
||||||
|
return strings.SplitN(a.Doc, "\n\n", 2)[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate the analyzer metadata.
|
||||||
|
func (a *Analyzer) Validate() error {
|
||||||
|
if a.Doc == "" {
|
||||||
|
return fmt.Errorf("missing doc")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Diagnostic is a message associated with a source location.
|
||||||
|
type Diagnostic struct {
|
||||||
|
Pos scanner.Position
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pass is the interface to the run function that analyzes DBC definitions.
|
||||||
|
type Pass struct {
|
||||||
|
Analyzer *Analyzer
|
||||||
|
File *dbc.File
|
||||||
|
Diagnostics []*Diagnostic
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reportf reports a diagnostic by building a message from the provided format and args.
|
||||||
|
func (pass *Pass) Reportf(pos scanner.Position, format string, args ...interface{}) {
|
||||||
|
msg := fmt.Sprintf(format, args...)
|
||||||
|
pass.Diagnostics = append(pass.Diagnostics, &Diagnostic{Pos: pos, Message: msg})
|
||||||
|
}
|
||||||
38
pkg/can-go/pkg/dbc/analysis/analysistest/analysistest.go
Normal file
38
pkg/can-go/pkg/dbc/analysis/analysistest/analysistest.go
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package analysistest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/dbc"
|
||||||
|
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/dbc/analysis"
|
||||||
|
"gotest.tools/v3/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Case struct {
|
||||||
|
Name string
|
||||||
|
Data string
|
||||||
|
Diagnostics []*analysis.Diagnostic
|
||||||
|
}
|
||||||
|
|
||||||
|
func Run(t *testing.T, a *analysis.Analyzer, cs []*Case) {
|
||||||
|
t.Helper()
|
||||||
|
for _, c := range cs {
|
||||||
|
p := dbc.NewParser(c.Name, []byte(strings.TrimLeft(c.Data, "\n")))
|
||||||
|
assert.NilError(t, p.Parse())
|
||||||
|
pass := &analysis.Pass{
|
||||||
|
Analyzer: a,
|
||||||
|
File: p.File(),
|
||||||
|
}
|
||||||
|
assert.NilError(t, a.Run(pass))
|
||||||
|
// allow omitting byte offsets and file names
|
||||||
|
for _, d := range c.Diagnostics {
|
||||||
|
d.Pos.Offset = 0
|
||||||
|
d.Pos.Filename = c.Name
|
||||||
|
}
|
||||||
|
for _, d := range pass.Diagnostics {
|
||||||
|
d.Pos.Offset = 0
|
||||||
|
}
|
||||||
|
assert.DeepEqual(t, c.Diagnostics, pass.Diagnostics)
|
||||||
|
}
|
||||||
|
}
|
||||||
60
pkg/can-go/pkg/dbc/analysis/passes/boolprefix/analyzer.go
Normal file
60
pkg/can-go/pkg/dbc/analysis/passes/boolprefix/analyzer.go
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
package boolprefix
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/dbc"
|
||||||
|
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/dbc/analysis"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Analyzer() *analysis.Analyzer {
|
||||||
|
return &analysis.Analyzer{
|
||||||
|
Name: "boolprefix",
|
||||||
|
Doc: "check that bools (1-bit signals) have a correct prefix",
|
||||||
|
Run: run,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func allowedPrefixes() []string {
|
||||||
|
return []string{
|
||||||
|
"Is",
|
||||||
|
"Has",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func run(pass *analysis.Pass) error {
|
||||||
|
for _, d := range pass.File.Defs {
|
||||||
|
messageDef, ok := d.(*dbc.MessageDef)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
SignalLoop:
|
||||||
|
for _, signalDef := range messageDef.Signals {
|
||||||
|
if signalDef.Size != 1 {
|
||||||
|
continue // skip all non-bool signals
|
||||||
|
}
|
||||||
|
for _, allowedPrefix := range allowedPrefixes() {
|
||||||
|
if strings.HasPrefix(string(signalDef.Name), allowedPrefix) {
|
||||||
|
continue SignalLoop // has allowed prefix
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// edge-case: allow non-prefixed 1-bit signals with value descriptions
|
||||||
|
for _, d := range pass.File.Defs {
|
||||||
|
valueDescriptionsDef, ok := d.(*dbc.ValueDescriptionsDef)
|
||||||
|
if !ok {
|
||||||
|
continue // not value descriptions
|
||||||
|
}
|
||||||
|
if valueDescriptionsDef.MessageID == messageDef.MessageID &&
|
||||||
|
valueDescriptionsDef.SignalName == signalDef.Name {
|
||||||
|
continue SignalLoop // has value descriptions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pass.Reportf(
|
||||||
|
signalDef.Pos,
|
||||||
|
"bool signals (1-bit) must have prefix %s",
|
||||||
|
strings.Join(allowedPrefixes(), " or "),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package boolprefix
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"text/scanner"
|
||||||
|
|
||||||
|
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/dbc/analysis"
|
||||||
|
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/dbc/analysis/analysistest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAnalyzer(t *testing.T) {
|
||||||
|
analysistest.Run(t, Analyzer(), []*analysistest.Case{
|
||||||
|
{
|
||||||
|
Name: "prefix has",
|
||||||
|
Data: `
|
||||||
|
BO_ 400 MOTOR_STATUS: 3 MOTOR
|
||||||
|
SG_ HasWheelError : 0|1@1+ (1,0) [0|0] "" DRIVER,IO
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
Name: "prefix is",
|
||||||
|
Data: `
|
||||||
|
BO_ 400 MOTOR_STATUS: 3 MOTOR
|
||||||
|
SG_ IsOverheated : 0|1@1+ (1,0) [0|0] "" DRIVER,IO
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
Name: "missing prefix",
|
||||||
|
Data: `
|
||||||
|
BO_ 400 MOTOR_STATUS: 3 MOTOR
|
||||||
|
SG_ WheelError : 0|1@1+ (1,0) [0|0] "" DRIVER,IO
|
||||||
|
`,
|
||||||
|
Diagnostics: []*analysis.Diagnostic{
|
||||||
|
{
|
||||||
|
Pos: scanner.Position{Line: 2, Column: 2},
|
||||||
|
Message: "bool signals (1-bit) must have prefix Is or Has",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
Name: "missing prefix with value descriptions",
|
||||||
|
Data: `
|
||||||
|
BO_ 400 MOTOR_STATUS: 3 MOTOR
|
||||||
|
SG_ Status : 0|1@1+ (1,0) [0|0] "" DRIVER,IO
|
||||||
|
|
||||||
|
VAL_ 400 Status 1 "ValidDataPresent" 0 "NoData" ;
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package definitiontypeorder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/dbc"
|
||||||
|
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/dbc/analysis"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Analyzer() *analysis.Analyzer {
|
||||||
|
return &analysis.Analyzer{
|
||||||
|
Name: "definitiontypeorder",
|
||||||
|
Doc: "check that definitions are in the correct order",
|
||||||
|
Run: run,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func orderOf(def dbc.Def) uint64 {
|
||||||
|
for i, orderDef := range []dbc.Def{
|
||||||
|
&dbc.VersionDef{},
|
||||||
|
&dbc.NewSymbolsDef{},
|
||||||
|
&dbc.BitTimingDef{},
|
||||||
|
&dbc.NodesDef{},
|
||||||
|
&dbc.ValueTableDef{},
|
||||||
|
&dbc.MessageDef{},
|
||||||
|
&dbc.MessageTransmittersDef{},
|
||||||
|
&dbc.EnvironmentVariableDef{},
|
||||||
|
&dbc.EnvironmentVariableDataDef{},
|
||||||
|
&dbc.CommentDef{},
|
||||||
|
&dbc.AttributeDef{},
|
||||||
|
&dbc.AttributeDefaultValueDef{},
|
||||||
|
&dbc.AttributeValueForObjectDef{},
|
||||||
|
&dbc.ValueDescriptionsDef{},
|
||||||
|
} {
|
||||||
|
if reflect.TypeOf(def) == reflect.TypeOf(orderDef) {
|
||||||
|
return uint64(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return math.MaxUint64
|
||||||
|
}
|
||||||
|
|
||||||
|
func run(pass *analysis.Pass) error {
|
||||||
|
minOrder := uint64(math.MaxUint64)
|
||||||
|
for i := range pass.File.Defs {
|
||||||
|
// diagnostics make more sense when going backwards
|
||||||
|
def := pass.File.Defs[len(pass.File.Defs)-i-1]
|
||||||
|
currOrder := orderOf(def)
|
||||||
|
if currOrder > minOrder {
|
||||||
|
pass.Reportf(def.Position(), "definition out of order")
|
||||||
|
} else {
|
||||||
|
minOrder = currOrder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user