開始使用 Fleet Engine 提升機群效能

透過 Fleet Engine Deliveries API 建立機群活動,建立第一套和最終 1 英里的交付作業。您可以使用 Android 和 iOS 適用的驅動程式 SDK,或是直接使用 HTTP REST 或 gRPC 呼叫來使用這個 API。

初始設定

您可以在 Google Cloud 控制台中設定 Fleet Engine Deliveries API。

確認設定正確無誤

建立服務帳戶後,請確認設定已完成,並可建立外送車輛。立即驗證設定可確保您解決設定專案時可能發生的常見授權問題。驗證設定的方法有兩種:

用戶端程式庫

如要在原始 gRPC 或 REST 上獲得更好的開發人員體驗,請使用多種常見程式設計語言的用戶端程式庫。如需取得伺服器應用程式用戶端程式庫的操作說明,請參閱用戶端程式庫

本文件中的 Java 範例假設您已經熟悉 gRPC。

資料結構

Fleet Engine Deliveries API 使用兩種資料結構來建立出貨和貨品交付模型:

  • 用來運送貨物的運輸車輛。
  • 取貨和貨品交付工作。

此外,您還可透過任務模擬駕駛作業,以及規劃一整天的停靠站。

交車

運輸車輛會從庫房到送貨地點,以及從上車地點到庫房的貨物運送。在某些情況下,他們也可以將貨物從取貨地點直接傳送到送貨地點。

使用 Driver SDK 在 Fleet Engine 中建立 DeliveryVehicle 物件,並傳送位置更新,以便追蹤運送與車隊。

注意:您最多可以將 500 項工作和 300 項車輛旅程路段指派給 DeliveryVehicle 物件。

工作

針對車輛在一天內執行的動作,您可以根據動作類型指派工作:

  • 針對自取和貨品交付事宜,指派出貨工作
  • 如果是駕駛人無法使用的時間 (例如必須中斷的工作),請指派「Unavailability 率工作」
  • 針對集放箱或客戶位置中的非駕駛工作,請指派排定的停止工作

您指派的每項工作都必須有專屬的工作 ID,但工作可能會共用相同的追蹤 ID。Fleet Engine 計算每項任務的預計到達時間時,會使用所有工作以及排定進行估算的順序。如要進一步瞭解工作 ID,請參閱工作 ID 指南

如要在 Fleet Engine 中建立工作,請使用驅動程式 SDK 工作管理員。

運送工作

建立自取和外送的出貨工作,並提供下列資訊:

  • 取貨或外送地點。
  • 追蹤號碼或 ID。
  • 將額外時間納入考量,以完成工作、尋找停車位或步行前往交接地點。
  • 不重複的工作 ID。請參閱「工作 ID 規範」。

詳情請參閱下列主題:

Android

iOS

無法預約的工作

無法使用的工作涵蓋車輛無法上車或送貨的時段,例如停車或司機休息片刻的休息時間。

使用下列資訊建立無法使用的工作:

  • 廣告插播的時間長度。
  • (選用) 中斷時間點的位置。您不一定要提供特定位置,但這樣可以提供更準確的每日預計到達時間。

詳情請參閱下列主題:

Android

iOS

已排定的停止工作

建立排程停靠工作,模擬運送車輛必須執行的停靠站。舉例來說,您可以在特定地點為每日排定的取貨停靠站建立排程停止工作,不考慮其他相同地點的送貨或取貨事宜。您亦可為集合 (取票箱)、「餵食器車輛」轉乘或停靠服務中心和服務點,

詳情請參閱下列主題:

Android

iOS

工作 ID 規範

建立工作 ID 時,請遵循下列內容和格式規範:

  • 建立專屬工作 ID
  • 請勿揭露任何個人識別資訊 (PII) 或清楚的文字資料。
  • 請使用有效的 Unicode 字串。
  • 最多只能使用 64 個半形字元。
  • 請勿加入下列任一 ASCII 字元:「/」、「:」、「\」、「?」或「#」。
  • 依據 Unicode 正規化表單 C 進行正規化。

以下是一些良好的工作 ID 範例:

  • 566c33d9-2a31-4b6a-9cd4-80ba1a0c643b
  • e4708eabcfa39bf2767c9546c9273f747b4626e8cc44e9630d50f6d129013d38
  • NTA1YTliYWNkYmViMTI0ZmMzMWFmOWY2NzNkM2Jk

下表列出不支援的工作 ID 示例:

不支援的工作 ID 原因
8/31/2019-20:48-46.70746,-130.10807,-85.17909,61.33680 違反 PII 和字元規定:逗號、句號、冒號和斜線。
JohnDoe-577b484da26f-Cupertino-SantaCruz 違反 PII 規定。
4R0oXLToF"112 Summer Dr. East Hartford, CT06118"577b484da26f8a 違反 PII 和字元規定:空格、逗號和引號。超過 64 個半形字元。

其他資源

如要查看每個資料結構中包含的特定欄位,請參閱 DeliveryVehicle (gRPCREST) 和 Task (gRPCREST) 的 API 參考資料說明文件。

車輛的生活

DeliveryVehicle 物件代表第一英里或最後一里送貨車輛。您可以使用下列指令建立 DeliveryVehicle 物件:

  • Google Cloud 專案的 ID,其中包含用於呼叫 Fleet Engine API 的服務帳戶。
  • 客戶擁有的車輛 ID。

請使用每輛車的專屬車輛 ID。請勿重複使用車輛 ID,除非原始車輛沒有正在執行的工作。

Fleet Engine 會自動刪除七天內未使用 UpdateDeliveryVehicle 更新的 DeliveryVehicle 物件。如何查看車輛是否存在:

  1. 撥打 UpdateDeliveryVehicle
  2. 如果發生 NOT_FOUND 錯誤,請呼叫 CreateDeliveryVehicle 重新建立車輛。如果來電退回車輛,還是可以更新。

車輛屬性

DeliveryVehicle 實體包含 DeliveryVehicleAttribute 的重複欄位。ListDeliveryVehicles API 包含 filter 欄位,可限制傳回的 DeliveryVehicle 實體僅限具有指定屬性的實體。DeliveryVehicleAttribute 不會影響 Fleet Engine 的轉送行為。

請勿在屬性中加入個人識別資訊 (PII) 或機密資訊,因為使用者可能會看到這個欄位。

工作的生命週期

您可以使用 Deliveries API gRPC 或 REST 介面在 Fleet Engine 中建立、更新及查詢工作。

Task 物件具有狀態欄位,可在其生命週期內追蹤進度。值會從「OPEN」移至「關閉」。新工作會以 OPEN 狀態建立,這代表下列其中一項:

  • 這項工作尚未指派給外送車輛。
  • 外送車輛尚未通過任務指定的車輛停靠站。

工作指南

您只能指派處於「開啟」狀態的車輛。

如要取消工作,請從車輛停靠站清單中移除任務,系統會自動將工作狀態設為「關閉」。

工作的車輛完成任務的車輛停靠時:

  1. 將工作的結果欄位更新為「成功」或「失敗」。

  2. 指定事件的時間戳記。

    接著,JavaScript 機群追蹤程式庫指出工作結果,且工作狀態會自動設為「關閉」。詳情請參閱「使用 JavaScript 機群追蹤程式庫追蹤機群」一文。

與車輛一樣,Fleet Engine 會刪除七天後仍未更新的工作,如果您嘗試建立具有現有 ID 的工作,則會傳回錯誤。

注意:Fleet Engine 不支援明確刪除工作。服務會在七天後自動刪除工作,但不會更新。如果您想保留工作資料超過七天,就必須自行實作這項功能。

工作屬性

Task 實體包含 TaskAttribute 的重複欄位,屬性值可以是下列 3 種類型之一:字串、數字和布林值。ListTasks API 包含 filter 欄位,可限制傳回的 Task 實體僅傳回具有指定屬性的實體。工作屬性不會影響 Fleet Engine 的轉送行為。

請勿在屬性中加入個人識別資訊 (PII) 或其他機密資訊,因為使用者或許能看見這些屬性。

管理車輛和任務的生命週期

提醒:您的內部系統是 Fleet Engine Deliveries API 代您擴充的可靠資料來源。

如要在系統中管理車輛和工作生命週期,請使用 Fleet Engine Deliveries API 建立、更新及追蹤車輛和相關工作。

與此同時,驅動程式應用程式會直接與 Fleet Engine 通訊,以更新裝置的位置和路徑資訊。這個模型讓 Fleet Engine 有效管理即時位置資訊這項工具會直接將位置資訊傳送至追蹤程式庫,方便您更新消費者訂單狀態。

舉例來說,假設您遇到以下情況:

  • 司機位於外送站附近。驅動程式應用程式將其位置傳送至 Fleet Engine。
  • Fleet Engine 會將裝置位置傳送至追蹤程式庫,供消費者應用程式利用該程式庫通知消費者與套件的距離。
  • 司機完成送貨流程後,在駕駛人應用程式中點選「已送達」按鈕。
  • 「已送達」動作會將資訊傳送至您的後端系統,該系統會執行必要的商家驗證和驗證步驟。
  • 系統會確認工作狀態為成功,並使用 Deliveryies API 更新 Fleet Engine。

下圖以一般層級說明這些程序。也會顯示系統、用戶端和 Fleet Engine 之間的標準關係。

整合 Deliveries API 圖表>

管理用戶端憑證

源自驅動程式應用程式的位置更新並直接傳送至 Fleet Engine,因此需要授權權杖。如要處理從用戶端到 Fleet Engine 的更新,建議使用下列做法:

  1. 使用 Fleet Engine Delivery 不受信任的驅動程式使用者服務帳戶角色產生憑證。

  2. 請為驅動程式應用程式提供受限範圍的權杖。此範圍僅允許該元件在 Fleet Engine 中更新裝置位置。

這個方法可確保來自行動裝置 (被視為低信任環境) 的呼叫,符合最低權限原則

其他服務帳戶角色

反之,如果您要授權驅動程式應用程式進行 Fleet Engine 直接更新,而更新範圍超出了「不受信任驅動程式」角色的限制 (例如執行特定工作更新),您可以使用「信任的驅動程式」角色。如要瞭解使用信任的駕駛角色的模型,請參閱信任的駕駛模型

如要進一步瞭解不受信任和信任的驅動程式角色的用途,請參閱 Cloud 專案設定

建立工作日模特兒

下表說明一人或最後一里的駕駛員在某一天的上班日,他們可能會是配送和物流公司。各家公司可能在細節上略有不同,但您可以瞭解如何為工作日建立模型。

時間活動模擬
當日開始的 24 小時內 調度員指派運輸工作給遞送車輛或路線。 您可以事先在 Fleet Engine 中為運送、取貨、中斷和其他作業建立工作。舉例來說,您可以建立送貨取貨工作送貨運送工作排定的停靠時間排定的停靠站

一組遞送包裹和提交訂單的提交順序確定後,請將工作指派給車輛。
當天開始時間 駕駛人登入駕駛應用程式,從庫房開始全新的一天。 初始化 Delivery Driver API。 視需要在 Fleet Engine 中建立運輸車輛
司機將貨運載運到貨車上、掃描運送過程。 如果您未提前建立運送商品交付工作,請在掃描時建立出貨工作
驅動程式會確認任務執行順序。 如未事先建立,請建立出貨取貨工作排定的停靠時間排定的停靠站
驅動程式會離開庫區,修訂下一個要完成的工作數量。 透過完成順序將所有工作或部分工作指派給車輛。
司機配送。 抵達停靠站後,執行與車輛抵達停靠站的相關動作。出貨後,請關閉送貨工作,並視需要選擇商店運送狀態和其他中繼資訊。在停靠站和開始開車到下一個停靠站前完成所有工作後,請執行與車輛完成停靠站車輛行駛到下一個停靠站的相關動作。
司機與送貨車輛不同,將其他貨物運送到貨運車上。 餵食器和運輸車輛之間的轉乘點應模擬為「排定的停靠站」

轉移及掃描運送商品後,如果尚未建立運送工作,請建立送貨工作。接著,請將工作指派給車輛更新工作順序,藉此更新工作完成順序。
司機收到取貨要求的通知。 接受取貨要求後,請建立取貨取貨工作。 接著,請將工作指派給車輛更新工作順序,藉此更新工作執行順序。
中午 駕駛人午休時間了。 如果地點與無法使用的工作相關聯,請比照其他工作處理。執行與車輛抵達某個站點的車輛相關動作、車輛停靠停靠站車輛行駛到下一個停靠站

