Fleet Engine 使用入门

借助 Fleet Engine On-demand Rides and Deliveries API,您可以管理行程和订单进度应用的行程和车辆状态。它会处理驱动程序 SDK、使用方 SDK 和后端服务之间的事务,这些后端服务可以通过进行 gRPCREST 调用与 Fleet Engine 进行通信。

前提条件

开发时,请务必安装 Cloud SDK (gcloud) 并针对您的项目进行身份验证。

shell

gcloud auth login

您应该会看到如下所示的成功消息:

You are now logged in as [my-user@example.com].
Your current project is [project-id].  You ...

检查按需行程和交付解决方案 Fleet Engine API 是否配置正确。

shell

gcloud --project=project-id services enable fleetengine.googleapis.com

如果此命令导致错误,请与您的项目管理员和 Google 支持代表联系,以获取访问权限。

日志记录

Fleet Engine 可以将收到的 API 调用的相关日志消息写入 Google Cloud Platform 日志。如需简要了解如何读取和分析日志,请参阅 Cloud Logging 文档。

对于 2022 年 2 月 10 日之前创建的项目,日志记录功能可能未默认启用。如需了解详情,请参阅日志记录文档

客户端库

我们以多种常用编程语言发布客户端库。与原始 REST 或 gRPC 相比,这些库有助于为开发者提供更好的体验。如需了解如何为服务器应用获取客户端库的说明,请参阅客户端库

本文档中的 Java 示例假定您熟悉 gRPC。

身份验证与授权

您可以通过 Google Cloud 控制台配置行程和订单进度提供的功能。这些 API 和 SDK 需要使用通过使用从 Cloud Console 创建的服务帐号签名的 JSON Web 令牌。

Cloud 项目设置

要设置您的云项目,请先创建项目,然后创建服务帐号。

如需创建 Google Cloud 项目,请执行以下操作:

  1. 使用 Google Cloud 控制台创建 Google Cloud 项目。
  2. 使用 API 和服务信息中心,启用 Local Rides and Deliveries API。

服务账号与一个或多个角色相关联。这些令牌用于创建 JSON 网络令牌,这些令牌可根据角色授予不同的权限集。通常,为了降低滥用的可能性,您可以创建多个服务帐号,为每个服务帐号分配一组所需的最少角色。

行程和订单进度使用以下角色:

角色说明
Fleet Engine Consumer SDK 用户

roles/fleetengine.consumerSdkUser
授予搜索车辆以及检索车辆和行程相关信息的权限。由具有此角色的服务账号创建的令牌通常在拼车或送餐服务的消费者应用移动设备上使用。
Fleet Engine Driver SDK 用户

roles/fleetengine.driverSdkUser
授予更新车辆位置和路线以及检索车辆和行程相关信息的权限。由具有此角色的服务账号创建的令牌通常在拼车或送餐司机应用的移动设备上使用。
Fleet Engine On-demand Admin

roles/fleetengine.ondemandAdmin
授予对所有车辆和行程资源的读写权限。 具有此角色的主账号不需要使用 JWT,而应使用应用默认凭据。自定义 JWT 声明会被忽略。此角色应仅限受信任的环境(客户后端)使用。
FleetEngine Service Super User **(已弃用)**

roles/fleetengine.serviceSuperUser
授予对所有车辆和行程 API 的权限。由具有此角色的服务帐号创建的令牌通常从后端服务器使用。此角色已弃用。首选 roles/fleetengine.ondemandAdmin

例如,为这三个角色分别创建一个服务帐号,并为其分配各自的角色。

gcloud --project=project-id iam service-accounts create fleet-engine-consumer-sdk
gcloud projects add-iam-policy-binding project-id \
       --member=serviceAccount:fleet-engine-consumer-sdk@project-id.iam.gserviceaccount.com \
       --role=roles/fleetengine.consumerSdkUser

gcloud --project=project-id iam service-accounts create fleet-engine-driver-sdk
gcloud projects add-iam-policy-binding project-id \
       --member=serviceAccount:fleet-engine-driver-sdk@project-id.iam.gserviceaccount.com \
       --role=roles/fleetengine.driverSdkUser

gcloud --project=project-id iam service-accounts create fleet-engine-su
gcloud projects add-iam-policy-binding project-id \
       --member=serviceAccount:fleet-engine-su@project-id.iam.gserviceaccount.com \
       --role=roles/fleetengine.serviceSuperUser

驱动程序 SDK 和使用方 SDK 是围绕这些标准角色构建的。

或者,您可以创建自定义角色,将任意一组权限捆绑在一起。 驱动程序 SDK 和使用方 SDK 将在缺少所需权限时显示错误消息。因此,我们强烈建议使用上述标准角色集,而不是使用自定义角色。

为方便起见,如果您需要为不受信任的客户端创建 JWT 令牌,只需将用户添加到 Service Account Token Creator 角色,即可让他们使用 gcloud 命令行工具创建令牌。

gcloud projects add-iam-policy-binding project-id \
       --member=user:my-user@example.com \
       --role=roles/iam.serviceAccountTokenCreator

其中,my-user@example.com 是用于通过 gcloud (gcloud auth list --format='value(account)') 进行身份验证的电子邮件。

Fleet Engine 身份验证库

Fleet Engine 使用 JSON Web 令牌 (JWT) 限制对 Fleet Engine API 的访问。新的 Fleet Engine Auth 库可在 GitHub 上获取,它简化了 Fleet Engine JWT 的构建过程并对其进行安全签名。

该库具有以下优势:

  • 简化了创建 Fleet Engine 令牌的流程。
  • 提供除使用凭据文件以外的令牌签名机制(例如模拟服务帐号)。
  • 将签名令牌附加到从 gRPC 桩或 GAPIC 客户端发出的出站请求。

创建用于授权的 JSON Web 令牌 (JWT)

如果不使用 Fleet Engine Auth 库,则需要直接在代码库中创建 JSON Web 令牌 (JWT)。因此,您需要深入了解 JWT 及其与 Fleet Engine 之间的关系。因此,我们强烈建议您充分利用 Fleet Engine Auth 库。

在 Fleet Engine 中,JSON Web 令牌 (JWT) 可提供短期身份验证,并确保设备只能修改其有权修改的车辆、行程或任务。JWT 包含标头和声明部分。标头部分包含要使用的私钥(从服务帐号获取)和加密算法等信息。声明部分包含令牌的创建时间、令牌存留时间、令牌所声明访问权限的服务,以及用于确定访问权限范围的其他授权信息(例如车辆 ID)。

JWT 标头部分包含以下字段:

