Skip to content

feat(vehicle): add support for LeapMotor API#29666

Draft
syphernl wants to merge 10 commits into
evcc-io:masterfrom
syphernl:feat/leapmotor_api_integration
Draft

feat(vehicle): add support for LeapMotor API#29666
syphernl wants to merge 10 commits into
evcc-io:masterfrom
syphernl:feat/leapmotor_api_integration

Conversation

@syphernl
Copy link
Copy Markdown
Contributor

@syphernl syphernl commented May 5, 2026

Summary

Adds Leapmotor cloud API support (same API the official app uses). The API is unofficial but community-documented:

App certs download automatically from markoceri/leapmotor-certs on startup, no files to provide.

Supported data

SoC, charge state, range, odometer, finish time, climate state, GPS position, SoC limit.

Caveats

  • Unofficial API, may break without warning
  • First startup requires GitHub reachability to fetch app certificates; cached locally after that.

Status

Not tested on a real car yet. Draft until someone validates it on a T03, B10, or C10.

Tested by @ngehrsitz on a T03: #29666 (review)

References

syphernl added 2 commits May 5, 2026 15:30
- Remove EnsureAuth (dead code, had TOCTOU race)
- Make addAuthHeaders void (return value was always ignored)
- Drop Python-style inline comments in deriveP12Password
- Collapse SM4 section divider to single comment line
Copy link
Copy Markdown
Contributor

@ngehrsitz ngehrsitz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested it briefly and it works so far 👍
Image

Comment thread vehicle/leapmotor/identity.go
Comment thread vehicle/leapmotor/provider.go
Comment thread vehicle/leapmotor.go Outdated
Comment thread vehicle/leapmotor/identity.go Outdated
Comment thread vehicle/leapmotor.go
Comment thread vehicle/leapmotor/identity.go Outdated
Comment thread vehicle/leapmotor/identity.go Outdated
Comment thread vehicle/leapmotor/identity.go Outdated
Comment thread vehicle/leapmotor/api.go
Comment thread vehicle/leapmotor/api.go
- Rename AppCert/AppKey → auto-download app certs from markoceri/leapmotor-certs
  (removes file path requirement; certs fetched in parallel via errgroup)
- Replace custom SM4 impl with github.com/emmansun/gmsm (key recovered from
  APK round key schedule); cipher block initialised once at package init
- Replace manual JWT parsing with golang-jwt/jwt in deriveSessionDeviceID
- Stable deviceID: SHA256(email)[:16] replaces per-restart random bytes
- NewIdentity accepts PEM bytes instead of file paths
- apiPost uses request.Helper.DoBody (adds HTTP status code checking)
- Add postAndParse[T] helper combining POST + envelope decode
- Add VehicleClimater, VehiclePosition, SocLimiter interfaces
- Add templates/definition/vehicle/leapmotor.yaml for UI discovery
- Use hex.EncodeToString instead of fmt.Sprintf("%x")
@syphernl syphernl requested a review from ngehrsitz May 12, 2026 18:58
Copy link
Copy Markdown
Contributor

@ngehrsitz ngehrsitz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new readouts work 👍
Image

Comment thread vehicle/leapmotor.go Outdated
Comment thread vehicle/leapmotor/provider.go Outdated
Comment thread vehicle/leapmotor/identity.go Outdated
Comment thread vehicle/leapmotor/identity.go Outdated
Comment thread vehicle/leapmotor/identity.go Outdated
Comment thread vehicle/leapmotor/provider.go
Comment thread vehicle/leapmotor/identity.go Outdated
Comment thread vehicle/leapmotor/identity.go Outdated
Comment thread vehicle/leapmotor/identity.go
Comment on lines +274 to +279
acctCert, err := loadAccountCert(p12Bytes, pwd)
if err != nil {
return fmt.Errorf("load account cert (derived password): %w", err)
}
id.acctClient = newMTLSClient(&acctCert)
return nil
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should also persist the client certificate so that we can immediately try to use that and only fallback to the complex login flow when it doesn´t work or is missing.

- Cache app certs in DB; fetch from GitHub only when missing or on TLS
  failure (avoids unauthenticated rate-limit hits on every startup)
- Add TryRestore(): load persisted session (token, acct cert) from DB,
  skip full login when session is still valid; login() persists session
  after each successful auth
- SinglePhaseLeapmotor wrapper reports Phases()=1 for T03
- extractSessionDeviceID: typed usernameClaims struct, return *string
  (no fallback param); fix len check >= 3 instead of >= 4
- Merge addAuthHeaders into buildSignedHeaders (userID/token params)
- maps.Copy + slices.Collect(maps.Keys) + slices.Sort in buildSignedHeaders
- hex.EncodeToString(mac.Sum(nil)) instead of fmt.Sprintf("%x", ...)
- Replace init() block with package-level var for p12SM4Block
- slices.DeleteFunc for VIN filtering in Vehicles()
- Add unit tests for extractSessionDeviceID, deriveSignKey,
  p12MemoryEncode, deriveP12Password
@syphernl syphernl requested a review from ngehrsitz May 16, 2026 08:13
syphernl added 3 commits May 16, 2026 10:17
Implements api.ChargeController via REMOTE_CTL_CHARGE_START/STOP
(cmdId=193) when a vehicle PIN is configured. PIN is optional —
omitting it preserves existing read-only behaviour.

New config field: `pin` (redacted in logs).

- Identity: AES-128-CBC PIN encryption, cert sync, HasPin/EncryptedPin
- API: ChargeToggle — PIN verify + remote/ctl two-step flow
- LeapmotorWithControl / SinglePhaseLeapmotorWithControl types expose
  ChargeEnable only when PIN is present
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants