February 12, 2025

On this article, we’ll discover create an utility that controls HVAC capabilities and retrieves photographs from cameras in a car outfitted with Android Automotive OS (AAOS) 14.

Android companion app

The cellphone have to be linked to the automotive’s Wi-Fi, and communication between the Head Unit and the cellphone is required. The Android companion app will make the most of the HTTP protocol for this objective.

In AAOS 14, the Automobile {Hardware} Abstraction Layer (VHAL) will create an HTTP server to deal with our instructions. This performance is mentioned intimately within the article “Exploring the Structure of Automotive Electronics: Area vs. Zone“.

Creating the cellular utility

To develop the cellular utility, we’ll use Android Studio. Begin by choosing File -> New Venture -> Cellphone and Pill -> Empty Exercise from the menu. It will create a primary Android venture construction.

Subsequent, you should create the Android companion app format, as proven within the supplied screenshot.

AAOS application

Under is the XML code for the instance format:

<?xml model="1.0" encoding="utf-8"?>

<!-- Copyright 2013 The Android Open Supply Venture -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/view"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:orientation="vertical">

        <Button
            android:id="@+id/evs"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textual content="EVS ON" />

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">

            <TextView
                android:id="@+id/temperatureText"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginStart="20dp"
                android:layout_marginTop="8dp"
                android:layout_marginEnd="20dp"
                android:textual content="16.0"
                android:textSize="60sp" />

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical">

                <Button
                    android:id="@+id/tempUp"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:textual content="Temperature UP" />

                <Button
                    android:id="@+id/tempDown"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:textual content="Temperature Down" />
            </LinearLayout>
        </LinearLayout>

        <Button
            android:id="@+id/getPhoto"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textual content="GET PHOTO" />

        <ImageView
            android:id="@+id/evsImage"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:srcCompat="@drawable/grapeup_logo" />

    </LinearLayout>

    <View
        android:layout_width="fill_parent"
        android:layout_height="1dp"
        android:background="@android:coloration/darker_gray" />
</LinearLayout>

Including performance to the buttons

After organising the format, the following step is to attach actions to the buttons. Right here’s how you are able to do it in your MainActivity:

Button tempUpButton = findViewById(R.id.tempUp);
tempUpButton.setOnClickListener(new View.OnClickListener() 
    @Override
    public void onClick(View v) 
        tempUpClicked();
    
);

Button tempDownButton = findViewById(R.id.tempDown);
tempDownButton.setOnClickListener(new View.OnClickListener() 
    @Override
    public void onClick(View v) 
        tempDownClicked();
    
);

Button evsButton = findViewById(R.id.evs);
evsButton.setOnClickListener(new View.OnClickListener() 
    @Override
    public void onClick(View v) 
        evsClicked();
    
);

Button getPhotoButton = findViewById(R.id.getPhoto);
getPhotoButton.setOnClickListener(new View.OnClickListener() 
    @Override
    public void onClick(View v) 
        Log.w("GrapeUpController", "getPhotoButton clicked");
        new DownloadImageTask((ImageView) findViewById(R.id.evsImage))
            .execute("http://192.168.1.53:8081/");
    
);

Downloading and displaying a picture

To retrieve a picture from the automotive’s digital camera, we use the DownloadImageTask class, which downloads a JPEG picture within the background and shows it:

non-public class DownloadImageTask extends AsyncTask<String, Void, Bitmap> 
    ImageView bmImage;

    public DownloadImageTask(ImageView bmImage) 
        this.bmImage = bmImage;
    

    @Override
    protected Bitmap doInBackground(String... urls) 
        String urldisplay = urls[0];
        Bitmap mIcon11 = null;
        attempt 
            Log.w("GrapeUpController", "doInBackground: " + urldisplay);
            InputStream in = new java.web.URL(urldisplay).openStream();
            mIcon11 = BitmapFactory.decodeStream(in);
         catch (Exception e) 
            Log.e("Error", e.getMessage());
            e.printStackTrace();
        
        return mIcon11;
    

    @Override
    protected void onPostExecute(Bitmap end result) 
        bmImage.setImageBitmap(end result);
    

Adjusting the temperature

To alter the automotive’s temperature, you’ll be able to implement a operate like this:

non-public void tempUpClicked() 
    mTemperature += 0.5f;

    new Thread(new Runnable() 
        @Override
        public void run() 
            doInBackground("http://192.168.1.53:8080/set_temp/" +
                String.format(Locale.US, "%.01f", mTemperature));
        
    ).begin();

    updateTemperature();

Endpoint overview