字段说明
alg 要使用的算法。`RS256`。
typ 令牌的类型。“JWT”。
儿童 您的服务帐号的私钥 ID。您可以在服务帐号 JSON 文件的“private_key_id”字段中找到此值。 请务必使用具有正确权限级别的服务账号中的密钥。

JWT 声明部分包含以下字段:

字段说明
iss 您的服务帐号的电子邮件地址。
sub 您的服务帐号的电子邮件地址。
aud 您的服务帐号的 SERVICE_NAME,在本示例中为 https://fleetengine.googleapis.com/
iat 令牌创建的时间戳,自世界协调时间 (UTC) 1970 年 1 月 1 日 00:00:00 起经过的秒数指定。等待 10 分钟,使数据出现偏差。如果时间戳离现在太远或将来的时间,服务器可能会报告错误。
exp 令牌到期的时间戳,自世界协调时间 (UTC) 1970 年 1 月 1 日 00:00:00 起经过的秒数指定。如果时间戳超过未来一小时,则请求失败。
授权 可能包含“vehicleid”或“tripid”,具体取决于用例。

创建 JWT 令牌意味着对其进行签名。如需查看创建和 JWT 签名的说明和代码示例,请参阅不使用 OAuth 的服务帐号授权。然后,您可以将已签名的令牌附加到 gRPC 调用或用于访问 Fleet Engine 的其他方法。

JWT 声明

创建 JWT 载荷时,请在授权部分添加一个额外的声明,将键 vehicleidtripid 设置为要为其发出调用的车辆 ID 或行程 ID 的值。

无论是在行程中还是在车辆中操作,驱动程序 SDK 始终使用 vehicleid 声明。在进行修改之前,Fleet Engine 后端会确保车辆与请求的行程相关联。

使用方 SDK 始终使用 tripid 声明。

拼车或配送服务提供商应使用带“*”的 vehicleidtripid 来匹配所有车辆和行程。请注意,JWT 可以包含这两个令牌(即使不是必需的),这可以简化令牌签名实现。

JWT 使用场景

下面显示了提供商服务器的示例令牌:

{
  "alg": "RS256",
  "typ": "JWT",
  "kid": "private_key_id_of_provider_service_account"
}
.
{
  "iss": "provider@yourgcpproject.iam.gserviceaccount.com",
  "sub": "provider@yourgcpproject.iam.gserviceaccount.com",
  "aud": "https://fleetengine.googleapis.com/",
  "iat": 1511900000,
  "exp": 1511903600,
  "authorization": {
     "vehicleid": "*",
     "tripid": "*"
   }
}

下面显示了消费者应用的示例令牌:

{
  "alg": "RS256",
  "typ": "JWT",
  "kid": "private_key_id_of_consumer_service_account"
}
.
{
  "iss": "consumer@yourgcpproject.iam.gserviceaccount.com",
  "sub": "consumer@yourgcpproject.iam.gserviceaccount.com",
  "aud": "https://fleetengine.googleapis.com/",
  "iat": 1511900000,
  "exp": 1511903600,
  "authorization": {
     "tripid": "trip_54321"
   }
}

下面显示了驱动程序应用的示例令牌:

{
  "alg": "RS256",
  "typ": "JWT",
  "kid": "private_key_id_of_driver_service_account"
}
.
{
  "iss": "driver@yourgcpproject.iam.gserviceaccount.com",
  "sub": "driver@yourgcpproject.iam.gserviceaccount.com",
  "aud": "https://fleetengine.googleapis.com/",
  "iat": 1511900000,
  "exp": 1511903600,
  "authorization": {
     "vehicleid": "driver_12345"
   }
}
  • 对于标头中的 kid 字段,请指定您服务帐号的私钥 ID。您可以在服务帐号 JSON 文件的 private_key_id 字段中找到此值。
  • 对于 isssub 字段,请指定您的服务帐号的电子邮件地址。您可以在服务帐号 JSON 文件的 client_email 字段中找到此值。
  • 对于 aud 字段,请指定 https://SERVICE_NAME/
  • 对于 iat 字段,请使用创建令牌时的时间戳,以自世界协调时间 (UTC) 1970 年 1 月 1 日 00:00:00 以来经过的秒数表示。等待 10 分钟,使数据出现偏差。如果时间戳过于久远或将来,服务器可能会报告错误。
  • 对于 exp 字段,请使用令牌到期时的时间戳,以自世界协调时间 (UTC) 1970 年 1 月 1 日 00:00:00 以来的秒数指定。允许的最大值为 iat + 3600。

在对要传递给移动设备的 JWT 进行签名时,请务必使用 Driver SDK 或 Consumer SDK 角色的服务帐号。否则,移动设备将能够更改不应具有的状态。

同样,在为用于特权调用的 JWT 签名时,请确保使用具有超级用户角色的服务帐号。否则,操作将失败。

生成用于测试的 JWT

在测试时,从终端生成令牌会很有帮助。

如需执行以下步骤,您的用户帐号必须具有 Service Account Token Creator 角色:

gcloud projects add-iam-policy-binding project-id \
       --member=user:my-user@example.com \
       --role=roles/iam.serviceAccountTokenCreator

使用以下内容创建名为 unsigned_token.json 的新文件。iat 属性是纪元之后的当前时间(以秒为单位),可通过在终端中运行 date +%s 来检索。exp 属性是纪元之后的到期时间(以秒为单位),可通过将 3600 添加到 iat 来计算。到期时间(将来)不能超过一个小时。

{
  "aud": "https://fleetengine.googleapis.com/",
  "iss": "super-user-service-account@project-id.iam.gserviceaccount.com",
  "sub": "super-user-service-account@project-id.iam.gserviceaccount.com",
  "iat": iat,
  "exp": exp,
  "authorization": {
     "vehicleid": "*",
     "tripid": "*"
   }
}

然后,运行以下 gcloud 命令,代表超级用户服务帐号对令牌进行签名:

gcloud beta iam service-accounts sign-jwt --iam-account=super-user-service-account@project-id.iam.gserviceaccount.com unsigned_token.json signed_token.jwt

现在,以 Base64 编码的 JWT 应存储在文件 signed_token.jwt 中。令牌在下一个小时内有效。

现在,您可以通过对 List Vehicles REST 端点运行 curl 命令来测试令牌:

curl -X GET "https://fleetengine.googleapis.com/v1/providers/project-id/vehicles" -H "Authorization: Bearer $(cat signed_token.jwt)"

交通工具及其生命周期

Vehicle 是表示驾驶员-车辆对的实体。目前,无法单独跟踪驾驶员和车辆。拼车或配送服务提供商使用提供商 ID(必须与 Google Cloud 项目(包含用于调用 Fleet Engine API 的服务帐号)的项目 ID 相同)和拼车或配送服务提供商拥有的车辆 ID 来创建车辆。

