tesla_fleet_api 1.0.0
tesla_fleet_api: ^1.0.0 copied to clipboard
A Dart package for interacting with Tesla Fleet API
import 'dart:io';
import 'package:tesla_fleet_api/tesla_fleet_api.dart';
void main() async {
print('=== Tesla Fleet API Example ===\n');
print('Choose authentication method:');
print('1. Client Credentials Flow (for business/testing)');
print('2. Authorization Code Flow (for accessing personal vehicles)');
stdout.write('Enter your choice (1 or 2): ');
final choice = stdin.readLineSync();
if (choice == '2') {
await runAuthorizationCodeFlow();
} else {
await runClientCredentialsFlow();
}
}
Future<void> runAuthorizationCodeFlow() async {
print('\n=== Authorization Code Flow ===');
print('This flow allows access to personal Tesla vehicles.\n');
print('Choose scope for authorization:');
print('1. Read-Only Access (vehicle data, energy data, user info)');
print('2. Vehicle Control Access (includes read + vehicle commands)');
print('3. Full Access (includes all scopes)');
stdout.write('Enter your choice (1, 2, or 3): ');
final scopeChoice = stdin.readLineSync();
List<String> selectedScopes;
String scopeDescription;
switch (scopeChoice) {
case '1':
selectedScopes = [
'openid',
'offline_access',
'user_data',
'vehicle_device_data',
'energy_device_data',
];
scopeDescription = 'Read-Only Access';
break;
case '2':
selectedScopes = [
'openid',
'offline_access',
'user_data',
'vehicle_device_data',
'vehicle_cmds',
'vehicle_charging_cmds',
];
scopeDescription = 'Vehicle Control Access';
break;
case '3':
selectedScopes = [
'openid',
'offline_access',
'user_data',
'vehicle_device_data',
'vehicle_cmds',
'vehicle_charging_cmds',
'energy_device_data',
'energy_cmds',
];
scopeDescription = 'Full Access';
break;
default:
selectedScopes = [
'openid',
'offline_access',
'user_data',
'vehicle_device_data',
];
scopeDescription = 'Basic Read-Only Access';
break;
}
// Initialize authentication with redirect URI
// Replace with your actual credentials from Tesla Developer Portal
final auth = TeslaAuth(
clientId: 'YOUR_CLIENT_ID_HERE',
clientSecret: 'YOUR_CLIENT_SECRET_HERE',
privateKey: '',
redirectUri: 'YOUR_REDIRECT_URI_HERE',
);
// Generate authorization URL
final authUrl = auth.generateAuthorizationUrl(
scopes: selectedScopes,
state: 'example-state-${DateTime.now().millisecondsSinceEpoch}',
);
print('\nπ Selected: $scopeDescription');
print('π Scopes: ${selectedScopes.join(', ')}');
print('\nπ Please visit this URL to authorize:');
print(authUrl);
print('\nπ After authorization, copy the code from the redirect URL:');
stdout.write('Enter authorization code: ');
final code = stdin.readLineSync();
if (code == null || code.trim().isEmpty) {
print('β No code provided');
return;
}
try {
await auth.exchangeAuthorizationCode(code.trim());
await runApiTests(TeslaFleetClient(auth: auth), selectedScopes);
} catch (e) {
print('β Authorization failed: $e');
}
}
Future<void> runClientCredentialsFlow() async {
print('\n=== Client Credentials Flow ===');
print('This flow is for business applications (limited access).\n');
// Initialize authentication
// Replace with your actual credentials from Tesla Developer Portal
final auth = TeslaAuth(
clientId: 'YOUR_CLIENT_ID_HERE',
clientSecret: 'YOUR_CLIENT_SECRET_HERE',
privateKey: '', // Not used with client_credentials flow
);
try {
// Client credentials flow has very limited scopes
// Typically only vehicle_device_data is available
await runApiTests(TeslaFleetClient(auth: auth), ['vehicle_device_data']);
} catch (e) {
print('β Error: $e');
}
}
Future<void> runApiTests(TeslaFleetClient client, List<String> scopes) async {
print('\n=== Available Tests Based on Scopes ===');
print('π Your scopes: ${scopes.join(', ')}');
// Check what tests are available based on scopes
final hasVehicleData = scopes.contains('vehicle_device_data');
final hasVehicleControl = scopes.contains('vehicle_cmds') ||
scopes.contains('vehicle_charging_cmds');
final hasEnergyData = scopes.contains('energy_device_data');
final hasEnergyControl = scopes.contains('energy_cmds');
final hasUserData = scopes.contains('user_data');
print('\nβ
Available tests:');
if (hasVehicleData) print(' β’ Vehicle Data Retrieval');
if (hasVehicleControl) print(' β’ Vehicle Control Commands');
if (hasEnergyData) print(' β’ Energy Data Retrieval');
if (hasEnergyControl) print(' β’ Energy Control Commands');
if (hasUserData) print(' β’ User Info');
print('\nβ Unavailable tests:');
if (!hasVehicleData)
print(' β’ Vehicle Data Retrieval (missing vehicle_device_data scope)');
if (!hasVehicleControl)
print(
' β’ Vehicle Control Commands (missing vehicle_cmds/vehicle_charging_cmds scope)');
if (!hasEnergyData)
print(' β’ Energy Data Retrieval (missing energy_device_data scope)');
if (!hasEnergyControl)
print(' β’ Energy Control Commands (missing energy_cmds scope)');
if (!hasUserData) print(' β’ User Info (missing user_data scope)');
print('\nChoose test type:');
print('1. Safe Data Retrieval Only (read-only operations)');
if (hasVehicleControl) {
print(
'2. Vehicle Control Commands (β οΈ CAUTION: will affect your vehicle)');
} else {
print('2. Vehicle Control Commands (β UNAVAILABLE - insufficient scopes)');
}
stdout.write('Enter your choice (1 or 2): ');
final testChoice = stdin.readLineSync();
if (testChoice == '2') {
if (!hasVehicleControl) {
print(
'β Vehicle control commands are not available with your current scopes');
print('π‘ You need vehicle_cmds and/or vehicle_charging_cmds scopes');
await runSafeDataTests(client, scopes);
return;
}
print('\nβ οΈ WARNING: You are about to test vehicle control commands!');
print(
'β οΈ This may cause your vehicle to flash lights, honk, or start climate control.');
stdout.write('Are you sure? Type "YES" to continue: ');
final confirmation = stdin.readLineSync();
if (confirmation != 'YES') {
print('β Vehicle control test cancelled for safety.');
await runSafeDataTests(client, scopes);
return;
}
await runVehicleControlTests(client, scopes);
} else {
await runSafeDataTests(client, scopes);
}
}
Future<void> runSafeDataTests(
TeslaFleetClient client, List<String> scopes) async {
try {
print('\n=== SAFE DATA RETRIEVAL TESTS ===');
print('(Read-only operations - no vehicle control)\n');
// Example 1: List all vehicles (requires vehicle_device_data scope)
if (!scopes.contains('vehicle_device_data')) {
print('β Vehicle tests skipped (missing vehicle_device_data scope)');
return;
}
print('=== Listing Vehicles ===');
List<Vehicle> vehicles = [];
int retryCount = 0;
const maxRetries = 3;
while (retryCount < maxRetries) {
try {
vehicles = await client.vehicles.list();
print('Found ${vehicles.length} vehicles');
break; // Success, exit retry loop
} catch (e) {
retryCount++;
if (e.toString().contains('412')) {
print(
'β Vehicle list unavailable (HTTP 412) - Attempt $retryCount/$maxRetries');
// Check if it's a registration issue
if (e
.toString()
.contains('must be registered in the current region')) {
print(
'π REGISTRATION REQUIRED: Account not registered in current region');
print('π Attempting automatic partner account registration...');
try {
await client.partner.registerPartner('undersoil.co.jp');
print('β
Partner account registration successful!');
print('β³ Waiting 30 seconds for registration to propagate...');
await Future.delayed(Duration(seconds: 30));
// Try once more after registration
print('π Retrying vehicle list after registration...');
continue; // This will retry the vehicle list
} catch (regError) {
print('β Automatic registration failed: $regError');
print('π Manual registration required:');
print(' 1. Visit Tesla Developer Portal');
print(' 2. Register your application for this region');
print(' 3. Wait 10-15 minutes after registration');
print(' 4. Try again');
throw e;
}
}
if (retryCount < maxRetries) {
print('β³ Waiting 10 seconds before retry...');
await Future.delayed(Duration(seconds: 10));
print('π Retrying vehicle list...');
} else {
print('β Failed to get vehicle list after $maxRetries attempts');
print('π‘ Possible solutions:');
print(' β’ Open Tesla mobile app and check vehicle connectivity');
print(
' β’ Wait for vehicle to wake up naturally (someone uses it)');
print(' β’ Try again in 15-30 minutes');
throw e; // Re-throw to trigger catch block
}
} else {
throw e; // Re-throw non-412 errors immediately
}
}
}
if (vehicles.isNotEmpty) {
final vehicle = vehicles.first;
print('Vehicle: ${vehicle.displayName} (${vehicle.vin})');
print('State: ${vehicle.state}');
print('Vehicle ID: ${vehicle.id}');
// Check if vehicle needs to be woken up
print('\n=== Checking Vehicle State ===');
print('Vehicle state: ${vehicle.state}');
// If vehicle is asleep, try to wake it up first (this is safe)
if (vehicle.state == 'asleep' || vehicle.state == 'offline') {
print('\n=== Waking Up Vehicle (Safe - required for data access) ===');
try {
final wakeResponse =
await client.vehicles.wakeUp(vehicle.id.toString());
print('Wake up result: ${wakeResponse.result}');
if (wakeResponse.result == true) {
print('Waiting 5 seconds for vehicle to wake up...');
await Future.delayed(Duration(seconds: 5));
}
} catch (e) {
print('β οΈ Could not wake up vehicle: $e');
print('β οΈ Some data may not be available');
}
}
// Example 2: Get vehicle data (safe - read-only)
print('\n=== Getting Vehicle Data (Safe) ===');
try {
final vehicleData =
await client.vehicles.getVehicleData(vehicle.id.toString());
// Battery information
if (vehicleData.chargeState != null) {
print('π Battery level: ${vehicleData.chargeState!.batteryLevel}%');
print('π Range: ${vehicleData.chargeState!.batteryRange} miles');
print('π Charging state: ${vehicleData.chargeState!.chargingState}');
print('π Charge limit: ${vehicleData.chargeState!.chargeLimitSoc}%');
}
// Location information
if (vehicleData.driveState != null) {
print(
'π Location: ${vehicleData.driveState!.latitude}, ${vehicleData.driveState!.longitude}');
print('π Speed: ${vehicleData.driveState!.speed ?? 0} mph');
print('π Gear: ${vehicleData.driveState!.shiftState ?? 'Unknown'}');
}
// Climate information
if (vehicleData.climateState != null) {
print('π‘οΈ Inside temp: ${vehicleData.climateState!.insideTemp}Β°C');
print(
'π‘οΈ Outside temp: ${vehicleData.climateState!.outsideTemp}Β°C');
print(
'π‘οΈ Driver temp setting: ${vehicleData.climateState!.driverTempSetting}Β°C');
print('π‘οΈ Climate on: ${vehicleData.climateState!.isClimateOn}');
}
// Vehicle state information
if (vehicleData.vehicleState != null) {
print('π Odometer: ${vehicleData.vehicleState!.odometer} miles');
print('π Software version: ${vehicleData.vehicleState!.carVersion}');
print('π Locked: ${vehicleData.vehicleState!.locked}');
print('π Valet mode: ${vehicleData.vehicleState!.valetMode}');
print('π User present: ${vehicleData.vehicleState!.isUserPresent}');
}
} catch (e) {
if (e.toString().contains('412')) {
print('β Vehicle data unavailable (HTTP 412)');
print('π‘ This usually means:');
print(' β’ Vehicle is asleep/offline and needs to be woken up');
print(' β’ Vehicle is not ready to respond to commands');
print(' β’ Network connectivity issues');
print('β οΈ Try waking up the vehicle first, then retry');
} else {
print('β Could not get vehicle data: $e');
}
}
}
// Safe energy and charging data (scope-dependent)
if (scopes.contains('energy_device_data')) {
await runSafeEnergyTests(client);
} else {
print('\nβ Energy tests skipped (missing energy_device_data scope)');
}
if (scopes.contains('vehicle_charging_cmds')) {
await runSafeChargingHistoryTests(client);
} else {
print(
'\nβ Charging history tests skipped (missing vehicle_charging_cmds scope)');
}
if (scopes.contains('user_data')) {
await runSafeUserInfoTests(client);
} else {
print('\nβ User info tests skipped (missing user_data scope)');
}
} catch (e) {
if (e.toString().contains('412')) {
if (e.toString().contains('must be registered in the current region')) {
print('β ACCOUNT NOT REGISTERED');
print('π Your Tesla account must be registered for Fleet API access');
print('π§ Quick fix:');
print(' dart run example/register_partner.dart');
print('π‘ Or visit Tesla Developer Portal to register manually');
} else {
print('β API request failed (HTTP 412)');
print('π‘ This usually means:');
print(' β’ Vehicle is in deep sleep mode');
print(' β’ Tesla servers are temporarily unavailable');
print(' β’ Rate limiting or API restrictions');
print('π Suggestion: Try again in a few minutes');
}
} else if (e.toString().contains('403')) {
print('β Access forbidden (HTTP 403)');
print('π‘ This usually means:');
print(' β’ Insufficient permissions/scopes');
print(' β’ Token has expired');
print(' β’ Application not properly authorized');
} else {
print('β Error in safe data tests: $e');
}
} finally {
client.dispose();
}
}
Future<void> runVehicleControlTests(
TeslaFleetClient client, List<String> scopes) async {
try {
print('\n=== β οΈ VEHICLE CONTROL TESTS ===');
print('(These commands will affect your vehicle!)\n');
// Get vehicles first
final vehicles = await client.vehicles.list();
if (vehicles.isEmpty) {
print('β No vehicles found for control tests');
return;
}
final vehicle = vehicles.first;
print('Controlling vehicle: ${vehicle.displayName} (${vehicle.vin})\n');
// Wake up vehicle first (necessary for most commands)
print('=== Waking Up Vehicle ===');
try {
final wakeResponse = await client.vehicles.wakeUp(vehicle.id.toString());
print('Wake up result: ${wakeResponse.result}');
// Wait a moment for the vehicle to wake up
print('Waiting 3 seconds for vehicle to wake up...');
await Future.delayed(Duration(seconds: 3));
} catch (e) {
print('β Could not wake up vehicle: $e');
return;
}
// Vehicle visual/audio commands (requires vehicle_cmds scope)
if (scopes.contains('vehicle_cmds')) {
print('\n=== Vehicle Visual/Audio Commands ===');
try {
// Flash lights
print('Flashing lights...');
final flashResponse =
await client.vehicles.flashLights(vehicle.id.toString());
print('π‘ Flash lights result: ${flashResponse.result}');
await Future.delayed(Duration(seconds: 2));
// Honk horn
print('Honking horn...');
final honkResponse =
await client.vehicles.honkHorn(vehicle.id.toString());
print('π― Honk horn result: ${honkResponse.result}');
await Future.delayed(Duration(seconds: 2));
} catch (e) {
print('β Visual/audio commands failed: $e');
}
} else {
print(
'\nβ Vehicle visual/audio commands skipped (missing vehicle_cmds scope)');
}
// Climate control commands (requires vehicle_cmds scope)
if (scopes.contains('vehicle_cmds')) {
print('\n=== Climate Control Commands ===');
try {
// Start climate control
print('Starting climate control...');
final climateResponse =
await client.vehicles.startClimate(vehicle.id.toString());
print('π‘οΈ Start climate result: ${climateResponse.result}');
await Future.delayed(Duration(seconds: 2));
// Set temperature
print('Setting temperature to 22Β°C...');
final tempResponse = await client.vehicles
.setTemperature(vehicle.id.toString(), 22.0, 22.0);
print('π‘οΈ Set temperature result: ${tempResponse.result}');
await Future.delayed(Duration(seconds: 2));
} catch (e) {
print('β Climate control commands failed: $e');
}
} else {
print(
'\nβ Climate control commands skipped (missing vehicle_cmds scope)');
}
// Charging commands (requires vehicle_charging_cmds scope)
if (scopes.contains('vehicle_charging_cmds')) {
print('\n=== Charging Commands ===');
try {
// Open charge port
print('Opening charge port...');
final openPortResponse =
await client.vehicles.openChargePort(vehicle.id.toString());
print('π Open charge port result: ${openPortResponse.result}');
await Future.delayed(Duration(seconds: 2));
// Set charge limit
print('Setting charge limit to 80%...');
final chargeLimitResponse =
await client.vehicles.setChargeLimit(vehicle.id.toString(), 80);
print('π Set charge limit result: ${chargeLimitResponse.result}');
} catch (e) {
print('β Charging commands failed: $e');
}
} else {
print(
'\nβ Charging commands skipped (missing vehicle_charging_cmds scope)');
}
print('\nβ
Vehicle control tests completed!');
} catch (e) {
print('β Error in vehicle control tests: $e');
} finally {
client.dispose();
}
}
Future<void> runSafeEnergyTests(TeslaFleetClient client) async {
try {
print('\n=== Energy Products (Safe) ===');
final energyProducts = await client.energy.list();
if (energyProducts.isEmpty) {
print('βΉοΈ No energy products found (Solar panels, Powerwall, etc.)');
print(
'π‘ Energy products need to be registered separately in Tesla account');
return;
}
print('Found ${energyProducts.length} energy products');
for (final product in energyProducts) {
print('Energy product: ${product.siteName ?? 'Unknown'}');
print(' Energy Site ID: ${product.energySiteId}');
print(' Resource Type: ${product.resourceType}');
if (product.energySiteId == null) {
print(' β οΈ Energy Site ID is null, skipping detailed data');
continue;
}
try {
// Get live status (safe - read-only)
final liveStatus =
await client.energy.getLiveStatus(product.energySiteId!);
print(' β‘ Solar power: ${liveStatus.solarPower}W');
print(' π Battery power: ${liveStatus.batteryPower}W');
print(' π Grid power: ${liveStatus.gridPower}W');
print(' π Battery level: ${liveStatus.percentageCharged}%');
// Get energy history (safe - read-only)
final history = await client.energy.getHistory(
product.energySiteId!,
period: 'day',
startDate: DateTime.now().subtract(Duration(days: 7)),
endDate: DateTime.now(),
);
print(
' π Energy history entries: ${history.timeSeries?.length ?? 0}');
} catch (e) {
print(' β Could not get energy data: $e');
}
}
} catch (e) {
print('β Energy products test failed: $e');
}
}
Future<void> runSafeChargingHistoryTests(TeslaFleetClient client) async {
try {
print('\n=== Charging History (Safe) ===');
final vehicles = await client.vehicles.list();
if (vehicles.isNotEmpty) {
final chargingSessions = await client.charging.getChargingHistory(
vin: vehicles.first.vin,
limit: 10,
);
print('Found ${chargingSessions.length} charging sessions');
if (chargingSessions.isNotEmpty) {
for (final session in chargingSessions.take(3)) {
// Show only first 3 for brevity
print(' π Session: ${session.sessionId}');
print(' π Location: ${session.siteLocationName}');
print(' β‘ Energy used: ${session.energyUsed} kWh');
print(' π° Cost: \$${session.chargeCost}');
print('');
}
}
}
} catch (e) {
print('β Charging history test failed: $e');
}
}
Future<void> runSafeUserInfoTests(TeslaFleetClient client) async {
try {
print('\n=== User Info (Safe) ===');
final user = await client.partner.getMe();
print('π€ User: ${user.fullName} (${user.email})');
} catch (e) {
if (e.toString().contains('403') &&
e.toString().contains('missing scopes')) {
print('β User info unavailable: Missing user_data scope');
print('π‘ This is normal for Client Credentials flow');
print('π‘ Use Authorization Code flow to access user information');
} else {
print('β User info test failed: $e');
}
}
}