Within the above examples, we used two endpoints: http://192.168.1.53:8080/ and http://192.168.1.53:8081/.

  • The primary endpoint corresponds to the AAOS 14 and the server applied within the VHAL, which handles instructions for controlling automotive capabilities.
  • The second endpoint is the server applied within the EVS Driver utility. It retrieves photographs from the automotive’s digital camera and sends them as an HTTP response.

For extra data on EVS setup in AAOS, you’ll be able to consult with the articles “Android AAOS 14 – Encompass View Parking Digital camera: The best way to Configure and Launch EVS (Exterior View System)” and “Android AAOS 14 – EVS community digital camera.“

EVS driver picture supplier

In our instance, the EVS Driver utility is chargeable for offering the picture from the automotive’s digital camera. This utility is situated within the packages/companies/Automobile/cpp/evs/sampleDriver/aidl/src listing. We’ll create a brand new thread inside this utility that runs an HTTP server. The server will deal with requests for photographs utilizing the v4l2 (Video4Linux2) interface.

EVS photo provider

Every HTTP request will initialize v4l2, set the picture format to JPEG, and specify the decision. After capturing the picture, the information will likely be despatched as a response, and the v4l2 stream will likely be stopped. Under is an instance code snippet that demonstrates this course of:

#embody <errno.h>
#embody <fcntl.h>
#embody <linux/videodev2.h>
#embody <stdint.h>
#embody <stdio.h>
#embody <string.h>
#embody <sys/ioctl.h>
#embody <sys/mman.h>
#embody <unistd.h>
#embody "cpp-httplib/httplib.h"

#embody <utils/Log.h>
#embody <android-base/logging.h>

uint8_t *buffer;
size_t bufferLength;
int fd;

static int xioctl(int fd, int request, void *arg)

    int r;
    do r = ioctl(fd, request, arg);
    whereas (-1 == r && EINTR == errno);

    if (r == -1) 
        ALOGE("xioctl error: %d, %s", errno, strerror(errno));
    

    return r;


int print_caps(int fd)

    struct v4l2_capability caps = ;
    if (-1 == xioctl(fd, VIDIOC_QUERYCAP, &caps))
    
        ALOGE("Querying Capabilities");
        return 1;
    

    ALOGI("Driver Caps:n"
          "  Driver: "%s"n"
          "  Card: "%s"n"
          "  Bus: "%s"n"
          "  Model: %d.%dn"          
          "  Capabilities: %08xn",
          caps.driver,
          caps.card,
          caps.bus_info,
          (caps.model >> 16) & 0xff,
          (caps.model >> 24) & 0xff,
          caps.capabilities);

    v4l2_format format;
    format.sort = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    format.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
    format.fmt.pix.width = 1280;
    format.fmt.pix.peak = 720;
    LOG(INFO) << __FILE__ << ":" << __LINE__ << " Requesting format: "
              << ((char*)&format.fmt.pix.pixelformat)[0]
              << ((char*)&format.fmt.pix.pixelformat)[1]
              << ((char*)&format.fmt.pix.pixelformat)[2]
              << ((char*)&format.fmt.pix.pixelformat)[3]
              << "(" << std::hex << std::setw(8)
              << format.fmt.pix.pixelformat << ")";

    if (ioctl(fd, VIDIOC_S_FMT, &format) < 0) 
        LOG(ERROR) << __FILE__ << ":" << __LINE__ << " VIDIOC_S_FMT failed " << strerror(errno);
    

    format.sort = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if (ioctl(fd, VIDIOC_G_FMT, &format) == 0) 
        LOG(INFO) << "Present output format:  "
                  << "fmt=0x" << std::hex << format.fmt.pix.pixelformat << ", " << std::dec
                  << format.fmt.pix.width << " x " << format.fmt.pix.peak
                  << ", pitch=" << format.fmt.pix.bytesperline;

        if (format.fmt.pix.pixelformat == V4L2_PIX_FMT_MJPEG) 
            ALOGI("V4L2_PIX_FMT_MJPEG detected");
        
        if (format.fmt.pix.pixelformat == V4L2_PIX_FMT_YUYV) 
            ALOGI("V4L2_PIX_FMT_YUYV detected");
        
     else 
        LOG(ERROR) << "VIDIOC_G_FMT failed";
    

    return 0;