七天后未通过 UpdateVehicle 更新的车辆将被自动删除。使用已存在的提供方 ID/车辆 ID 对调用 CreateVehicle 是错误做法。可以通过两种方式处理车辆未频繁更新的情况:频繁使用预期的提供方 ID/车辆 ID 对调用 CreateVehicle,如果车辆已存在,则舍弃错误;或者在 UpdateVehicle 返回 NOT_FOUND 错误后调用 CreateVehicle

车辆位置信息更新

为了让 Fleet Engine 达到最佳性能,请为其提供一系列车辆位置信息更新。使用以下任一方法提供这些更新:

  1. 使用驱动程序 SDK - 最简单的方法:Android 版iOS
  2. 使用自定义代码 - 如果位置信息通过后端进行中继,或者您使用 Android 或 iOS 以外的其他设备,则此类代码会很有用。

车辆类型

Vehicle 实体包含 VehicleType 的必填字段,其中包含一个 Category 枚举,可指定为 AUTOTAXITRUCKTWO_WHEELERBICYCLEPEDESTRIAN。车辆类型可用作 SearchVehiclesListVehicles 中的过滤条件。

如果类别设置为 AUTOTWO_WHEELERBICYCLEPEDESTRIAN,则车辆的所有路线都将使用相应的 RouteTravelMode。如果类别设置为 TAXITRUCK,则路由被视为与 AUTO 模式相同。

车辆属性

Vehicle 实体包含 VehicleAttribute 的重复字段。Fleet Engine 不会解读这些属性。SearchVehicles API 包含一个字段,要求匹配的 Vehicles 必须包含所有设置为指定值的包含属性。

请注意,属性字段是对 Vehicle 消息中其他几个受支持字段(例如 vehicle_typesupported_trip_types)的补充。

车辆剩余航点

车辆实体包含重复字段 TripWaypoint (RPC | REST),称为 waypoints(RPC | REST)。此字段包含行程中的其余航点(按车辆到达这些航点的顺序)。Fleet Engine 会在行程分配给车辆时计算此字段,并在行程状态更改时更新此字段。这些航点可以由 TripId 字段和 WaypointType 字段标识。

放宽车辆的匹配条件

通常,拼车或送货服务提供商的服务负责将行程请求与车辆进行匹配。该服务可以使用车辆属性,以便在大量搜索中包含车辆。例如,提供程序可以实现与车辆提供的福利或功能级别相对应的属性集。例如,三个级别可以是一组具有布尔值的属性:is_bronze_levelis_silver_levelis_gold_level。一辆车可以满足所有这三项条件。当 Fleet Engine 收到需要白银级功能的行程请求时,搜索范围包括该车辆。以这种方式使用属性的情况包括提供各种功能的车辆。

您可以通过以下两种方式更新车辆属性。一个是 UpdateVehicle API。使用此 API 时,整组车辆属性都会设为该值。不能只更新单个属性。另一种方法是 UpdateVehicleAttributes API。此方法只接受要更新的属性。请求中包含的属性将设置为新值或将添加;未指定的属性不会改变。

方法指南:制作车辆

必须为车队中要跟踪的每辆车创建一个 Vehicle 实体。

结合使用 CreateVehicle 端点和 CreateVehicleRequest 来创建车辆。

Vehicleprovider_id 必须是 Google Cloud 项目(包含将用于调用 Fleet Engine 的服务帐号)的项目 ID(例如 my-on-demand-project)。请注意,虽然多个服务帐号可以访问同一拼车或配送服务提供商的 Fleet Engine,但 Fleet Engine 目前不支持多个 Google Cloud 项目中的服务帐号访问同一 Vehicles

您可以创建处于 OFFLINEONLINE 状态的 Vehicle。如果创建了 ONLINE,则可能会立即返回该响应以响应 SearchVehicles 查询。

CreateVehicle 调用中可能包含初始 last_location。在获得允许的情况下,如果没有 last_location,则不得在 ONLINE 状态下创建 Vehicle

如需详细了解车辆类型字段,请参阅车辆类型

如需详细了解属性字段,请参阅车辆属性

CreateVehicle 返回的值是创建的 Vehicle 实体。

示例

shell

curl -X POST \
  "https://fleetengine.googleapis.com/v1/providers/project-id/vehicles?vehicleId=vid-8241890" \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  --data-binary @- << EOM
{
    "vehicleState": "OFFLINE",
    "supportedTripTypes": ["EXCLUSIVE"],
    "maximumCapacity": 4,
    "vehicleType": {"category": "AUTO"},
    "attributes": [{"key": "on_trip", "value": "false"}]
}
EOM

请参阅 providers.vehicles.create 参考文档。

Java

static final String PROJECT_ID = "project-id";

VehicleServiceBlockingStub vehicleService =
    VehicleService.newBlockingStub(channel);

String parent = "providers/" + PROJECT_ID;
Vehicle vehicle = Vehicle.newBuilder()
    .setVehicleState(VehicleState.OFFLINE)  // Initial state
    .addSupportedTripTypes(TripType.EXCLUSIVE)
    .setMaximumCapacity(4)
    .setVehicleType(VehicleType.newBuilder().setCategory(VehicleType.Category.AUTO))
    .addAttributes(VehicleAttribute.newBuilder()
        .setKey("on_trip").setValue("false"))  // Opaque to the Fleet Engine
    // Add .setBackToBackEnabled(true) to make this vehicle eligible for trip
    // matching while even if it is on a trip.  By default this is disabled.
    .build();

CreateVehicleRequest createVehicleRequest =
    CreateVehicleRequest.newBuilder()  // no need for the header
        .setParent(parent)
        .setVehicleId("vid-8241890")  // Vehicle ID assigned by Rideshare or Delivery Provider
        .setVehicle(vehicle)  // Initial state
        .build();

// In this case, the Vehicle is being created in the OFFLINE state and
// no initial position is being provided.  When the Driver App checks
// in with the Rideshare or Delivery Provider, the state can be set to ONLINE and
// the Driver App will update the Vehicle Location.

try {
  Vehicle createdVehicle =
      vehicleService.createVehicle(createVehicleRequest);
} catch (StatusRuntimeException e) {
  Status s = e.getStatus();
  switch (s.getCode()) {
    case ALREADY_EXISTS:
      break;
    case PERMISSION_DENIED:
      break;
  }
  return;
}
// If no Exception, Vehicle created successfully.

用于创建车辆的 Google Cloud Platform 日志