否則,您不需要採取其他行動,直到廣告插播結束為止。 如要移除工作,請確認下一個工作和其他工作,然後更新工作順序
司機 收到了貨物 這項模擬功能就像是配送停靠站一樣。執行與車輛抵達停靠站關閉工作的相關動作,並視需要儲存運送狀態和其他中繼資訊。在停靠站和開始開車到下一個停靠站前完成所有工作後,請執行與車輛完成停靠站車輛行駛到下一個停靠站的相關動作。注意:為確保帳單正確無誤,所有取貨服務都必須有相應的運送工作。如果想要上車地點將送到司機當天其他地點的上車地點,建議您將送貨工作建立為路線上任何其他運送工作的模型。如果司機將上車送回庫,建議您在庫房目的地建立運送工作。
司機事先排定停靠站,以便從集貨箱上取件。 這種模擬方式和任何其他上車停靠站一樣。執行與車輛抵達停靠站的相關動作,以及關閉工作。在停靠站完成所有工作並開始開車到下一個停靠站後,請執行與車輛抵達停靠站車輛行駛到下一個停靠站的相關動作。
司機會收到貨物已轉交給其他地點的通知。 將原本的運送工作狀態設為「COMPLETED」,並為新的送貨地點建立新的運送工作。詳情請參閱「重新轉送出貨」一文。
駕駛人嘗試遞送包裹,但無法達成。 此方法的模型類似成功的傳送停止,並將交付工作標示為已完成。執行與車輛抵達停靠站相關的動作。未能出貨後,請關閉工作,並視需要選擇商店運送狀態和其他中繼資訊。在停靠站和開始開車到下一個停靠站前完成所有工作後,請執行與車輛完成停靠站車輛行駛到下一個停靠站的相關動作。
司機已收到保留 (未送達) 貨物的通知。 收到通知並確認通知後,請將工作狀態設為「COMPLETED」。
司機已收到貨運通知,要求他們接下來要送貨,並更改已簽訂的送貨訂單。 更新工作順序
司機選擇將貨品依訂購的貨品交付。 更新工作順序,然後照常作業。
司機將分批出貨至單一地點。 運作方式與單一運送停靠站類似。抵達停靠站後,執行與車輛抵達停靠站的相關動作。提交每項出貨商品後,請關閉每項工作,並視需要儲存運送狀態和其他中繼資訊。在停靠站和開始開車到下一個停靠站前完成所有工作後,請執行與車輛完成停靠站車輛行駛到下一個停靠站的相關動作。
當天結束時間 司機回到庫房。 如果司機回到庫房,且在航線途中收貨,您也必須將每個包裹視為送貨工作來建立並關閉,以確保帳單正確無誤。方法是像任何其他傳送停靠站一樣建立 Depot 模型。 如未將 Depot 做為傳送停靠站,您也可以選擇將庫特模特兒當做排定的停靠站。建立停靠站模型後,司機就能查看前往庫房的路線,並掌握預估抵達時間。

位置更新的運作方式

為了讓 Fleet Engine 發揮最佳效能,請提供一連串車輛位置更新資料。請透過下列其中一種方式提供更新:

  1. 使用最簡單的方法:AndroidiOS
  2. 如果是透過後端轉發位置,或者您使用的裝置不是 Android 或 iOS,請使用自訂程式碼

無論您的車輛位置更新方式為何,在運送車輛轉送到停靠站 (包括庫房) 和抵達停靠站時,後端就必須負責更新 Fleet Engine。Fleet Engine 不會自動偵測這些事件。

車輛停靠站和送貨地點

「停靠站」是指外送車輛完成運送工作或某些其他任務的位置。它是存取點,例如載入座架或道路封閉位置。

送貨地點是指送貨或取貨的地點。如要上下車地點,可能需要從車輛停靠站步行一段時間。

舉例來說,當司機正在送貨到購物中心的某間商店時,接送車輛停在靠近商店入口處的停車場時。此為車輛停靠站。接著,駕駛人從車輛停靠站步行到商店所在的購物中心位置。此為送貨地點。

為了讓使用者享有最佳的運送追蹤體驗,請考慮如何將運送工作指派給停靠站,同時請注意,系統會向使用者回報運送工作的剩餘車輛停靠次數,協助使用者查看運送進度。

舉例來說,如果司機為單一辦公室建築物提供多項送貨服務,請考慮將所有的運送工作指派給同一輛車停靠站。如果每項運送工作都指派給專屬停靠站,使用者就無法享有運送追蹤服務,因為只有在車輛位於抵達目的地之前,系統才會在幾個車輛停靠下進行追蹤,因此這樣對使用者而言就比較沒有幫助。如果在短時間內完成許多車輛停靠站,不會讓使用者有足夠時間追蹤運送進度。

使用行動 SDK

呼叫 Driver SDK 之前,請務必先將其初始化。

初始化 Delivery Driver API

在驅動程式 SDK 中初始化 Delivery Driver API 之前,請務必初始化 Navigation SDK。接著,請初始化 Delivery Driver API,如以下範例所示:

static final String PROVIDER_ID = "provider-1234";
static final String VEHICLE_ID = "vehicle-8241890";

NavigationApi.getNavigator(
   this, // Activity.
   new NavigatorListener() {
     @Override
     public void onNavigatorReady(Navigator navigator) {
       DeliveryDriverApi.createInstance(DriverContext.builder(getApplication())
         .setNavigator(navigator)
         .setProviderId(PROVIDER_ID)
         .setVehicleId(VEHICLE_ID)
         .setAuthTokenFactory((context) -> "JWT") // AuthTokenFactory returns JWT for call context.
         .setRoadSnappedLocationProvider(NavigationApi.getRoadSnappedLocationProvider(getApplication()))
         .setNavigationTransactionRecorder(NavigationApi.getNavigationTransactionRecorder(getApplication()))
         .setStatusListener((statusLevel,statusCode,statusMsg) -> // Optional, surfaces polling errors.
             Log.d("TAG", String.format("New status update. %s, %s, %s", statusLevel, statusCode, statusMsg)))
         .build));
     }
     @Override
     public void onError(int errorCode) {
       Log.e("TAG", String.format("Error loading Navigator instance: %s", errorCode));
     }
   });

用途

本節說明如何使用 Deliveries API 建立常見用途模型。

專屬實體 ID

REST 呼叫中使用的唯一實體 ID 格式和值在 Fleet Engine 中不透明。請避免使用自動遞增的 ID,並確保 ID 不含任何個人識別資訊 (PII),例如駕駛人的電話號碼。

製作車輛

您可以透過驅動程式 SDK 建立車輛,也可以使用 gRPC 或 REST 從伺服器環境建立車輛。

gRPC

如要建立新車輛,請向 Fleet Engine 發出 CreateDeliveryVehicle 呼叫。請使用 CreateDeliveryVehicleRequest 物件定義新外送車輛的屬性。請注意,針對使用者指定 ID 的 API 指南,系統將忽略針對 Name 欄位指定的任何值。您應使用 DeliveryVehicleId 欄位設定車輛 ID。

建立 DeliveryVehicle 時,您可以選擇指定下列欄位:

  • 屬性
  • LastLocation
  • 類型

請勿設定任何其他欄位。如果您這麼做,Fleet Engine 就會傳回錯誤,因為這些欄位都是唯讀欄位,或只能透過呼叫 UpdateDeliveryVehicle 更新。

如要在不設定任何選填欄位的情況下建立車輛,可在 CreateDeliveryVehicleRequest 中不設定 DeliveryVehicle 欄位。

以下範例說明如何使用 Java gRPC 程式庫建立車輛:

    static final String PROJECT_ID = "my-delivery-co-gcp-project";
    static final String VEHICLE_ID = "vehicle-8241890"; // Avoid auto-incrementing IDs.

    DeliveryServiceBlockingStub deliveryService =
      DeliveryServiceGrpc.newBlockingStub(channel);

    // Vehicle settings
    String parent = "providers/" + PROJECT_ID;
    DeliveryVehicle vehicle = DeliveryVehicle.newBuilder()
      .addAttributes(DeliveryVehicleAttribute.newBuilder()
        .setKey("route_number").setValue("1"))  // Opaque to the Fleet Engine
      .build();

    // Vehicle request
    CreateDeliveryVehicleRequest createVehicleRequest =
      CreateDeliveryVehicleRequest.newBuilder()  // No need for the header
          .setParent(parent)
          .setDeliveryVehicleId(VEHICLE_ID)     // Vehicle ID assigned by the Provider
          .setDeliveryVehicle(vehicle)
          .build();

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

    try {
      DeliveryVehicle createdVehicle =
        deliveryService.createDeliveryVehicle(createVehicleRequest);
    } catch (StatusRuntimeException e) {
      Status s = e.getStatus();
      switch (s.getCode()) {
         case ALREADY_EXISTS:
           break;
         case PERMISSION_DENIED:
           break;
      }
      return;
    }

REST

如要從伺服器環境建立車輛,請對 CreateDeliveryVehicle 發出 HTTP REST 呼叫:

POST https://fleetengine.googleapis.com/v1/providers/<project_id>/deliveryVehicles?deliveryVehicleId=<id>

<id> 是車隊中運輸車輛的專屬 ID

要求標頭必須包含值為 Bearer <token>Authorization 欄位,其中 <token>由 Fleet Engine 權杖工廠核發的憑證

POST 主體代表要建立的 DeliveryVehicle 實體。您可以指定下列選填欄位:

  • attributes
  • lastLocation
  • 類型

curl 指令範例:

# Set $JWT, $PROJECT_ID, and $VEHICLE_ID in the local
# environment
curl -X POST "https://fleetengine.googleapis.com/v1/providers/${PROJECT_ID}/deliveryVehicles?deliveryVehicleId=${VEHICLE_ID}" \
  -H "Content-type: application/json" \
  -H "Authorization: Bearer ${JWT}" \
--data-binary @- << EOM
{
  "attributes": [{"key": "model", "value": "sedan"}],
  "lastLocation": {"location": {"latitude": 12.1, "longitude": 14.5}}
}
EOM

如為使用者指定 ID,機群引擎會忽略 DeliveryVehicle 實體的 name 欄位,請勿設定任何其他欄位。如果您這麼做,Fleet Engine 就會傳回錯誤,因為這些欄位都是唯讀欄位,或只能透過呼叫 UpdateDeliveryVehicle 來更新。

如要建立不設定任何欄位的車輛,請將 POST 要求的主體留空。然後,新建立的車輛會從 POST 網址的 deliveryVehicleId 參數中擷取車輛 ID。

curl 指令範例:

# Set $JWT, $PROJECT_ID, and $VEHICLE_ID in the local
# environment
curl -X POST "https://fleetengine.googleapis.com/v1/providers/${PROJECT_ID}/deliveryVehicles?deliveryVehicleId=${VEHICLE_ID}" \
  -H "Content-type: application/json" \
  -H "Authorization: Bearer ${JWT}"

建立出貨取貨工作

您可以透過 Driver SDK,或使用 gRPC 或 REST 從伺服器環境建立運送取貨工作。

gRPC

以下範例說明如何使用 Java gRPC 程式庫建立出貨取貨工作:

static final String PROJECT_ID = "my-delivery-co-gcp-project";

DeliveryServiceBlockingStub deliveryService =
  DeliveryServiceGrpc.newBlockingStub(channel);

// Task settings
String parent = "providers/" + PROJECT_ID;
Task task = Task.newBuilder()
  .setType(Task.Type.PICKUP)
  .setState(Task.State.OPEN)
  .setTrackingId("my-tracking-id")
  .setPlannedLocation(               // Grand Indonesia East Mall
    LocationInfo.newBuilder().setPoint(
      LatLng.newBuilder().setLatitude(-6.195139).setLongitude(106.820826)))
  .setTaskDuration(
    Duration.newBuilder().setSeconds(2 * 60))
  .setTargetTimeWindow(
    TimeWindow.newBuilder()
      .setStartTime(Timestamp.newBuilder().setSeconds(1680123600))
      .setEndTime(Timestamp.newBuilder().setSeconds(1680130800)))
  .addAttributes(TaskAttribute.newBuilder().setKey("foo").setStringValue("value"))
  .addAttributes(TaskAttribute.newBuilder().setKey("bar").setNumberValue(10))
  .addAttributes(TaskAttribute.newBuilder().setKey("baz").setBoolValue(false))
  .build();

// Task request
CreateTaskRequest createTaskRequest =
  CreateTaskRequest.newBuilder()  // No need for the header
      .setParent(parent)          // Avoid using auto-incrementing IDs for the taskId
      .setTaskId("task-8241890")  // Task ID assigned by the Provider
      .setTask(task)              // Initial state
      .build();

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

