Create a server

Learn how to create a server for a service definition.

Introduction

This example assumes that you have used the Velocitas App C++ Template to create a new repository and now want to modify it to be a grpc service server. The example files can also be found in the Vehicle App C++ SDK Github repository .

Velocitas components

Dependning on how you intend to deploy the Application and Databroker the number of Velocitas components required varies. Below is the minimum set needed in .velocitas.json if deploying Databroker on localhost.

    "components": [
        "devcontainer-setup",
        "vehicle-signal-interface",
        "grpc-interface-support",
        "sdk-installer",
        "build-system"
    ],

App configuration

In the AppManifest.json you need to specify that your server will provide the interfaces defined in the proto file. If it requires access to signals from the Databroker, they must also be specified. In this example the server declares to provide the interfaces from the Seats service defined in seats.proto and that it needs write access to the VSS signal Vehicle.Cabin.Seat.Row1.DriverSide.Position.


    "manifestVersion": "v3",
    "name": "SampleApp",
    "interfaces": [
        {
            "type": "grpc-interface",
            "config": {
                "src": "https://raw.githubusercontent.com/eclipse-kuksa/kuksa-incubation/0.4.0/seat_service/proto/sdv/edge/comfort/seats/v1/seats.proto",
                "provided": { }
            }
        },
        {
            "type": "vehicle-signal-interface",
            "config": {
                "src": "https://github.com/COVESA/vehicle_signal_specification/releases/download/v4.0/vss_rel_4.0.json",
                "datapoints": {
                    "required": [
                        {
                            "path": "Vehicle.Cabin.Seat.Row1.DriverSide.Position",
                            "access": "write"
                        }
                    ]
                }
            }
        }
    ]
}

File Generation

When rebuilding the devcontainer with the configuration above two files will be automatically created:

  • SeatsServiceImpl.cpp
  • SeatsServiceImpl.h

They contain the stubs for the server. You still need to fill in the actual implementation of the services.

SeatsServiceImpl.h

The service will access the vehicle model, in this tutorial we manage that by adding a private class variable vehicle to the file SeatsServiceImpl.h.

#ifndef VELOCITAS_SERVICE_IMPL_Seats_H
#define VELOCITAS_SERVICE_IMPL_Seats_H

#include "vehicle/Vehicle.hpp"
#include <grpc/grpc.h>
#include <services/seats/seats.grpc.pb.h>

namespace velocitas {

class SeatsService final : public sdv::edge::comfort::seats::v1::Seats::Service {
public:
    SeatsService(){};
    virtual ~SeatsService(){};

    // <auto-generated>
    ::grpc::Status Move(::grpc::ServerContext*                              context,
                        const ::sdv::edge::comfort::seats::v1::MoveRequest* request,
                        ::sdv::edge::comfort::seats::v1::MoveReply*         response) override;
    ::grpc::Status
    MoveComponent(::grpc::ServerContext*                                       context,
                  const ::sdv::edge::comfort::seats::v1::MoveComponentRequest* request,
                  ::sdv::edge::comfort::seats::v1::MoveComponentReply*         response) override;
    ::grpc::Status
    CurrentPosition(::grpc::ServerContext*                                         context,
                    const ::sdv::edge::comfort::seats::v1::CurrentPositionRequest* request,
                    ::sdv::edge::comfort::seats::v1::CurrentPositionReply* response) override;
    // </auto-generated>

private:
    vehicle::Vehicle vehicle;
};

} // namespace velocitas

#include "SeatsServiceImpl.cpp"

#endif // VELOCITAS_SERVICE_IMPL_Seats_H

SeatsServiceImpl.cpp

In the file SeatsServiceImpl.cpp we need to implement the services. In this example we only implement two of them. The example also shows basic error handling.

The business logic of the server is to wait for Move and CurrentPosition requests and when received just forward the request to the Databroker and return the result. You do not need to add SeatsServiceImpl.cpp to src/CMakeLists.txt as the header includes the *.cpp file and the header file will be included in the Launcher.cpp.

#include "SeatsServiceImpl.h"
#include <sdk/VehicleApp.h>
#include <sdk/VehicleModelContext.h>
#include <sdk/vdb/IVehicleDataBrokerClient.h>

