普通视图

发现新文章,点击刷新页面。
昨天以前木澤的研發腦

[AGV] 初探 OpenTCS 開源車隊管理系統 | Exploring OpenTCS

作者 木澤
2025年12月6日 00:33

This article briefly introduces OpenTCS as an open-source fleet management system and focuses on its positioning and suitable use cases, based on practical integration experience, to help engineers assess whether it fits their needs.

車隊管理系統 (Fleet Management System) 是調度多台AGV(AMR)時所需的必要系統,其功能主要包括整合上位系統、任務管理、交通管理、充電策略等等,目的是讓車輛的運行順暢和符合上位系統(MES or WCS)的期待,這篇文章將介紹筆者接觸到的開源解決方案 OpenTCS 是一個什麼樣的車隊系統,又適合哪些用戶來使用,協助工程師評估導入的可行性,不會包含詳細的功能說明和操作步驟。

目錄

  1. 中文內容
    1. OpenTCS 簡介
    2. 重點功能介紹
      1. 地圖
      2. 任務管理
      3. 路徑搜尋與成本
      4. 交通管理
      5. 待命區
      6. 充電策略
      7. 模擬車
      8. 整合性
    3. 使用心得
      1. 各功能評估
      2. 期望與落差
      3. 適用客群
  2. English Version
    1. Introduction to OpenTCS
    2. Key Features Overview
      1. Map
      2. Task Management
      3. Path Finding and Cost Calculation
      4. Traffic Management
      5. Parking Areas
      6. Charging Strategy
      7. Simulated Vehicles
      8. Integration Capabilities
    3. Practical Evaluation
      1. Feature Assessment
      2. Expectations vs Reality
      3. Target Users
  3. Reference

中文內容

OpenTCS 簡介

OpenTCS 是由德國公司 Fraunhofer IML 維護的開源專案,採用寬鬆友善的 MIT 授權,該車隊系統的Scope包含了一般對車隊系統的認知的功能,因爲供應商中立的特性,可以自行整合任意協定的車輛,這也意味着官方沒有爲了特定廠商開發協定的Adapter,需要自己動手開發對應的Adapter,唯一支援的協定,也是開源的AGV控制協定 VDA5050,github上有專門的 Repo 維護 VDA5050 Adapter,對於電梯或自動門等設備的整合,也可以開發週邊設備的Adapter自行整合,可以做到確保設備回到成功才讓車子拿到路徑資源允許通過。

其使用的程式語言爲 java,支援跨平臺使用,官方提供了 3 個桌面程式和1個控制台程式,分別是

  • Model Editor : 地圖編輯器,用來編輯地圖layout以及車輛資訊
  • Operations Desk : 觀看車輛、任務、路徑資源的即時狀態,可以進行派車、設定車輛可用性等
  • Kernel Control Center : 車輛和週邊設備的控制管理,可以設定車輛的 Adapter 類型、停用啓用、週邊設備控制,可以看一些 Kernel Watchdog 的訊息
  • Kernel : 車隊的主程式,沒有任何 UI,會將運行狀況以文字顯示在控制台界面上,前三者操作時,都是對 Kernel 進行連線後才能動作

整體而言,應用程式的 UI 非常工程師,算是堪用狀態,一些簡單的操作也需要做很多動作,屬於簡單直接沒有 UX 的 UI,更新方面,目前 OpenTCS 算是活躍的專案,約1-2個月就會發佈更新,持續修復問題和提供新功能,爲了方便描述,以下會將 OpenTCS 簡稱爲 TCS。

重點功能介紹

地圖

TCS的地圖屬於拓撲式,由點和線組成,比較特別的是兩點之間來回會算成2條線,而不是1條線,地圖上可以定義的元素如下

  • Point:定義坐標、角度、類型
  • Path:定義路線,包含開始與結束的Point、最高速度、是否鎖定
  • Location Type:站點類型,供站點參照,定義可執行的”操作“,像是對接、充電等,也可以定義可執行的設備”操作“,像是開門”關門等
  • Location:站點,關聯到特定的Point,作爲派車的目的地,也可以是設備的觸發點,會跟Path關聯,像是走到這條路就觸發開門,開門完成後才會把命令發給車子
  • Block:資源的集合,所謂的資源指的是前面的Point、Path、Location,不同類型有不同作用,像是僅允許一臺車或是僅允許相同入口,不符合條件時,車輛會在外面等待,直到可以爲止
  • Vehicle:定義車輛屬性,有名稱、最高速度、電量門檻、尺寸、允許接受的任務類型(一對多)、整合度,其中整合度可以決定車子是否占用資源、允許接收任務

爲了可以整合各種車輛和客製化邏輯,以上各元素都支援自定義屬性,這算是TCS的特色,TCS自己的屬性會用 tcs: 為前綴,整合 VDA5050 時,adapter 也會讀取 vda5050: 開頭的屬性,保留最大的彈性

任務管理

TCS 的搬運任務稱爲 TransportOrder,是上位派車時的最小單位,可包含多個目的地,每個目的地就像一個子任務,稱爲DriveOrder,TransportOrder 可以指定

  • 類型:對應 vehicle 的任務類型,可以做到不同類型的車接收各自類型的任務
  • 期限時間:用來當成排序得參考
  • 指定車輛:是否指定特定車輛執行
  • 自動取消:主要用於回待命區或充電的 Order,可自動取消並接受新的 Order 節省時間
  • 依賴:可設定A和B任務都完成後才執行此任務,TCS 會依照依賴順序執行 Order
  • 客製化屬性:這邊定義的屬性都可以在 Adapter 內被讀取到,用來實現客製化的功能,以 VDA5050 Adapter 爲例,可以定義在目的地要執行的 action

任務的排序和每個任務挑選車子的條件可在config中動態設定各條件的優先順序,可以做到回頭車、Order期限快到時優先執行等較複雜的任務管理

路徑搜尋與成本

