Skip to main content

SEV case study

1. Mô tả bài toán

Sơ đồ mô phỏng nhà máy SEV như hình bên dưới

sev-map.png

AMR sẽ xuất phát tại điểm STANDBY 1, chờ nhận nhiệm vụ từ server. Với mỗi nhiệm vụ, server sẽ gửi danh sách dữ liệu bao gồm bufferId: chỉ định danh của buffer để lấy hàng (UNLOAD) và lineId chỉ định danh của Line trả hàng (LOAD).

Vị trí các LINE trả hàng được ký hiệu là : Lxx_1, Lxx_2 trên bản đồ. Trong đó xx là id của line, hậu tố 1, 2 tương ứng với slot 1, 2 của line

Vị trí các buffer lấy hàng được ký hiệu là: Bxx_1, Bxx_2 trên bản đồ. Trong đó xx là id của buffer, hậu tố 1, 2 tương ứng với slot 1, 2 của buffer

Sau khi nhận nhiệm vụ, robot đi 1 vòng. Xuất phát từ STANDBY đi tới khu vực lấy hàng, bắt đầu bằng điểm B_IN1. Từ B_IN1, robot đi chậm để tiếp cận Bxx_1. Robot giảm tốc độ để tiếp cận các băng tải của Bxx_1 cho chính xác. Sau khi lấy hàng xong, robot tiếp tục đi tới điểm lấy hàng (Bxx_2) tiếp theo. Khi đã lấy hàng xong, robot sẽ đi chậm tới điểm B_Out1 để ra khỏi khu vực Buffer.

Sau khi ra khỏi khu vực Buffer, robot sẽ kiểm tra mức pin hiện tại, nếu pin còn thấp thì sẽ di chuyển vào khu vực thay pin.

Sau đó robot đi tới điểm LINE_IN, để vào khu vực trả hàng.

Khu vực trả hàng chia làm 2 khu, các line từ 1 tới 4 sẽ nằm ở 1 khu như trên bản đồ, các line từ 5 đến 8 sẽ nằm ở 1 khu khác.

Tại khu vực các line từ 1 tới 4. Các vị trí ghép băng chuyển để trả hàng (DLxx_1, DLxx_2) nằm trên đường đi chính. Robot sẽ từ LINE_IN rẽ phải tại điểm L04 và đi vào 1 trong những line này. Sau khi xong nhiệm vụ, robot sẽ đi thẳng ra điểm L01_Out và về Standby

Tại khu vực các line từ 5 đến 8. Vị trí ghép băng chuyền để trả hàng (DLxx_1, DLxx_2) sẽ nằm sau bên trong, nên để ghép vào các băng chuyền, robot phải di chuyển tới các điểm đầu line (Lxx_1, Lxx_2) và rẽ phải để đi chậm ghép vào các bằng chuyền (DLxx_1, DLxx_2). Sau khi xong nhiệm vụ, robot sẽ di chuyển ra điểm LO_016 để về STANDBY

2. Soạn TVC-NAV Mission

a. Phần buffer lấy hàng

Quy trinh di chuyển của robot tại buffer:

  1. Từ vị trí STANDBY, robot di chuyển tới điểm đầu hàng buffer B_IN
  2. Từ B_IN robot di chuyển chậm để tiếp cận chính xác vào vị trí Buffer Bxx_1. Sau đó lấy hàng từ buffer.
  3. Từ Bxx_1, robot di chuyển tới Bxx_2 và lấy hàng. Sau đó di chuyển tới điểm B_Out.

