# Python X Ev3 (EV3DEV)
# 部落格建立!!! 請轉移到[這裡](https://www.poyu39.tw/posts/ev3/python-x-ev3-ev3dev/)閱讀文章
### 使用 Python 來撰寫 EV3 程式~
###### tags: `EV3` `Python` `EV3DEV`
[
](https://www.ev3dev.org/)
> 參考資料:[Program in Python with EV3](https://education.lego.com/en-us/product-resources/mindstorms-ev3/teacher-resources/python-for-ev3/) | [www.ev3dev.org](https://www.ev3dev.org/) | [ev3dev](https://github.com/ev3dev) | [EV3DEV API](https://pybricks.com/ev3-micropython/startinstall.html)
## 介紹
### Visual Studio Code
Visual Studio Code 是一款由微軟開發且跨平台的免費原始碼編輯器,支援語法突顯、程式碼自動補全、程式碼重構功能,並且內建了命令列工具和 Git 版本控制系統,也可以透過安裝額外套件增加功能,支援 Windows 、 MacOS 和 Linux 。
### vscode-ev3dev-browser
與 LEGO® MINDSTORMS EV3Lab 的積木語言不同,使用 Visual Studio Code 進行編寫程式,安裝 EV3DEV 所推出的 [vscode-ev3dev-browser](https://github.com/ev3dev/vscode-ev3dev-browser) 擴充插件後,可以直接上傳程式到 EV3 主機,也支援使用 SSH 等控制台。
### EV3DEV
EV3DEV 是基於 Debain Linux-based 系統下運行,可在多個 LEGO® MINDSTORMS 兼容平台上運行,包括 LEGO® MINDSTORMS EV3 和基於 Raspberry Pi 的 BrickPi。
### Python
Python 是一種高階程式語言,也是一種解釋型語言,強調程式碼的可讀性、簡潔的語法。Python為了讓程式碼具備高度的可閱讀性,在設計時盡量使用了其它語言常用的符號和英文單字。
Python 支援使用反斜槓作為行接續符,將多個物理行合成為一個邏輯行,作為通用型的程式語言,運用在 YouTube 、 Google 、 Yahoo! 、 Facebook 、 Dropbox 、Gmail 等等知名平台都在大量使用的程式語言,其特色是支援多種編寫方式,包括物件導向、命令式、函數式、程序式,也和Python和Ruby、Perl、Scheme一樣,擁有動態語法、強制縮排、Garbage Collection 和自動記憶體管理等等諸多的優勢,於未來的需求量也越來越多,涉獵的領域像是大數據分析、自動化腳本、人工智慧。
而 EV3DEV 所使用的 MicroPython 是一款小型的 Python 直譯器,它擁有自己的 Python 直譯器,在保留絕大部分 Python 的核心特色同時縮減周邊模組(函式庫),好讓 Python 這種出了名的吃記憶體的語言也能在像是 EV3 這樣的微控制板上運行。
## 安裝
### EV3DEV
EV3DEV 是基於 Debain Linux-based 系統下的 MicroPython ,因此需要另外準備記憶卡當作系統碟,一般容量建議在 4GB 。
1. 準備一張 microSD 記憶卡,建議容量 4GB 以下。
2. 下載映像檔 [EV3 MicroPython micro SD card image](https://education.lego.com/v3/assets/blt293eea581807678a/blt9df409c9a182ab9c/5f88191a6ffd1b42dc42b8af/ev3micropythonv200sdcardimage.zip)
3. 下載燒錄軟體 [Etcher](https://www.balena.io/etcher)
4. 將記憶卡插入電腦後,依照下方步驟將映像檔燒錄至記憶卡。
5. 燒錄完成後,確認 EV3 在關機狀態下將記憶卡插入到側邊的 MicroSD 插槽並開機。
### VScode 延伸模組
為了讓 Python 程式檔案可以傳送至 EV3 ,需要安裝額外的延伸模組。
1. 點選左邊延伸模組商店,輸入 EV3 點選 LEGO® MINDSTORMS® EV3 MicroPython。
2. 重啟 IDE。
3. 安裝成功會在左側欄中出現擴充的延伸模組。

### Python
和 MicroPython 語法基本一致,可以在燒錄記憶卡時先安裝在電腦上練習撰寫程式~
1. 首先到官網下載 Python 的安裝檔。

2. 點選 Downloads 進到下載頁面。

3. 目前最新版本是 3.11,這邊是直接安裝此版本到環境中,因此如果有多個版本會需要額外設定,也有許多版本可下載,不同版本之間的語法有些小差異。

4. 開啟安裝執行檔,並勾選 "Add python.exe to PATH" 自動設定路徑,並點選 Install Now 開始安裝。

5. 安裝完後可以開啟 CMD ,並輸入 `python` ,如果安裝成功就可以進入到 Python,

6. 撰寫第一個程式來測試看看吧!
```python=
print("Hello World")
```
> 快看! 電腦在向世界說 Hello 呢! (@^0^)
## 連線 EV3
首先將 EV3 開機並且透過數據線連接至電腦,可以看見開機後的 EV3 主畫面也不同了,不過不用擔心,只要關機並且將記憶卡取出後再開機,就可以回到官方系統了。

接著創建一個新的專案,並輸入檔名。

接著左下方的可以看到我們剛剛所安裝的 EV3DEV Device Browser,點選 `Click here to connect to a device` 並選擇 ev3dev 。

等待出現綠色圓圈和 `Status` 可以顯示狀態就表示成功了!

## EV3DEV Device Browser 功能
### 尋找設備
自動發現任何連接的 ev3dev 主機,不需要額外設定。


### 遠程瀏覽文件
每個設備的文件都列在 Brickman 中。

### 將文件下載到設備
單擊一下即可將當前 VS Code 項目發送到 ev3dev 設備。

### 遠程運行程序
單擊任何可執行文件以運行它。

也可以右鍵點擊選擇 `Run`

錯誤消息將顯示在輸出窗格中。

### 單擊一次(或 F5)即可構建、下載和運行
需創建一個 `ev3devBrowser `的 launch.json 文件以使用此功能。
```json=
{
"version": "0.2.0",
"configurations": [
{
"name": "Download and Run",
"type": "ev3devBrowser",
"request": "launch",
"program": "/home/robot/${workspaceRootFolderName}/hello",
"preLaunchTask": "build"
}
]
}
```
### 啟動遠程 SSH
可以通過右鍵單擊設備在控制台中啟動 SSH 。
```linux=
sudo hostnamectl set-hostname <name>
```

### 營幕截圖
可以通過右鍵單擊設備輕鬆截取營幕。


## 來撰寫 EV3 的第一個 Python 程式吧!
首先來介紹自動生成的 `main.py` ,這個檔案室 EV3 程式開始的地方,我們先來了解一下 Python 的語法以及這自動生成的程式碼是甚麼吧!
粉紅色框內所表示的是 `from ... import ...` 這段程式碼是代表匯入「函式庫」,函式庫像是外部的我們之前 EV3Lab 積木語言所學到匯入 MyBlock ,將外部的程式碼匯入就可以在這個檔案中的程式使用。
藍色框內所表示的是設定硬體的物件,當我們需要使用馬達或是感測器時,需要告訴 EV3 主機,我的硬體是插在哪個 port 。
黃色框內就是我們主要撰寫程式碼的地方了!

首先來撰寫我們在 EV3 上的第一個 Python 程式吧!
在 `ev3.speaker.beep()` 後方加上
```python=
print("Hello World")
```
接著點選左側邊欄中的偵測與執行,接著點選上方的開始鍵,就可以將剛剛所撰寫的程式下載至 EV3 並且執行。

可以看到下面的輸出欄位,印出了 `Hello World` ,這次換 EV3 打招呼了喔~

那有沒有辦法將 `Hello World` 顯示到 EV3 螢幕上呢?
試試看剛剛改成下方這段程式碼吧,並且下載到 EV3 執行看看。
```python=
ev3.screen.print("Hello World!") # 在螢幕上顯示 Hello World
wait(5000) # 等待 5000 毫秒
```
成功了!

Q. 為甚麼要加上 `wait(5000)` ,等待 5000 毫秒呢?
A. 如果不加上的話,程式會印出Hello World! 結束,程式一結束便會回到主畫面,如此一來就來不及看到是否有印出了。
## Python 流程控制
### while
在Python中,while是一種迴圈語句,它允許在滿足特定條件的情況下重複執行程式碼塊。while語句的基本構造是:
```pyhton=
while 條件:
程式碼
```
在這個語句中,當條件為True時,程式碼將被執行。每次執行完程式碼塊後,Python都會檢查條件是否仍為True,如果是,則再次執行程式碼。這個過程將繼續,直到條件變為False為止。
以下是一個while循環的簡單範例,該範例使用while循環顯示一系列數字,直到達到指定數字:
```python=
num = 0
while num < 5:
print(num)
num += 1
```
這段程式碼將從0開始顯示一系列數字,每個數字之間相差1,直到顯示數字5之前,因為條件“num < 5”仍然為True。在顯示數字5之後,條件變為False,因此while循環將停止執行。
需要注意的是,如果while循環的條件永遠不為False,則程式碼將陷入無限循環,這種情況應盡量避免。
#### 小試身手
Q. 請使用 while 印出五個 `Hello World!` 在 EV3 螢幕上。
```pyhton=
# ans
num = 0
while num < 5:
ev3.screen.print("Hello World!") # 在螢幕上顯示 Hello World
num += 1
wait(1000) # 等待 1 秒
```
### for
for 迴圈是一個重要的控制流程工具,與剛才介紹的 `while` 相似,新增了用於對序列物件(例如列表、元組、字串等)進行迭代的主要功能。
使用 for 迴圈的語法為:
```python=
for 變數 in 序列:
# 程式碼
```
在上述語法中,變數是用來存放每次迭代中序列中的一個元素,而包裹其中的程式碼是在每次迭代時需要執行的一段程式碼。當迭代完成後,for 迴圈也就結束了。
以下是一個使用 for 迴圈遍歷列表的範例:
```python=
fruits = ['蘋果', '香蕉', '橘子']
for fruit in fruits:
print(fruit)
```
在上述範例中,我們使用了一個名為 fruits 的列表,並使用 for 迴圈來遍歷這個列表中的每個元素。在每次迭代中,變數 fruit 會被賦值為列表中的一個元素,然後進入程式碼塊中執行 print 函式,將 fruit 輸出到螢幕上。
for 迴圈也可以和其他控制流程結構(例如 if、else 等)結合使用,來實現更複雜的邏輯運算。使用 for 迴圈,可以輕鬆地對序列物件進行迭代,讓我們可以更加靈活地處理大量資料。
### if else
if else 是最常見的條件判斷工具,可以用來根據不同的條件執行不同的程式碼。使用 if else 的語法為:
```python=
if 條件1:
# 條件1成立時要執行的程式碼
else:
# 條件1不成立時要執行的程式碼
```
條件1 是一個 `bool` 布林值(True 或 False),如果條件1 為 True,則執行 if 後的程式碼,否則執行 else 後的程式碼。
以下是一個使用 if else 判斷 `Touch Sensor` 是否按下的程式。
```python=
btn = TouchSensor(Port.S1) # 創建一個連結在Port.S1的TouchSensor
while True: # 使用無限迴圈讓程式持續執行
if btn.pressed(): # 當按鈕按下
ev3.screen.print("btn pressed")
else:
ev3.screen.print("btn not pressed")
```


## 程式實作
### 馬達緩加減
首先我們先來拼裝出簡單的循跡車

> [模型下載連結](https://drive.google.com/drive/folders/1-OdZlftMtYUOCPeAOonF-JetDtFlFsgs?usp=sharing) | [PDF組裝說明書](https://education.lego.com/v3/assets/blt293eea581807678a/bltc0dd681a7b3bfa8b/5ec7c6a6cd4cf750c888fa6f/ev3-rem-driving-base.pdf)
馬達緩加減是指讓馬達在啟動或停止的時候,逐漸地加速或減速,不是瞬間就開始或停止。這樣做的原因是因為如果馬達瞬間啟動或停止,會對機器和電子元件產生很大的應力,導致損壞或壞掉,更會導致馬達的精準度不佳。
在 `EV3` 的輪型機器人上則是會輪子的打滑,起步和煞車如果過快,機器會因慣性和摩擦力導致行走起來不完整。
- 緩減速
```python=
bMotor = Motor(Port.B)
target = 1000 # 目標角度
bMotor.reset_angle(0) # 將現在的角度歸零
while bMotor.angle() < target: # 當現在的角度小於目標角度時
speed = (target - bMotor.angle()) * 0.5 # 計算速度
if speed > 100: # 限制速度在 10~100 之間
speed = 100
elif speed < 10:
speed = 10
bMotor.dc(speed) # 設定速度
ev3.screen.print(bMotor.angle()) # 顯示現在的角度
bMotor.hold() # 停止馬達
```
- 緩加減速
```python=
# 緩動函數 cos
def easeInOutSine(x):
return -(math.cos(math.pi * x) - 1) / 2
# 彈跳函數
def easeOutBounce(x):
n1 = 7.5625
d1 = 2.75
if x < 1 / d1:
return n1 * x * x
elif x < 2 / d1:
x -= 1.5 / d1
return n1 * x * x + 0.75
elif x < 2.5 / d1:
x -= 2.25 / d1
return n1 * x * x + 0.9375
else:
x -= 2.625 / d1
return n1 * x * x + 0.984375
# 映射區間
def mapping(x, in_min, in_max, out_min, out_max):
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min
# 緩加減速
def smoothMove(time, distance, minSpeed, maxSpeed):
i = 0
step = time / 100 / 2
target = bMotor.angle() + distance
while i <= 1:
t_angle = mapping(easeInOutSine(i), 0, 1, 0, target)
error = t_angle - bMotor.angle()
t_speed = error * 1.5
bMotor.dc(t_speed)
i += 0.01
wait(step)
while bMotor.angle() < target:
bMotor.dc(20)
bMotor.stop()
bMotor.hold()
# 使用範例 在3秒內移動到1000度
smoothMove(3000, 1000)
```
### 循跡
```python=
# 初始化馬達
left_motor = Motor(Port.B)
right_motor = Motor(Port.C)
# 初始化顏色感應器
line_sensor = ColorSensor(Port.S3)
# 初始化Drive
robot = DriveBase(left_motor, right_motor, wheel_diameter=55.5, axle_track=104)
# 計算反射光閥值
BLACK = 9
WHITE = 85
threshold = (BLACK + WHITE) / 2
# 設定移動速度為每秒 100 毫米。
DRIVE_SPEED = 100
# 設定比例控制器的增益。這意味著對於每一個光強度百分比偏離閥值,
# 我們就將駕駛座的轉向速度設置為 1.2 度/秒。
# 例如,如果反光值偏離閥值 10,則機器人的轉向速度為 10*1.2 = 12 度/秒。
PROPORTIONAL_GAIN = 1.2
# 開始無限地跟隨線條。
while True:
# 計算光線強度與閥值之間的偏差。
# 加負號可以走在線的另一邊
deviation = line_sensor.reflection() - threshold
# 計算轉向速度。
turn_rate = PROPORTIONAL_GAIN * deviation
# 設置駕駛座速度和轉向速度。
robot.drive(DRIVE_SPEED, turn_rate)
# 您可以在此循環中等待一小段時間或做其他事情。
wait(10)
```