try {
  Task createdTask = deliveryService.createTask(createTaskRequest);
} catch (StatusRuntimeException e) {
  Status s = e.getStatus();
  switch (s.getCode()) {
     case ALREADY_EXISTS:
       break;
     case PERMISSION_DENIED:
       break;
  }
  return;
}

REST

如要從伺服器環境建立取貨取貨工作,請向 CreateTask 發出 HTTP REST 呼叫:

`POST https://fleetengine.googleapis.com/v1/providers/<project_id>/tasks?taskId=<id>`

<id> 是工作的專屬 ID。請勿使用運送追蹤號碼。如果系統中沒有工作 ID,您可能會產生通用唯一識別碼 (UUID)。

要求標頭必須包含值為 Bearer <token>Authorization 欄位,其中 <token>由 Fleet Engine 權杖工廠核發的憑證

要求主體必須包含 Task 實體:

  • 必填欄位:

    欄位
    類型 Type.PICKUP
    state State.OPEN
    trackingId 您用於追蹤運送狀態的數字或 ID。
    plannedLocation 工作完成的地點,本例中為出貨地點。
    taskDuration 預計在上車地點取貨所需的時間 (以秒為單位)。

  • 選填欄位:

    欄位
    targetTimeWindow 完成工作的時間範圍。這不會影響轉送行為。
    attributes 自訂任務屬性清單。每個屬性都必須有一個專屬索引鍵。

建立實體時,系統會忽略該實體中的所有其他欄位。如果要求包含指派的 deliveryVehicleId,Fleet Engine 就會擲回例外狀況。您可以使用 UpdateDeliveryVehicleRequest 指派工作。詳情請參閱「將工作指派給車輛」和「UpdateDeliveryVehicleRequest」。

curl 指令範例:

# Set $JWT, $PROJECT_ID, $TRACKING_ID, and $TASK_ID in the local
# environment
curl -X POST "https://fleetengine.googleapis.com/v1/providers/${PROJECT_ID}/tasks?taskId=${TASK_ID}" \
  -H "Content-type: application/json" \
  -H "Authorization: Bearer ${JWT}" \
  --data-binary @- << EOM
{
  "type": "PICKUP",
  "state": "OPEN",
  "trackingId": "${TRACKING_ID}",
  "plannedLocation": {
     "point": {
        "latitude": -6.195139,
        "longitude": 106.820826
     }
  },
  "taskDuration": "90s",
  "targetTimeWindow": {
    "startTime": "2023-03-29T21:00:00Z",
    "endTime": "2023-03-29T23:00:00Z"
  }
}
EOM

建立運送工作

透過驅動程式 SDK 或使用 gRPC 或 REST 的伺服器環境建立運送工作。

gRPC

以下範例說明如何使用 Java gRPC 程式庫建立運送貨品交付工作:

static final String PROJECT_ID = "my-delivery-co-gcp-project";

DeliveryServiceBlockingStub deliveryService =
  DeliveryServiceGrpc.newBlockingStub(channel);

// Task settings
String parent = "providers/" + PROJECT_ID;
Task task = Task.newBuilder()
  .setType(Task.Type.DELIVERY)
  .setState(Task.State.OPEN)
  .setTrackingId("my-tracking-id")
  .setPlannedLocation(               // Grand Indonesia East Mall
    LocationInfo.newBuilder().setPoint(
      LatLng.newBuilder().setLatitude(-6.195139).setLongitude(106.820826)))
  .setTaskDuration(
    Duration.newBuilder().setSeconds(2 * 60))
  .setTargetTimeWindow(
    TimeWindow.newBuilder()
      .setStartTime(Timestamp.newBuilder().setSeconds(1680123600))
      .setEndTime(Timestamp.newBuilder().setSeconds(1680130800)))
  .addAttributes(TaskAttribute.newBuilder().setKey("foo").setStringValue("value"))
  .addAttributes(TaskAttribute.newBuilder().setKey("bar").setNumberValue(10))
  .addAttributes(TaskAttribute.newBuilder().setKey("baz").setBoolValue(false))
  .build();

// Task request
CreateTaskRequest createTaskRequest =
  CreateTaskRequest.newBuilder()  // No need for the header
      .setParent(parent)          // Avoid using auto-incrementing IDs for the taskId
      .setTaskId("task-8241890")  // Task ID assigned by the Provider
      .setTask(task)              // Initial state
      .build();

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

try {
  Task createdTask = deliveryService.createTask(createTaskRequest);
} catch (StatusRuntimeException e) {
  Status s = e.getStatus();
  switch (s.getCode()) {
     case ALREADY_EXISTS:
       break;
     case PERMISSION_DENIED:
       break;
  }
  return;
}

REST

如要使用 gRPC 或 REST 從伺服器環境建立出貨工作,請向 CreateTask 發出 HTTP REST 呼叫:

`POST https://fleetengine.googleapis.com/v1/providers/<project_id>/tasks?taskId=<id>`

<id> 是工作的專屬 ID。請勿使用運送追蹤號碼。如果系統中沒有工作 ID,您可能會產生通用唯一識別碼 (UUID)。

要求標頭必須包含值為 Bearer <token>Authorization 欄位,其中 <token>由 Fleet Engine 權杖工廠核發的憑證

要求主體必須包含 Task 實體:

  • 必填欄位:

    欄位
    類型 Type.DELIVERY
    state State.OPEN
    trackingId 您用於追蹤運送狀態的數字或 ID。
    plannedLocation 工作完成的地點 (在本例中為這項出貨商品的送貨地點)。
    taskDuration 將貨物送達送達地點所需的時間 (以秒為單位)。

  • 選填欄位:

    欄位
    targetTimeWindow 完成工作的時間範圍。這不會影響轉送行為。
    attributes 自訂任務屬性清單。每個屬性都必須有一個專屬索引鍵。

建立實體時,系統會忽略該實體中的所有其他欄位。如果要求包含指派的 DeliveryVehicleId,Fleet Engine 就會擲回例外狀況。您可以使用 UpdateDeliveryVehicleRequest 指派工作。詳情請參閱「將工作指派給車輛」和「UpdateDeliveryVehicleRequest」。

curl 指令範例:

# Set $JWT, $PROJECT_ID, $TRACKING_ID, and $TASK_ID in the local
# environment
curl -X POST "https://fleetengine.googleapis.com/v1/providers/${PROJECT_ID}/tasks?taskId=${TASK_ID}" \
  -H "Content-type: application/json" \
  -H "Authorization: Bearer ${JWT}" \
  --data-binary @- << EOM
{
  "type": "DELIVERY",
  "state": "OPEN",
  "trackingId": "${TRACKING_ID}",
  "plannedLocation": {
     "point": {
        "latitude": -6.195139,
        "longitude": 106.820826
     }
  },
  "taskDuration": "90s",
  "targetTimeWindow": {
    "startTime": "2023-03-29T21:00:00Z",
    "endTime": "2023-03-29T23:00:00Z"
  }
}
EOM

批次建立工作

您可以使用 gRPC 或 REST 從伺服器環境建立批次工作。

gRPC

以下範例說明如何使用 Java gRPC 程式庫建立兩項工作,一個用於傳送,另一個則用於在同一處取貨:

static final String PROJECT_ID = "my-delivery-co-gcp-project";

DeliveryServiceBlockingStub deliveryService =
  DeliveryServiceGrpc.newBlockingStub(channel);

// Delivery Task settings
Task deliveryTask = Task.newBuilder()
  .setType(Task.Type.DELIVERY)
  .setState(Task.State.OPEN)
  .setTrackingId("delivery-tracking-id")
  .setPlannedLocation(               // Grand Indonesia East Mall
    LocationInfo.newBuilder().setPoint(
      LatLng.newBuilder().setLatitude(-6.195139).setLongitude(106.820826)))
  .setTaskDuration(
    Duration.newBuilder().setSeconds(2 * 60))
  .build();

// Delivery Task request
CreateTaskRequest createDeliveryTaskRequest =
  CreateTaskRequest.newBuilder()  // No need for the header or parent fields
      .setTaskId("task-8312508")  // Task ID assigned by the Provider
      .setTask(deliveryTask)      // Initial state
      .build();

// Pickup Task settings
Task pickupTask = Task.newBuilder()
  .setType(Task.Type.PICKUP)
  .setState(Task.State.OPEN)
  .setTrackingId("pickup-tracking-id")
  .setPlannedLocation(               // Grand Indonesia East Mall
    LocationInfo.newBuilder().setPoint(
      LatLng.newBuilder().setLatitude(-6.195139).setLongitude(106.820826)))
  .setTaskDuration(
    Duration.newBuilder().setSeconds(2 * 60))
  .build();

// Pickup Task request
CreateTaskRequest createPickupTaskRequest =
  CreateTaskRequest.newBuilder()  // No need for the header or parent fields
      .setTaskId("task-8241890")  // Task ID assigned by the Provider
      .setTask(pickupTask)        // Initial state
      .build();

// Batch Create Tasks settings
String parent = "providers/" + PROJECT_ID;

// Batch Create Tasks request
BatchCreateTasksRequest batchCreateTasksRequest =
  BatchCreateTasksRequest.newBuilder()
      .setParent(parent)
      .addRequests(createDeliveryTaskRequest)
      .addRequests(createPickupTaskRequest)
      .build();

// Error handling
// If Fleet Engine does not have any task(s) with these task ID(s) and the
// credentials of the requestor pass, the service creates the task(s)
// successfully.

try {
  BatchCreateTasksResponse createdTasks = deliveryService.batchCreateTasks(
    batchCreateTasksRequest);
} catch (StatusRuntimeException e) {
  Status s = e.getStatus();
  switch (s.getCode()) {
     case ALREADY_EXISTS:
       break;
     case PERMISSION_DENIED:
       break;
  }
  return;
}

REST

如要從伺服器環境建立傳送和取貨工作,請向 BatchCreateTasks 發出 HTTP REST 呼叫:

POST https://fleetengine.googleapis.com/v1/providers/<project_id>/tasks:batchCreate

要求標頭必須包含值為 Bearer <token>Authorization 欄位,其中 <token>由 Fleet Engine 權杖工廠核發的憑證

要求主體必須包含 BatchCreateTasksRequest 實體:

  • 必填欄位:

    欄位
    項要求 陣列<CreateTasksRequest>

  • 選填欄位:

    欄位
    標頭 「DeliveryRequestHeader」

requests 中的每個 CreateTasksRequest 元素都必須以 CreateTask 要求的形式傳遞相同的驗證規則,但 parentheader 欄位為選用欄位。進行這項設定時,必須與頂層 BatchCreateTasksRequest 各自的欄位相同。請參閱「建立運送取貨工作」一節,並為各產品專屬的驗證規則建立運送工作

詳情請參閱 BatchCreateTasks 的 API 參考說明文件 (gRPCREST)。

curl 指令範例:

# Set $JWT, $PROJECT_ID, $DELIVERY_TRACKING_ID, $DELIVERY_TASK_ID,
# $PICKUP_TRACKING_ID, and $PICKUP_TASK_ID in the local environment
curl -X POST "https://fleetengine.googleapis.com/v1/providers/${PROJECT_ID}/tasks:batchCreate" \
  -H "Content-type: application/json" \
  -H "Authorization: Bearer ${JWT}" \
  --data-binary @- << EOM
{
  "requests" : [
    {
      "taskId": "${DELIVERY_TASK_ID}",
      "task" : {
        "type": "DELIVERY",
        "state": "OPEN",
        "trackingId": "${DELIVERY_TRACKING_ID}",
        "plannedLocation": {
          "point": {
              "latitude": -6.195139,
              "longitude": 106.820826
          }
        },
        "taskDuration": "90s"
      }
    },
    {
      "taskId": "${PICKUP_TASK_ID}",
      "task" : {
        "type": "PICKUP",
        "state": "OPEN",
        "trackingId": "${PICKUP_TRACKING_ID}",
        "plannedLocation": {
          "point": {
              "latitude": -6.195139,
              "longitude": 106.820826
          }
        },
        "taskDuration": "90s"
      }
    }
  ]
}
EOM

預定停用

您可以透過 Driver SDK,或從伺服器環境使用 gRPC 或 REST 建立指出無法使用的工作 (例如驅動程式故障或車輛退休)。已排定的「無法使用」工作不得包含追蹤 ID。您可以選擇提供位置。

gRPC