Phần mission tương ứng với quy trình này như sau.

  1. Thêm runtime-variable tương ứng với markerId đánh dấu vị trí của Bxx_1 và Bxx_2. Biến bufferMarkerId dùng để cập nhật marker id tương ứng với Bxx_1, biến bufferMarkerId2 dùng để cập nhật marker id tương ứng với Bxx_2

  2. Các action và thứ tự action trong quy trình này: screenshot_from_2024-07-22_11-54-57.png

  3. Robot bắt đầu thực hiện nhiệm vụ từ vị trí STANDBY, nên action đầu tiên, Robot di chuyển từ STANDBY tới B_IN: Action Follow Path Go to B_In

  4. Sau đó robot di chuyển từ B_IN tới buffer đầu tiền Bxx_1: action Follow Patht STLBuffer1 tham số cho phần này như sau: screenshot_from_2024-07-22_11-58-20.png

    • Trong phần tham số webhook host, là URL gọi tới API thực hiện nhiệm vụ khi robot tới điểm Bxx_1. Ngay sau action Follow Path là action pause để cho robot dừng chờ cho tới khi thực hiện lấy hàng xong. Sau khi thực hiện lấy hàng xong, robot sẽ gọi tới API để resume (unpaused)
  5. Tương tự như trên, robot thực hiên lấy hàng ở Bxx_2, bao gôm action Follow Path STLBuffer2Pause tham số cho phần này: screenshot_from_2024-07-22_13-26-19.png

  6. Sau khi lấy hàng xong ở Bxx_2, robot di chuyển ra điểm B_Out để ra khỏi khu vực Buffer: Action STL B_Out screenshot_from_2024-07-22_13-38-30.png

b. Phần kiểm tra mức pin và thay pin

Quy trình cho phần này:

  • Sau ra khỏi khu vực Buffer, robot di chuyển từ vị trí B_Out tới vị trí OBS_IN. Tại vị trí này robot gọi API để kiểm tra mức pin.
  • Nếu mức pin thấp (vd. dưới 30%) robot sẽ di chuyển vào vị trí BAT để thay pin, sau đó tiếp tục nhiệm vụ đi tới vị trí LINE_IN

Chi tiết soạn các action cho phần này: screenshot_from_2024-07-22_14-35-01.png

  1. Đầu tiên, ta thêm vào 1 runtime-variables để cập nhật mức pin: batteryLevel

  2. Thêm action Follow Path Go to Collision, trong action này, trong phần Webhook Callback, ta gọi tới API để cập nhật mức pin vào biến batteryLevel, Đi liền sau action Follow Path là 1 action PAUSE để chờ API cập nhật biến: screenshot_from_2024-07-22_14-42-17.png

  3. Action tiếp theo là Action điều kiện, nếu mức pin thấp, sẽ thực hiện các action để đi thay pin, nếu không robot sẽ đi thẳng tới vị trí LINE_IN. tại phần cài đặt điều kiện, kiếm tra biến batteryLevel: screenshot_from_2024-07-22_14-45-50.png

    • Trong phần điều kiện đúng (mức pin thấp): Ta thêm vào action Follow Path Go to change Battery, tại đây action sẽ điều khiển robot đi tới vị trí BAT để thay pin. Ngay sau đó là action PAUSE để chờ robot thay pin. Ngay sau action thay pin là action Follow Path Go to Line_IN.
  • Trong phần điều kiện sai (mức pin đủ dùng): Thêm action Follow Path tới vị trí LINE_IN

c. Phần đi vào line để trả hàng.

Quy trình của phần này:

  • Như đã thấy trên bản đồ, phần các line nhận hàng được chia thành 2 khu vực được bố trí khác nhau:
    • Mỗi line sẽ có 2 băng chuyền nhận hàng (trừ line số 1), vị trí ghép các bằng chuyền được ký hiệu là Lxx_1Lxx_2.
    • Từ line số 1 tới số 4: các băng chuyền trả hàng của các line được bố trí xếp thành hàng liên tiếp, nên robot trả hàng tại các line này sẽ trả hàng ngay trên đường đi chính.
    • Từ line số 5 tới line số 8: vị trí các băng chuyền không được bố trí trên đường đi chính mà phải rẽ nhánh đi vào. Sau khi robot trả hàng xong sẽ đi lùi ra và trở lại đường chính.
    • Sau khi robot trả hàng xong, robot sẽ đi tiếp trên đường đi chính tới vị trí Line Out, cụ thể là vị trí LO_01 với các line từ 1 tới 4, và vị trí LO_0162 với các line từ 5 tới 8. Riêng tại vị trí bằng chuyền L08_2, sau khi trả hàng xong robot không đi lùi lại mà đi tiến thẳng tới vị trí LO_016.

