# Building the first Dart/Flutter client for MG/SAIC connected vehicles: where AI did more than write code

> Source: <https://dev.to/tanguymossion/building-the-first-dartflutter-client-for-mgsaic-connected-vehicles-where-ai-did-more-than-write-3anc>
> Published: 2026-06-04 07:24:53+00:00

Last weekend, I unlocked my car using my own Dart package (according to pub.dev, the first one ever built for it). I walked down to the parking lot, opened the app, tapped the button, heard the clunk. "Worth it." Then I locked it again, went back upstairs, and called it a productive Sunday.

But let's get back a few months earlier, when I had bought a connected MG. I opened the official app, and like any developer who gets a new piece of technology, I spent about five minutes enjoying it as a normal person. Then I started wondering how it worked, what APIs were behind the app, and whether I could build something on top of it myself.

A quick search confirmed that no Dart package existed for the SAIC iSmart API. That's how I ended up building the first one.

My initial approach was to decompile the official app.

I extracted the APK directly from my phone via `adb`

and decompiled it with `apktool`

. The official iSmart app is a native Kotlin Android app.

Most of the business logic is protected by ijiami encryption, so the core algorithms aren't readable statically. From the unprotected resources (strings, drawables, layout files) I was able to confirm a handful of things: undocumented features, error codes, model naming conventions. Useful context, but not enough. The actual API protocol wasn't there. It lives in the encrypted DEX (the compiled Android bytecode), and I had hit a wall.

I wasn't the first to hit that wall. A GitHub organization called [SAIC-iSmart-API](https://github.com/SAIC-iSmart-API) has been reverse-engineering the SAIC API for years via traffic capture. They have a Python client, a Java client, a Home Assistant integration, an MQTT gateway, and even a [dedicated documentation repo](https://github.com/SAIC-iSmart-API/documentation) with real captured API responses.

What wasn't covered was mobile-first development. No Dart client, no Swift client, no TypeScript package designed for mobile development. The Java client exists but is built for server-side use (Maven, MQTT gateways, Spring). A workaround exists: run the Python client on a server and use it as a proxy. But a mobile app should ideally call the API directly.

So, as a Flutter developer, I built `saic_ismart`

: the first pure Dart package for the SAIC iSmart API. MG, Roewe, Maxus/LDV: brands that together represent millions of connected vehicles across Europe, Australia, India, and beyond.

The SAIC API isn't officially documented. There's no Swagger file, no official SDK. Everything I know about it came from reading the Python and Java clients line by line (well, Claude did most of the reading), cross-referenced with the community documentation repo.

Here's what's actually going on under the hood.

The SAIC API isn't REST. It's encrypted REST, with AES-128-CBC on every request, HMAC-SHA-256 signatures, and an event-id polling loop for asynchronous commands. Non-trivial to implement, but the Python client had a test vector. Matching it exactly in Dart was the first real milestone.

A few other things worth knowing before you use it:

`sha256(vin)`

`sword:sword_secret`

Some of these quirks are just amusing. Others are real constraints. The package surfaces them clearly so you know exactly what you're dealing with.

Claude Code was part of my stack on this one, and it made a real difference. Not just for code generation.

Before writing a single line of Dart, I had Claude analyze both the Python and Java repos and produce a concrete technical spec: authentication flow, encryption pipeline, endpoint behavior, known quirks. What would have taken days of reading became a structured document in a fraction of the time. That's how `TECHNICAL_REFERENCE.md`

was born, not as an afterthought but as the foundation the implementation was built on.

Claude also helped port the AES-128-CBC crypto pipeline from Python to Dart and write the HMAC-SHA-256 implementation with the right key derivation. Technically straightforward once you know what to do, but tedious to get right.

There's a limit to what AI can do here though. No language model can plug a phone into a car and tell you what the raw values actually mean in the real world. For that, I had to test on my MG3 EU. *(Though technically, an MCP integration could have automated that too. Maybe next time.)*

The Python client leaves some fields uninterpreted. So I ran the code, looked at the numbers, and reasoned about them. A few examples:

| Field | Raw value | Unit | Converted |
|---|---|---|---|
| Mileage | `243790` |
decameters | 24 379 km |
| Tyre pressure (FL) | `61` |
PSI×2 | 30.5 PSI / 2.1 bar |
| Tyre pressure (RR) | `70` |
PSI×2 | 35 PSI / 2.4 bar |

Many other fields follow similar patterns: sentinel values, unexpected scales, units that only make sense once you cross-reference with the real world. All of these field experiments enriched the `TECHNICAL_REFERENCE.md`

, turning it from a protocol spec into something that actually reflects how the API behaves on a real vehicle.

Here's what the package actually lets you do:

`vehicleModelConfiguration`

and exposes 23 typed gettersA few lines of setup, then straight to the data:

```
final client = SaicClient(
  config: SaicConfig(
    username: 'you@example.com',
    password: 'yourpassword',
    region: SaicRegion.europe,
  ),
);

await client.login();

final vehicles = await client.getVehicles();
final vin = vehicles.first.vin;

// Status
final status = await client.getVehicleStatus(vin);
print(status.basicVehicleStatus?.mileageKm);                // 24447.0
print(status.basicVehicleStatus?.lockState);                // LockStatus.locked
print(status.basicVehicleStatus?.frontLeftTyrePressureBar); // 2.48

// Remote commands
await client.lockVehicle(vin);
await client.startClimate(vin, temperatureIndex: 8);
await client.controlHeatedSeats(vin, driverLevel: HeatLevel.medium);
await client.findMyCar(vin);
```

All API calls are serialized. The SAIC server doesn't support concurrent requests on the same session.

The first time I unlocked my car from my own Flutter app, not from iSmart, not from a terminal, but from a UI I had built on top of code I had written, was a genuinely satisfying moment. And it kept happening: the first successful `findMyCar()`

triggering the horn from across the street, the first time heated seats started before I even got to the car. Each feature that went from "POST /vehicle/control" to a real physical reaction felt like a small proof of concept becoming real.

452 tests. 160/160 on pub.dev. 23 vehicle feature detection getters. Tested in production on a real MG3 EU. Pure Dart, no native code, works in any Flutter or Dart project.

The package is on pub.dev: [saic_ismart](https://pub.dev/packages/saic_ismart)

EV features like battery SoC, charging management, and climate scheduling are the natural next milestone. I don't own an EV myself, so I can't develop or validate those features without the right hardware. I would be genuinely happy to welcome contributors who own an MG4, ZS EV, or Roewe. The architecture is in place and ready to be extended. Open an issue on GitHub.

The client is pure Dart, so wherever Flutter runs, this can run too. The package is a foundation. Build what's missing.

*Built with significant help from the research work of the SAIC-iSmart-API community. Their work greatly accelerated my development.*
