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
- Large button design: Consider driving scenarios, interactive elements should be large enough
- Voice control: Integrate voice recognition for hands-free operation
- Dark mode: Reduce visual interference during night driving
- 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
- Create an in-vehicle application project in AGC console
- Configure special permissions for in-vehicle applications
- Generate special signing certificate for vehicle systems
- Build dedicated package for in-vehicle applications
- 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