收到对 CreateVehicle 端点的调用时,Fleet Engine API 会通过 Google Cloud Platform 日志写入日志条目。日志条目包含与 CreateVehicle 请求中的值相关的信息。如果调用成功,则还包括返回的 Vehicle 的相关信息。

shell

gcloud --project=project-id logging read --freshness=1h '
  jsonPayload.request.vehicleId="vid-8241890"
  jsonPayload.@type="type.googleapis.com/maps.fleetengine.v1.CreateVehicleLog"
'

应输出类似于以下内容的记录:

---
insertId: c2cf4d3a180251c1bdb892137c14f022
jsonPayload:
  '@type': type.googleapis.com/maps.fleetengine.v1.CreateVehicleLog
  request:
    vehicle:
      attributes:
      - key: on_trip
        value: 'false'
      maximumCapacity: 4
      state: VEHICLE_STATE_OFFLINE
      supportedTrips:
      - EXCLUSIVE_TRIP
      vehicleType:
        vehicleCategory: AUTO
    vehicleId: vid-8241890
  response:
    attributes:
    - key: on_trip
      value: 'false'
    availableCapacity: 4
    currentRouteSegmentHandle: AdSiwAwCO9gZ7Pw5UZZimOXOo41cJTjg/r3SuwVPQmuuaV0sU3+3UCY+z53Cl9i6mWHLoCKbBt9Vsj5PMRgOJ8zX
    maximumCapacity: 4
    name: providers/project-id/vehicles/vid-8241890
    state: VEHICLE_STATE_OFFLINE
    supportedTrips:
    - EXCLUSIVE_TRIP
    vehicleType:
      vehicleCategory: AUTO
labels:
  vehicle_id: vid-8241890
logName: projects/project-id/logs/fleetengine.googleapis.com%2Fcreate_vehicle
receiveTimestamp: '2021-09-22T03:25:16.361159871Z'
resource:
  labels:
    location: global
    resource_container: projects/project-id
  type: fleetengine.googleapis.com/Fleet
timestamp: '2021-09-22T03:25:15.724998Z'

用于创建车辆的 Cloud Pub/Sub 通知

创建新车辆时,Fleet Engine API 会通过 Cloud Pub/Sub 发布通知。要接收这些通知,请按照此处的说明进行操作。

方法指南:更新车辆的位置

如果不使用 Driver SDK 更新车辆的位置信息,您可以直接调用 Fleet Engine 并提供车辆的位置信息。对于任何活跃的车辆,Fleet Engine 都期望每分钟至少更新一次位置信息,最多每 5 秒更新一次。这些更新只需要 Fleet Engine Driver SDK 用户权限。

示例

shell

curl -X PUT \
  "https://fleetengine.googleapis.com/v1/providers/project-id/vehicles/vid-8241890?updateMask=last_location" \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  --data-binary @- << EOM
{
    "supplementalLocation": {"latitude": 12.1, "longitude": 14.5},
    "supplementalLocationTime": "$(date -u --iso-8601=seconds)",
    "supplementalLocationSensor": "CUSTOMER_SUPPLIED_LOCATION",
    "supplementalLocationAccuracy": 15
}
EOM

请参阅 providers.vehicles.update 参考文档。

Java

static final String PROJECT_ID = "project-id";
static final String VEHICLE_ID = "vid-8241890";

VehicleServiceBlockingStub vehicleService = VehicleService.newBlockingStub(channel);

String vehicleName = "providers/" + PROJECT_ID + "/vehicles/" + VEHICLE_ID;
Vehicle updatedVehicle = Vehicle.newBuilder()
    .setLastLocation(VehicleLocation.newBuilder()
        .setSupplementalLocation(LatLng.newBuilder()
            .setLatitude(37.3382)
            .setLongitude(121.8863))
        .setSupplementalLocationTime(now())
        .setSupplementalLocationSensor(LocationSensor.CUSTOMER_SUPPLIED_LOCATION)
        .setSupplementalLocationAccuracy(DoubleValue.of(15.0)))  // Optional)
    .build();

UpdateVehicleRequest updateVehicleRequest = UpdateVehicleRequest.newBuilder()
    .setName(vehicleName)
    .setVehicle(updatedVehicle)
    .setUpdateMask(FieldMask.newBuilder()
        .addPaths("last_location"))
    .build();

try {
  Vehicle updatedVehicle =
      vehicleService.updateVehicle(updateVehicleRequest);
} catch (StatusRuntimeException e) {
  Status s = e.getStatus();
  switch (s.getCode()) {
    case NOT_FOUND:
      // Most implementations will call CreateVehicle in this case
      break;
    case PERMISSION_DENIED:
      break;
  }
  return;
}
// If no Exception, Vehicle updated successfully.

方法指南:更新其他车辆字段

车辆状态其他属性的更新频率低于位置更新。对 last_location 以外的属性进行更新需要 Fleet Engine 超级用户权限。

UpdateVehicleRequest 包含一个 update_mask,用于指示要更新的字段。该字段的行为与 Protobuf 文档中有关字段掩码的行为相同。

车辆属性中所述,更新 attributes 字段需要写入需要保留的所有属性。无法在 UpdateVehicle 调用中只更新一个键值对的值。如需更新特定属性的值,可以使用 UpdateVehicleAttributes API。

示例

此示例启用了 back_to_back

shell

curl -X PUT \
  "https://fleetengine.googleapis.com/v1/providers/project-id/vehicles/vid-8241890?updateMask=vehicle_state,attributes,back_to_back_enabled" \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  --data-binary @- << EOM
{
    "vehicleState": "ONLINE",
    "attributes": [
      {"key": "on_trip", "value": "true"},
      {"key": "cash_only", "value": "false"}
    ],
    "backToBackEnabled": true
}
EOM

请参阅 providers.vehicles.update 参考文档。

Java

static final String PROJECT_ID = "project-id";
static final String VEHICLE_ID = "vid-8241890";

VehicleServiceBlockingStub vehicleService = VehicleService.newBlockingStub(channel);

String vehicleName = "providers/" + PROJECT_ID + "/vehicles/" + VEHICLE_ID;
Vehicle updatedVehicle = Vehicle.newBuilder()
    .setVehicleState(VehicleState.ONLINE)
    .addAllAttributes(ImmutableList.of(
        VehicleAttribute.newBuilder().setKey("on_trip").setValue("true").build(),
        VehicleAttribute.newBuilder().setKey("cash_only").setValue("false").build()))
    .setBackToBackEnabled(true)
    .build();

UpdateVehicleRequest updateVehicleRequest = UpdateVehicleRequest.newBuilder()
    .setName(vehicleName)
    .setVehicle(updatedVehicle)
    .setUpdateMask(FieldMask.newBuilder()
        .addPaths("vehicle_state")
        .addPaths("attributes")
        .addPaths("back_to_back_enabled"))
    .build();

