Practical Development of Smart Connected Car Applications Based on HarmonyOS Next



This content originally appeared on DEV Community and was authored by linzhongxue

Practical Development of Smart Connected Car Applications Based on HarmonyOS Next

Building Your In-Vehicle Infotainment System from Scratch

After test driving several smart cars recently, I was deeply impressed by their stunning central control systems. As developers, we can use HarmonyOS Next and AppGallery Connect to build similar in-vehicle applications. Today, I’ll guide you through developing a smart connected car application that includes vehicle status monitoring, navigation, and entertainment features.

Project Initialization and Environment Setup

Open DevEco Studio and select the “Automotive” template to create a new project. This template comes pre-configured with the necessary settings and permissions for in-vehicle applications.

// Application entry file EntryAbility.ts
import UIAbility from '@ohos.app.ability.UIAbility';
import window from '@ohos.window';
import logger from '@ohos.hilog';

export default class EntryAbility extends UIAbility {
  onCreate() {
    logger.info(0x0000, 'CarApp', 'In-vehicle application initialization completed');
  }

  onWindowStageCreate(windowStage: window.WindowStage) {
    windowStage.loadContent('pages/MainPage', (err) => {
      if (err) {
        logger.error(0x0000, 'CarApp', 'Failed to load main page %{public}s', JSON.stringify(err));
        return;
      }
      logger.info(0x0000, 'CarApp', 'Main page loaded successfully');
    });
  }
}

Vehicle Data Access and Display

Retrieving Vehicle CAN Bus Data

HarmonyOS provides vehicle data interfaces to safely read vehicle status information.

// Vehicle service VehicleService.ts
import { vehicle } from '@kit.DeviceCapabilityKit';

class VehicleService {
  private static vehicleInstance: vehicle.Vehicle;

  // Initialize vehicle service
  static async init() {
    try {
      this.vehicleInstance = await vehicle.getVehicleInstance();
      logger.info(0x0000, 'CarApp', 'Vehicle service initialized successfully');
    } catch (error) {
      logger.error(0x0000, 'CarApp', 'Vehicle service initialization failed %{public}s', JSON.stringify(error));
    }
  }

  // Get current speed
  static async getSpeed(): Promise<number> {
    try {
      const speed = await this.vehicleInstance.get(vehicle.DataType.SPEED);
      return speed?.value || 0;
    } catch (error) {
      logger.error(0x0000, 'CarApp', 'Failed to get speed %{public}s', JSON.stringify(error));
      return 0;
    }
  }

  // Get remaining fuel level
  static async getFuelLevel(): Promise<number> {
    try {
      const fuel = await this.vehicleInstance.get(vehicle.DataType.FUEL_LEVEL);
      return fuel?.value || 0;
    } catch (error) {
      logger.error(0x0000, 'CarApp', 'Failed to get fuel level %{public}s', JSON.stringify(error));
      return 0;
    }
  }
}

export default VehicleService;

Implementing Vehicle Dashboard UI

Design a modern digital dashboard to display key vehicle data.

// Dashboard component Dashboard.ets
@Component
struct SpeedGauge {
  @Prop currentSpeed: number
  @Prop maxSpeed: number = 220

  build() {
    Column() {
      // Speed dial
      Stack() {
        Circle({ width: 200, height: 200 })
          .strokeWidth(15)
          .stroke('#333333')

        Circle({ width: 200, height: 200 })
          .strokeWidth(15)
          .stroke('#4CAF50')
          .sweepAngle(this.currentSpeed / this.maxSpeed * 270)
          .startAngle(135)
      }

      // Speed value
      Text(this.currentSpeed.toString())
        .fontSize(36)
        .fontWeight(FontWeight.Bold)
        .margin({ top: 10 })

      Text('km/h')
        .fontSize(16)
        .fontColor('#999999')
    }
    .width('100%')
    .margin({ top: 20 })
  }
}