int init_mmap(int fd)

    struct v4l2_requestbuffers req;
    req.rely = 1;
    req.sort = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    req.reminiscence = V4L2_MEMORY_MMAP;

    if (-1 == xioctl(fd, VIDIOC_REQBUFS, &req))
    
        perror("Requesting Buffer");
        return 1;
    

    struct v4l2_buffer buf;
    buf.sort = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.reminiscence = V4L2_MEMORY_MMAP;
    buf.index = 0;
    if (-1 == xioctl(fd, VIDIOC_QUERYBUF, &buf))
    
        perror("Querying Buffer");
        return 1;
    

    buffer = (uint8_t *)mmap(NULL, buf.size, PROT_READ 

size_t capture_image(int fd)

    struct v4l2_buffer buf;
    buf.sort = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.reminiscence = V4L2_MEMORY_MMAP;
    buf.index = 0;
    if (-1 == xioctl(fd, VIDIOC_QBUF, &buf))
    
        perror("Question Buffer");
        return 0;
    

    if (-1 == xioctl(fd, VIDIOC_STREAMON, &buf.sort))
    
        perror("Begin Seize");
        return 0;
    

    fd_set fds;
    FD_ZERO(&fds);
    FD_SET(fd, &fds);
    struct timeval television;
    television.tv_sec = 2;
    int r = choose(fd + 1, &fds, NULL, NULL, &television);
    if (-1 == r)
    
        perror("Ready for Body");
        return 0;
    

    if (-1 == xioctl(fd, VIDIOC_DQBUF, &buf))
    
        perror("Retrieving Body");
        return 0;
    

    return buf.bytesused;


bool initGetPhoto()

    fd = open("/dev/video0", O_RDWR);
    if (fd == -1)
    
        perror("Opening video gadget");
        return false;
    

    if (print_caps(fd))
        return false;

    if (init_mmap(fd))
        return false;

    return true;


bool closeGetPhoto()

    int sort = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if (ioctl(fd, VIDIOC_STREAMOFF, &sort) == -1) 
        perror("VIDIOC_STREAMOFF");
    

    // Inform the L4V2 driver to launch our streaming buffers
    v4l2_requestbuffers bufrequest;
    bufrequest.sort = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    bufrequest.reminiscence = V4L2_MEMORY_MMAP;
    bufrequest.rely = 0;
    ioctl(fd, VIDIOC_REQBUFS, &bufrequest);

    shut(fd);

    return true;


void getPhotoTask()

    ALOGI("getPhotoTask beginning ");
    ALOGI("HTTPServer beginning ");

    httplib::Server svr;

    svr.Get("https://grapeup.com/", [](const httplib::Request &, httplib::Response &res) 
        ALOGI("HTTPServer New request /");

        bool end result = initGetPhoto();
        ALOGI("initGetPhoto %b", end result);
        size_t imgSize = capture_image(fd);
        ALOGI("capture_image %zu", imgSize);

        closeGetPhoto();
        res.set_content((char *)buffer, imgSize, "picture/jpeg");
    );

    ALOGI("HTTPServer hear");
    svr.hear("0.0.0.0", 8081);


How the code works

1. Initialization: The initGetPhoto() operate opens the video gadget (/dev/video0) and units up the required format and reminiscence mappings for capturing photographs utilizing the v4l2 interface.

2. Picture Seize: The capture_image() operate captures a picture from the video stream. It makes use of choose() to attend for the body after which dequeues the buffer containing the picture.

3. HTTP Server: The getPhotoTask() operate begins an HTTP server utilizing the cpp-httplib library. When a request is acquired, the server initializes the digital camera, captures a picture, and sends it as a JPEG response.

4. Cleanup: After capturing the picture and sending it, the closeGetPhoto() operate stops the video stream, releases the buffers, and closes the video gadget.

This setup ensures that every picture is captured on demand, permitting the applying to manage when the digital camera is lively and minimizing pointless useful resource utilization.

Conclusion

On this article, we walked by way of the method of making an Android companion app that enables customers to manage HVAC capabilities and retrieve photographs from a automotive’s digital camera system utilizing a easy HTTP interface. The applying was developed in Android Studio, the place we designed a user-friendly interface and applied performance to regulate the car’s temperature and seize photographs remotely. On the server aspect, we prolonged the EVS Driver by incorporating a customized thread to deal with HTTP requests and seize photographs utilizing v4l2, offering a primary but efficient resolution for distant car interplay.

This venture serves as a conceptual demonstration of integrating smartphone-based controls with automotive methods, nevertheless it’s vital to acknowledge that there’s vital potential for enchancment and growth. As an example, enhancing the information dealing with layer to supply extra strong error checking, using the HTTP/2 protocol for quicker and extra environment friendly communication, and making a extra seamless integration with the EVS Driver might tremendously enhance the efficiency and reliability of the system.

In its present kind, this resolution gives a foundational strategy that might be expanded right into a extra refined utility, able to supporting a wider vary of automotive capabilities and delivering a extra polished consumer expertise. Future developments might additionally discover extra superior security measures, improved information codecs, and tighter integration with the broader ecosystem of Android Automotive OS to completely leverage the capabilities of recent automobiles.