// Attributes and vehicle state are being updated, so both are
// included in the field mask.  Note that of on_trip were
// not being updated, but rather cash_only was being changed,
// the desired value of "on_trip" would still need to be written
// as the attributes are completely replaced in an update operation.

try {
  Vehicle updatedVehicle =
      vehicleService.updateVehicle(updateVehicleRequest);
} catch (StatusRuntimeException e) {
  Status s = e.getStatus();
  switch (s.getCode()) {
    case NOT_FOUND:
      // Most implementations will call CreateVehicle in this case
      break;
    case PERMISSION_DENIED:
      break;
  }
  return;
}
// If no Exception, Vehicle updated successfully.

适用于车辆更新的 Google Cloud Platform 日志

收到对 UpdateVehicle 端点的调用时,Fleet Engine API 会通过 Google Cloud Platform 日志写入日志条目。日志条目包含与 UpdateVehicle 请求中的值相关的信息。如果调用成功,则还包括返回的 Vehicle 的相关信息。

shell

gcloud --project=project-id logging read --freshness=1h '
  jsonPayload.request.vehicleId="vid-8241890"
  jsonPayload.@type="type.googleapis.com/maps.fleetengine.v1.UpdateVehicleLog"
'

用于车辆更新的 Cloud Pub/Sub 通知

现有车辆更新时,Fleet Engine API 会通过 Cloud Pub/Sub 发布通知。要接收这些通知,请按照此处的说明进行操作。

方法指南:搜索车辆

Fleet Engine 支持搜索车辆。借助 SearchVehicles API,您可以查找附近最适合执行叫车或送货请求等任务的司机。SearchVehicles API 会返回一个司机列表,其中列出了将任务属性与车队车辆属性匹配的司机。如需了解详情,请参阅查找附近的司机

示例

在搜索可用车辆时,Fleet Engine 会默认排除有效行程中的车辆。拼车或送货服务提供商的服务需要明确包含在搜索请求中。以下示例展示了如何将这些车辆包含在搜索与从印度尼西亚大印尼东购物中心到雅加达会展中心的行程匹配的车辆中。

shell

首先,更新我们在前面的步骤中创建的车辆的位置信息,使其符合条件。在现实中,这可通过在车载 Android 或 iOS 设备上运行的驱动程序 SDK 来完成。

curl -X PUT \
  "https://fleetengine.googleapis.com/v1/providers/project-id/vehicles/vid-8241890?updateMask=last_location,attributes" \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  --data-binary @- << EOM
{
  "lastLocation": {
    "updateTime": "$( date -u +"%Y-%m-%dT%H:%M:%SZ" )",
    "location": {
      "latitude": "-6.195139",
      "longitude": "106.820826"
    }
  },
  "attributes": [{"key": "on_trip", "value": "false"}]
}
EOM

进行搜索时,至少应该让出该车辆。

curl -X POST \
  "https://fleetengine.googleapis.com/v1/providers/project-id/vehicles:search" \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  --data-binary @- << EOM
{
  "pickupPoint": {
    "point": {"latitude": "-6.195139", "longitude": "106.820826"}
  },
  "dropoffPoint": {
    "point": {"latitude": "-6.1275", "longitude": "106.6537"}
  },
  "pickupRadiusMeters": 2000,
  "count": 10,
  "minimumCapacity": 2,
  "tripTypes": ["EXCLUSIVE"],
  "vehicleTypes": [{"category": "AUTO"}],
  "filter": "attributes.on_trip=\"false\"",
  "orderBy": "PICKUP_POINT_ETA",
  "includeBackToBack": true
}
EOM

请参阅 providers.vehicles.search 参考文档。

Java

static final String PROJECT_ID = "project-id";

VehicleServiceBlockingStub vehicleService = VehicleService.newBlockingStub(channel);

String parent = "providers/" + PROJECT_ID;
SearchVehiclesRequest searchVehiclesRequest = SearchVehiclesRequest.newBuilder()
    .setParent(parent)
    .setPickupPoint( // Grand Indonesia East Mall
        TerminalLocation.newBuilder().setPoint(
            LatLng.newBuilder().setLatitude(-6.195139).setLongitude(106.820826)))
    .setDropoffPoint( // Balai Sidang Jakarta Convention Center
        TerminalLocation.newBuilder().setPoint(
            LatLng.newBuilder().setLatitude(-6.213796).setLongitude(106.807195)))
    .setPickupRadiusMeters(2000)
    .setCount(10)
    .setMinimumCapacity(2)
    .addTripTypes(TripType.EXCLUSIVE)
    .addVehicleTypes(VehicleType.newBuilder().setCategory(VehicleType.Category.AUTO))
    .setFilter("attributes.on_trip=\"false\"")
    .setOrderBy(VehicleMatchOrder.PICKUP_POINT_ETA)
    .setIncludeBackToBack(true) // Fleet Engine includes vehicles that are en route.
    .build();

// Error handling
// If matches are returned and the authentication passed, the request completed
// successfully

try {
  SearchVehiclesResponse searchVehiclesResponse =
      vehicleService.searchVehicles(searchVehiclesRequest);

  // Search results: Each vehicle match contains a vehicle entity and information
  // about the distance and ETA to the pickup point and dropoff point.
  List<VehicleMatch> vehicleMatches = searchVehiclesResponse.getMatchesList();
} catch (StatusRuntimeException e) {
  Status s = e.getStatus();
  switch (s.getCode()) {
    case NOT_FOUND:
      break;
    case PERMISSION_DENIED:
      break;
  }
  return;
}

车辆过滤查询

SearchVehiclesListVehicles 支持使用过滤条件查询对车辆属性进行过滤。如需了解过滤条件查询语法,请参阅 AIP-160 中的示例。

请注意,过滤条件查询仅支持对车辆属性进行过滤,不能用于其他字段。过滤条件查询以 AND 子句的形式运行,并具有其他约束条件,例如 SearchVehiclesRequest 中的 minimum_capacityvehicle_types

方法指南:列出车辆

SearchVehicles 经过优化,可以非常快速地按排名顺序查找少量车辆,主要用于查找附近最适合某项任务的司机。但是,有时即使需要对结果进行分页,也需要查找符合某些条件的所有车辆。ListVehicles 就是专为该用例设计的。

借助 ListVehicles API,您可以查找满足某些特定请求选项的所有车辆。ListVehicles API 会返回项目中符合某些要求的车辆的分页列表。

如需按车辆属性进行过滤,请参阅车辆过滤查询

示例