@Entry
@Component
struct DashboardPage {
  @State speed: number = 0
  @State fuelLevel: number = 80

  aboutToAppear() {
    // Simulate data updates
    setInterval(async () => {
      this.speed = await VehicleService.getSpeed();
      this.fuelLevel = await VehicleService.getFuelLevel();
    }, 1000);
  }

  build() {
    Column() {
      // Top status bar
      Row() {
        Image($r('app.media.car_icon'))
          .width(24)
          .height(24)
          .margin({ right: 10 })

        Text('My Vehicle')
          .fontSize(18)
          .fontWeight(FontWeight.Bold)
      }
      .width('100%')
      .justifyContent(FlexAlign.Start)
      .margin({ bottom: 20 })

      // Main dashboard
      SpeedGauge({ currentSpeed: this.speed })

      // Fuel level indicator
      Row() {
        Image($r('app.media.fuel_icon'))
          .width(20)
          .height(20)
          .margin({ right: 5 })

        Progress({
          value: this.fuelLevel,
          total: 100,
          type: ProgressType.Linear
        })
          .width('70%')
          .height(10)

        Text(this.fuelLevel + '%')
          .fontSize(14)
          .margin({ left: 5 })
      }
      .width('90%')
      .margin({ top: 30 })
    }
    .width('100%')
    .height('100%')
    .padding(20)
    .backgroundColor('#111111')
  }
}

In-Vehicle Navigation System Development

Integrating AGC Map Services

Use AppGallery Connect’s map services to add navigation functionality to the in-vehicle system.

// Navigation service NavigationService.ts
import agconnect from '@hw-agconnect/api-ohos';
import '@hw-agconnect/map-ohos';

class NavigationService {
  private static mapInstance: any;

  // Initialize map service
  static async init(context: any) {
    try {
      agconnect.instance().init(context);
      this.mapInstance = await agconnect.map().createMapView({
        container: 'mapContainer',
        center: { lng: 116.404, lat: 39.915 },
        zoom: 15
      });
      logger.info(0x0000, 'CarApp', 'Map service initialized successfully');
    } catch (error) {
      logger.error(0x0000, 'CarApp', 'Map initialization failed %{public}s', JSON.stringify(error));
    }
  }

  // Set destination
  static async setDestination(lng: number, lat: number) {
    try {
      await this.mapInstance.setDestination({ lng, lat });
      logger.info(0x0000, 'CarApp', 'Destination set successfully');
    } catch (error) {
      logger.error(0x0000, 'CarApp', 'Failed to set destination %{public}s', JSON.stringify(error));
    }
  }
}

export default NavigationService;

Navigation Interface Implementation

// Navigation page NavigationPage.ets
@Entry
@Component
struct NavigationPage {
  @State currentLocation: string = 'Locating...'
  @State destination: string = ''

  aboutToAppear() {
    NavigationService.init(this.context);
  }

  build() {
    Column() {
      // Top search bar
      Row() {
        TextInput({ placeholder: 'Enter destination' })
          .width('80%')
          .onChange((value: string) => {
            this.destination = value;
          })

        Button('Search')
          .width('20%')
          .onClick(() => {
            if (this.destination) {
              // In actual development, call geocoding service
              NavigationService.setDestination(116.404, 39.915);
            }
          })
      }
      .width('100%')
      .margin({ bottom: 15 })

      // Current location display
      Text('Current location: ' + this.currentLocation)
        .fontSize(16)
        .margin({ bottom: 10 })
        .width('100%')
        .textAlign(TextAlign.Start)

      // Map container
      Stack() {
        // Actual map will be rendered by AGC SDK into this container
        Div()
          .id('mapContainer')
          .width('100%')
          .height('80%')
          .backgroundColor('#333333')

        // Navigation control buttons
        Column() {
          Button('Start Navigation')
            .width(120)
            .height(40)
            .type(ButtonType.Capsule)
            .backgroundColor('#4CAF50')
        }
        .width('100%')
        .position({ x: 0, y: '80%' })
        .justifyContent(FlexAlign.Center)
      }
      .width('100%')
      .height('70%')
    }
    .width('100%')
    .height('100%')
    .padding(15)
  }
}