Chi tiết soạn mission cho quy trình này:

  1. Quy trình vào line trả hàng sử dụng các runtime-variables sau đây:

    • lineId: id của line trả hàng
    • lineMarkerId: marker id đánh dấu vị trí đầu line Lxx_1
    • lineMarkerId2: marker id đánh dấu vị trí đầu line Lxx_2
    • DLMarkerId: marker đánh dấu vị trí ghép băng chuyền số 1
    • DLMarkerId2: marker đánh dấu vị trí ghép bằng chuyền số 2
  2. Các actions cho quy trình này: screenshot_from_2024-07-22_16-20-09.png

  3. Thêm action điều kiện, kiểm tra xem line thuộc nhóm <=4 hay >4: chi tiết tham số:screenshot_from_2024-07-22_16-32-15.png

  4. Nếu line thuộc nhóm từ 1 tới 4, robot sẽ thực hiên các action sau đấy:

    • 4.1. Action Follow Path từ marker LINE_IN tới marker L04.

    • 4.2. Action Follow Path đi từ marker L04 tới điểm đầu line Lxx_1: screenshot_from_2024-07-22_16-34-54.png

    • 4.3 Action điều kiện kiểm tra line 1 hay không:

      • 4.3.1 Nếu là line 1, thì sẽ gọi tới action Follow Path Go to DL1, ở mode đi chính xac, sau khi tới điểm đích, sẽ gọi callback trả hàng ở cả 2 băng chuyền. Đồng thời, trong phần callback của action sẽ gọi tới API để trả hàng ở cả 2 băng chuyền. screenshot_from_2024-07-22_16-37-37.png

      • 4.3.2 Sau action trên là action Follow Path, đi tới điểm Line out L01_Out

      • 4.3.3 Nếu không phải line 1 (line 2-4), thì robot sẽ trả hàng tại 2 bằng chuyền. Danh sách action sẽ bao gồm

      • 4.3.4 Action Follow Path Go to DL1, điều khiển robot đi châm tiếp cận chính xác vị trí ghép bằng chuyền số 1 DLxx_1. Xong action này, sẽ gọi tới callback để trả hàng và tiếp tục nhiệm vụ sau khi xong (unpause).

      • 4.3.5 Action Follow Path Go to DL2, điều khiển robot đi chậm tiếp cận chính xác vị trí ghép băng chuyền số 2 DLxx_2. Xong action này, sẽ gọi tới callback đê trả hàng vào băng chuyền số 2 và tiếp tục mission.

      • 4.3.6 Action Follow Path Go to Line Out. Robot di chuyển tới điểm ngoài khu vực trả hàng L01_Out.

      • Sau khi robot tới điểm L01_Out, thực hiện action Follow Path Go to Standby.

  5. Nếu line thuộc nhóm từ 5 tới 8, robot sẽ thực hiện các action sau đây: screenshot_from_2024-07-23_09-39-24.png _ 5.1 Đi tới điểm đầu vào Lxx_1, action Follow Path Go to Line slot 1 screenshot_from_2024-07-23_09-40-47.png _ 5.2 Rẽ phải di chuyển vào điểm ghép băng chuyền DLxx_1, action Follow Path STL to DL1, tham số của action như dưới đây: screenshot_from_2024-07-23_10-13-28.png _ 5.3 Lùi di chuyển trờ lại đường đi chính Lxx_1, action Follow Path STL DL1 to Line 1, tham số như dưới đây: screenshot_from_2024-07-23_10-18-44.png _ 5.4 Di chuyển sang đầu line Lxx_2. action Follow Path Go to Line Slot 2, screenshot_from_2024-07-23_10-33-34.png _ 5.5 Rẽ phải di chuyển vào điểm ghép băng chuyền DLxx_2, action Follow Path STL to DL2, tham số của action như dưới đây: screenshot_from_2024-07-23_10-36-11.png _ 5.6 Tại thời điểm này, nếu là line 8 thì robot thì đi thẳng ra điểm LINE_OUT, nếu không thì robot sẽ lùi quay trở lại đường chính. Do đó, action điều kiện Check Line 8

    • Nếu là line 8, thực hiện action Follow Path ra điểm LINE_OUT

    • Nếu không phải line 8, Lùi di chuyển trờ lại đường đi chính Lxx_1, action Follow Path STL DL2 to Line 2. Sau đó di chuyển ra điểm LINE_OUT

    • Di chuyển từ điểm LINE_OUT quay trở lại vị trí STANDBY