此示例使用 filter 字符串对 vehicle_type 和属性执行过滤。

shell

curl -X POST \
  "https://fleetengine.googleapis.com/v1/providers/project-id/vehicles:list" \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  --data-binary @- << EOM
{
  "vehicleTypes": [{"category": "AUTO"}],
  "filter": "attributes.on_trip=\"false\"",
}
EOM

请参阅 providers.vehicles.list 参考文档。

Java

static final String PROJECT_ID = "project-id";

VehicleServiceBlockingStub vehicleService = VehicleService.newBlockingStub(channel);

String parent = "providers/" + PROJECT_ID;
ListVehiclesRequest listVehiclesRequest = ListVehiclesRequest.newBuilder()
    .setParent(parent)
    .addTripTypes(TripType.EXCLUSIVE)
    .addVehicleTypes(VehicleType.newBuilder().setCategory(VehicleType.Category.AUTO))
    .setFilter("attributes.on_trip=\"false\"")
    .setIncludeBackToBack(true) // Fleet Engine includes vehicles that are en route.
    .build();

// Error handling
// If matches are returned and the authentication passed, the request completed
// successfully

try {
  ListVehiclesResponse listVehiclesResponse =
      vehicleService.listVehicles(listVehiclesRequest);
} catch (StatusRuntimeException e) {
  Status s = e.getStatus();
  switch (s.getCode()) {
    case NOT_FOUND:
      break;
    case PERMISSION_DENIED:
      break;
  }
  return;
}

行程及其生命周期

Trip API 和生命周期与 Vehicle API 和生命周期类似。拼车提供商负责使用 Fleet Engine 界面创建行程。Fleet Engine 同时提供 RPC 服务 TripService 和 REST 资源 provider.trips。这些界面支持创建行程实体、信息请求、搜索功能和更新功能。

Trip 有一个状态字段,用于跟踪其在生命周期中的进度。这些值从 NEW 变为 COMPLETE 以及 CANCELEDUNKNOWN_TRIP_STATUS。请参阅适用于 RPC 的 trip_status适用于 REST 的 TripStatus

  • NEW
  • ENROUTE_TO_PICKUP
  • ARRIVED_AT_PICKUP
  • ENROUTE_TO_INTERMEDIATE_DESTINATION
  • ARRIVED_AT_INTERMEDIATE_DESTINATION
  • ENROUTE_TO_DROPOFF
  • COMPLETE

您的服务可以将行程从这些状态更新为CANCELED。当服务创建行程时,引擎会将状态设置为 NEWvehicle_id 是可选的。与车辆一样,这些服务会在 7 天后自动删除行程,并且不会更新。如果您的服务尝试使用已存在的 ID 创建行程,则系统会返回错误。如果行程处于 COMPLETECANCELED 以外的状态,则会被视为“活跃”。这种区别在 Vehicle 实体和 SearchTripsRequestactive_trips 字段中非常重要。

只有在行程处于活动状态时,您的服务才能更改分配给行程的 vehicle_id。例如,当驾驶员在途中取消行程,而行程被重新分配给其他车辆时,您需要执行此操作。

在实现连续行程支持时,此状态非常重要。此支持使提供程序能够在车辆正在进行行程时为其分配新的行程。用于创建连续行程的代码与单个行程的代码相同,且使用相同的车辆 ID。Fleet Engine 会将新行程的出发地和目的地添加到车辆的航点中。如需详细了解连续行程,请参阅创建多航点行程

行程剩余航点

Trip 实体包含名为 remainingWaypoints (RPC | REST) 的重复 TripWaypoint(RPC | REST) 字段。此字段包含在本次行程的最终下车点之前按顺序行驶的所有航点。它根据车辆的剩余航点计算。在“返回”和“拼车”用例中,此列表包含将在此行程之前遍历的其他行程的航点,但不包含此行程之后的所有航点。列表中的航点可以通过其 TripIdWaypointType 标识。

行程状态与车辆剩余航点之间的关系

当 Fleet Engine 收到行程状态更改请求时,车辆的剩余航点 (RPC | REST) 将更新。当 tripStatus(RPC | REST) 从其他状态更改为 ENROUTE_TO_XXX 时,系统将从车辆的剩余航点列表中移除上一个航点。也就是说,当行程状态从 ENROUTE_TO_PICKUP 更改为 ARRIVED_AT_PICKUP 时,行程的上车点仍然位于车辆的剩余航点列表中,但是当行程状态更改为 ENROUTE_TO_INTERMEDIATE_DESTINATION 或 ENROUTE_TO_DROPOFF 时,将从车辆的上车点移除剩余的航点。

ARRIVED_AT_INTERMEDIATE_DESTINATION 和 ENROUTE_TO_INTERMDEDIATE_DESTINATION 也是如此。如果为 ARRIVED_AT_INTERMEDIATE_DESTINATION,则在车辆报告其正在前往下一个航点之前,系统不会从车辆的剩余航点列表中移除当前的中间目的地。

当行程状态更改为 COMPLETED 时,此行程的任何航点都不会出现在车辆的剩余航点列表中。

方法指南:创建行程

必须创建一个 Trip 实体,才能跟踪每个行程请求并将其与车队中的车辆进行匹配。结合使用 CreateTrip 端点和 CreateTripRequest 来创建行程。

以下属性是创建行程所必需的:

  • parent - 一个字符串,其中包含创建 Google Cloud 项目时创建的提供方 ID。
  • trip_id - 由拼车服务提供商创建的字符串。
  • trip - 包含描述行程的基本元数据的容器。
    • trip_type - 枚举,用于表示行程可能有来自不同出发地和目的地的其他乘客在同一辆车上 (SHARED),还是仅限单方乘客 (EXCLUSIVE)。
    • pickup_point - TerminalLocation,表示行程的出发地。参阅 RPC 参考文档REST 参考文档

创建行程时,您可以提供 number_of_passengersdropoff_pointvehicle_id。虽然这些字段不是必填字段,但如果您提供,系统会保留这些字段。所有其他“行程”字段均会被忽略。例如,即使您在创建请求中传入 trip_statusCANCELED,所有行程的 trip_status 也都是 NEW

示例

以下示例创建了前往印尼东购物中心的行程。此行程仅限两名乘客,为专属行程。Tripprovider_id 必须与项目 ID 相同。在此示例中,拼车服务提供商创建了 Google Cloud 项目 project-id。此项目必须具有用于调用 Fleet Engine 的服务帐号。行程的状态为 NEW

之后,在该服务将行程与车辆匹配后,该服务可以调用 UpdateTrip,并在将行程分配给车辆时更改 vehicle_id

shell