TCS內建 Dijkstra 算法找尋成本最低的路徑,成本則可以在 config 中多選,像是距離、Point數量、時間、群組、尺寸,其中群組是可以在 Path 和 Vehicle 定義屬性,讓相同群組的車使用對應的成本值,可以做到特定路讓特定車優先,尺寸則是在 Point 限定允許通過的大小,若 Vehicle 定義的大小超過則成本無限大,不允許通過,路徑一旦算出來後,就不會再改變了,基本上Path的鎖定沒有變更的話,算幾次都是一樣的結果

交通管理

有資源管理的概念,每個資源同時只能被一台車持有,正常狀況下,每台車會持有所在的 Point,有任務路徑時,會持有後續 N 段 Path 和 Point,如果路徑有交錯,先拿先贏,其他車只能等待釋出後再取得。

有些時候兩台車算出來的路徑會有衝突,例如都在同一條雙向道上,兩台車都各拿1個點,就會造成黑羊白羊過橋問題卡住,這時候可以設定 Block 來限定特定區域只能一台車來避免問題,但 Block 是把雙面刃,過度設置也會造成交通癱瘓

另外針對設備的部分,可以在走到特定路線時,對特定的設備 Adapter 發起 Open 的命令,等到設備確實回報成功,才會將行走的命令發給車子,並且繼續往後占有路權

待命區

Point 的類型可以選擇待命區,然後 config 檔設定閒置時自動回待命區,還可以設置待命區的優先順序,或是特定車輛停在特定待命區,更進階的還可以設定當優先權高的待命區空出時,重新把低優先權的車派過去停。

充電策略

每台車可以設定幾個門檻

  • 低 critical energy level 於門檻時,不接任務且前往充電
  • 低 good energy level 於門檻時,閒置時會去充電,但一有任務就會中斷去執行任務
  • 高 full (sufficiently) recharged energy level 於門檻時,停止充電,回到待命區

也可以設定每輛車優先或特定的充電站,但僅此而已

模擬車

TCS 有提供模擬車輛,可以接收任務模擬行走,但是真實度有限,行走部分並不會參考速度,而是一個點一個點跳躍,可以用來測試地圖的可運行性,另外有支援耗電跟充電功能,可以測試充電策略

整合性

針對外部系統整合部分,有提供Restful Web API,跟 Swagger 可以了解格式,但是沒有 event trigger 的方法,只能不斷輪詢,頂多有一個 long polling 的API可以接收所有類型的 event,使用過後還是直接輪詢特定資訊的 API 比較方便

對於車子的部分,需要自行開發Adapter,這部分可以參考模擬車的專案進行修改,將 TCS 這邊定義的 command 發給車子,並把車子回傳的狀態,對應到TCS的車輛狀態,像是電量、是否有貨、閒置還是執行中等等,就可以控制車子

對於設備的部分,也是有開放Adapter的開發彈性,看你是要整合電梯還是自動門等,有明確的協定就可以完成

使用心得

各功能評估

以下對針對各功能評估對於一般使用情境是否足夠,尚缺哪些必要機制

✅:滿足所需
🆖:堪用但不足
❌:不滿足需求

功能名稱評價評論
工具軟體🆖Model Editor 操作有大量待優化空間,尤其是地圖設定自定義屬性的操作步驟過多,且沒有檢查機制,很容易漏東漏西

Operations Desk 可派簡易的循環任務,但不支援派含有自定義屬性的任務,只能自己打 API,另外可以看每台車的路權,不過想要看交互關係(A車卡B車、B車卡C車)就只能自己分析了

Kernel Control Center 操作部分較少,算是堪用,但要看問題,內建的 watchdog 資訊還是太少

以上工具還有一個缺點,就是一定要執行程式,沒有提供網頁界面快速瀏覽,也沒有提供權限功能,一定程度上很難給非維護單位使用,需要自行開發好用的 UI

Kernel 有些機制不方便,會影響維運,像是上傳地圖會中斷並清空所有任務,重啓也不會記憶先前的任務,也沒有歷史資訊可供查詢,所以要上傳地圖或是升版的話,就必須取消所有任務,事後再重派
地圖✅基礎屬性已涵蓋大部份功能,且支援自定義屬性,有高度擴充性
任務管理✅有支援優先權概念和距離概念,也可以設定車子對應不同任務類型的優先權,算是非常完整足夠

針對一連串任務有 sequence 功能支援綁定同一台車,避免車輛在任務中間空檔接收其他任務
路徑搜尋與成本🆖有基礎的功能可用,但成本沒辦法考慮旋轉次數,會以純距離或是時間來看,有些人覺得很繞的路在TCS看來成本是一樣的,需要再自行擴充需要的功能

如果現場想要特別繞遠路,原生雖有支援,但設定方式非常繁瑣和不好維護
交通管理❌這部分是真的不夠用,除非你的場域非常足夠,可以每條路都兩線道,只有簡單十字路口要交管,就可以勉強應付

Block 的維護很看人的經驗,尤其是考慮避障後,像是要轉彎的地方或是十字路口,或是兩條路某一段太接近,爲了避免兩車太近觸發避障,就要廣設 Block,漏設就等着現場反映問題

另外雖支援大車不能走小路,但沒有動態切換功能,像是有些路空車可以走,載貨體積變大不能走,目前是做不到的

不支援動態路徑,看起來會覺得很不聰明,只能等待後通過
待命區✅蠻完整的,可設定優先權甚至重新停到高優先權,可以依照不同情境設定
充電策略🆖只有門檻機制過於簡單,多車共用充電站時,沒辦法交換充電,除非一直有任務進來,才會剛好離開空出充電站,但還是沒辦法避免多車同時沒電的問題
模擬車🆖僅能驗證地圖和交管機制,缺乏速度模擬跟對接等待時間,沒辦法用來算精確的時間
整合性✅WebAPI速度算快,幾十ms就會回傳,即使輪詢也足以負荷