In-Vehicle Entertainment System Development

Music Player Implementation

// Music service MusicService.ts
import { media } from '@kit.MediaKit';

class MusicService {
  private static audioPlayer: media.AudioPlayer;

  // Initialize player
  static async init() {
    try {
      this.audioPlayer = await media.createAudioPlayer();
      logger.info(0x0000, 'CarApp', 'Music player initialized successfully');
    } catch (error) {
      logger.error(0x0000, 'CarApp', 'Player initialization failed %{public}s', JSON.stringify(error));
    }
  }

  // Play music
  static async play(url: string) {
    try {
      await this.audioPlayer.reset();
      await this.audioPlayer.setSource(url);
      await this.audioPlayer.play();
      logger.info(0x0000, 'CarApp', 'Started playing music');
    } catch (error) {
      logger.error(0x0000, 'CarApp', 'Playback failed %{public}s', JSON.stringify(error));
    }
  }

  // Pause playback
  static async pause() {
    try {
      await this.audioPlayer.pause();
      logger.info(0x0000, 'CarApp', 'Music paused');
    } catch (error) {
      logger.error(0x0000, 'CarApp', 'Pause failed %{public}s', JSON.stringify(error));
    }
  }
}

export default MusicService;

Music Player Interface

// Music player page MusicPlayerPage.ets
@Component
struct SongItem {
  @Prop songName: string
  @Prop artist: string
  @Prop isPlaying: boolean = false

  build() {
    Row() {
      Column() {
        Text(this.songName)
          .fontSize(16)
          .fontColor(this.isPlaying ? '#4CAF50' : '#FFFFFF')

        Text(this.artist)
          .fontSize(12)
          .fontColor('#999999')
      }
      .margin({ left: 10 })
      .layoutWeight(1)

      if (this.isPlaying) {
        Image($r('app.media.playing_icon'))
          .width(20)
          .height(20)
      }
    }
    .width('100%')
    .padding(15)
    .backgroundColor(this.isPlaying ? '#333333' : 'transparent')
    .borderRadius(5)
  }
}

@Entry
@Component
struct MusicPlayerPage {
  @State currentSong: string = ''
  @State isPlaying: boolean = false
  @State songList: Array<{name: string, artist: string, url: string}> = [
    { name: 'Drive', artist: 'Incubus', url: 'asset:///songs/drive.mp3' },
    { name: 'Ride', artist: 'Twenty One Pilots', url: 'asset:///songs/ride.mp3' },
    { name: 'Fast Car', artist: 'Tracy Chapman', url: 'asset:///songs/fast_car.mp3' }
  ]

  aboutToAppear() {
    MusicService.init();
  }

  playSong(url: string, name: string) {
    MusicService.play(url);
    this.currentSong = name;
    this.isPlaying = true;
  }

  togglePlay() {
    if (this.isPlaying) {
      MusicService.pause();
      this.isPlaying = false;
    } else if (this.currentSong) {
      MusicService.play(this.songList.find(s => s.name === this.currentSong)?.url || '');
      this.isPlaying = true;
    }
  }