namespace velocitas {

::grpc::Status SeatsService::Move(::grpc::ServerContext*                              context,
                                  const ::sdv::edge::comfort::seats::v1::MoveRequest* request,
                                  ::sdv::edge::comfort::seats::v1::MoveReply*         response) {
    (void)context;
    (void)request;
    (void)response;
    auto seat     = request->seat();
    auto location = seat.location();
    auto row      = location.row();
    auto pos      = location.index();

    std::cout << "Got Move Request!" << std::endl;

    try {
        auto status =
            vehicle.Cabin.Seat.Row1.DriverSide.Position.set(seat.position().base())->await();

        if (status.ok()) {

            std::cout << "OK!" << std::endl;
            return ::grpc::Status(::grpc::StatusCode::OK, "");
        } else {
            std::cout << "Some error!" << std::endl;
            // This could for instance happen if datapoint is not known by databroker
            // then message will be UNKNOWN_DATAPOINT.
            return ::grpc::Status(::grpc::StatusCode::CANCELLED, status.errorMessage());
        }
    } catch (AsyncException& e) {
        std::cout << "Async Exception!" << std::endl;
        // This could typically be that Databroker is not running or not reachable
        // or that your Velocitas environment uses an API incompatible with what your Databroker
        // instance supports
        return ::grpc::Status(::grpc::StatusCode::UNAVAILABLE, "");
    }
}

::grpc::Status
SeatsService::MoveComponent(::grpc::ServerContext*                                       context,
                            const ::sdv::edge::comfort::seats::v1::MoveComponentRequest* request,
                            ::sdv::edge::comfort::seats::v1::MoveComponentReply*         response) {
    (void)context;
    (void)request;
    (void)response;
    // This is an example of an unimplemented method
    return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, "");
}

::grpc::Status SeatsService::CurrentPosition(
    ::grpc::ServerContext*                                         context,
    const ::sdv::edge::comfort::seats::v1::CurrentPositionRequest* request,
    ::sdv::edge::comfort::seats::v1::CurrentPositionReply*         response) {
    (void)context;
    (void)request;

    std::cout << "Got CurrentPosition Request!" << std::endl;

    auto seat          = response->mutable_seat();
    auto seat_position = seat->mutable_position();
    try {
        auto seatPos = vehicle.Cabin.Seat.Row1.DriverSide.Position.get()->await().value();

        std::cout << "Success!!" << std::endl;
        seat_position->set_base(seatPos);
        return ::grpc::Status(::grpc::StatusCode::OK, "");
    } catch (AsyncException& e) {
        std::cout << "Async Exception!" << std::endl;
        // This could typically be that Databroker is not running or not reachable
        // or that your Velocitas environment uses an API incompatible with what your Databroker
        // instance supports
        return ::grpc::Status(::grpc::StatusCode::OK, "");
    } catch (InvalidValueException& e) {
        std::cout << "Invalid Value!" << std::endl;
        // This could be given if Databroker has no current value for Position
        // (Have you set it manually using kuksa-client or databroker-cli?)
        return ::grpc::Status(::grpc::StatusCode::OK, "");
    }
}

} // namespace velocitas

Launcher.cpp

You need to have a file that that starts the server, in this example we modify the file Launcher.cpp that already exist in the template . It has very simple logic, it only starts the service and waits until the service shuts down, if ever.

#include "SeatsServiceImpl.h"
#include <sdk/middleware/Middleware.h>
#include <services/seats/SeatsServiceServerFactory.h>

#include "vehicle/Vehicle.hpp"

#include <memory>

using namespace velocitas;

int main(int argc, char** argv) {
    auto seatsImpl = std::make_shared<SeatsService>();

    velocitas::VehicleModelContext::getInstance().setVdbc(
        velocitas::IVehicleDataBrokerClient::createInstance("vehicledatabroker"));

    auto seatServer = SeatsServiceServerFactory::create(Middleware::getInstance(), seatsImpl);

    seatServer->Wait();

    std::cout << "Waiting!" << std::endl;
    return 0;
}

Building and Running

To (re-)build the App after changing the code you can use the build script . As preparation for running you must also set two environment variables to define where the address/port of the server and the Databroker. The environment variable needs to be set in the same terminal as used for starting the application.

./build.sh
export SDV_SEATS_ADDRESS=grpc://127.0.0.1:5555
export SDV_VEHICLEDATABROKER_ADDRESS=grpc://127.0.0.1:55555

If Databroker is running and has a current value for the wanted signal, then everthing shall work when the client is started. Output similar to below is expected when client has run once.

vscode ➜ /workspaces/erik_server_241021 (main) $ build/bin/app
2024-10-22 09:51:41, INFO  : Connecting to data broker service 'vehicledatabroker' via '127.0.0.1:55555'
2024-10-22 09:51:41, INFO  : Server sdv::edge::comfort::seats::v1::Seats listening on 127.0.0.1:5555
Got Move Request!
OK!
Got CurrentPosition Request!
Success!
Last modified October 30, 2024: Improve service documentation (#129) (5a3f592)