如果想要 event 機制,就需要自行擴充,內部可以接收 event 再看要用什麼機制發出

期望與落差

雖然更新頻繁,但是看到新的功能時,不要太過興奮跟腦補,往往都只是提供最陽春的功能而已,舉個例子,某一版開始可以設定車子和點的長寬高,讓大車不能走小路,但僅此而已,占用路權時並不會用這個條件考慮,讓車身彼此不重疊,所以新功能務必試過後才能確定運作機制

適用客群

TCS 需要二次開發,沒辦法開箱即用,即使使用 VDA5050 協定的 Adapter,多多少少還是有一些邏輯需要客製修改,bug 也還是有,筆者本身也提報過一些 bug 給官方修改,所以適合原本已有做車子的廠商,或者是一開始就鎖定發展車隊系統的廠商

比起平地造山,還是可以省掉不少工,但就是要花點心思看懂TCS內部程式架構,TCS的架構算是設計的不錯,雖然功能較多切分的class非常多,抽象化得很徹底,但一旦釐清後,修改或替換的工相對少,日後也不難維護

English Version

Introduction to OpenTCS

OpenTCS is an open-source project maintained by Fraunhofer IML in Germany and released under the permissive MIT license.
The scope of OpenTCS covers the common functionality expected from a fleet management system. Due to its vendor-neutral design, it allows integration with vehicles using arbitrary protocols. However, this also means that OpenTCS does not provide protocol adapters tailored to specific vendors out of the box; users are expected to implement the required adapters themselves.

The only officially supported vehicle protocol is the open-source AGV control protocol VDA 5050. A dedicated GitHub repository is maintained for the VDA 5050 adapter.
For integrating equipment such as elevators or automatic doors, OpenTCS supports peripheral device adapters, which can be implemented to coordinate vehicle movement with external devices—for example, ensuring that a vehicle is granted path resources only after the device operation has completed successfully.

OpenTCS is implemented in Java and supports cross-platform usage. The official distribution includes three desktop applications and one console application:

  • Model Editor:
    A map editor used to define the layout, plant model, and vehicle information.
  • Operations Desk:
    Provides real-time visibility into vehicle states, transport orders, and path resources. It also allows manual dispatching and configuration of vehicle availability.
  • Kernel Control Center:
    Used for managing vehicles and peripheral devices, including adapter configuration, enabling or disabling components, and monitoring Kernel watchdog messages.
  • Kernel:
    The core fleet management service. It has no graphical user interface and outputs runtime information as text in a console. All three desktop applications must connect to the Kernel in order to operate.

Overall, the user interface of OpenTCS is highly engineering-oriented and functional but minimal. Even simple operations often require multiple steps, reflecting a straightforward design with little emphasis on user experience.
In terms of maintenance and development activity, OpenTCS is an active project, with releases approximately every one to two months, continuously addressing issues and introducing new features.

For convenience, OpenTCS will be referred to as TCS in the following sections.Introduction to OpenTCS

OpenTCS is an open-source project maintained by Fraunhofer IML in Germany and released under the permissive MIT license.
The scope of OpenTCS covers the common functionality expected from a fleet management system. Due to its vendor-neutral design, it allows integration with vehicles using arbitrary protocols. However, this also means that OpenTCS does not provide protocol adapters tailored to specific vendors out of the box; users are expected to implement the required adapters themselves.

The only officially supported vehicle protocol is the open-source AGV control protocol VDA 5050. A dedicated GitHub repository is maintained for the VDA 5050 adapter.
For integrating equipment such as elevators or automatic doors, OpenTCS supports peripheral device adapters, which can be implemented to coordinate vehicle movement with external devices—for example, ensuring that a vehicle is granted path resources only after the device operation has completed successfully.

OpenTCS is implemented in Java and supports cross-platform usage. The official distribution includes three desktop applications and one console application:

  • Model Editor:
    A map editor used to define the layout, plant model, and vehicle information.
  • Operations Desk:
    Provides real-time visibility into vehicle states, transport orders, and path resources. It also allows manual dispatching and configuration of vehicle availability.
  • Kernel Control Center:
    Used for managing vehicles and peripheral devices, including adapter configuration, enabling or disabling components, and monitoring Kernel watchdog messages.
  • Kernel:
    The core fleet management service. It has no graphical user interface and outputs runtime information as text in a console. All three desktop applications must connect to the Kernel in order to operate.

Overall, the user interface of OpenTCS is highly engineering-oriented and functional but minimal. Even simple operations often require multiple steps, reflecting a straightforward design with little emphasis on user experience.
In terms of maintenance and development activity, OpenTCS is an active project, with releases approximately every one to two months, continuously addressing issues and introducing new features.

For convenience, OpenTCS will be referred to as TCS in the following sections.

Key Features Overview

Map

The map model in TCS is topological, composed of points and paths. One notable characteristic is that bidirectional movement between two points is represented as two separate paths, rather than a single bidirectional path.

The following elements can be defined in the map:

  • Point: Defines a coordinate, orientation, and point type.
  • Path: Defines a route between two points, including the source and destination points, maximum allowed speed, and whether the path is locked.
  • Location Type: Defines the type of a location and the set of executable operations associated with it, such as docking or charging. It can also define executable device operations, such as opening or closing a door.
  • Location: Represents a destination associated with a specific point. Locations can be used as dispatch targets for transport orders or as trigger points for peripheral devices. Locations can be associated with paths—for example, reaching a specific path may trigger a door-opening operation, and only after the operation is completed will the command be sent to the vehicle.
  • Block: Represents a collection of resources. Resources refer to points, paths, and locations. Different block types enforce different constraints, such as allowing only one vehicle at a time or restricting access to vehicles entering from the same direction. If the constraints are not satisfied, vehicles will wait outside the block until access is granted.
  • Vehicle: Defines vehicle properties such as name, maximum speed, battery thresholds, physical dimensions, accepted transport order types (one-to-many), and integration level. The integration level determines whether the vehicle is allowed to allocate resources and receive transport orders.