以下範例說明如何使用 Java gRPC 程式庫建立無法使用的工作:

    static final String PROJECT_ID = "my-delivery-co-gcp-project";

    DeliveryServiceBlockingStub deliveryService =
      DeliveryServiceGrpc.newBlockingStub(channel);

    // Task settings
    String parent = "providers/" + PROJECT_ID;
    Task task = Task.newBuilder()
      .setType(Task.Type.UNAVAILABLE)
      .setState(Task.State.OPEN)
      .setTaskDuration(
        Duration.newBuilder().setSeconds(60 * 60))  // 1hr break
      .build();

    // Task request
    CreateTaskRequest createTaskRequest =
      CreateTaskRequest.newBuilder()  // No need for the header
          .setParent(parent)          // Avoid using auto-incrementing IDs for the taskId
          .setTaskId("task-8241890")  // Task ID assigned by the Provider
          .setTask(task)              // Initial state
          .build();

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

    try {
      Task createdTask = deliveryService.createTask(createTaskRequest);
    } catch (StatusRuntimeException e) {
      Status s = e.getStatus();
      switch (s.getCode()) {
         case ALREADY_EXISTS:
           break;
         case PERMISSION_DENIED:
           break;
      }
      return;
    }

REST

如要從伺服器環境建立無法使用的工作,請對 CreateTask 發出 HTTP REST 呼叫:

`POST https://fleetengine.googleapis.com/v1/providers/<project_id>/tasks?taskId=<id>`

<id> 是工作的專屬 ID。如果系統中沒有工作 ID,您可以產生通用唯一識別碼 (UUID)。

要求標頭必須包含值為 Bearer <token>Authorization 欄位,其中 <token>由 Fleet Engine 權杖工廠核發的憑證

要求主體必須包含 Task 實體:

  • 必填欄位:

    欄位
    類型 Type.UNAVAILABLE
    state State.OPEN
    taskDuration 廣告插播的時間長度 (以秒為單位)。

  • 選填欄位:

    欄位
    plannedLocation 中斷點位置 (如必須在特定位置拍攝)。

建立實體時,系統會忽略該實體中的所有其他欄位。如果要求包含指派的 DeliveryVehicleId,Fleet Engine 就會擲回例外狀況。您可以使用 UpdateDeliveryVehicleRequest 指派工作。詳情請參閱「將工作指派給車輛」和「UpdateDeliveryVehicleRequest」。

curl 指令範例:

    # Set $JWT, $PROJECT_ID, and $TASK_ID in the local environment
    curl -X POST "https://fleetengine.googleapis.com/v1/providers/${PROJECT_ID}/tasks?taskId=${TASK_ID}" \
      -H "Content-type: application/json" \
      -H "Authorization: Bearer ${JWT}" \
      --data-binary @- << EOM
    {
      "type": "UNAVAILABLE",
      "state": "OPEN",
      "plannedLocation": {
         "point": {
            "latitude": -6.195139,
            "longitude": 106.820826
         }
      },
      "taskDuration": "300s"
    }
    EOM

排定的停靠站

您可以透過 Driver SDK,或使用 gRPC 或 REST 從伺服器環境建立排定的停止工作。排定的停止工作可能不會包含追蹤 ID。

gRPC

以下範例說明如何使用 Java gRPC 程式庫建立排定的停止工作:

static final String PROJECT_ID = "my-delivery-co-gcp-project";

DeliveryServiceBlockingStub deliveryService =
  DeliveryServiceGrpc.newBlockingStub(channel);

// Task settings
String parent = "providers/" + PROJECT_ID;
Task task = Task.newBuilder()
  .setType(Task.Type.SCHEDULED_STOP)
  .setState(Task.State.OPEN)
  .setPlannedLocation(               // Grand Indonesia East Mall
    LocationInfo.newBuilder().setPoint(
      LatLng.newBuilder().setLatitude(-6.195139).setLongitude(106.820826)))
  .setTaskDuration(
    Duration.newBuilder().setSeconds(2 * 60))
  .build();

// Task request
CreateTaskRequest createTaskRequest =
  CreateTaskRequest.newBuilder()  // No need for the header
      .setParent(parent)
      .setTaskId("task-8241890")  // Task ID assigned by the Provider
      .setTrip(task)              // Initial state
      .build();

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

try {
  Task createdTask = deliveryService.createTask(createTaskRequest);
} catch (StatusRuntimeException e) {
  Status s = e.getStatus();
  switch (s.getCode()) {
     case ALREADY_EXISTS:
       break;
     case PERMISSION_DENIED:
       break;
  }
  return;
}

REST

如要從伺服器環境建立排定的停止工作,請對 CreateTask 發出 HTTP REST 呼叫:

`POST https://fleetengine.googleapis.com/v1/providers/<project_id>/tasks?taskId=<id>`

<id> 是工作的專屬 ID。如果系統中沒有工作 ID,您可能會產生通用唯一識別碼 (UUID)。

要求標頭必須包含值為 Bearer <token>Authorization 欄位,其中 <token>由 Fleet Engine 權杖工廠核發的憑證

要求主體必須包含 Task 實體:

  • 必填欄位:

    欄位
    類型 Type.SCHEDULED_STOP
    state State.OPEN
    plannedLocation 停靠站的位置。
    taskDuration 停靠站預期長度 (以秒為單位)。

  • 選填欄位:

建立實體時,系統會忽略該實體中的所有其他欄位。如果要求包含指派的 DeliveryVehicleId,Fleet Engine 就會擲回例外狀況。您可以使用 UpdateDeliveryVehicleRequest 指派工作。詳情請參閱「將工作指派給車輛」和「UpdateDeliveryVehicleRequest」。

curl 指令範例:

    # Set $JWT, $PROJECT_ID, and $TASK_ID in the local environment
    curl -X POST "https://fleetengine.googleapis.com/v1/providers/${PROJECT_ID}/tasks?taskId=${TASK_ID}" \
      -H "Content-type: application/json" \
      -H "Authorization: Bearer ${JWT}" \
      --data-binary @- << EOM
    {
      "type": "SCHEDULED_STOP",
      "state": "OPEN",
      "plannedLocation": {
         "point": {
            "latitude": -6.195139,
            "longitude": 106.820826
         }
      },
      "taskDuration": "600s"
    }
    EOM

設定目標時間範圍

目標時間範圍是指工作應完成的 TimeWindow。舉例來說,如果您向收件者傳達送達時間範圍,可以使用工作目標時間範圍擷取這個時間範圍,然後使用該欄位產生快訊或分析行程後的表現。

目標時間範圍包含開始時間和結束時間,可以針對任何工作類型設定。目標時間範圍不會影響轉送行為。

gRPC

以下範例說明如何使用 Java gRPC 程式庫設定工作時間範圍:

    static final String PROJECT_ID = "my-delivery-co-gcp-project";
    static final String TASK_ID = "task-8241890";

    DeliveryServiceBlockingStub deliveryService =
      DeliveryServiceGrpc.newBlockingStub(channel);

    // Task settings
    String taskName = "providers/" + PROJECT_ID + "/tasks/" + TASK_ID;
    Task task = Task.newBuilder()
      .setName(taskName)
      .setTargetTimeWindow(
        TimeWindow.newBuilder()
          .setStartTime(Timestamp.newBuilder().setSeconds(1680123600))
          .setEndTime(Timestamp.newBuilder().setSeconds(1680130800)))
      .build();

    // Task request
    UpdateTaskRequest updateTaskRequest =
      UpdateTaskRequest.newBuilder()  // No need for the header
          .setTask(task)
          .setUpdateMask(FieldMask.newBuilder().addPaths("targetTimeWindow"))
          .build();

    try {
      Task updatedTask = deliveryService.updateTask(updateTaskRequest);
    } catch (StatusRuntimeException e) {
      Status s = e.getStatus();
      switch (s.getCode()) {
         case NOT_FOUND:
           break;
         case PERMISSION_DENIED:
           break;
      }
      return;
    }

REST

如要使用 HTTP 設定工作時間範圍,請呼叫 UpdateTask

`PATCH https://fleetengine.googleapis.com/v1/providers/<project_id>/tasks/<id>?updateMask=targetTimeWindow`

<id> 是工作的專屬 ID

要求標頭必須包含值為 Bearer <token>Authorization 欄位,其中 <token>由 Fleet Engine 權杖工廠核發的憑證

要求主體必須包含 Task 實體:

  • 必填欄位:

    欄位
    targetTimeWindow 完成工作的時間範圍。這項設定不會影響轉送行為

  • 選填欄位:

更新時會忽略實體中的所有其他欄位。

curl 指令範例:

# Set JWT, PROJECT_ID, and TASK_ID in the local environment
curl -X PATCH "https://fleetengine.googleapis.com/v1/providers/${PROJECT_ID}/tasks/${TASK_ID}?updateMask=targetTimeWindow" \
  -H "Content-type: application/json" \
  -H "Authorization: Bearer ${JWT}" \
  --data-binary @- << EOM
{
  "targetTimeWindow": {
    "startTime": "2023-03-29T21:00:00Z",
    "endTime": "2023-03-29T23:00:00Z"
  }
}
EOM

設定工作追蹤瀏覽權限設定

在工作上設定 TaskTrackingViewConfig,可以依據每個工作,控管「運送追蹤」程式庫中的資料顯示設定,以及呼叫 GetTaskTrackingInfo 呼叫傳回的資料。詳情請參閱「進行中的車輛工作」一節。在建立或更新工作時可以完成此步驟。以下範例說明如何使用這項設定更新工作:

gRPC

以下範例說明如何使用 Java gRPC 程式庫設定工作追蹤檢視設定:

static final String PROJECT_ID = "my-delivery-co-gcp-project";
static final String TASK_ID = "task-8241890";

DeliveryServiceBlockingStub deliveryService =
  DeliveryServiceGrpc.newBlockingStub(channel);

// Task settings
String taskName = "providers/" + PROJECT_ID + "/tasks/" + TASK_ID;
Task task = Task.newBuilder()
  .setName(taskName)
  .setTaskTrackingViewConfig(
    TaskTrackingViewConfig.newBuilder()
      .setRoutePolylinePointsVisibility(
        VisibilityOption.newBuilder().setRemainingStopCountThreshold(3))
      .setEstimatedArrivalTimeVisibility(
        VisibilityOption.newBuilder().remainingDrivingDistanceMetersThreshold(5000))
      .setRemainingStopCountVisibility(
        VisibilityOption.newBuilder().setNever(true)))
  .build();

// Task request
UpdateTaskRequest updateTaskRequest =
  UpdateTaskRequest.newBuilder()  // No need for the header
      .setTask(task)
      .setUpdateMask(FieldMask.newBuilder().addPaths("taskTrackingViewConfig"))
      .build();

try {
  Task updatedTask = deliveryService.updateTask(updateTaskRequest);
} catch (StatusRuntimeException e) {
  Status s = e.getStatus();
  switch (s.getCode()) {
      case NOT_FOUND:
        break;
      case PERMISSION_DENIED:
        break;
  }
  return;
}

REST

如要使用 HTTP 設定工作追蹤檢視設定視窗,請呼叫 UpdateTask

`PATCH https://fleetengine.googleapis.com/v1/providers/<project_id>/tasks/<id>?updateMask=taskTrackingViewConfig`

<id> 是工作的專屬 ID

要求標頭必須包含值為 Bearer <token>Authorization 欄位,其中 <token>由 Fleet Engine 權杖工廠核發的憑證

要求主體必須包含 Task 實體:

  • 必填欄位:

    欄位
    taskTrackingViewConfig 工作追蹤設定,指定使用者在什麼情況下可以看到哪些資料元素。

  • 選填欄位:

更新時會忽略實體中的所有其他欄位。

curl 指令範例:

# Set JWT, PROJECT_ID, and TASK_ID in the local environment
curl -X PATCH "https://fleetengine.googleapis.com/v1/providers/${PROJECT_ID}/tasks/${TASK_ID}?updateMask=taskTrackingViewConfig" \
  -H "Content-type: application/json" \
  -H "Authorization: Bearer ${JWT}" \
  --data-binary @- << EOM
{
  "taskTrackingViewConfig": {
    "routePolylinePointsVisibility": {
      "remainingStopCountThreshold": 3
    },
    "estimatedArrivalTimeVisibility": {
      "remainingDrivingDistanceMetersThreshold": 5000
    },
    "remainingStopCountVisibility": {
      "never": true
    }
  }
}
EOM

將工作指派給車輛

只要更新車輛的工作順序,即可指派工作到貨車上。車輛的工作排序是由交付車輛的停靠站清單決定,您可以為每個車輛停靠站指派一或多項任務。詳情請參閱「更新工作順序」。