curl -X POST \
  "https://fleetengine.googleapis.com/v1/providers/project-id/trips?tripId=tid-1f97" \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  --data-binary @- << EOM
{
  "tripType": "EXCLUSIVE",
  "numberOfPassengers": 2,
  "pickupPoint": {
    "point": {"latitude": "-6.195139", "longitude": "106.820826"}
  },
  "dropoffPoint": {
    "point": {"latitude": "-6.1275", "longitude": "106.6537"}
  }
}
EOM

请参阅 providers.trips.create 参考文档。

Java

static final String PROJECT_ID = "project-id";

TripServiceBlockingStub tripService = TripService.newBlockingStub(channel);

String parent = "providers/" + PROJECT_ID;
Trip trip = Trip.newBuilder()
    .setTripType(TripType.EXCLUSIVE) // Use TripType.SHARED for carpooling
    .setPickupPoint(                 // Grand Indonesia East Mall
        TerminalLocation.newBuilder().setPoint(
            LatLng.newBuilder().setLatitude(-6.195139).setLongitude(106.820826)))
    // Provide the number of passengers if available.
    .setNumberOfPassengers(2)
    // Provide the drop-off point if available.
    .setDropoffPoint(
        TerminalLocation.newBuilder().setPoint(
            LatLng.newBuilder().setLatitude(-6.1275).setLongitude(106.6537)))
    .build();

CreateTripRequest createTripRequest =
    CreateTripRequest.newBuilder()  // no need for the header
        .setParent(parent)
        .setTripId("tid-1f97")  // Trip ID assigned by the Provider
        .setTrip(trip)              // Initial state
        .build();

// Error handling
// If Fleet Engine does not have trip with that id and the credentials of the
// requestor pass, the service creates the trip successfully.

try {
  Trip createdTrip =
      tripService.createTrip(createTripRequest);
} catch (StatusRuntimeException e) {
  Status s = e.getStatus();
  switch (s.getCode()) {
    case ALREADY_EXISTS:
      break;
    case PERMISSION_DENIED:
      break;
  }
  return;
}

用于创建行程的 Google Cloud Platform 日志

收到对 CreateTrip 端点的调用时,Fleet Engine API 会使用 Google Cloud Platform 日志写入日志条目。日志条目包含与 CreateTrip 请求中的值相关的信息。如果调用成功,则还包括返回的 Trip 的相关信息。

方法指南:更新行程

Trip 实体包含用于启用服务跟踪的字段,以及用于通过 Driver SDK 和 Consumer SDK 报告行程进度的字段。如需更新属性,请使用 UpdateTripRequest 消息。这样会根据请求的 field_mask 更新“行程”字段。请参阅 UpdateTripRequest

拼车服务提供商负责更新以下属性:

  • 行程状态。
  • 车辆 ID。可以在创建时创建,也可以在将车辆与行程匹配之后。
  • 更改上车点、下车点或航点。

通过驱动程序 SDK 或消费者 SDK 使用旅程分享功能时,Fleet Engine 会自动更新以下字段:

  • 路由
  • 预计到达时间
  • 剩余距离
  • 车辆位置
  • 其余航点

请参阅 RPC 中的 TripREST 中的 Resource.Trip

用于记录行程更新的 Google Cloud Platform 日志

收到对 UpdateTrip 端点的调用时,Fleet Engine API 会使用 Google Cloud Platform 日志写入日志条目。日志条目包含与 UpdateTrip 请求中的值相关的信息。如果调用成功,则还包括返回的 Trip 的相关信息。

方法指南:搜索行程

Fleet Engine 支持搜索行程。如前所述,行程会在 7 天后自动删除,因此 SearchTrips 不会显示所有行程的完整历史记录。

虽然 SearchTrips 是一个灵活的 API,但以下列表考虑了两个用例。

  • 确定车辆的有效行程 - 提供商可以确定车辆的当前有效行程。在 SearchTripsRequest 中,vehicle_id 设置为考虑中的车辆,active_trips_only 应设置为 true

  • 协调提供程序和 Fleet Engine 状态 - 提供程序可以使用 SearchTrips 确保其行程状态与 Fleet Engine 的行程状态匹配。 这对 TripStatus 而言尤为重要。如果分配给车辆的行程状态未正确设置为 COMPLETECANCELED,则该车辆不会包含在 SearchVehicles 中。

如需以这种方式使用 SearchTrips,请将 vehicle_id 留空,将 active_trips_only 设置为 true,并将 minimum_staleness 设置为大于大多数行程时长的时间。例如,您可以使用 1 小时。结果包括未完成或已取消且超过一小时未更新的行程。提供商应检查这些行程,以确保其在 Fleet Engine 中的状态已正确更新。

问题排查

如果出现 DEADLINE_EXCEEDED 错误,Fleet Engine 的状态将未知。Provider 应再次调用 CreateTrip,它会返回 201 (CREATED) 或 409 (CONFLICT)。在后一种情况下,上一个请求在 DEADLINE_EXCEEDED 之前成功。如需详细了解如何处理行程错误,请参阅 Consumer API 指南:AndroidiOS

拼车行程支持

您可以为支持 TripType.SHARED 的车辆分配多个 SHARED 行程。当您为共享行程(在 CreateTripUpdateTrip 请求中)分配 vehicle_id 时,需要通过 Trip.vehicle_waypoints 为此次共享行程中分配给车辆的所有行程指定所有未传递航点的顺序。请参阅适用于 RPC 的 vehicle_waypointsvehicleWaypoints(适用于 REST)

支持多个目的地

确定中间目的地

Trip (RPC | REST) 中的字段 intermediateDestinations 和字段 intermediateDestinationIndex 会合并以用于表示目的地。

更新中间目的地

您可以通过 UpdateTrip 更新中间目的地。更新中间目的地时,您必须提供中间目的地的完整列表,包括已访问的目的地,而不仅仅是新添加或要修改的中间目的地。当 intermediateDestinationIndex 指向新添加/修改的中间目的地的位置之后的索引时,新的/更新后的中间目的地不会添加到车辆的 waypoints 或行程的 remainingWaypoints 中。原因在于,intermediateDestinationIndex 之前的所有中间目的地都会被视为已访问。

行程状态变更

在发送到 Fleet Engine 的行程状态更新请求中,必须使用 (RPC | REST) 中的 intermediateDestinationsVersion 字段,以指示中间目的地已通过。目标中间目的地通过 intermediateDestinationIndex 字段指定。当 tripStatus (RPC | REST) 为 ENROUTE_TO_INTERMEDIATE_DESTINATION 时,[0..N-1] 之间的数字表示车辆接下来将穿过的中间目的地。当 tripStatus 为 ARRIVED_AT_INTERMEDIATE_DESTINATION 时,[0..N-1] 之间的数字表示车辆当前所处的中间目的地。