  build() {
    Column() {
      // Currently playing song
      if (this.currentSong) {
        Column() {
          Text('Now Playing')
            .fontSize(14)
            .fontColor('#4CAF50')
            .margin({ bottom: 5 })

          Text(this.currentSong)
            .fontSize(20)
            .fontWeight(FontWeight.Bold)
            .margin({ bottom: 3 })

          Text(this.songList.find(s => s.name === this.currentSong)?.artist || '')
            .fontSize(14)
            .fontColor('#999999')
        }
        .width('100%')
        .margin({ bottom: 20 })
      }

      // Playback control buttons
      Row() {
        Button('Previous')
          .width(80)
          .margin({ right: 10 })

        Button(this.isPlaying ? 'Pause' : 'Play')
          .width(80)
          .type(ButtonType.Capsule)
          .backgroundColor('#4CAF50')
          .onClick(() => this.togglePlay())

        Button('Next')
          .width(80)
          .margin({ left: 10 })
      }
      .width('100%')
      .margin({ bottom: 30 })
      .justifyContent(FlexAlign.Center)

      // Song list
      List({ space: 5 }) {
        ForEach(this.songList, (song) => {
          ListItem() {
            SongItem({
              songName: song.name,
              artist: song.artist,
              isPlaying: this.isPlaying && this.currentSong === song.name
            })
            .onClick(() => this.playSong(song.url, song.name))
          }
        })
      }
      .width('100%')
      .layoutWeight(1)
    }
    .width('100%')
    .height('100%')
    .padding(20)
    .backgroundColor('#111111')
  }
}

Application Optimization and Release

In-Vehicle Application Optimization Recommendations

  1. Large button design: Consider driving scenarios, interactive elements should be large enough
  2. Voice control: Integrate voice recognition for hands-free operation
  3. Dark mode: Reduce visual interference during night driving
  4. Quick response: Optimize performance to ensure immediate feedback
// Voice control example VoiceControl.ts
import { voiceAssistant } from '@kit.VoiceKit';

class VoiceControl {
  static async init() {
    try {
      await voiceAssistant.init();
      await voiceAssistant.registerCommand({
        commands: ['Play music', 'Pause', 'Navigate to'],
        callback: (command: string) => {
          switch (command) {
            case 'Play music':
              MusicService.play('asset:///songs/default.mp3');
              break;
            case 'Pause':
              MusicService.pause();
              break;
          }
        }
      });
      logger.info(0x0000, 'CarApp', 'Voice control initialized successfully');
    } catch (error) {
      logger.error(0x0000, 'CarApp', 'Voice control initialization failed %{public}s', JSON.stringify(error));
    }
  }
}

export default VoiceControl;

Publishing to In-Vehicle App Market

  1. Create an in-vehicle application project in AGC console
  2. Configure special permissions for in-vehicle applications
  3. Generate special signing certificate for vehicle systems
  4. Build dedicated package for in-vehicle applications
  5. Submit to Huawei’s in-vehicle app market for review
// config.json example for in-vehicle applications
{
  "module": {
    "abilities": [
      {
        "name": "EntryAbility",
        "type": "page",
        "autoLaunch": true,
        "label": "Smart Connected Car",
        "icon": "$media:app_icon",
        "launchType": "standard",
        "metadata": [
          {
            "name": "hwc-theme",
            "value": "dark"  // Default to dark theme
          }
        ]
      }
    ],
    "reqPermissions": [
      {
        "name": "ohos.permission.ACCESS_VEHICLE_DATA",
        "reason": "For reading vehicle status information"
      },
      {
        "name": "ohos.permission.USE_VOICE_ASSISTANT",
        "reason": "To implement voice control features"
      }
    ]
  }
}

Project Summary and Future Prospects

Through this project, we’ve implemented a fully functional in-vehicle infotainment system. HarmonyOS Next provides powerful support for in-vehicle application development, especially its distributed capabilities and cross-device collaboration features, making it particularly suitable for in-vehicle scenarios.

Future expansions could include:

  • Implementing seamless connection between mobile phones and vehicle systems
  • Adding remote vehicle control functionality
  • Developing driving behavior analysis systems
  • Integrating more third-party in-vehicle services

We hope this tutorial helps you quickly get started with HarmonyOS in-vehicle application development. Remember that driving safety is paramount—all feature designs must prioritize not interfering with driving!


This content originally appeared on DEV Community and was authored by linzhongxue