如要從一輛車改為另一輛車,請先關閉原始工作,然後重新指派新車輛。如果為已指派給其他車輛的工作更新工作順序,就會收到錯誤訊息。

更新工作排序

您可以更新指派給車輛的訂單工作,透過驅動程式 SDK 或伺服器環境執行。請勿使用這兩種方法避免發生競爭狀況,並維持單一可靠的資料來源。

更新車輛的工作順序時,系統也會執行以下操作:

  • 指派車輛新的工作。
  • 關閉先前指派給車輛,但不依新版順序排序的所有工作。

如要變更車輛到另一輛車的運送資訊,請關閉原始工作,然後重新建立該工作,然後再指派新車輛。如果為已指派給其他車輛的工作更新工作順序,就會收到錯誤訊息。

您隨時可以更新工作順序。

gRPC

以下範例說明如何使用 Java gRPC 程式庫更新車輛的工作順序:

static final String PROJECT_ID = "my-delivery-co-gcp-project";
static final String VEHICLE_ID = "vehicle-8241890";
static final String TASK1_ID = "task-756390";
static final String TASK2_ID = "task-849263";

DeliveryServiceBlockingStub deliveryService =
  DeliveryServiceGrpc.newBlockingStub(channel);

// Vehicle settings
String vehicleName = "providers/" + PROJECT_ID + "/deliveryVehicles/" + VEHICLE_ID;
DeliveryVehicle deliveryVehicle = DeliveryVehicle.newBuilder()
    .addRemainingVehicleJourneySegments(VehicleJourneySegment.newBuilder()  // 1st stop
       .setStop(VehicleStop.newBuilder()
           .setPlannedLocation(LocationInfo.newBuilder()
               .setPoint(LatLng.newBuilder()
                   .setLatitude(37.7749)
                   .setLongitude(122.4194)))
           .addTasks(TaskInfo.newBuilder().setTaskId(TASK1_ID))
           .setState(VehicleStop.State.NEW)))
    .addRemainingVehicleJourneySegments(VehicleJourneySegment.newBuilder()  // 2nd stop
       .setStop(VehicleStop.newBuilder()
           .setPlannedLocation(LocationInfo.newBuilder()
               .setPoint(LatLng.newBuilder()
                   .setLatitude(37.3382)
                   .setLongitude(121.8863)))
           .addTasks(TaskInfo.newBuilder().setTaskId(TASK2_ID))
           .setState(VehicleStop.State.NEW)))
    .build();

// DeliveryVehicle request
UpdateDeliveryVehicleRequest updateDeliveryRequest =
  UpdateDeliveryVehicleRequest.newBuilder()  // No need for the header
      .setName(vehicleName)
      .setDeliveryVehicle(deliveryVehicle)
      .setUpdateMask(FieldMask.newBuilder().addPaths("remaining_vehicle_journey_segments"))
      .build();

try {
  DeliveryVehicle updatedDeliveryVehicle =
      deliveryService.updateDeliveryVehicle(updateDeliveryVehicleRequest);
} catch (StatusRuntimeException e) {
  Status s = e.getStatus();
  switch (s.getCode()) {
     case NOT_FOUND:
       break;
     case PERMISSION_DENIED:
       break;
  }
  return;
}

REST

如要從伺服器環境更新車輛的工作順序,請對 UpdateDeliveryVehicle 發出 HTTP REST 呼叫:

`PATCH https://fleetengine.googleapis.com/v1/providers/<project_id>/deliveryVehicles/<id>?updateMask=remainingVehicleJourneySegments`

<id> 是您想要更新工作順序的車隊中運輸車輛的專屬 ID。這是您建立車輛時指定的 ID。

要求標頭必須包含值為 Bearer <token>Authorization 欄位,其中 <token>由 Fleet Engine 權杖工廠核發的憑證

要求主體必須包含 DeliveryVehicle 實體:

  • 必填欄位:

    欄位
    remainingVehicleJourneySegments 按照工作執行順序排列的工作歷程區隔清單。系統會先執行清單中的第一項工作。
    剩餘 VehicleJourneySegments[i].stop 清單中工作的停止動作 i
    剩餘 VehicleJourneySegments[i].stop.plannedLocation 停靠站的規劃地點。
    剩餘 VehicleJourneySegments[i].stop.tasks 要在這輛車停靠站執行的工作清單。
    remainingVehicleJourneySegments[i].stop.state State.NEW

  • 選填欄位:

更新時會忽略實體中的所有其他欄位。

curl 指令範例:

# Set JWT, PROJECT_ID, VEHICLE_ID, TASK1_ID, and TASK2_ID in the local
# environment
curl -X PATCH "https://fleetengine.googleapis.com/v1/providers/${PROJECT_ID}/deliveryVehicles/${VEHICLE_ID}?updateMask=remainingVehicleJourneySegments" \
  -H "Content-type: application/json" \
  -H "Authorization: Bearer ${JWT}" \
  --data-binary @- << EOM
{
  "remainingVehicleJourneySegments": [
    {
      "stop": {
        "state": "NEW",
        "plannedLocation": {
          "point": {
            "latitude": 37.7749,
            "longitude": -122.084061
          }
        },
        "tasks": [
          {
            "taskId": "${TASK1_ID}"
          }
        ]
      }
    },
    {
      "stop": {
        "state": "NEW",
        "plannedLocation": {
          "point": {
            "latitude": 37.3382,
            "longitude": 121.8863
          }
        },
        "tasks": [
          {
            "taskId": "${TASK2_ID}"
          }
        ]
      }
    }
  ]
}
EOM

車輛正在前往下一個停靠站

當車輛從停靠站出發或開始導航時,必須通知機群引擎。您可以從 Driver SDK 或伺服器環境使用 gRPC 或 REST 通知 Fleet Engine。請勿使用兩種方法來避免競爭狀況,並維持單一可靠資料來源。

gRPC

以下範例說明如何使用 Java gRPC 程式庫,通知 Fleet Engine 車輛正在通往下一個停靠站。

    static final String PROJECT_ID = "my-delivery-co-gcp-project";
    static final String VEHICLE_ID = "vehicle-8241890";

    DeliveryServiceBlockingStub deliveryService =
      DeliveryServiceGrpc.newBlockingStub(channel);

    // Vehicle settings
    DeliveryVehicle deliveryVehicle = DeliveryVehicle.newBuilder()
        // Next stop marked as ENROUTE
        .addRemainingVehicleJourneySegments(VehicleJourneySegment.newBuilder()  // 1st stop
           .setStop(VehicleStop.newBuilder()
               .setPlannedLocation(LocationInfo.newBuilder()
                   .setPoint(LatLng.newBuilder()
                       .setLatitude(37.7749)
                       .setLongitude(122.4194)))
               .addTasks(TaskInfo.newBuilder().setTaskId(TASK1_ID))
               .setState(VehicleStop.State.ENROUTE)))
        // All other stops marked as NEW
        .addRemainingVehicleJourneySegments(VehicleJourneySegment.newBuilder()  // 2nd stop
           .setStop(VehicleStop.newBuilder()
               .setPlannedLocation(LocationInfo.newBuilder()
                   .setPoint(LatLng.newBuilder()
                       .setLatitude(37.3382)
                       .setLongitude(121.8863)))
               .addTasks(TaskInfo.newBuilder().setTaskId(TASK2_ID))
               .setState(VehicleStop.State.NEW)))
        .build();

    // DeliveryVehicle request
    UpdateDeliveryVehicleRequest updateDeliveryVehicleRequest =
      UpdateDeliveryVehicleRequest.newBuilder()  // No need for the header
          .setName(vehicleName)
          .setDeliveryVehicle(deliveryVehicle)
          .setUpdateMask(FieldMask.newBuilder().addPaths("remaining_vehicle_journey_segments"))
          .build();

    try {
      DeliveryVehicle updatedDeliveryVehicle =
          deliveryService.updateDeliveryVehicle(updateDeliveryVehicleRequest);
    } catch (StatusRuntimeException e) {
      Status s = e.getStatus();
      switch (s.getCode()) {
         case NOT_FOUND:
           break;
         case PERMISSION_DENIED:
           break;
      }
      return;
    }

REST

如要通知 Fleet Engine 有車輛從伺服器環境轉送至其下一個停靠站,請向 UpdateDeliveryVehicle 發出 HTTP REST 呼叫:

`PATCH https://fleetengine.googleapis.com/v1/providers/<project_id>/deliveryVehicles/<id>?updateMask=remainingVehicleJourneySegments`

<id> 是您要更新工作順序的車隊中運輸車輛的專屬 ID。這是您建立車輛時指定的 ID。

要求標頭必須包含值為 Bearer <token>Authorization 欄位,其中 <token>由 Fleet Engine 權杖工廠核發的憑證

要求主體必須包含 DeliveryVehicle 實體:

  • 必填欄位:

    欄位
    remainingVehicleJourneySegments 其餘車輛停靠站清單,狀態會標示為「State.NEW」。清單上第一個停靠站的狀態必須標示為 State.ENROUTE。

  • 選填欄位:

針對該通知,系統會忽略實體中的所有其他欄位。

curl 指令範例:

# Set JWT, PROJECT_ID, VEHICLE_ID, TASK1_ID, and TASK2_ID in the local
# environment
curl -X PATCH "https://fleetengine.googleapis.com/v1/providers/${PROJECT_ID}/deliveryVehicles/${VEHICLE_ID}?updateMask=remainingVehicleJourneySegments" \
  -H "Content-type: application/json" \
  -H "Authorization: Bearer ${JWT}" \
  --data-binary @- << EOM
{
  "remainingVehicleJourneySegments": [
    {
      "stop": {
        "state": "ENROUTE",
        "plannedLocation": {
          "point": {
            "latitude": 37.7749,
            "longitude": -122.084061
          }
        },
        "tasks": [
          {
            "taskId": "${TASK1_ID}"
          }
        ]
      }
    },
    {
      "stop": {
        "state": "NEW",
        "plannedLocation": {
          "point": {
            "latitude": 37.3382,
            "longitude": 121.8863
          }
        },
        "tasks": [
          {
            "taskId": "${TASK2_ID}"
          }
        ]
      }
    }
  ]
}
EOM

更新車輛位置

如果您不使用驅動程式 SDK 更新車輛的位置,可以直接呼叫車輛位置給 Fleet Engine。針對任何運作中的車輛,Fleet Engine 預期每分鐘位置更新至少一次,且最多每 5 秒更新一次。

gRPC

以下範例說明如何使用 Java gRPC 程式庫更新 Fleet Engine 中的車輛位置:

static final String PROJECT_ID = "my-delivery-co-gcp-project";
static final String VEHICLE_ID = "vehicle-8241890";

DeliveryServiceBlockingStub deliveryService =
  DeliveryServiceGrpc.newBlockingStub(channel);

// Vehicle settings
String vehicleName = "providers/" + PROJECT_ID + "/deliveryVehicles/" + VEHICLE_ID;
DeliveryVehicle myDeliveryVehicle = DeliveryVehicle.newBuilder()
    .setLastLocation(DeliveryVehicleLocation.newBuilder()
        .setSupplementalLocation(LatLng.newBuilder()
            .setLatitude(37.3382)
            .setLongitude(121.8863))
        .setSupplementalLocationTime(now())
        .setSupplementalLocationSensor(DeliveryVehicleLocationSensor.CUSTOMER_SUPPLIED_LOCATION)
        .setSupplementalLocationAccuracy(DoubleValue.of(15.0)))  // Optional
    .build();

// DeliveryVehicle request
UpdateDeliveryVehicleRequest updateDeliveryVehicleRequest =
  UpdateDeliveryVehicleRequest.newBuilder()  // No need for the header
      .setName(vehicleName)
      .setDeliveryVehicle(myDeliveryVehicle)
      .setUpdateMask(FieldMask.newBuilder()
          .addPaths("last_location"))
      .build();

try {
  DeliveryVehicle updatedDeliveryVehicle =
      deliveryService.updateDeliveryVehicle(updateDeliveryVehicleRequest);
} catch (StatusRuntimeException e) {
  Status s = e.getStatus();
  switch (s.getCode()) {
     case NOT_FOUND:
       break;
     case PERMISSION_DENIED:
       break;
  }
  return;
}

REST

如要使用 HTTP REST 更新 Fleet Engine 中的車輛位置,請呼叫 UpdateDeliveryVehicle

`PATCH https://fleetengine.googleapis.com/v1/providers/<project_id>/deliveryVehicles/<id>?updateMask=last_location`