3. Code ACS Client

  • Ở bước trên, ta đã soạn ra một khuôn mẫu mission cho TVC-NAV. Tuy nhiên để TVC-NAV mission hoạt động được thì ta cần có 1 module gửi dữ liệu (tùy theo từng nhiệm vụ) cho TVC-NAV thông qua các runtime-variables đã định nghĩa bên trên.
  • Dưới đây là pseudo-code (javascript) mô tả cách viết 1 module điểu khiển hoạt động của TVC-NAV mission đã soạn ở trên với dữ liệu nhận từ ACS.

1. Xử lý dữ liệu nhận từ ACS

  • Ở bước này Client nhận bản tin bufferId nhận hàng và lineId trả hàng từ ACS.
  • Từ dữ liệu trên, client cần tìm ra markerId của các điểm cần dừng để gửi vào mission.
const MISSION_ID = "7c0a7c7f-75ad-4536-8825-7555595cbd16";
function sevBufferIdToMarkerName(bufferId) {
// chuyển đổi từ bufferId sang tên marker
return {
bufferMarkerId: `B${String(bufferId).padStart(2, "0")}_1`,
bufferMarkerId2: `B${String(bufferId).padStart(2, "0")}_2`,
};
}

function sevLineIdToMarkerName(lineId) {
// chuyển đổi từ lineID sang tên của line marker, vị trí ghép băng chuyền
return {
lineMarkerId: `L${String(lineId).padStart(2, "0")}_1`,
DLMarkerId: `DL${String(lineId).padStart(2, "0")}_1`,
lineMarkerId2: `L${String(lineId).padStart(2, "0")}_2`,
DLMarkerId2: `DL${String(lineId).padStart(2, "0")}_2`,
};
}

let executedMission;

async function prepareVariables(bufferId, lineId) {
// lấy ra danh sách tên các marker dùng trong mission
const bufferMarkerNames = sevBufferIdToMarkerName(bufferId);
const lineMarkerNames = sevLineIdToMarkerName(lineId);
// const listMarkerNames = bufferMarkerNames.concat(lineMarkerNames);
const initialVariables = Object.assign(
{},
bufferMarkerNames,
lineMarkerNames,
);

const mapId = await webappAPI.getActiveMapId();
if (mapId === undefined) {
utils.log("Lỗi gọi nhiệm vụ vì chưa kích hoạt map.");
return;
}

const markers = await webappAPI.getMarkers(mapId);
if (markers === undefined) {
utils.log("Lỗi gọi nhiệm vụ vì không lấy được markers.");
return;
}

// lưu ra danh sách gôm tên và id của các marker
Object.keys(initialVariables).forEach((varName) => {
const markerName = initialVariables[varName];
initialVariables[varName] = webappAPI.getMarkerIdByName(
markers,
markerName,
);
});

// add lineId
initialVariables.lineId = lineId;

return initialVariables;
}

2. Code gọi thực hiện Start Mission

  • Do TVC-NAV Mission khi chạy sẽ cần tới dữ liệu để chạy tới line lấy hàng đầu tiên, nên khi gọi API start mission cho TVC-NAV, ta cần gửi cùng với dữ liệu của line đầu tiên
  startMission: async (bufferId, lineId) => {
// set initialVariable
const initialVariable = await prepareVariables(bufferId, lineId);
const initialVariables = Object.keys(initialVariable).map((key) => ({key, value: initialVariable[key]}));
// console.log("execute mission: e", params);
executedMission = (await webappAPI.executeMission(MISSION_ID, { initialVariables}))?.data;
console.log("execute mission: ", executedMission);
},

3. Code callback kiểm tra mức pin


router.post("/check-battery", async (req, res) => {
await sevProcess.handleCheckBattery(res);
});

handleCheckBattery: async(res) => {
const batteryLevel = getBatteryLevel();
// set battery Variablese
await webappAPI.setRuntimeVariable(executedMission.id, "batteryLevel", batteryLevel);
// unpaused
res.sendStatus(200);

setTimeout(rosService.unpause, 1000);
},

4. Code callback lấy hàng


router.post("/handle-sev-unload", async (req, res) => {
await sevProcess.handleUnload(res);
});

handleUnload: async (res) => {
unLoad();
// unpaused

res.sendStatus(200);
setTimeout(rosService.unpause, 1000);
},

4. Code callback trả hàng


router.post("/handle-sev-load-both", (req, res) => {
sevProcess.handleLoadBoth(res);
});

handleLoadBoth: (res) => {
loadBoth();
// unpaused

res.sendStatus(200);
setTimeout(rosService.unpause, 1000);
},