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 日之前创建的项目,默认情况下可能未启用 Logging。如需了解详情,请参阅日志记录文档

客户端库

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

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

身份验证与授权

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

Cloud 项目设置

如需设置云项目,请先创建项目,然后再创建服务帐号。

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

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

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

Trip and Order Progress 使用以下角色:

角色说明
Fleet Engine Consumer SDK User

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

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

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

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

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

Driver SDK 和 Consumer 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 的访问。GitHub 上提供了新的 Fleet Engine Auth 库,它简化了 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 网络令牌 (JWT) 提供短期身份验证,可确保设备只能修改其获得授权的车辆、行程或任务。JWT 包含一个标头和一个声明部分。 标头部分包含要使用的私钥(从服务帐号中获取)和加密算法等信息。声明部分包含如下信息:令牌的创建时间、令牌的存留时间、它声明访问权限的服务,以及用于缩小访问权限范围的其他授权信息;例如车辆 ID。

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

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

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

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

创建 JWT 令牌指的是对其进行签名。如需了解如何创建和为 JWT 签名,请参阅不使用 OAuth 的服务帐号授权。然后,您可以将已签名的令牌附加到用于访问 Fleet Engine 的 gRPC 调用或其他方法。

JWT 声明

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

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

Consumer 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 字段,使用创建令牌时的时间戳,指定为自 1970 年 1 月 1 日 00:00:00 UTC 以来经过的秒数。允许 10 分钟偏差。如果时间戳是过于久远的过去或将来,服务器可能会报告错误。
  • 对于 exp 字段,使用令牌到期时的时间戳,指定为自 1970 年 1 月 1 日 00:00:00 UTC 以来的秒数。允许的最大值为 iat + 3600。

为要传递给移动设备的 JWT 签名时,请务必使用具有 Driver 或 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 属性是纪元后的秒数的到期时间,可以通过在 iat 上加上 3600 计算得出。到期时间不能超过未来的 1 小时。

{
  "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)"

车辆及其生命周期

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

七天后仍未通过 UpdateVehicle 更新的车辆将被自动删除。使用已存在的提供方 ID/车辆 ID 对调用 CreateVehicle 会导致出错。车辆不经常更新的情况可通过两种方式处理:频繁使用预期的提供方 ID/车辆 ID 对调用 CreateVehicle,并在车辆已存在时舍弃错误;或者在 UpdateVehicle 返回 NOT_FOUND 错误后调用 CreateVehicle

车辆位置信息更新

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

  1. 使用驱动程序 SDK - AndroidiOS - 最简单的选项。
  2. 使用自定义代码 - 适用于通过后端中继位置信息,或使用 Android 或 iOS 以外的设备的情况。

车辆类型

车辆实体包含 VehicleType 的必填字段,其中包含可指定为 AUTOTAXITRUCKTWO_WHEELERBICYCLEPEDESTRIANCategory 枚举。车辆类型可用作 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 必须是包含用于调用 Fleet Engine 的服务帐号的 Google Cloud 项目的项目 ID(例如 my-on-demand-project)。请注意,虽然多个服务帐号可以访问同一拼车或送餐服务提供商的 Fleet Engine,但 Fleet Engine 目前不支持来自访问同一 Vehicles 的多个 Google Cloud 项目的服务帐号。

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

初始 last_location 可以包含在 CreateVehicle 调用中。在允许的情况下,不应在没有 last_locationONLINE 状态下创建 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 设备上运行的 Driver 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_statusREST 的 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 以外的状态,则被视为“活动”。这种区别在车辆实体的 active_trips 字段和 SearchTripsRequest 中非常重要。

仅当状态为 NEWCANCELED 时,服务才能更改分配给 Trip 的 vehicle_id。如果驾驶员在途中取消行程,必须在更改或清除 vehicle_id 之前将行程状态设置为 NEWCANCELED

在实现背靠背行程支持时,此状态非常重要。通过这项支持,提供方可以在车辆处于有效行程时为其分配新的行程。用于创建背靠背行程的代码与单个行程相同,并使用相同的车辆 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 来创建 Trip。

如需创建行程,必须提供以下属性:

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

创建行程时,您可以提供 number_of_passengersdropoff_pointvehicle_id。虽然这些字段不是必填字段,但如果您提供,系统会保留它们。所有其他行程字段均会被忽略。例如,所有行程都从 NEWtrip_status 开始,即使您在创建请求传入 CANCELEDtrip_status 也是如此。

示例

以下示例创建了去往 Grand Indonesia East 购物中心的行程。此次行程仅限两名乘客,且不重复。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 的信息。

方法:更新行程

“行程”实体包含一些字段,这些字段支持通过服务进行跟踪,并通过司机 SDK 和消费者 SDK 报告行程进度。如需更新属性,请使用 UpdateTripRequest 消息。这将根据请求的 field_mask 更新“行程”字段。请参阅 UpdateTripRequest

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

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

通过 Driver SDK 或 Consumer 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 的状态未知。提供程序应再次调用 CreateTrip,这将返回 201 (CREATED) 或 409 (CONFLICT)。在后一种情况下,上一个请求在 DEADLINE_EXCEEDED 之前成功。如需详细了解如何处理行程错误,请参阅 Consumer API 指南:AndroidiOS

拼车行程支持

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

多目的地支持

确定中间目的地

行程中的 intermediateDestinations 字段和 intermediateDestinationIndex 字段 (RPC | REST) 组合用于指示目的地。

更新中间目标位置

您可以通过 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"
    }
  }
}