<id> 是車上運送車輛或想要更新地點的專屬 ID。這是您建立車輛時指定的 ID。

要求標頭必須包含值為 Bearer <token>Authorization 欄位,其中 <token>由 Fleet Engine 權杖工廠核發的憑證

要求主體必須包含 DeliveryVehicle 實體:

  • 必填欄位:

    欄位
    lastLocation.supplementalLocation 車輛的位置。
    lastLocation.supplementalLocationTime 車輛在這個地點的最後已知時間戳記。
    lastLocation.supplementalLocationSensor 應填入 CUSTOMER_SUPPLIED_LOCATION。

  • 選填欄位:

    欄位
    lastLocation.supplementalLocationAccuracy 提供位置的準確度 (以公尺為單位)。

curl 指令範例:

# Set JWT, PROJECT_ID, VEHICLE_ID, TASK1_ID, and TASK2_ID in the local
# environment
curl -X PATCH "https://fleetengine.googleapis.com/v1/providers/${PROJECT_ID}/deliveryVehicles/${VEHICLE_ID}?updateMask=remainingVehicleJourneySegments" \
  -H "Content-type: application/json" \
  -H "Authorization: Bearer ${JWT}" \
  --data-binary @- << EOM
{
  "lastLocation": {
    "supplementalLocation": {"latitude": 12.1, "longitude": 14.5},
    "supplementalLocationTime": "$(date -u --iso-8601=seconds)",
    "supplementalLocationSensor": "CUSTOMER_SUPPLIED_LOCATION",
    "supplementalLocationAccuracy": 15
  }
}
EOM

車輛抵達停靠站

車輛抵達停靠站時,機群引擎必須收到通知。您可以從 Driver SDK 或伺服器環境使用 gRPC 或 REST 通知 Fleet Engine。請勿使用兩種方法來避免競爭狀況,並維持單一可靠資料來源。

gRPC

以下範例說明如何使用 Java gRPC 程式庫來通知 Fleet Engine 有車輛抵達停靠站:

static final String PROJECT_ID = "my-delivery-co-gcp-project";
static final String VEHICLE_ID = "vehicle-8241890";

DeliveryServiceBlockingStub deliveryService =
  DeliveryServiceGrpc.newBlockingStub(channel);

// Vehicle settings
String vehicleName = "providers/" + PROJECT_ID + "/deliveryVehicles/" + VEHICLE_ID;
DeliveryVehicle deliveryVehicle = DeliveryVehicle.newBuilder()
    // Marking the arrival at stop.
    .addRemainingVehicleJourneySegments(VehicleJourneySegment.newBuilder()
       .setStop(VehicleStop.newBuilder()
           .setPlannedLocation(LocationInfo.newBuilder()
               .setPoint(LatLng.newBuilder()
                   .setLatitude(37.7749)
                   .setLongitude(122.4194)))
           .addTasks(TaskInfo.newBuilder().setTaskId(TASK1_ID))
           .setState(VehicleStop.State.ARRIVED)))
    // All other remaining stops marked as NEW.
    .addRemainingVehicleJourneySegments(VehicleJourneySegment.newBuilder()  // 2nd stop
       .setStop(VehicleStop.newBuilder()
           .setPlannedLocation(LocationInfo.newBuilder()
               .setPoint(LatLng.newBuilder()
                   .setLatitude(37.3382)
                   .setLongitude(121.8863)))
           .addTasks(TaskInfo.newBuilder().setTaskId(TASK2_ID))
           .setState(VehicleStop.State.NEW))) // Remaining stops must be NEW.
    .build();

// DeliveryVehicle request
UpdateDeliveryVehicleRequest updateDeliveryVehicleRequest =
  UpdateDeliveryVehicleRequest.newBuilder()  // No need for the header
      .setName(vehicleName)
      .setDeliveryVehicle(deliveryVehicle)
      .setUpdateMask(FieldMask.newBuilder()
          .addPaths("remaining_vehicle_journey_segments"))
      .build();

try {
  DeliveryVehicle updatedDeliveryVehicle =
      deliveryService.updateDeliveryVehicle(updateDeliveryVehicleRequest);
} catch (StatusRuntimeException e) {
  Status s = e.getStatus();
  switch (s.getCode()) {
     case NOT_FOUND:
       break;
     case PERMISSION_DENIED:
       break;
  }
  return;
}

REST

如要通知 Fleet Engine 有車輛在伺服器環境的停靠站抵達後,請對 UpdateDeliveryVehicle 發出 HTTP REST 呼叫:

`PATCH https://fleetengine.googleapis.com/v1/providers/<project_id>/deliveryVehicles/<id>?updateMask=remainingVehicleJourneySegments`

<id> 是您要更新工作順序的車隊中運輸車輛的專屬 ID。這是您建立車輛時指定的 ID。

要求標頭必須包含值為 Bearer <token>Authorization 欄位,其中 <token>由 Fleet Engine 權杖工廠核發的憑證

要求主體必須包含 DeliveryVehicle 實體:

  • 必填欄位:

    欄位
    remainingVehicleJourneySegments 您到達的停靠站狀態設為 State.ARRIVED,接著是其餘車輛停靠站清單,各停靠站的狀態會標示為「State.NEW」。

  • 選填欄位:

更新時會忽略實體中的所有其他欄位。

curl 指令範例:

# Set JWT, PROJECT_ID, VEHICLE_ID, TASK1_ID, and TASK2_ID in the local
# environment
curl -X PATCH "https://fleetengine.googleapis.com/v1/providers/${PROJECT_ID}/deliveryVehicles/${VEHICLE_ID}?updateMask=remainingVehicleJourneySegments" \
  -H "Content-type: application/json" \
  -H "Authorization: Bearer ${JWT}" \
  --data-binary @- << EOM
{
  "remainingVehicleJourneySegments": [
    {
      "stop": {
        "state": "ARRIVED",
        "plannedLocation": {
          "point": {
            "latitude": 37.7749,
            "longitude": -122.084061
          }
        },
        "tasks": [
          {
            "taskId": "${TASK1_ID}"
          }
        ]
      }
    },
    {
      "stop": {
        "state": "NEW",
        "plannedLocation": {
          "point": {
            "latitude": 37.3382,
            "longitude": 121.8863
          }
        },
        "tasks": [
          {
            "taskId": "${TASK2_ID}"
          }
        ]
      }
    }
  ]
}
EOM

車輛完成停靠站

車輛抵達停靠站時,必須通知車隊引擎。這會導致所有與停靠站相關聯的工作都設為「已關閉」狀態。您可以透過驅動程式 SDK,或從伺服器環境使用 gRPC 或 REST 通知 Fleet Engine。請勿使用兩種方法來避免競爭狀況,並維持單一可靠資料來源。

gRPC

以下範例說明如何使用 Java gRPC 程式庫來通知 Fleet Engine 有車輛已完成停靠。

static final String PROJECT_ID = "my-delivery-co-gcp-project";
static final String VEHICLE_ID = "vehicle-8241890";

DeliveryServiceBlockingStub deliveryService =
  DeliveryServiceGrpc.newBlockingStub(channel);

// Vehicle settings
String vehicleName = "providers/" + PROJECT_ID + "/deliveryVehicles/" + VEHICLE_ID;
DeliveryVehicle deliveryVehicle = DeliveryVehicle.newBuilder()
    // This stop has been completed and is commented out to indicate it
    // should be removed from the list of vehicle journey segments.
    // .addRemainingVehicleJourneySegments(VehicleJourneySegment.newBuilder()
    //    .setStop(VehicleStop.newBuilder()
    //        .setPlannedLocation(LocationInfo.newBuilder()
    //            .setPoint(LatLng.newBuilder()
    //                .setLatitude(37.7749)
    //                .setLongitude(122.4194)))
    //        .addTasks(TaskInfo.newBuilder().setTaskId(TASK1_ID))
    //        .setState(VehicleStop.State.ARRIVED)))
    // All other remaining stops marked as NEW.
    // The next stop could be marked as ENROUTE if the vehicle has begun
    // its journey to the next stop.
    .addRemainingVehicleJourneySegments(VehicleJourneySegment.newBuilder()  // Next stop
       .setStop(VehicleStop.newBuilder()
           .setPlannedLocation(LocationInfo.newBuilder()
               .setPoint(LatLng.newBuilder()
                   .setLatitude(37.3382)
                   .setLongitude(121.8863)))
           .addTasks(TaskInfo.newBuilder().setTaskId(TASK2_ID))
           .setState(VehicleStop.State.NEW)))
    .build();

// DeliveryVehicle request
UpdateDeliveryVehicleRequest updateDeliveryVehicleRequest =
  UpdateDeliveryVehicleRequest.newBuilder()  // no need for the header
      .setName(vehicleName)
      .setDeliveryVehicle(deliveryVehicle)
      .setUpdateMask(FieldMask.newBuilder()
          .addPaths("remaining_vehicle_journey_segments"))
      .build();

try {
  DeliveryVehicle updatedDeliveryVehicle =
      deliveryService.updateDeliveryVehicle(updateDeliveryVehicleRequest);
} catch (StatusRuntimeException e) {
  Status s = e.getStatus();
  switch (s.getCode()) {
     case NOT_FOUND:
       break;
     case PERMISSION_DENIED:
       break;
  }
  return;
}

REST

如要通知 Fleet Engine 即將完成伺服器環境的停靠站,請對 UpdateDeliveryVehicle 發出 HTTP REST 呼叫:

`PATCH https://fleetengine.googleapis.com/v1/providers/<project_id>/deliveryVehicles/<id>?updateMask=remaining_vehicle_journey_segments`

<id> 是您要更新工作順序的車隊中運輸車輛的專屬 ID。這是您建立車輛時指定的 ID。

要求標頭必須包含值為 Bearer <token>Authorization 欄位,其中 <token>由 Fleet Engine 權杖工廠核發的憑證

要求主體必須包含 DeliveryVehicle 實體:

  • 必填欄位:

    欄位
    remaining_vehicle_journey_segments 您已完成的停靠站應該不會出現在其餘車輛停靠站清單中。

  • 選填欄位:

更新時會忽略實體中的所有其他欄位。

curl 指令範例:

    # Set JWT, PROJECT_ID, VEHICLE_ID, TASK1_ID, and TASK2_ID in the local
    # environment
    curl -X PATCH "https://fleetengine.googleapis.com/v1/providers/${PROJECT_ID}/deliveryVehicles/${VEHICLE_ID}?updateMask=remainingVehicleJourneySegments" \
      -H "Content-type: application/json" \
      -H "Authorization: Bearer ${JWT}" \
      --data-binary @- << EOM
    {
      "remainingVehicleJourneySegments": [
        {
          "stop": {
            "state": "NEW",
            "plannedLocation": {
              "point": {
                "latitude": 37.3382,
                "longitude": 121.8863
              }
            },
            "tasks": [
              {
                "taskId": "${TASK2_ID}"
              }
            ]
          }
        }
      ]
    }
    EOM

更新工作

大部分的工作欄位都無法變更。不過,您可以直接更新工作實體,藉此修改狀態、工作結果、工作結果時間、工作結果位置和屬性。舉例來說,在工作尚未指派給車輛的情況下,您可以直接更新狀態來關閉任務。

gRPC

這個範例說明如何透過 gRPC 更新工作。

REST

以下是透過 REST 更新工作的示例。

關閉工作

如要關閉已指派給車輛的工作,請通知 Fleet Engine 指出車輛已完成工作停靠站,或將該項工作從車輛停靠站清單中移除。做法是設定其餘車輛停靠站清單,就像更新車輛的工作順序一樣。

如果工作尚未獲指派車輛且需要關閉,請將工作更新為關閉狀態。但是您不能重新開啟「已關閉」的工作。

關閉工作不代表工作成功或失敗。表示該工作不再考慮於進行中。以機群追蹤來說,請務必指出工作的實際結果,以便顯示交付結果。

gRPC

static final String PROJECT_ID = "my-delivery-co-gcp-project";
static final String TASK_ID = "task-8241890";

DeliveryServiceBlockingStub deliveryService =
  DeliveryServiceGrpc.newBlockingStub(channel);

// Task settings
String taskName = "providers/" + PROJECT_ID + "/tasks/" + TASK_ID;
Task task = Task.newBuilder()
  .setName(taskName)
  .setState(Task.State.CLOSED) // You can only directly CLOSE a
  .build();                    // task that is NOT assigned to a vehicle.