To support integration with various vehicle types and custom logic, all of the above elements support custom properties, which is a key design feature of TCS.
Built-in OpenTCS properties use the tcs: prefix, while adapters—such as the VDA 5050 adapter—can read properties prefixed with vda5050:, preserving maximum flexibility for extensions and integrations.

Task Management

In TCS, a transport task is called a TransportOrder, which is the smallest unit submitted by higher-level systems for dispatching.
A TransportOrder may contain multiple destinations, where each destination acts as a sub-task referred to as a DriveOrder.

A TransportOrder can be configured with the following attributes:

  • Type:
    Corresponds to the task types supported by vehicles, allowing different vehicle types to accept only their respective task categories.
  • Deadline:
    Used as a reference for task prioritization and sorting.
  • Intended Vehicle:
    Specifies whether a particular vehicle is assigned to execute the order.
  • Dispensable:
    Primarily used for orders such as returning to a parking area or charging.
    These orders can be automatically canceled when a new order becomes available, reducing idle time.
  • Dependencies:
    Allows defining prerequisite orders. For example, an order may only be executed after both Order A and Order B are completed.
    TCS schedules and executes transport orders according to the defined dependency chain.
  • Custom Properties:
    Properties defined here can be read by the vehicle adapter to implement custom behaviors.
    For example, in the VDA 5050 adapter, actions to be executed at a destination can be defined via these properties.

Task prioritization and vehicle selection criteria are configurable via configuration files, where the priority of different factors can be adjusted.
This enables more advanced task management strategies, such as prioritizing return trips, or executing orders with approaching deadlines ahead of others.

Path Finding and Cost Calculation

TCS uses the built-in Dijkstra algorithm to find the lowest-cost path.
The cost function is configurable and can be composed of multiple factors, such as distance, number of points, travel time, group, and size.

The group factor can be defined as a property on both paths and vehicles, allowing vehicles within the same group to apply specific cost values. This makes it possible to prioritize certain routes for specific vehicles.

The size constraint is defined on points to restrict the maximum allowed vehicle size. If a vehicle’s defined size exceeds the allowed limit, the cost is treated as infinite, and the path becomes non-traversable.

Once a route is calculated, it remains unchanged. As long as path locks do not change, repeated calculations will yield the same result.

Traffic Management

TCS adopts a resource management model in which each resource can be held by only one vehicle at a time.
Under normal conditions, a vehicle holds the point it is currently occupying. When executing a transport order, it additionally reserves the next N segments of paths and points along its planned route.

When multiple routes intersect, resource allocation follows a first-come, first-served principle. Other vehicles must wait until the resources are released before proceeding.

In some cases, route conflicts may still occur. For example, when two vehicles plan routes along the same bidirectional path and each holds one point, a classic deadlock situation—similar to the “bridge crossing” problem—can arise.

To mitigate this, Blocks can be configured to restrict a specific area to a single vehicle at a time. However, blocks are a double-edged sword: excessive or overly coarse block definitions can significantly reduce throughput and even lead to traffic congestion.

For peripheral device integration, TCS allows issuing open or control commands to a specific device adapter when a vehicle reaches a designated path.
Only after the device reports successful completion will the movement command be sent to the vehicle, and the vehicle will continue reserving subsequent route resources.

Parking Areas

Points can be configured with a parking position type.
Through configuration files, vehicles can be set to automatically return to a parking position when they become idle.

Parking positions can be assigned priorities, or specific vehicles can be restricted to park at designated parking positions.
More advanced configurations allow vehicles parked at lower-priority positions to be reassigned and moved when a higher-priority parking position becomes available.

Charging Strategy

Each vehicle can be configured with multiple battery thresholds:

  • Critical energy level: When the battery level falls below this threshold, the vehicle will stop accepting transport orders and proceed directly to charging.
  • Good energy level: When the battery level is below this threshold, the vehicle will go charging while idle, but will interrupt charging and execute a transport order as soon as a new order is assigned.
  • Full (sufficiently recharged) energy level: When the battery level reaches this threshold, charging stops and the vehicle returns to a parking position.

Vehicles can also be configured to prefer or restrict themselves to specific charging stations.
Beyond these options, the built-in charging strategy remains relatively simple.

Simulated Vehicles

TCS provides simulated vehicles that can receive orders and simulate movement. However, realism is limited: movement is discrete, point by point, without considering actual speed. It is mainly useful for testing map feasibility.

The simulation also supports battery consumption and charging, allowing you to test charging strategies in a controlled environment.

Integration Capabilities

For external system integration, OpenTCS provides a RESTful Web API, along with Swagger documentation for understanding request and response formats.
However, the API does not provide true event-based callbacks. Consumers must rely on continuous polling, with the only alternative being a long-polling API that delivers all event types in a single stream.

In practice, after experimenting with the long-polling approach, polling specific APIs for targeted information often turns out to be more practical and easier to manage.

For vehicle integration, a custom Communication Adapter must be implemented.
The built-in simulated vehicle adapter serves as a good reference and can be extended or modified. The adapter is responsible for translating OpenTCS commands into vehicle-specific control commands, and mapping vehicle feedback back into OpenTCS vehicle states, such as battery level, load status, idle or executing state, etc.
Once this mapping is in place, the vehicle can be fully controlled by TCS.

For peripheral devices, OpenTCS also provides flexible adapter extension points.
Whether integrating elevators, automatic doors, or other equipment, as long as a clear communication protocol exists, the integration can be implemented via a custom adapter.

Practical Evaluation

Feature Assessment

The following evaluates whether each major feature is sufficient for typical real-world use cases, and highlights areas where additional mechanisms are required:

  • ✅ Meets requirements
  • 🆖 Usable but insufficient
  • ❌ Does not meet requirements
評價評論
Tools & Software🆖Model Editor: The interface has significant room for optimization. Editing custom properties in maps requires too many steps and lacks validation, making it easy to miss configurations.