示例

以下代码示例演示了如何更新行程的状态,使其在前往其第一个中间目的地的途中,假设您已创建多目的地行程,并且该行程已超过其上车点。

Java

static final String PROJECT_ID = "project-id";
static final String TRIP_ID = "multi-destination-trip-A";

String tripName = "providers/" + PROJECT_ID + "/trips/" + TRIP_ID;
Trip trip = …; // Fetch trip object from FleetEngine or your storage.

TripServiceBlockingStub tripService = TripService.newBlockingStub(channel);

// Trip settings to update.
Trip trip = Trip.newBuilder()
    // Trip status cannot go back to a previous status once it is passed
    .setTripStatus(TripStatus.ENROUTE_TO_INTERMEDIATE_DESTINATION)
    // Enrouting to the first intermediate destination.
    .setIntermediateDestinationIndex(0)
    // intermediate_destinations_version MUST be provided to ensure you
    // have the same picture on intermediate destinations list as FleetEngine has.
    .setIntermediateDestinationsVersion(
        trip.getIntermediateDestinationsVersion())
    .build();

// Trip update request
UpdateTripRequest updateTripRequest =
    UpdateTripRequest.newBuilder()
        .setName(tripName)
        .setTrip(trip)
        .setUpdateMask(
            FieldMask.newBuilder()
                .addPaths("trip_status")
                .addPaths("intermediate_destination_index")
                // intermediate_destinations_version must not be in the
                // update mask.
                .build())
        .build();

// Error handling
try {
  Trip updatedTrip = tripService.updateTrip(updateTripRequest);
} catch (StatusRuntimeException e) {
  Status s = e.getStatus();
  switch (s.getCode()) {
    case NOT_FOUND:  // Trip does not exist.
      break;
    case FAILED_PRECONDITION:  // The given trip status is invalid, or the
                                // intermediate_destinations_version
                                // doesn’t match FleetEngine’s.
      break;
    case PERMISSION_DENIED:
      break;
  }
  return;
}

方法:通过 Fleet Engine API 订阅通知消息

Fleet Engine API 使用 Google Cloud Pub/Sub 发布有关使用方 Google Cloud 项目创建的主题的通知。默认情况下,Google Cloud 项目上的 Fleet Engine 不会启用 Pub/Sub。请提交支持请求或联系您的客户工程师以启用 Pub/Sub。

如需为您的 Cloud 项目创建主题,请按照这些说明操作。 主题 ID 必须是“fleet_engine_notifications”。

必须在调用 Fleet Engine API 的同一 Cloud 项目中创建主题。

创建主题后,您需要向 Fleet Engine API 授予针对该主题发布消息的权限。为此,请点击您刚刚创建的主题,然后添加新权限。您可能需要点击显示信息面板才能打开权限编辑器。 主账号应为 geo-fleet-engine@system.gserviceaccount.com,角色应为 Pub/Sub publisher

如需设置您的 Cloud 项目以订阅通知,请按照这些说明操作

Fleet Engine API 将以两种不同的数据格式(protobufjson)发布每条通知。每条通知的数据格式在 PubsubMessage 属性中指明,其中键为 data_format,值为 protobufjson

通知架构:

Protobuf

// A batch of notifications that is published by the Fleet Engine service using
// Cloud Pub/Sub in a single PubsubMessage.
message BatchNotification {
  // Required. At least one notification must exist.
  // List of notifications containing information related to changes in
  // Fleet Engine data.
  repeated Notification notifications = 1;
}

// A notification related to changes in Fleet Engine data.
// The data provides additional information specific to the type of the
// notification.
message Notification {
  // Required. At least one type must exist.
  // Type of notification.
  oneof type {
    // Notification related to changes in vehicle data.
    VehicleNotification vehicle_notification = 1;
  }
}

// Notification sent when a new vehicle was created.
message CreateVehicleNotification {
  // Required.
  // Vehicle must contain all fields that were set when it was created.
  Vehicle vehicle = 1;
}

// Notification sent when an existing vehicle is updated.
message UpdateVehicleNotification {
  // Required.
  // Vehicle must only contain name and fields that are present in the
  // field_mask field below.
  Vehicle vehicle = 1;

  // Required.
  // Contains vehicle field paths that were specifically requested
  // by the Provider.
  google.protobuf.FieldMask field_mask = 2;
}

// Notification related to changes in vehicle data.
message VehicleNotification {
  // Required. At least one type must be set.
  // Type of notification.
  oneof type {
    // Notification sent when a new vehicle was created.
    CreateVehicleNotification create_notification = 1;
    // Notification sent when an existing vehicle is updated.
    UpdateVehicleNotification update_notification = 2;
  }
}

JSON

BatchNotification: {
  "description": "A batch of notifications that is published by the Fleet Engine service using Cloud Pub/Sub in a single PubsubMessage.",
  "type": "object",
  "required": ["notifications"],
  "properties": {
    "notifications": {
      "description": "At least one notification must exist. List of notifications containing information related to changes in Fleet Engine data.",
      "type": "Notification[]"
    }
  }
}

Notification: {
  "description": "A notification related to changes in Fleet Engine data. The data provides additional information specific to the type of the notification.",
  "type": "object",
  "properties": {
    "vehicleNotification": {
      "description": "Notification related to changes in vehicle data.",
      "type": "VehicleNotification"
    }
  }
}

VehicleNotification: {
  "description": "Notification related to changes in vehicle data.",
  "type": "object",
  "properties": {
    "createNotification": {
      "description": "Notification sent when a new vehicle was created.",
      "type": "CreateVehicleNotification"
    },
    "updateNotification": {
      "description": "Notification sent when an existing vehicle is updated.",
      "type": "UpdateVehicleNotification"
    }
  }
}

CreateVehicleNotification: {
  "description": "Notification sent when a new vehicle was created.",
  "type": "object",
  "required": ["vehicle"],
  "properties": {
    "vehicle": {
      "description": "Vehicle must contain all fields that were set when it was created.",
      "type": "Vehicle"
    }
  }
}

UpdateVehicleNotification: {
  "description": "Notification sent when an existing vehicle is updated.",
  "type": "object",
  "required": ["vehicle", "fieldMask"],
  "properties": {
    "vehicle": {
      "description": "Vehicle must only contain name and fields that are present in the fieldMask field below.",
      "type": "Vehicle"
    },
    "fieldMask": {
      "description": "Contains vehicle field paths that were specifically requested by the Provider.",
      "type": "FieldMask"
    }
  }
}