// Task request
UpdateTaskRequest updateTaskRequest =
  UpdateTaskRequest.newBuilder()  // No need for the header
      .setTask(task)
      .setUpdateMask(FieldMask.newBuilder().addPaths("state"))
      .build();

try {
  Task updatedTask = deliveryService.updateTask(updateTaskRequest);
} catch (StatusRuntimeException e) {
  Status s = e.getStatus();
  switch (s.getCode()) {
     case NOT_FOUND:
       break;
     case PERMISSION_DENIED:
       break;
  }
  return;
}

REST

如要從伺服器環境將任務標示為已關閉,請對 UpdateTask 發出 HTTP REST 呼叫:

`PATCH https://fleetengine.googleapis.com/v1/providers/<project_id>/tasks/<id>?updateMask=state`

<id> 是工作的專屬 ID

要求標頭必須包含值為 Bearer <token>Authorization 欄位,其中 <token>由 Fleet Engine 權杖工廠核發的憑證

您必須在要求主體中加入 Task 實體:

  • 必填欄位:

    欄位
    state State.CLOSED

  • 選填欄位:

    欄位
    taskOutcome 結果失敗或結果失敗
    taskOutcomeTime 工作完成的時間。
    taskOutcomeLocation 完成工作的位置。除非供應商手動覆寫,否則 Fleet Engine 會將這個欄位預設為最後的車輛位置。

更新時會忽略實體中的所有其他欄位。

curl 指令範例:

    # Set JWT, PROJECT_ID, and TASK_ID in the local environment
    curl -X PATCH "https://fleetengine.googleapis.com/v1/providers/${PROJECT_ID}/tasks/${TASK_ID}?updateMask=state,taskOutcome,taskOutcomeTime" \
      -H "Content-type: application/json" \
      -H "Authorization: Bearer ${JWT}" \
      --data-binary @- << EOM
    {
      "state": "CLOSED",
      "taskOutcome": "SUCCEEDED",
      "taskOutcomeTime": "$(date -u --iso-8601=seconds)"
    }
    EOM

設定工作結果和結果位置

關閉工作不會指出成功或失敗,則表示該工作不再視為進行中。以機群追蹤來說,請務必指出工作的實際結果,以便系統顯示交付結果,並為服務設定適當的費用。設定後即無法變更工作結果。不過,您可以在設定工作結果時間和工作結果位置後,加以修改。

處於「已關閉」狀態的工作,可以將結果設為「成功」或「失敗」。Fleet Engine 只會針對狀態為 SUCCEEDED 的傳送工作收費。

標記工作結果時,Fleet Engine 會自動填入工作結果位置,提供最後已知的車輛位置。您可以覆寫這個行為。

gRPC

設定結果時,您可以選擇設定工作結果位置。設定位置可防止 Fleet Engine 將其設定為最後一個車輛位置的預設值。您也可以稍後再覆寫工作結果位置 Fleet Engine。Fleet Engine 絕不會覆寫您提供的工作結果位置。您無法為未設定工作結果的工作設定工作結果位置。您可以在同一個要求內設定工作結果和工作結果位置。

以下範例說明如何使用 Java gRPC 程式庫將工作結果設為 SUCCEEDED,並設定完成工作的位置:

static final String PROJECT_ID = "my-delivery-co-gcp-project";
static final String TASK_ID = "task-8241890";

DeliveryServiceBlockingStub deliveryService =
  DeliveryServiceGrpc.newBlockingStub(channel);

// Task settings
String taskName = "providers/" + PROJECT_ID + "/tasks/" + TASK_ID;
Task task = Task.newBuilder()
  .setName(taskName)
  .setTaskOutcome(TaskOutcome.SUCCEEDED)
  .setTaskOutcomeTime(now())
  .setTaskOutcomeLocation(               // Grand Indonesia East Mall
    LocationInfo.newBuilder().setPoint(
      LatLng.newBuilder().setLatitude(-6.195139).setLongitude(106.820826)))
  .build();

// Task request
UpdateTaskRequest updateTaskRequest =
  UpdateTaskRequest.newBuilder()  // No need for the header
      .setTask(task)
      .setUpdateMask(FieldMask.newBuilder().addPaths("task_outcome", "task_outcome_time", "task_outcome_location"))
      .build();

try {
  Task updatedTask = deliveryService.updateTask(updateTaskRequest);
} catch (StatusRuntimeException e) {
  Status s = e.getStatus();
  switch (s.getCode()) {
     case NOT_FOUND:
       break;
     case PERMISSION_DENIED:
       break;
  }
  return;
}

REST

如要從伺服器環境將工作標示為已完成,請對 UpdateTask 發出 HTTP REST 呼叫:

`PATCH https://fleetengine.googleapis.com/v1/providers/<project_id>/tasks/<id>?updateMask=taskOutcome,taskOutcomeTime,taskOutcomeLocation`

<id> 是工作的專屬 ID

要求標頭必須包含值為 Bearer <token>Authorization 欄位,其中 <token>由 Fleet Engine 權杖工廠核發的憑證

要求主體必須包含 Task 實體:

  • 必填欄位:

    欄位
    taskOutcome 結果失敗或結果失敗
    taskOutcomeTime 設定工作結果的時間戳記 (來自提供者)。這是指工作完成的時間。

  • 選填欄位:

    欄位
    taskOutcomeLocation 完成工作的位置。除非供應商手動覆寫,否則 Fleet Engine 會將這個欄位預設為最後的車輛位置。

更新時會忽略實體中的所有其他欄位。

curl 指令範例:

# Set JWT, PROJECT_ID, and TASK_ID in the local environment
curl -X PATCH "https://fleetengine.googleapis.com/v1/providers/${PROJECT_ID}/tasks/${TASK_ID}?updateMask=taskOutcome,taskOutcomeTime,taskOutcomeLocation" \
  -H "Content-type: application/json" \
  -H "Authorization: Bearer ${JWT}" \
  --data-binary @- << EOM
{
  "taskOutcome": "SUCCEEDED",
  "taskOutcomeTime": "$(date -u --iso-8601=seconds)",
  "taskOutcomeLocation": {
    "point": {
      "latitude": -6.195139,
      "longitude": 106.820826
    }
  }
}
EOM

重新規劃路線

運送工作建立後即無法變更預定位置。如要重新轉送出貨,請在不設定結果的情況下關閉出貨工作,然後使用更新後的預定位置建立新工作。建立新工作後,請將工作指派給同一輛車。詳情請參閱關閉運送工作指派工作

使用餵食器和外送車輛

如果您使用送禮車整天將貨運運送到貨車上,請針對運送車輛的排程停止工作建立運送模型。為了確保地點追蹤的準確度,請只將已轉移的貨運工作載入運輸車輛後,才指派運送工作。詳情請參閱排定的停靠站

商店運送狀態和其他中繼資訊

完成出貨工作後,工作狀態和結果就會記錄在工作中。但是,建議您更新其他與運送商品相關的中繼資訊。如要儲存可在 Fleet Engine 服務以外參照的其他中繼資料,請使用與工作相關聯的追蹤 ID 做為外部資料表中的索引鍵。

詳情請參閱任務生命週期

查詢車輛

您可以透過驅動程式 SDK 查詢車輛,也可以使用 gRPC 或 REST 從伺服器環境查詢車輛。

gRPC

以下範例說明如何使用 Java gRPC 程式庫查詢車輛:

static final String PROJECT_ID = "my-delivery-co-gcp-project";
static final String VEHICLE_ID = "vehicle-8241890";

DeliveryServiceBlockingStub deliveryService =
  DeliveryServiceGrpc.newBlockingStub(channel);

// Vehicle request
String name = "providers/" + PROJECT_ID + "/deliveryVehicles/" + VEHICLE_ID;
GetDeliveryVehicleRequest getVehicleRequest = GetDeliveryVehicleRequest.newBuilder()  // No need for the header
    .setName(name)
    .build();

try {
  DeliveryVehicle vehicle = deliveryService.getDeliveryVehicle(getVehicleRequest);
} catch (StatusRuntimeException e) {
  Status s = e.getStatus();
  switch (s.getCode()) {
     case NOT_FOUND:
       break;
     case PERMISSION_DENIED:
       break;
  }
  return;
}

REST

如要從伺服器環境查詢車輛,請發出 HTTP REST 呼叫 GetVehicle

`GET https://fleetengine.googleapis.com/v1/providers/<project_id>/deliveryVehicles/<vehicleId>`

<id> 是工作的專屬 ID

<vehicleId>:要查詢的車輛 ID。

要求標頭必須包含值為 Bearer <token>Authorization 欄位,其中 <token>由 Fleet Engine 權杖工廠核發的憑證

要求主體必須為空白。

如果查詢成功,回應主體會包含車輛實體。

curl 指令範例:

# Set JWT, PROJECT_ID, and VEHICLE_ID in the local environment
curl -H "Authorization: Bearer ${JWT}" \
  "https://fleetengine.googleapis.com/v1/providers/${PROJECT_ID}/deliveryVehicles/${VEHICLE_ID}"

查詢工作

您可以使用 gRPC 或 REST 從伺服器環境查詢工作。Driver SDK 不支援查詢工作。

gRPC

以下範例說明如何使用 Java gRPC 程式庫查詢工作:

static final String PROJECT_ID = "my-delivery-co-gcp-project";
static final String TASK_ID = "task-8597549";

DeliveryServiceBlockingStub deliveryService =
  DeliveryServiceGrpc.newBlockingStub(channel);

// Task request
String taskName = "providers/" + PROJECT_ID + "/tasks/" + TASK_ID;
GetTaskRequest getTaskRequest = GetTaskRequest.newBuilder()  // No need for the header
    .setName(taskName)
    .build();

try {
  Task task = deliveryService.getTask(getTaskRequest);
} catch (StatusRuntimeException e) {
  Status s = e.getStatus();
  switch (s.getCode()) {
     case NOT_FOUND:
       break;

     case PERMISSION_DENIED:
       break;
  }
  return;
}

REST

如要從伺服器環境查詢工作,請對 GetTask 發出 HTTP REST 呼叫:

`GET https://fleetengine.googleapis.com/v1/providers/<project_id>/tasks/<taskId>`

<id> 是工作的專屬 ID

<taskId> 是要查詢的工作 ID。

要求標頭必須包含值為 Bearer <token>Authorization 欄位,其中 <token>由 Fleet Engine 權杖工廠核發的憑證

要求主體必須為空白。

如果查詢成功,回應主體會包含工作實體。

curl 指令範例:

    # Set JWT, PROJECT_ID, and TASK_ID in the local environment
    curl -H "Authorization: Bearer ${JWT}" \
      "https://fleetengine.googleapis.com/v1/providers/${PROJECT_ID}/tasks/${TASK_ID}"

依據追蹤 ID 查詢運送工作資訊

您可以透過下列方式查詢機群工作資訊,每種方法都有不同用途:

  • 依工作 ID:供具備完整工作檢視權限的機群操作員使用。
  • 透過追蹤 ID:用戶端軟體用來為使用者提供有限資訊,例如包裹預計會放在家中的時間。

本節討論如何按追蹤 ID 查詢工作資訊。如要依工作 ID 查詢工作,請參閱查詢工作

若要按追蹤 ID 查詢資訊,您可以使用下列任一種方式:

查詢要求

  • 追蹤 ID 提供的運送資訊,必須符合「控管追蹤位置的瀏覽權限」一文所述的瀏覽權限規則。

  • 使用 Fleet Engine 依據追蹤 ID 查詢運送資訊。驅動程式 SDK 不支援依追蹤 ID 查詢資訊。如要使用 Fleet Engine 完成這項作業,請使用伺服器或瀏覽器環境。

  • 盡可能使用最小的符記來限制安全性風險。舉例來說,如果您使用傳送消費者權杖,則任何 Fleet Engine Deliveries API 呼叫只會傳回與使用者相關的資訊,例如貨運公司或貨物接收端。系統會遮蓋回覆中的所有其他資訊。如需更多有關憑證的資訊,請參閱「建立 JSON Web Token (JWT) 以進行授權」。

使用 gRPC 與 Java 查詢

以下範例說明如何使用 Java gRPC 程式庫,透過追蹤 ID 查詢運送工作的相關資訊。

static final String PROJECT_ID = "my-delivery-co-gcp-project";
static final String TRACKING_ID = "TID-7449w087464x5";

DeliveryServiceBlockingStub deliveryService =
  DeliveryServiceGrpc.newBlockingStub(channel);

// Tasks request
String parent = "providers/" + PROJECT_ID;
GetTaskTrackingInfoRequest getTaskTrackingInfoRequest = GetTaskTrackingInfoRequest.newBuilder()  // No need for the header
    .setParent(parent)
    .setTrackingId(TRACKING_ID)
    .build();