Operations Desk: Supports simple cyclical task dispatching, but does not support tasks with custom properties—these must be submitted via API. It allows viewing each vehicle’s reserved resources, but analyzing interactions (e.g., Vehicle A blocking B, Vehicle B blocking C) must be done manually.

Kernel Control Center: Limited functionality; usable but minimal. The built-in watchdog provides insufficient information for debugging.

General tools limitation: All tools require running the programs; there is no web-based interface for quick overview, nor role-based access control. This makes it difficult for non-maintenance personnel to use without developing a custom UI.

Kernel: Some mechanisms complicate operations and maintenance. Uploading a new map interrupts and clears all existing tasks, and restarting does not retain prior tasks. No historical data is available. Updating maps or upgrading versions requires canceling all tasks and redispatching afterward.
Map✅Basic properties cover most needs and support custom attributes, offering high extensibility.
Task Management✅Supports priority and distance concepts. Vehicle order type mapping for prioritization is available, making it very complete.

Supports sequencing to bind multiple orders to the same vehicle, preventing idle gaps during task execution.
Path Finding & Cost🆖Basic functionality is available, but cost calculation does not account for rotation counts; it considers only distance or time. Some routes that feel circuitous may appear equal in cost in TCS.

Additional functionality may require custom extension.
Native support exists for intentionally choosing longer routes, but configuration is cumbersome and difficult to maintain.
Traffic Management❌Limited for complex environments. Only works reasonably in spacious areas where paths can accommodate two lanes, and only simple intersections require traffic management.

Block maintenance is highly experience-dependent. Considering collision avoidance, corners, intersections, or closely spaced paths may require extensive Block setup; missing blocks can lead to on-site issues.

Supports restricting large vehicles from small paths, but no dynamic switching. For example, empty vehicles can use certain paths, but vehicles carrying cargo exceeding size limits cannot dynamically switch—this is not supported.

Dynamic rerouting is unsupported; vehicles must wait for resource release, which may appear unintelligent in practice.
Parking Areas✅Quite complete. Supports priorities and reassigning vehicles to higher-priority positions. Configurable for various scenarios.
Charging Strategy🆖Only uses a simple threshold mechanism. When multiple vehicles share a charging station, there is no active handover. Vehicles may free the station only if new tasks arrive, but cannot avoid situations where multiple vehicles simultaneously run out of battery.
Simulated Vehicles🆖Useful for validating maps and traffic management logic. Lacks speed simulation and docking wait time, so it cannot provide accurate timing estimates.
Integration✅Web API is fast (tens of milliseconds), and even polling can handle the load.

Event-driven mechanisms require custom development.
TCS can internally receive events, but you must implement your own mechanism to trigger them externally.

Expectations vs Reality

Although OpenTCS is updated frequently, new features should not be overhyped or assumed to be fully mature. Often, they provide only the most basic functionality.

For example, a recent version added the ability to define vehicle and point dimensions to prevent large vehicles from using small paths. However, this feature does not affect resource occupation calculations—vehicles may still overlap in space. Therefore, new features must always be tested in practice to understand their actual behavior and limitations.

Target Users

TCS requires custom development and cannot be used straight out of the box. Even when using a VDA5050-compliant adapter, some logic still needs to be customized, and bugs are still present—the author has personally reported several to the official maintainers.

TCS is most suitable for companies that already operate vehicles or those that are committed to developing a fleet management system from the start.

Compared to building everything from scratch, TCS can save considerable effort. However, it requires time and focus to understand the internal architecture. The system is well-designed: although there are many classes and extensive abstraction, once the architecture is understood, modifications or replacements require minimal effort, and maintenance is straightforward in the long term.

Reference

[Web]自帶解答的迷宮產生器

作者 木澤
2021年12月5日 15:13

記得小時候,我很喜歡玩迷宮遊戲,老爸買給我的迷宮解謎本,我可以主動寫完一整本,時不時還拿出來回味。前一陣子,剛好看到這篇文章,講述了一種比較土炮的迷宮產生方法,還做成一個小遊戲,覺得挺有趣的,就想研究一下迷宮是如何生成的,恰巧之前又接觸到路徑搜尋算法,所幸就結合在一起,做了一個產生迷宮,也能搜尋路徑的迷宮產生器,按我前往展示網頁

研究後發現方法不少種,效果也各不相同,於是挑一個神奇的作法來實作,Eller’s Algorithm,最後的參考會放許多相關的說明以及我有參考的程式碼,效果如上面的動畫。首次進入時,想要給使用者一個驚艷的感覺,所以會依據螢幕大小產生滿版的迷宮,也可以調整上方的寬高來重新產生迷宮。另外,點選路徑按鈕,可先後點選啟始結束點,程式會使用這篇文章的方法 A*,將路徑找出來,並以紅線畫出,原始碼可參考Github MazeCreator

關於Eller’s Algorithm,筆者在這邊說明一下

建立首列,每格為獨立集合
隨機決定每一格的右牆和下牆是否開放,相連的格子屬於同一個集合
複製上一層,若上一層無下牆,下層集合繼承上一層
  1. 決定下一層每一格的右牆
  2. 若該格與右邊相同集合,則必須有牆
  3. 若是最後一列,且上一個條件不符合,則不能有右牆
  4. 其餘隨機決定,將未定義的集合分配
沒有右牆的格子,就將集合合併,合併包含該所有格子
決定下一層每一格的下牆,如果該格的集合數只有一個格子,則不能有下牆,若是最後一列,則必須有下牆,其餘隨機決定
建立最後一列,重複步驟3、4、5、6

這個方法很神奇,一列一列照規則建下來,竟然就完成了,和其他類型的迷宮演算法相比,簡單很多,但也是有缺點,若迷宮太小,最後一列為了讓各集合相通,很容易就會出現沒有牆壁的直通路,若建立大一點的迷宮,各集合在之前就已相通,這個缺點就比較不明顯。

