Python Manual Vehicle Model Creation
Setup a Python Package manually
A Vehicle Model should be defined in its own Python Package. This allows to distribute the Vehicle Model later as a standalone package and to use it in different Vehicle App projects.
The name of the Vehicle Model package will be my_vehicle_model
for this walkthrough.
-
Start Visual Studio Code
-
Select File > Open Folder (File > Open… on macOS) from the main menu.
-
In the Open Folder dialog, create a
my_vehicle_model
folder and select it. Then click Select Folder (Open on macOS). -
Create a new file
setup.py
undermy_vehicle_model
:from setuptools import setup setup(name='my_vehicle_model', version='0.1', description='My Vehicle Model', packages=['my_vehicle_model'], zip_safe=False)
This is the Python package distribution script.
-
Create an empty folder
my_vehicle_model
undermy_vehicle_model
. -
Create a new file
__init__.py
undermy_vehicle_model/my_vehicle_model
.
At this point the source tree of the Python package should look like this:
my_vehicle_model
├── my_vehicle_model
│ └── __init__.py
└── setup.py
To verify that the package is created correctly, install it locally:
pip3 install .
The output of the above command should look like this:
Defaulting to user installation because normal site-packages is not writeable
Processing /home/user/projects/my-vehicle-model
Preparing metadata (setup.py) ... done
Building wheels for collected packages: my-vehicle-model
Building wheel for my-vehicle-model (setup.py) ... done
Created wheel for my-vehicle-model: filename=my_vehicle_model-0.1-py3-none-any.whl size=1238 sha256=a619bc9fbea21d587f9f0b1c1c1134ca07e1d9d1fdc1a451da93d918723ce2a2
Stored in directory: /home/user/.cache/pip/wheels/95/c8/a8/80545fb4ff73c974ac1716a7bff6f7f753f92022c41c2e376f
Successfully built my-vehicle-model
Installing collected packages: my-vehicle-model
Successfully installed my-vehicle-model-0.1
Finally, uninstall the package again:
pip3 uninstall my_vehicle_model
Add Vehicle Models manually
-
Install the Python Vehicle App SDK:
pip3 install git+https://github.com/eclipse-velocitas/vehicle-app-python-sdk.git
The output of the above command should end with:
Successfully installed sdv-x.y.z
Now it is time to add some Vehicle Models to the Python package. At the end of this section you will have a Vehicle Model, that contains a
Cabin
model, aSeat
model and has the following tree structure:Vehicle └── Cabin └── Seat (Row, Pos)
-
Create a new file
Seat.py
undermy_vehicle_model/my_vehicle_model
:from sdv.model import Model class Seat(Model): def __init__(self, parent): super().__init__(parent) self.Position = DataPointFloat("Position", self)
This creates the Seat model with a single data point of type
float
namedPosition
. -
Create a new file
Cabin.py
undermy_vehicle_model/my_vehicle_model
:from sdv.model import Model class Cabin(Model): def __init__(self, parent): super().__init__(parent) self.Seat = SeatCollection("Seat", self) class SeatCollection(Model): def __init__(self, name, parent): super().__init__(parent) self.name = name self.Row1 = self.RowType("Row1", self) self.Row2 = self.RowType("Row2", self) def Row(self, index: int): if index < 1 or index > 2: raise IndexError(f"Index {index} is out of range") _options = { 1 : self.Row1, 2 : self.Row2, } return _options.get(index) class RowType(Model): def __init__(self, name, parent): super().__init__(parent) self.name = name self.Pos1 = Seat("Pos1", self) self.Pos2 = Seat("Pos2", self) self.Pos3 = Seat("Pos3", self) def Pos(self, index: int): if index < 1 or index > 3: raise IndexError(f"Index {index} is out of range") _options = { 1 : self.Pos1, 2 : self.Pos2, 3 : self.Pos3, } return _options.get(index)
This creates the
Cabin
model, which contains a set of sixSeat
models, referenced by their names or by rows and positions:- row=1, pos=1
- row=1, pos=2
- row=1, pos=3
- row=2, pos=1
- row=2, pos=2
- row=2, pos=3
-
Create a new file
vehicle.py
undermy_vehicle_model/my_vehicle_model
:from sdv.model import Model from my_vehicle_model.Cabin import Cabin class Vehicle(Model): """Vehicle model""" def __init__(self, name): super().__init__() self.name = name self.Speed = DataPointFloat("Speed", self) self.Cabin = Cabin("Cabin", self) vehicle = Vehicle("Vehicle")
The root model of the Vehicle Model tree should be called Vehicle by convention and is specified, by setting parent to None
. For all other models a parent model must be specified as the 2nd argument of the Model
constructor, as can be seen by the Cabin
and the Seat
models above.
A singleton instance of the Vehicle Model called vehicle
is created at the end of the file. This instance is supposed to be used in the Vehicle Apps. Creating multiple instances of the Vehicle Model should be avoided for performance reasons.
Add a Vehicle Service
Vehicle Services provide service interfaces to control actuators or to trigger (complex) actions. E.g. they communicate with the vehicle internal networks like CAN or Ethernet, which are connected to actuators, electronic control units (ECUs) and other vehicle computers (VCs). They may provide a simulation mode to run without a network interface. Vehicle Services may feed data to the Databroker and may expose gRPC endpoints, which can be invoked by Vehicle Apps over a Vehicle Model.
In this section, we add a Vehicle Service to the Vehicle Model.
-
Create a new folder
proto
undermy_vehicle_model/my_vehicle_model
. -
Copy your proto file under
my_vehicle_model/my_vehicle_model/proto
As example you could use the protocol buffers message definition seats.proto provided by the KUKSA services which describes a seat control service .
-
Install the grpcio tools including mypy types to generate the Python classes out of the proto-file:
pip3 install grpcio-tools mypy_protobuf
-
Generate Python classes from the
SeatService
message definition:python3 -m grpc_tools.protoc -I my_vehicle_model/proto --grpc_python_out=./my_vehicle_model/proto --python_out=./my_vehicle_model/proto --mypy_out=./my_vehicle_model/proto my_vehicle_model/proto/seats.proto
This creates the following gRPC files under the
proto
folder:- seats_pb2.py
- seats_pb2_grpc.py
- seats_pb2.pyi
-
Create the
SeatService
class and wrap the gRPC service:from sdv.model import Service from my_vehicle_model.proto.seats_pb2 import ( CurrentPositionRequest, MoveComponentRequest, MoveRequest, Seat, SeatComponent, SeatLocation, ) from my_vehicle_model.proto.seats_pb2_grpc import SeatsStub class SeatService(Service): "SeatService model" def __init__(self): super().__init__() self._stub = SeatsStub(self.channel) async def Move(self, seat: Seat): response = await self._stub.Move(MoveRequest(seat=seat), metadata=self.metadata) return response async def MoveComponent( self, seatLocation: SeatLocation, component: SeatComponent, position: int, ): response = await self._stub.MoveComponent( MoveComponentRequest( seat=seatLocation, component=component, # type: ignore position=position, ), metadata=self.metadata, ) return response async def CurrentPosition(self, row: int, index: int): response = await self._stub.CurrentPosition( CurrentPositionRequest(row=row, index=index), metadata=self.metadata, ) return response
Some important remarks about the wrapping
SeatService
class shown above:- The
SeatService
class must derive from theService
class provided by the Python SDK. - The
SeatService
class must use the gRPC channel from theService
base class and provide it to the_stub
in the__init__
method. This allows the SDK to manage the physical connection to the gRPC service and use service discovery of the middleware. - Every method needs to pass the metadata from the
Service
base class to the gRPC call. This is done by passing theself.metadata
argument to the metadata of the gRPC call.
- The