EV3 直接命令 - 第 3 課 遙控車輛
移動電機是EV3的核心。機器人需要移動的能力,因此你需要知道它是如何完成的。請參閱文件EV3 Firmware Developer Kit 中的第 4.9 部分,以獲得與電機相對應的操作的第一印象。幾乎所有這些都以相同的兩個引數開頭:
-
LAYER:如果組合多個 EV3 brick 作為單個機器,則宣告其中一個是主機,最多3個額外 brick 作為主機從機。然後將操作傳送到主機,主機將其傳送給主機從機。這就是 LAYER 的含義。Layer 0x|00| 是主機,layers 0x|01|,0x|02| 和 0x|03| 是它的可選從機。在沒有從機的標準情況下,我們將設定 LAYER = 0x|00|。我們的直接命令程式碼可以連線多個 EV3 裝置。因此,我認為不需要不同的引數 LAYER 值。
-
NO 或 NOS:電機埠以字母 A,B,C 和 D 標識。在直接命令的層面,你可以通過數字命名它們,A = 0x|01|,B = 0x|02|,C = 0x|04| 和 D = 0x|08|。如果你將這些數字寫為二進位制表示,則 A = 0b 0000 0001,B = 0b 0000 0010,C = 0b 0000 0100 和 C = 0b 0000 1000,你看,後半個位元組可以解釋為一系列的四個標誌。你可以組合這些標誌。如果你處理埠 A 和 C 的電機操作,則設定 NOS = 0x|05| 或 0b 0000 0101。如果引數名稱為 NO,則只能處理單個電機埠。NOS是說,你可以處理一個或多個埠。
啟動和停止電機
將一些電機插入你選擇的埠,然後以全功率啟動它們。這通過以下直接命令完成:
---------------------------------------------- \len \cnt \ty\hd\op\la\no\power\op\la\no\ ---------------------------------------------- 0x|0D:00|2A:00|80|00:00|A4|00|0F|81:64|A6|00|0F| ---------------------------------------------- \13\42\no\0,0 \O \0 \a \100 \O \0 \a \ \\\ \\u \ \l \\u \ \l \ \\\ \\t \ \l \\t \ \l \ \\\ \\p \ \ \\p \ \ \ \\\ \\u \ \ \\u \ \ \ \\\ \\t \ \ \\t \ \ \ \\\ \\_ \ \ \\_ \ \ \ \\\ \\P \ \ \\S \ \ \ \\\ \\o \ \ \\t \ \ \ \\\ \\w \ \ \\a \ \ \ \\\ \\e \ \ \\r \ \ \ \\\ \\r \ \ \\t \ \ \ ----------------------------------------------
過了一段時間,你,你的父母,兄弟姐妹或孩子會對全動力馬達的聲音感到緊張。如果你是一個愛好和平的人,你應該傳送以下直接命令:
---------------------------------- \len \cnt \ty\hd\op\la\no\br\ ---------------------------------- 0x|09:00|2A:00|00|00:00|A3|00|0F|00| ---------------------------------- \9\42\re\0,0 \O \0 \a \no\ \\\ \\u \ \l \ \ \\\ \\t \ \l \ \ \\\ \\p \ \ \ \ \\\ \\u \ \ \ \ \\\ \\t \ \ \ \ \\\ \\_ \ \ \ \ \\\ \\S \ \ \ \ \\\ \\t \ \ \ \ \\\ \\o \ \ \ \ \\\ \\p \ \ \ \ ----------------------------------
我們看到了三個新操作:
-
opOutput_Power = 0x|A4|,引數是
- LAYER
- NOS
- POWER:指定輸出功率 [-100 – 100 %](負值意味著相反的方向)
-
opOutput_Start = 0x|A6|,引數是
- LAYER
- NOS
-
opOutput_Stop = 0x|A3|,引數是
- LAYER
- NOS
- BRAKE:指定製動級別 [0:浮動,1:制動]。這不是被動制動器,其中電動機固定在其位置上。它是一個主動的,電機試圖回到原來的位置,這需要能量!
通過這三個操作,我們可以啟動和停止電機。我們還應該討論另一個,它設定了極性。通常,電動機的運動以前後方向描述,並且使用正數和負數。如果我們的機器結構導致極性相反,則有一個操作,可以改變它:
-
opOutput_Polarity = 0x|A7|,引數是
- LAYER
- NOS
-
POLARITY:指定極性 [-1, 0, 1]
- -1:電機將向後執行
- 0:電機將反方向執行
- -1:電機將向前執行
定義速度而不是功率通常是更好的選擇。幸運的是,樂高電機固定的電機,我們可以通過以下操作輕鬆定義電機運動的恆定速度:
-
opOutput_Speed = 0x|A5|,引數是
- LAYER
- NOS
- SPEED:指定輸出速度 [-100 – 100 %](負值意味著相反的方向)
請構造一輛由兩個電機驅動的車輛,一個用於左側,另一個用於右側。精心設計的例子是:
- ofollow,noindex">http://robotsquare.com/2015/10/06/explor3r-building-instructions
- http://robotsquare.com/wp-content/uploads/2013/10/45544_educator.pdf
- http://www.lego.com/en-gb/mindstorms/build-a-robot/track3r
- http://www.lego.com/en-gb/mindstorms/build-a-robot/robodoz3r
但一個簡單的也將滿足我們的需求。將右側電機連線到埠 A,將左側電機連線到埠 D,併發送以下直接命令:
------------------------------------------- \len \cnt \ty\hd\op\la\no\sp\op\la\no\ ------------------------------------------- 0x|0C:00|2A:00|80|00:00|A5|00|09|3D|A6|00|09| ------------------------------------------- \12\42\no\0,0 \O \0 \A \-3\O \0 \A \ \\\ \\u \ \+ \ \u \ \+ \ \\\ \\t \ \D \ \t \ \D \ \\\ \\p \ \ \ \p \ \ \ \\\ \\u \ \ \ \u \ \ \ \\\ \\t \ \ \ \t \ \ \ \\\ \\_ \ \ \ \_ \ \ \ \\\ \\S \ \ \ \S \ \ \ \\\ \\p \ \ \ \t \ \ \ \\\ \\e \ \ \ \a \ \ \ \\\ \\e \ \ \ \r \ \ \ \\\ \\d \ \ \ \t \ \ \ -------------------------------------------
希望你的車輛以低速直行。使用上面的直接命令(opOutput_Stop)來停止它。
遙控車輛
現在是編寫實際應用程式的時候了。使用遙控 EV3 車輛給你的朋友或孩子留下深刻印象。我們從一個簡單的遙控器開始,並使用鍵盤箭頭鍵來改變速度和方向。首先將新操作新增到 EV3 類,然後編寫遠端控制程式。像往常一樣,我已經以python3 完成了它:
#!/usr/bin/env python3 import curses import ev3 def move(speed: int, turn: int)->None: global myEV3, stdscr stdscr.addstr(5, 0, 'speed: {}, turn: {} '.format(speed, turn)) if turn > 0: speed_right = speed speed_left= round(speed * (1 - turn / 100)) else: speed_right = round(speed * (1 + turn / 100)) speed_left= speed ops = b''.join([ ev3.opOutput_Speed, ev3.LCX(0),# LAYER ev3.LCX(ev3.PORT_A),# NOS ev3.LCX(speed_right),# SPEED ev3.opOutput_Speed, ev3.LCX(0),# LAYER ev3.LCX(ev3.PORT_D),# NOS ev3.LCX(speed_left),# SPEED ev3.opOutput_Start, ev3.LCX(0),# LAYER ev3.LCX(ev3.PORT_A + ev3.PORT_D)# NOS ]) myEV3.send_direct_cmd(ops) def stop()->None: global myEV3, stdscr stdscr.addstr(5, 0, 'vehicle stopped ') ops = b''.join([ ev3.opOutput_Stop, ev3.LCX(0),# LAYER ev3.LCX(ev3.PORT_A + ev3.PORT_D), # NOS ev3.LCX(0)# BRAKE ]) myEV3.send_direct_cmd(ops) def react(c): global speed, turn if c in [ord('q'), 27, ord('p')]: stop() return elif c == curses.KEY_LEFT: turn += 5 turn = min(turn, 200) elif c == curses.KEY_RIGHT: turn -= 5 turn = max(turn, -200) elif c == curses.KEY_UP: speed += 5 speed = min(speed, 100) elif c == curses.KEY_DOWN: speed -= 5 speed = max(speed, -100) move(speed, turn) def main(window)->None: global stdscr stdscr = window stdscr.clear()# print introduction stdscr.refresh() stdscr.addstr(0, 0, 'Use Arrows to navigate your EV3-vehicle') stdscr.addstr(1, 0, 'Pause your vehicle with key <p>') stdscr.addstr(2, 0, 'Terminate with key <q>') while True: c = stdscr.getch() if c in [ord('q'), 27]: react(c) break elif c in [ord('p'), curses.KEY_RIGHT, curses.KEY_LEFT, curses.KEY_UP, curses.KEY_DOWN]: react(c) speed = 0 turn= 0 myEV3 = ev3.EV3(protocol=ev3.BLUETOOTH, host='00:16:53:42:2B:99') stdscr = None # ops = opOutput_Polarity + b'\x00' + LCX(PORT_A + PORT_D) + LCX(-1) # myEV3.send_direct_cmd(ops) curses.wrapper(main)
一些說明:
-
我使用模組
curses
來實現鍵盤事件的事件處理程式。 -
方法
wrapper
控制資源鍵盤和終端。它改變終端的行為,建立一個視窗物件並呼叫函式main
。 -
函式
main
將一些文字寫入終端,然後等待鍵事件(方法getch
)。 -
如果其中一個相關鍵命中,則呼叫函式
react
,鍵<q>
或<Ctrl-c>
打破迴圈。 -
變數
speed
定義了更快輪子的速度。 -
變數
turn
定義方向。值 0 表示直行,正值表示左轉,負值表示右轉。轉彎的絕對值大意味著轉彎的半徑小。最大值+200 和最小值 -200 意味著在原地盤旋。在下一課中,我們將回到驅動轉彎的這個定義。 -
我在第 5 步改變了
speed
和turn
。這似乎是精確度和反應速度之間的良好折衷。 - 也許,你的車輛結構需要改變兩個車輪的極性。這由註釋掉的程式碼完成。
- 右輪連線到埠 A,左側連線到埠 D。
- 對於低速,由於四捨五入到整數值,轉彎半徑的精度變得更差。
中斷作為一種設計理念
上一課,我們將中斷與不耐煩和乖巧的人進行了比較。本課程表明,這種行為恰恰是一種切實可行的設計理念。
遙控程式傳送無限的命令,如果不被下一個命令中斷,它將永久地移動電機。控制駕駛的人類希望糾正立即生效。這是絕對正確的,新命令會中斷舊命令。
該設計概念的另一個積極方面是直接命令的執行時間短。它們不耗時。電機的運動持續很長時間,但是當電機獲得新引數時,直接命令已經執行完成。控制立即返回,程式是自由的,以執行下一個任務。資源 EV3 裝置未被阻塞,它可用於下一個直接命令。
你已經看到了如何編碼遠端控制的中斷過程。中心部分是一個請求關鍵事件的迴圈。如果相關的按鍵事件發生,則呼叫一個函式,該函式更改運動的引數並向 EV3 裝置傳送直接命令。
當我們在 c’’(上一課)中玩三合一時,我們避免了中斷。我們有一個固定的所有音調時間表。我們的意見是,每次中斷都會擾亂計劃,需要加以防範。在繪製三角形時,我們已經討論了兩種時序選擇,通過直接命令或本地程式。也可以使用本地程式中的排程並使用中斷來播放三元組。這是獲得正確時序的替代方法:
#!/usr/bin/env python3 import ev3, time my_ev3 = ev3.EV3(protocol=ev3.BLUETOOTH, host='00:16:53:42:2B:99') ops = b''.join([ ev3.opUI_Write, ev3.LED, ev3.LED_RED, ev3.opSound, ev3.TONE, ev3.LCX(1), ev3.LCX(262), ev3.LCX(0) ]) my_ev3.send_direct_cmd(ops) time.sleep(0.5) ops = b''.join([ ev3.opUI_Write, ev3.LED, ev3.LED_GREEN, ev3.opSound, ev3.TONE, ev3.LCX(1), ev3.LCX(330), ev3.LCX(0) ]) my_ev3.send_direct_cmd(ops) time.sleep(0.5) ops = b''.join([ ev3.opUI_Write, ev3.LED, ev3.LED_RED, ev3.opSound, ev3.TONE, ev3.LCX(1), ev3.LCX(392), ev3.LCX(0) ]) my_ev3.send_direct_cmd(ops) time.sleep(0.5) ops = b''.join([ ev3.opUI_Write, ev3.LED, ev3.LED_RED_FLASH, ev3.opSound, ev3.TONE, ev3.LCX(1), ev3.LCX(523), ev3.LCX(0) ]) my_ev3.send_direct_cmd(ops) time.sleep(2) ops = b''.join([ ev3.opUI_Write, ev3.LED, ev3.LED_GREEN, ev3.opSound, ev3.BREAK ]) my_ev3.send_direct_cmd(ops)
此版本還實現了固定的排程,但時間發生在程式中而不是 EV3 裝置上(在直接命令中)。同樣,這有一定的優勢,它不阻塞 EV3 裝置。只要我們一次只執行一個任務,阻塞就無關緊要了。如果我們試圖結合獨立的聲音和點選驅動,且並行執行任務,則本地時序和中斷是必須的。
如果我們在上面的程式中設定 sync_mode == SYNC,資料流量會增長,但我們不會聽到任何差異。值得反思的是,該程式同步執行,因為沒有直接命令是耗時的。sync_mode == ASYNC 或 sync_mode == STD 是為非同步執行設計的,但我們也可以使用這些設定編寫同步執行程式碼。非同步執行僅在直接命令耗時(時間由直接命令完成)且本地程式不等待直接命令結束時。中斷有助於避免耗時的直接命令,並且是同步執行。
繼承 EV3
到目前為止,我們直接在我們的程式中編寫了操作。這需要封裝和抽象。我想特化類,一個用於帶兩個驅動輪的車輛,一個用於音調和音樂等等。設計應該允許它們平並行使用。我的解決方案是大量的 EV3 類的子類,它們都與同一個 EV3 裝置通訊。這就是說,它們必須共享資源。為了實現這一點,我修改了 EV3 類的建構函式:
|__init__(self, protocol:str=None, host:str=None, ev3_obj=None) |Establish a connection to a LEGO EV3 device | |Keyword Arguments (either protocol and host or ev3_obj): |protocol: None, 'Bluetooth', 'Usb' or 'Wifi' |host: None or mac-address of the LEGO EV3 (f.i. '00:16:53:42:2B:99') |ev3_obj: None or an existing EV3 object (its connections will be used)
程式碼是:
class EV3: def __init__(self, protocol: str=None, host: str=None, ev3_obj=None): assert ev3_obj or protocol, \ 'Either protocol or ev3_obj needs to be given' if ev3_obj: assert isinstance(ev3_obj, EV3), \ 'ev3_obj needs to be instance of EV3' self._protocol = ev3_obj._protocol self._device = ev3_obj._device self._socket = ev3_obj._socket elif protocol: assert protocol in [BLUETOOTH, WIFI, USB], \ 'Protocol ' + protocol + 'is not valid' self._protocol = None self._device = None self._socket = None if protocol == BLUETOOTH: assert host, 'Protocol ' + protocol + 'needs host-id' self._connect_bluetooth(host) elif protocol == WIFI: self._connect_wifi() elif protocol == USB: self._connect_usb() self._verbosity = 0 self._sync_mode = STD self._msg_cnt = 41
一些註解:
-
有兩種建立類 EV3 的新例項的方式:
-
和以前一樣,使用引數
protocol
和host
呼叫建構函式以獲取新連線。 -
另一種方法是使用現有的 EV3 物件作為引數
ev3_obj
呼叫它,使用其中的連線。
-
和以前一樣,使用引數
- 這不僅限於連線。對於將來的擴充套件,我們可以共享任何資源。
-
每個類都有自己的
_verbosity
和_sync_mode
。這是 OK 的,但我們使訊息計數器_msg_cnt
成為一個類屬性:class EV3: _msg_cnt = 41
考慮到這一點,我編寫了類TwoWheelVehicle
,這裡是它的建構函式:
class TwoWheelVehicle(ev3.EV3): def __init__( self, protocol: str=None, host: str=None, ev3_obj: ev3.EV3=None ): super().__init__(protocol=protocol, host=host, ev3_obj=ev3_obj) self._polarity = 1 self._port_left = ev3.PORT_D self._port_right = ev3.PORT_A
我添加了兩個方法:
def move(self, speed: int, turn: int)->None: assert self._sync_mode != ev3.SYNC, "no unlimited operations allowed in sync_mode SYNC" assert isinstance(speed, int), "speed needs to be an integer value" assert -100 <= speed and speed <= 100, "speed needs to be in range [-100 - 100]" assert isinstance(turn, int), "turn needs to be an integer value" assert -200 <= turn and turn <= 200, "turn needs to be in range [-200 - 200]" if self._polarity == -1: speed *= -1 if self._port_left < self._port_right: turn *= -1 if turn > 0: speed_right = speed speed_left= round(speed * (1 - turn / 100)) else: speed_right = round(speed * (1 + turn / 100)) speed_left= speed ops = b''.join([ ev3.opOutput_Speed, ev3.LCX(0),# LAYER ev3.LCX(PORT_A),# NOS ev3.LCX(speed_right),# SPEED ev3.opOutput_Speed, ev3.LCX(0),# LAYER ev3.LCX(PORT_D),# NOS ev3.LCX(speed_left),# SPEED ev3.opOutput_Start, ev3.LCX(0),# LAYER ev3.LCX(PORT_A + PORT_D) # NOS ]) self.send_direct_cmd(ops)
和
def stop(self, brake: bool=False)->None: assert isinstance(brake, bool), "brake needs to be a boolean value" if brake: br = 1 else: br = 0 ops_stop = b''.join([ ev3.opOutput_Stop, ev3.LCX(0),# LAYER ev3.LCX(self._port_left + self._port_right), # NOS ev3.LCX(br)# BRAKE ]) self.send_direct_cmd(ops)
我們需要三個屬性:
@property def polarity(self): return self._polarity @polarity.setter def polarity(self, value: int): assert isinstance(value, int), "polarity needs to be of type int" assert value in [1, -1], "allowed polarity values are: -1 or 1" self._polarity = value @property def port_right(self): return self._port_right @port_right.setter def port_right(self, value: int): assert isinstance(value, int), "port needs to be of type int" assert value in [ev3.PORT_A, ev3.PORT_B, ev3.PORT_C, ev3.PORT_D], "value is not an allowed port" self._port_right = value @property def port_left(self): return self._port_left @port_left.setter def port_left(self, value: int): assert isinstance(value, int), "port needs to be of type int" assert value in [ev3.PORT_A, ev3.PORT_B, ev3.PORT_C, ev3.PORT_D], "value is not an allowed port" self._port_left = value
就是這樣,我們有了一個新類TwoWheelVehicle
,它具有這樣的 API:
Help on module ev3_vehicle: NAME ev3_vehicle - EV3 vehicle CLASSES ev3.EV3(builtins.object) TwoWheelVehicle class TwoWheelVehicle(ev3.EV3) |ev3.EV3 vehicle with two drived Wheels | |Method resolution order: |TwoWheelVehicle |ev3.EV3 |builtins.object | |Methods defined here: | |__init__(self, protocol:str=None, host:str=None, ev3_obj:ev3.EV3=None) |Establish a connection to a LEGO EV3 device | |Keyword Arguments (either protocol and host or ev3_obj): |protocol |BLUETOOTH == 'Bluetooth' |USB == 'Usb' |WIFI == 'Wifi' |host: mac-address of the LEGO EV3 (f.i. '00:16:53:42:2B:99') |ev3_obj: an existing EV3 object (its connections will be used) | |move(self, speed:int, turn:int) -> None |Start unlimited movement of the vehicle | |Arguments: |speed: speed in percent [-100 - 100] |> 0: forward |< 0: backward |turn: type of turn [-200 - 200] |-200: circle right on place |-100: turn right with unmoved right wheel |0: straight |100: turn left with unmoved left wheel |200: circle left on place | |stop(self, brake:bool=False) -> None |Stop movement of the vehicle | |Arguments: |brake: flag if activating brake | |---------------------------------------------------------------------- |Data descriptors defined here: | |polarity |polarity of motor rotation (values: -1, 1, default: 1) | |port_left |port of left wheel (default: PORT_D) | |port_right |port of right wheel (default: PORT_A) | |---------------------------------------------------------------------- |Methods inherited from ev3.EV3: | |__del__(self) |closes the connection to the LEGO EV3 | |send_direct_cmd(self, ops:bytes, local_mem:int=0, global_mem:int=0) -> bytes |Send a direct command to the LEGO EV3 | |Arguments: |ops: holds netto data only (operations), the following fields are added: |length: 2 bytes, little endian |counter: 2 bytes, little endian |type: 1 byte, DIRECT_COMMAND_REPLY or DIRECT_COMMAND_NO_REPLY |header: 2 bytes, holds sizes of local and global memory | |Keyword Arguments: |local_mem: size of the local memory |global_mem: size of the global memory | |Returns: |sync_mode is STD: reply (if global_mem > 0) or message counter |sync_mode is ASYNC: message counter |sync_mode is SYNC: reply of the LEGO EV3 | |wait_for_reply(self, counter:bytes) -> bytes |Ask the LEGO EV3 for a reply and wait until it is received | |Arguments: |counter: is the message counter of the corresponding send_direct_cmd | |Returns: |reply to the direct command | |---------------------------------------------------------------------- |Data descriptors inherited from ev3.EV3: | |__dict__ |dictionary for instance variables (if defined) | |__weakref__ |list of weak references to the object (if defined) | |sync_mode |sync mode (standard, asynchronous, synchronous) | |STD:Use DIRECT_COMMAND_REPLY if global_mem > 0, |wait for reply if there is one. |ASYNC: Use DIRECT_COMMAND_REPLY if global_mem > 0, |never wait for reply (it's the task of the calling program). |SYNC:Always use DIRECT_COMMAND_REPLY and wait for reply. | |The general idea is: |ASYNC: Interruption or EV3 device queues direct commands, |control directly comes back. |SYNC:EV3 device is blocked until direct command is finished, |control comes back, when direct command is finished. |STD:NO_REPLY like ASYNC with interruption or EV3 queuing, |REPLY like SYNC, synchronicity of program and EV3 device. | |verbosity |level of verbosity (prints on stdout).
從現在開始,當我們使用兩個驅動車輪移動車輛時,我們將使用ClassWheelVehicle
。實際上這不僅僅是一個練習,而是一個真實的東西,但下一課,我們將為這個類新增功能。你可能會問,如果努力和利益真的是平衡的話。但是看,我們的程式變短了:
#!/usr/bin/env python3 import curses import ev3, ev3_vehicle def react(c): global speed, turn, my_vehicle if c in [ord('q'), 27, ord('p')]: my_vehicle.stop() return elif c == curses.KEY_LEFT: turn += 5 turn = min(turn, 200) elif c == curses.KEY_RIGHT: turn -= 5 turn = max(turn, -200) elif c == curses.KEY_UP: speed += 5 speed = min(speed, 100) elif c == curses.KEY_DOWN: speed -= 5 speed = max(speed, -100) stdscr.addstr(5, 0, 'speed: {}, turn: {} '.format(speed, turn)) my_vehicle.move(speed, turn) def main(window)->None: global stdscr stdscr = window stdscr.clear()# print introduction stdscr.refresh() stdscr.addstr(0, 0, 'Use Arrows to navigate your EV3-vehicle') stdscr.addstr(1, 0, 'Pause your vehicle with key <p>') stdscr.addstr(2, 0, 'Terminate with key <q>') while True: c = stdscr.getch() if c in [ord('q'), 27]: react(c) break elif c in [ord('p'), curses.KEY_RIGHT, curses.KEY_LEFT, curses.KEY_UP, curses.KEY_DOWN]: react(c) speed = 0 turn= 0 my_vehicle = ev3_vehicle.TwoWheelVehicle(protocol=ev3.BLUETOOTH, host='00:16:53:42:2B:99') stdscr = None curses.wrapper(main)
如果你下載了ev3-python3
的模組ev3_vehicle.py
,你需要以兩個額外的引數radius_wheel
和tread
呼叫TwoWheelVehicle
的建構函式。目前你可以將它們設定為任何正值。
結論
現在我們知道了,如何移動電機,我們獲得了兩個重要設計概念的經驗,即中斷和子類化。此外我們編寫的東西像個真正的應用程式了,一個車輛的遙控器。
我希望,你的遙控器工作良好,並使你成為一個技術明星。我們即將結束本課程。瀏覽文件EV3 Firmware Developer Kit 告訴您,移動電機存在許多額外的操作。其中大多數是為了精確,同步和平滑的運動。這將是我們下一課的主題。我希望能再次見到你。