感謝觀看,希望有勾起你小時候玩迷宮的快樂回憶~

參考

[經驗]無人搬運車(AGV)廠商合作模式分享

作者 木澤
2021年11月22日 22:17

筆者曾因工作需求,接觸過一些無人搬運車(AGV)廠商,商談合作細節,今天就來分享我遇過或周遭朋友遇過的產品特性和廠商合作模式,首先介紹一下使用的背景,公司已經有自己開發的完整MES系統,因此所有對內的整合都可以自己處理,不考慮外包給廠商,一方面考慮私有系統機密性,另一方面都已經有養人了,對自己系統也熟,做起來一定比廠商還要快。

接著介紹產品特性和合作模式,概略可分為這幾種方式,會一一說明,以下為筆者自身和周邊遇到的經驗,不代表整個業界~

  1. 成熟穩定的產品,使用說明詳細,卻不能客製功能
  2. 有基礎功能,想要更多,就專案合作
  3. 功能較陽春,願意配合修改
  4. 簡單易用,但無法擴充

成熟穩定的產品,使用說明詳細,卻不能客製功能

這種類型通常都是國際大廠,因為產品已發展相當成熟,容易上手使用,設定上相當開放,即使是專業的設定,也開放給客戶自行調整,不管你想的到或想不到的功能,都一併提供,並在說明手冊上詳細說明,提供教育訓練和技術諮詢。用日常例子的話,就像買汽車,交車時示範一次給你看,後續你自己回家研究,真的有問題可以打去車行問一下,但不可能手把手完全帶你。

以筆者的經驗,使用的是SLAM導航,除了第一次原廠人員來示範一些常用操作以外,後續就讓客戶自己處理了,從掃描地圖、設站點和到站行為、設定地圖規則(行走速度、規定路線、單行道等),甚至是安全距離這種專業參數都可以讓客戶調整,設定多達1~2百項。當然遇到一些不瞭解或是不知道怎麼解的問題,原廠除了提供諮詢,也願意前來幫忙,但是不可能隨叫隨到,都是要提前預約時間。

優點:

  1. 軟體設計得很完整成熟,客戶掌握度高,不需要凡事麻煩原廠處理
  2. 客戶最了解自己的需求,掌握車子功能後,可以自行調整最合適的流程
  3. 適用大多數使用場景,無須等待開發時程,到貨即可使用

缺點:

  1. 需要花時間熟悉,並且細部調整都需要自己來,建地圖容易,但如果場地有一些限制,需要調到順暢使用就很花時間,這也是廠商打的如意算盤吧,把這個功給客戶做,自己可以專心賣產品,服務相對少很多
  2. 功能雖多,但也就是這樣了,有些特定作法只能用原廠提供的方式迂迴實現,如果需要客製化功能,就要看你的單若夠不夠大量,基本上都很難說服原廠幫您加入

有基礎功能,想要更多,就專案合作

這一類跟上一個案例對比,就是兩個極端,從頭到尾都需要原廠的人來介入,提供使用的軟體非常基礎,甚至有工程版的錯覺,就是那種工程師自己會用,介面完全沒有針對外觀和UX優化過的,身為工程師的我,一眼就看的出來了XD。並且詢問一些相對基礎的功能時,軟體也沒有提供,都只能透過原廠工程師設定,隨便舉幾個例子:

  1. 我想要可以自己設定車子上的Wifi,但沒有提供任何有線連入方式修改 (當然工程師自己一定可以,只是沒有另外寫程式把這個功能開放出來)
  2. 我想要設定車子行走時的音樂,同樣沒有開放這個功能
  3. 我想要調整車子的速度,只能依照原廠設置的速度機制,寬敞時快,狹窄時慢,但是在我方場地實在是過慢…

針對以上功能,原廠都說可以專案合作,但會收相對費用,就看客戶能不能接受這種做法了,很不巧筆者因為有使用過成熟的產品,並且是以系統整合的角度去看,所以覺得由奢入儉難XD,不過還是要平衡一下,至少有客製這個選項,對於有特殊流程,或者公司沒有養工程師,想要連系統整合都外包的客戶,就非常適合。

優點:

  1. 對於無資訊背景之客戶,可以全部包到好
  2. 有客製化選項,你有特殊需求可以專案處理

缺點:

  1. 採購後還需要開發時程,不適合有時程壓力的客戶
  2. 對於有工程師的客戶,無法完全掌握,凡事都需易透過原廠

功能較陽春,願意配合修改

這一類跟上一類有點類似,但是產品又更陽春一些,像是只有藍芽而沒有Wifi功能,所以不容易整合內部系統取得車輛運作資訊,或是沒有車隊系統,調度派車都是透過手動觸發,比較適合無須整合系統的客戶使用。

話雖如此,但廠商配合度高,如果你可以自己處理藍芽和Wifi之間的橋接,廠商是願意提供協定細節的,不會因為牽涉太底層就視為機密。可能因為產品發展時間還不夠久,或是他們的客層就是鎖定簡單用戶,也積極想要了解進階客戶需求,慢慢發展上去。

不過如果真的要整合起來,客戶這邊工會很重,有一種我做這個系統,只是為了完善廠商產品的感覺,說到底,具體還是要看需求,喜歡自己來的老闆可能會覺得賺到。

簡單易用,但無法擴充

這類型就沒什麼太多可以說的,主要都是以簡單應用為主,不具備任何整合功能,通常都是單機使用,價錢也相當親民,適合傳產這類的客戶使用。

以上就是筆者與接觸過的AGV廠商經驗分享,多少會帶有一些主觀想法,不能代表全部,廠商百百間,相信只要認真找,一定會有適合的廠商。

[Web]A*路徑搜尋使用自訂義地圖格式

作者 木澤
2021年8月23日 23:10