try {
  TaskTrackingInfo taskTrackingInfo = deliveryService.getTaskTrackingInfo(getTaskTrackingInfoRequest);
} catch (StatusRuntimeException e) {
  Status s = e.getStatus();
  switch (s.getCode()) {
     case NOT_FOUND:
       break;

     case PERMISSION_DENIED:
       break;
  }
  return;
}

使用 HTTP 查詢

如要從瀏覽器查詢運送工作,請對 GetTaskTrackingInfo 發出 HTTP REST 呼叫:

`GET https://fleetengine.googleapis.com/v1/providers/<project_id>/taskTrackingInfo/<tracking_id>`

<tracking_id> 是與工作相關聯的追蹤 ID。

要求標頭必須包含值為 Bearer <token>Authorization 欄位,其中 <token>由 Fleet Engine 權杖工廠核發的憑證

如果查詢成功,回應主體會包含 taskTrackingInfo 實體。

curl 指令範例:

# Set JWT, PROJECT_ID, and TRACKING_ID in the local environment
curl -H "Authorization: Bearer ${JWT}" \
  "https://fleetengine.googleapis.com/v1/providers/${PROJECT_ID}/taskTrackingInfo/${TRACKING_ID}"

列出工作

您可以在伺服器或瀏覽器環境中列出工作。驅動程式 SDK 不支援列出工作。

列出工作需要取得工作的廣泛存取權。只有信任的使用者能查看工作清單。提出列出工作要求時,使用 Delivery Fleet Reader 或 Delivery 超級使用者驗證權杖。

列出的工作會遮蓋下列欄位:

  • VehicleStop.planned_location
  • VehicleStop.state
  • VehicleStop.TaskInfo.taskId

大部分工作屬性都可以篩選列出的工作。如需篩選查詢語法,請參閱 AIP-160。以下清單列出可用來篩選的有效工作屬性:

  • attributes
  • delivery_vehicle_id
  • state
  • planned_location
  • task_duration
  • task_outcome
  • task_outcome_location
  • task_outcome_location_source
  • task_outcome_time
  • tracking_id
  • 類型

請根據 Google API 改善提案使用下列欄位格式:

欄位類型 形式 範例
時間戳記 RFC-3339 task_outcome_time = 2022-03-01T11:30:00-08:00
時間長度 後面接著 s 的秒數 task_duration = 120s
列舉 字串 state = CLOSED AND type = PICKUP
位置 point.latitudepoint.longitude planned_location.point.latitude > 36.1 AND planned_location.point.longitude < -122.0

如需篩選查詢運算子的完整清單,請參閱 AIP-160

如未指定篩選查詢,則會列出所有工作。

工作清單會經過分頁。您可以在清單工作要求中指定頁面大小。如果指定頁面大小,傳回的工作數量不會大於指定頁面大小。如果沒有頁面大小,則會使用合理的預設值。如果要求的網頁大小超過內部最大值,則會使用內部最大值。

工作清單可以包含用於讀取下一頁結果的符記。將頁面符記用於與前一項要求相同的要求,以擷取下一頁的工作。如果傳回的頁面符記為空白,就沒有其他工作可供擷取。

gRPC

以下範例說明如何使用 Java gRPC 程式庫列出 DeliveryVehicleId 和工作屬性的工作。成功的回應仍然可以使用空。空白回應表示沒有任何工作與提供的 DeliveryVehicleId 建立關聯。

static final String PROJECT_ID = "my-delivery-co-gcp-project";
static final String TRACKING_ID = "TID-7449w087464x5";

DeliveryServiceBlockingStub deliveryService =
  DeliveryServiceGrpc.newBlockingStub(channel);

// Tasks request
String parent = "providers/" + PROJECT_ID;
ListTasksRequest listTasksRequest = ListTasksRequest.newBuilder()  // No need for the header
    .setParent(parent)
    .setFilter("delivery_vehicle_id = 123 AND attributes.foo = true")
    .build();

try {
  ListTasksResponse listTasksResponse = deliveryService.listTasks(listTasksRequest);
} catch (StatusRuntimeException e) {
  Status s = e.getStatus();
  switch (s.getCode()) {
     case NOT_FOUND:
       break;

     case PERMISSION_DENIED:
       break;
  }
  return;
}

REST

如要從瀏覽器列出工作,請對 ListTasks 發出 HTTP REST 呼叫:

`GET https://fleetengine.googleapis.com/v1/providers/<project_id>/tasks`

如要對列出的工作套用篩選器,請加入「篩選器」網址參數,並以網址逸出的篩選器查詢做為值。

要求標頭必須包含值為 Bearer <token>Authorization 欄位,其中 <token>由 Fleet Engine 權杖工廠核發的憑證

如果查詢成功,回應主體會包含結構如下的資料:

    // JSON representation
    {
      "tasks": [
        {
          object (Task)
        }
      ],
      "nextPageToken": string,
      "totalSize": integer
    }

成功的回應仍然可以保持空白。空白回應表示沒有任何工作符合指定的篩選條件。

curl 指令範例:

    # Set JWT, PROJECT_ID, and VEHICLE_ID in the local environment
    curl -H "Authorization: Bearer ${JWT}" \
      "https://fleetengine.googleapis.com/v1/providers/${PROJECT_ID}/tasks?filter=state%20%3D%20OPEN%20AND%20delivery_vehicle_id%20%3D%20${VEHICLE_ID}"

列出運輸車輛

您可以透過伺服器或瀏覽器環境列出運輸車輛。驅動程式 SDK 不支援列出運送車輛。

列出外送車輛會要求一般的運輸車輛存取權,並僅適用於信任的使用者。提出列出外送車輛要求時,使用 Delivery Fleet Reader 或 Delivery 超級使用者驗證權杖。

下列運送車輛會因對回應大小的影響而遮蓋下列欄位:

  • CurrentRouteSegment
  • RemainingVehicleJourneySegments

您可以依據 attributes 屬性篩選送貨車輛清單。舉例來說,如要查詢索引鍵 my_key 和值 my_value 的屬性,請使用 attributes.my_key = my_value。如要查詢多個屬性,請使用邏輯 ANDOR 運算子彙整查詢,如 attributes.key1 = value1 AND attributes.key2 = value2 中所示。如需篩選查詢語法的完整說明,請參閱 AIP-160

您可以使用 viewport 要求參數,依位置篩選列出的外送車輛。viewport 要求參數會使用兩個定界座標定義可視區域:high (northeast) 和 low (southwest) 經緯度座標組合。如果要求包含的緯度在地理位置低於較低的緯度值,就會遭到拒絕。

根據預設,外送車輛清單會使用合理的頁面大小進行分頁。如果您指定頁面大小,要求只會傳回限制指定的車輛數量。如果要求的網頁大小超過內部最大值,則會使用內部最大值。預設和最大頁面大小都是 100 輛車。

外送車輛清單可包含用來讀取下一頁結果的符記。只有在有更多運送車輛可供擷取時,回應中才會顯示頁面符記。如要擷取下一頁的工作,請使用頁面符記搭配與先前要求相同的要求。

gRPC

以下範例說明如何使用 Java gRPC 程式庫,列出具有特定屬性的特定地區的外送車輛。成功的回應仍然可以保持空白。此時,就表示指定的可視區域中已有所有具備指定屬性的車輛。

static final String PROJECT_ID = "my-delivery-co-gcp-project";

DeliveryServiceBlockingStub deliveryService =
  DeliveryServiceGrpc.newBlockingStub(channel);

// Tasks request
String parent = "providers/" + PROJECT_ID;
ListDeliveryVehiclesRequest listDeliveryVehiclesRequest =
  ListDeliveryVehiclesRequest.newBuilder()  // No need for the header
      .setParent(parent)
      .setViewport(
            Viewport.newBuilder()
              .setHigh(LatLng.newBuilder()
                  .setLatitude(37.45)
                  .setLongitude(-122.06)
                  .build())
              .setLow(LatLng.newBuilder()
                  .setLatitude(37.41)
                  .setLongitude(-122.11)
                  .build())
      .setFilter("attributes.my_key = my_value")
      .build();

try {
  ListDeliveryVehiclesResponse listDeliveryVehiclesResponse =
      deliveryService.listDeliveryVehicles(listDeliveryVehiclesRequest);
} catch (StatusRuntimeException e) {
  Status s = e.getStatus();
  switch (s.getCode()) {
      case NOT_FOUND:
          break;

      case PERMISSION_DENIED:
          break;
  }
  return;
}

REST

如要從瀏覽器列出工作,請對 ListDeliveryVehicles 發出 HTTP REST 呼叫:

`GET https://fleetengine.googleapis.com/v1/providers/<project_id>/deliveryVehicles`

如要對列出的工作套用篩選器,請納入「篩選器」網址參數,並以網址逸出的篩選器查詢做為值。

要求標頭必須包含值為 Bearer <token>Authorization 欄位,其中 <token>由 Fleet Engine 權杖工廠核發的憑證

如果查詢成功,回應主體會包含結構如下的資料:

// JSON representation
{
  "deliveryVehicles": [
    {
      object (DeliveryVehicle)
    }
  ],
  "nextPageToken": string,
  "totalSize": integer
}

成功的回應仍然可以保持空白。這表示系統沒有找到符合指定篩選查詢和可視區域的運輸車輛。

curl 指令範例:

# Set JWT, PROJECT_ID, and VEHICLE_ID in the local environment
curl -H "Authorization: Bearer ${JWT}" \
  "https://fleetengine.googleapis.com/v1/providers/${PROJECT_ID}/deliveryVehicles?filter=attributes.my_key%20%3D%20my_value%20&viewport.high.latitude=37.45&viewport.high.longitude=-122.06&viewport.low.latitude=37.41&viewport.low.longitude=-122.11"

車隊追蹤

透過 Fleet Engine Deliveries API 啟用機群追蹤功能有以下兩種:

  • 建議採用:使用 JavaScript 機群追蹤程式庫。這個程式庫可讓您以視覺化方式呈現在 Fleet Engine 中追蹤的車輛位置和搜尋點位置。其中包含用於直接取代標準 google.maps.Map 物件的 JavaScript 地圖元件,以及與 Fleet Engine 連結的資料元件。這個元件可讓您在網頁或行動應用程式中提供可自訂的動畫機群追蹤體驗。

  • 在 Fleet Engine Deliveries API 中導入自有機群追蹤功能。

關鍵是依追蹤 ID 查詢機群工作

記錄

您可以設定 Fleet Engine 將遠端程序呼叫 (RPC) 記錄檔傳送至 Cloud Logging。詳情請參閱 Logging

授權角色和權杖

如「管理車輛和工作生命週期」一文以及個別用途的授權注意事項所述,如要呼叫 Fleet Engine,必須使用以服務帳戶憑證簽署的 JSON Web Token 進行驗證。用來核發憑證的服務帳戶可能具備一或多個角色,每個角色都會授予不同的權限組合。

詳情請參閱驗證及授權

排解常見問題

如有任何問題,請參閱以下各節的說明。

彈性

Fleet Engine 不被視為可靠資料來源。如有需要,您必須負責還原系統狀態,不必依賴 Fleet Engine。

機群引擎遺失狀態

使用 Fleet Engine 時,請實作用戶端,讓系統在發生故障時自行修復。舉例來說,Fleet Engine 嘗試更新車輛時,可能會收到一則回應,指出車輛不存在。接著用戶端應會在新狀態中重新建立車輛。雖然這個問題很少發生,但請確保您的系統具備足夠彈性,能夠處理這個問題。

在 Fleet Engine 發生災難性發生極少數的情況下,您可能需要重新建立大部分或所有車輛與工作。如果建立頻率過高,部分要求可能會因為配額問題而再次失敗,因為系統會檢查配額來避免阻斷服務 (DOS) 攻擊。在這種情況下,請使用輪詢策略來嘗試重試,以減緩重建率。

驅動程式應用程式中的遺失狀態

如果驅動程式應用程式異常終止,應用程式必須在驅動程式 SDK 中重新建立目前狀態。應用程式應嘗試重新建立工作,確保工作存在並還原目前狀態。應用程式也應重新建立並明確設定 Driver SDK 的停靠站清單。

常見問題

如果司機停止執行工作,該怎麼辦?

在這種情況下,請先更新工作順序,然後照常執行,將抵達點、工作完成和其他詳細資料標示。否則系統可能會以不一致的方式呈現預計到達時間,也可能會回報非預期的錯誤。