最近剛好有需求研究A*路徑搜尋,查找了許多範例,發現地圖格式幾乎都是二維陣列,和我們定義的地圖格式不同,考量到地圖點位數量直接影響運算效率,我一直在思考如何使用原本的地圖格式進行運算,有空時就不自覺在腦海中想著可能的方式,終於靈光乍現,讓我想到最終實作出一個Demo網頁,點我前往

本篇會直接說明修改方式,需要對於A*有基礎了解,比較能看懂後續的說明,可以到最下方的參考清單看看。先介紹一下,網上常見範例都是這種二維陣列的網格,再標記該點是否可通行,如下圖的(1, 1), (1, 2)是不能通行的,其他則是可通行。

而我的地圖格式則是定義好存在的點,再用線連起來代表這兩點可通行,每個點是有xy座標的,也允許非網格式排法,像是不等距,不規則形,資料結構也有所不同,如下所示

class Point {
    Id = -1;
    X = -1;
    Y = -1;

    constructor(id, x, y) {
        this.Id = id;
        this.X = x;
        this.Y = y;
    }
}
//定義點位
let points = [
	new Point(0, 0, 0),
	new Point(1, 0, 100),
	new Point(2, 0, 200),
	new Point(3, 0, 300),
	new Point(4, 100, 300),
	new Point(5, 200, 300),
	new Point(6, 200, 200),
	new Point(7, 200, 100),
	new Point(8, 300, 300),
	new Point(9, 300, 200),
	new Point(10, 300, 100),
	new Point(11, 300, 0),
	new Point(12, 200, 0),
	new Point(13, 100, 0),
	new Point(14, 100, 100)
];

//定義兩點相連的線
let lines = [
	[0, 1],
	[1, 2],
	[2, 3],
	[3, 4],
	[4, 5],
	[5, 6],
	[5, 8],
	[6, 7],
	[6, 9],
	[8, 9],
	[9, 10],
	[10, 11],
	[11, 12],
	[12, 13]
];

關鍵的地方在於,只要你有方式可以找到鄰居的點,是不是二維陣列也沒那麼重要了,核心算法不變,都可以依據你的格式來演算,真該早點發現這點的,被範例洗腦了。考量到線未來可以乘載更多的屬性影響路徑成本,我希望同樣一個點,但來自不同方向,也可以有不同的成本區別,為此我增加了路徑屬性,紀錄該點的移動歷程。

//紀錄該點成本
class PointCost extends Point {
    Cost = -1;
    Path = [];

    /**
    * @var Point here
    * @var Point start
    * @var Point end
    * @var PointCost last
    */
    constructor(here, start, end, last = null) {
        super(here.Id, here.X, here.Y);
        this.Cost = PointCost.CalCost(here, start, end);
        if (last !== null) {
            //若有上一個點,則繼承其路徑,並加入自身點為最後一點
            this.Path = last.Path.concat();
        }
        this.Path.push(here.Id);
    }

    /**
    * 計算該點路徑的成本,和起訖點xy座標的差值,因為我的情境不走斜線,
    * 所以這樣就行,未來有某些特殊需求要優先的,都是在這邊增加對應的判斷
    * @var Point here current point
    * @var Point start start point
    * @var Point end end point
    */
    static CalCost(here, start, end) {
        return (Math.abs(here.X - start.X) + Math.abs(here.Y - start.Y) + 
                   Math.abs(here.X - end.X) + Math.abs(here.Y - end.Y));
    }
}
/**
    * 計算該點路徑的成本,和起訖點xy座標的差值,因為我的情境不走斜線,
    * 所以這樣就行,另外會判斷是否避免轉彎,若轉彎會增加額外Cost
    * 未來有某些特殊需求的話,都需要在Cost這邊做文章,影響下一個點的選擇
    * @var Point here current point
    * @var Point start start point
    * @var Point end end point
    */
CalCost(here, start, end) {
    //distance between start and end points
    let cost = (Math.abs(here.X - start.X) + Math.abs(here.Y - start.Y) + Math.abs(here.X - end.X) + Math.abs(here.Y - end.Y));

    //calculate turn need at least 3 points
    if (this.Config?.AvoidRotation && here.Path.length >= 3) {
        let turn_cost = 0;
        for (let i = 0; i < here.Path.length - 2; i++) {
            let x_offset = Math.abs(this.Points[here.Path[i]].X - this.Points[here.Path[i + 1]].X);
            let y_offset = Math.abs(this.Points[here.Path[i]].Y - this.Points[here.Path[i + 1]].Y);
            x_offset += Math.abs(this.Points[here.Path[i + 1]].X - this.Points[here.Path[i + 2]].X);
            y_offset += Math.abs(this.Points[here.Path[i + 1]].Y - this.Points[here.Path[i + 2]].Y);

            if (x_offset > 0 && y_offset > 0) {
                turn_cost += this._TURN_COST_VALUE;
            }
        }
        cost += turn_cost;
    }
    return cost;
}

尋找相鄰點的方式,簡單粗暴,過濾包含該點的值,攤平後,再去掉當前點

/** 
* @var int id
*/
FindNearPoint(id) {
    let near_id =
        this.Lines.filter(x => x.includes(id)) //x=2, [1, 2], [2,3]
            .flat() //[1,2,2,3]
            .filter(x => x != id); //[1,3]
    return near_id;
}

主要的搜尋方式,由於已經有路徑屬性,因此可以省略CloseList,將起始點計算成本加入OpenList,接著不斷從中取鄰近點,需判斷是否為回頭路,避免無窮迴圈

/**
* @var Point start start point
* @var Point end end point
*/
GetPath(start, end) {
    this.OpenList.push(new PointCost(start, start, end));
    let min_idx = 0;
    let short_path = null;
    while (this.OpenList.length > 0) {
        //找到成本最低的路線
        min_idx = 0;
        let next = this.OpenList.length === 1 ? this.OpenList[0] : this.OpenList.reduce((prev, curr, idx) => {
            if (prev.Cost < curr.Cost) {
                return prev;
            } else {
                min_idx = idx;
                return curr;
            }
        });
        let nearPointIds = this.FindNearPoint(next.Id);
        this.OpenList.splice(min_idx, 1);

        for (let i = 0; i < nearPointIds.length; i++) {
        //若該點路徑已包含鄰近點,代表回頭路,跳過不處理
        if (next.Path.includes(nearPointIds[i])) {
                continue;
            }
            let point_cost = new PointCost(this.Points[nearPointIds[i]], start, end, next);
            //若鄰近點為終點,找到路徑,清空OpenList,跳出迴圈
            if (nearPointIds[i] === end.Id) {
                short_path = point_cost.Path;
                this.OpenList.length = 0;
                break;
            }
            //將鄰近點放入OpenList,繼續探索新路徑
            this.OpenList.push(point_cost);
        }
    }
    return short_path;
}

寫完後參考Wiki上的A*才發現,原來英文版有提示這種作法,但中文版沒有,看來原文還是比較詳細~

最後,這次有發現幾個要注意的點和學習到的用法,像是

  • 陣列複製,要注意不能複製物件,要複製值,避免影響運算結果
  • 陣列刪除元素要使用slice避免長度不變
  • 如何用reduce一行從陣列找出屬性值最小的物件

參考

[C#] 歷遍JSON各層屬性

作者 木澤
2021年8月2日 23:22

這個題目是筆者在StackckOverflow上回答問題,後續被其他使用者用評論追問的題目,不過可惜研究出來後,因為解法較複雜,請對方再開一個問題,結果就沒下文了~可能對方已經自己找出答案了吧,為了不浪費,就拿來當這次的題材吧XD

首先介紹一下原題目 How to get list of values by key’s name across Json using C# 題目需求算明確,就是如何一次取得每一層特定屬性名稱的值,JSON範例如下:

{
   "objectId": "123",
   "properties": {
     "objectId": "456"
   },
   "variables": [
     {
       "objectId": "789"
     },
     {
       "objectId": "012"
     }
  ]  
}

需要取得所有objectId的值,並存在一個陣列中,期望輸出是

["123", "456", "789", "012"]

當初為了解這題也是去Google了一下,後來發現到關鍵方法DescendantsAndSelf可以將本身這一層以及底下每一層元素,算是打掉層數,扁平化到同一層的概念,接著再篩選一下屬性名稱取得值就可以了

var root = (JContainer)JToken.Parse(json);
var list = root.DescendantsAndSelf().OfType<JProperty>()
  .Where(p => p.Name == "objectId")
  .Select(p => p.Value.Value<string>());
Console.WriteLine(string.Join(",", list.ToArray()));

這邊算是開胃菜,接著有網友詢問如果要看兩個屬性,且需要成對,objectId屬性要對同一層的objectvalue屬性,若缺其一就忽略或者使用null,期望輸出一個二維陣列,但我覺得做成Dictionary應該會比較適合

{
  "objectId": "123",
  "properties": {
    "objectId": "456",
    "objectvalue": "aaa"
  },
  "variables": [
    {
      "objectId": "789",
      "objectvalue": "bbb"
    },
    {
      "objectId": "012",
      "objectvalue": "ccc"
    }
  ]
}

看起來沒辦法像之前用遍平化的方式處理,因為需要在不同層各判斷是否有成對的屬性,因此決定將每一層歷遍,同時再判斷屬性是否成對,同值將值寫入Dictionary中,解法如下:

private static Dictionary<string, string> result = new Dictionary<string, string>();

private static void Main(string[] args)
{
    var json = "{\"objectId\":\"123\",\"properties\":{\"objectId\":\"456\",\"objectvalue\":\"aaa\"},\"variables\":[{\"objectId\":\"789\",\"objectvalue\":\"bbb\"},{\"objectId\":\"012\",\"objectvalue\":\"ccc\"}]}";
    var root = JObject.Parse(json);
  
    //取得這一層所有的屬性
    var propertyList = root.Properties();
    handleIdValue(propertyList);

    Console.WriteLine("Keys: " + string.Join(",", result.Keys)); //456, 789, 012
    Console.WriteLine("Values: " + string.Join(",", result.Values)); //aaa, bbb, ccc
}


private static void handleIdValue(IEnumerable<JProperty> propertyList)
{
    //依據每一層判斷是否有成對的屬性,再依照屬性型別深入,確定成對後寫入result中
    var haveId = false;
    var id = "";
    foreach (var property in propertyList)
    {
        //若值是字串,判斷成對名稱
        if (property.Value.Type == JTokenType.String)
        {
            if (property.Name == "objectId")
            {
                id = property.Value.Value<string>();
                haveId = true;
            }
            if (haveId && property.Name == "objectvalue")
            {
                result.Add(id, property.Value.Value<string>());
            }
        }
        //若是陣列,依序將每一個物件取得屬性,遞迴處理
        else if (property.Value.Type == JTokenType.Array)
        {
            var array = property.Value.Value<JArray>();
            foreach (var p in array)
            {
                if (p.Type == JTokenType.Object)
                {
                    handleIdValue(p.Value<JObject>().Properties());
                }
            }
        }
        //若是物件,取得屬性,遞迴處理
        else if (property.Value.Type == JTokenType.Object)
        {
            handleIdValue(property.Value.Value<JObject>().Properties());
        }
    }
}

依照這個解法可以達到題目的要求,不過陣列那邊,如果有多維陣列的話,就需要將處理陣列的部分也分離一個functiont出來,同樣用遞迴處理。藉由這次的解題,也讓我對JSON.NET有更深入的認識,平常頂多就是序列化跟反序列化而已,很少需要客製的分析JSON字串

每次在解stackoverflow的題目時,都還蠻緊張的,因為新題目大家都在搶,稍微慢一點,可能就已經選出答案了,但後來比較釋懷了,好的答案比較重要,也有長尾效應,如果真的有幫助,最後還是會獲得不少認同分數的~

❌
❌