---
# System prepended metadata

title: 軟體
tags: [NTU Master Thesis]

---

###### tags: `NTU Master Thesis`

# 軟體
軟體執行在智慧型害蟲監測系統的樹莓派 Raspbian 作業系統中，整個通訊演算法係透過 docker 映像檔 [tonewworld/multi-sensor-image](https://hub.docker.com/r/tonewworld/multi-sensor-image) 執行的。軟體的程式語言為 Python

## 軟體架構和程式碼說明
軟體架構如下圖所示，docker 映像檔`tonewworld/multi-sensor-image`中含有兩個 python 檔案，`run_multiWSN.py`和`multiWSN.py`。該映像檔的執行邏輯是先以`run_multiWSN.py`讀取設定檔裡面的 VMSM 藍牙位址、感測器樣式等資訊，再藉由 Python 的 os 及 argparse 兩個函式庫，一次性執行多個`multiWSN.py`檔。`multiWSN.py`執行的數量取決於設定檔中的字串行數。在部署 VMSM 時，可以透過設定檔的內容與行數，決定要連接到該台 I2PDM 的 VMSM 數量與種類。一支`multiWSN.py`程式對應到一個 VMSM 中藍牙模組的位址，也就是說一支`multiWSN.py`程式負責 I2PDM 與一台 VMSM 間的通訊

![](https://i.imgur.com/GpoV2rL.png)

接下來，講述`run_multiWSN.py`和`multiWSN.py`兩支程式的重點程式碼

首先，`run_multiWSN.py`的部分如下所示，這是多支`multiWSN.py`程式能與多台 VMSM 一對一通訊的原因。程式中的 if/else 能視 config 檔案夾中的 BTNAME.txt 長度，執行相對應的`multiWSN.py`。並且，每支`multiWSN.py`收到的 sensorType、shadowName、blue_address 都是不一樣的
```python
if len(shadowName) == 1:
    os.system("python3 multiWSN.py -l {} -t {} -s {} -b {}".format(fieldName, sensorType[0], shadowName[0], blue_address[0]))
elif len(shadowName) == 2:
    os.system("python3 multiWSN.py -l {} -t {} -s {} -b {} & python3 multiWSN.py -l {} -t {} -s {} -b {}".format(fieldName, sensorType[0], shadowName[0], blue_address[0], fieldName, sensorType[1], shadowName[1], blue_address[1]))
elif len(shadowName) == 3:
    os.system("python3 multiWSN.py -l {} -t {} -s {} -b {} & python3 multiWSN.py -l {} -t {} -s {} -b {} & python3 multiWSN.py -l {} -t {} -s {} -b {}".format(fieldName, sensorType[0], shadowName[0], blue_address[0], fieldName, sensorType[1], shadowName[1], blue_address[1], fieldName, sensorType[2], shadowName[2], blue_address[2]))
elif len(shadowName) == 4:
    os.system("python3 multiWSN.py -l {} -t {} -s {} -b {} & python3 multiWSN.py -l {} -t {} -s {} -b {} & python3 multiWSN.py -l {} -t {} -s {} -b {} & python3 multiWSN.py -l {} -t {} -s {} -b {}".format(fieldName, sensorType[0], shadowName[0], blue_address[0], fieldName, sensorType[1], shadowName[1], blue_address[1], fieldName, sensorType[2], shadowName[2], blue_address[2], fieldName, sensorType[3], shadowName[3], blue_address[3]))
elif len(shadowName) == 5:
    os.system("python3 multiWSN.py -l {} -t {} -s {} -b {} & python3 multiWSN.py -l {} -t {} -s {} -b {} & python3 multiWSN.py -l {} -t {} -s {} -b {} & python3 multiWSN.py -l {} -t {} -s {} -b {} & python3 multiWSN.py -l {} -t {} -s {} -b {}".format(fieldName, sensorType[0], shadowName[0], blue_address[0], fieldName, sensorType[1], shadowName[1], blue_address[1], fieldName, sensorType[2], shadowName[2], blue_address[2], fieldName, sensorType[3], shadowName[3], blue_address[3], fieldName, sensorType[4], shadowName[4], blue_address[4]))
```

再來，是`multiWSN.py`的部分。如下程式碼所示，要想完成藍芽連線有兩個步驟。首先，要先更改 bluepy 內建的 handleNotification 成員函數，讓傳進來的 data 變成是 global 的。接下來，要宣告 Peripheral 類別的物件，並把藍芽的 mac address 傳給 Peripheral 物件。此時，若 p 物件建立成功，樹莓派與 Arduino 上的藍芽模組便建立起了通訊。建立通訊後，將 ReadDelegate 當作參數丟進 Peripheral 的成員函數 withDelegate 中，即可完成藍芽設定
```python
### bluepy Peripheral class setting
global dataByte
dataByte = 0
class ReadDelegate(btle.DefaultDelegate):
    def handleNotification(self, cHandle, data):
        # Access global variables insid class
        global dataByte
        dataByte = data

### assign Peripheral p 
p = btle.Peripheral(blue_address)
p.withDelegate(ReadDelegate())
```

完成藍芽設定後，如下程式碼所示，要想收到來自 Arduino 傳送過來的資料，要使用   Peripheral 的成員函數 waitForNotifications。若有資料送進來，waitForNotifications 會結束執行並自動跑到下一行，進而把收到的 global data 之長度 print 出來

```python
### use bluepy function to get data
p.waitForNotifications(0)
print("Data Length : ", len(dataByte))
```

## 軟體使用到的套件和 Docker 設定
軟體使用到的套件中，比較特別的是 bluepy 和 argparse。使用 [bluepy](https://ianharvey.github.io/bluepy-doc/) 能和 Raspberry Pi 上面的 BLE 藍牙做溝通；利用 [argparse](https://docs.python.org/zh-tw/3/howto/argparse.html) 能使每支 `multiWSN.py` 收到不同藍芽模組的 Mac Address

另外，docker 設定如下所示，這是 multi-sensor-image 最新版的 dockerfile。其中，值得注意的是，開發者必須在 AWS 帳號中找到自己的 Root Key，並將之複製貼上到下列的 YourAccessKey 和 YourSecretKey 中，如此一來，裝置上傳的 Json 訊息才會順利地送到先前設定的 IoT Core 實物中。另外，awscli 和 boto3 的版本也必須是 dockerfile 中的版本號，否則上傳時會有 error 出現
```dockerfile
### Set up image and update it
FROM navikey/raspbian-buster:latest
RUN apt-get update -y

### Install Packages
# Python3.7 and pip3
RUN apt-get install -y python3
RUN apt-get install -y python3-pip

# AWS
RUN pip3 install awscli==1.18.220 --no-cache-dir
RUN pip3 install boto3==1.19.6 --no-cache-dir

# Bluetooth
RUN pip3 install bluepy --no-cache-dir
RUN apt-get install bluez-hcidump

# Others
RUN apt-get install -y libatlas-base-dev 
RUN apt-get --reinstall install -y libraspberrypi-bin 

### Set up the folder and AWS
WORKDIR /app
ENV TZ Asia/Taipei

### Set up AWS
RUN aws configure set aws_access_key_id YourAccessKey
RUN aws configure set aws_secret_access_key YourSecretKey
RUN aws configure set region us-east-1
RUN aws configure set output json

### Copy my code
COPY run_multiWSN.py ./
COPY multiWSN.py ./

### Run python code
CMD ["python3", "./run_multiWSN.py"]
```

## 在樹莓派開機後自動執行 Docker
需使用 shell 和[開機自動開啟 Termial ](https://yayar.medium.com/%E6%A8%B9%E8%8E%93%E6%B4%BE-%E9%96%8B%E6%A9%9F%E8%87%AA%E5%8B%95%E9%96%8B%E5%95%9Fterminal%E5%9F%B7%E8%A1%8Cpython%E7%A8%8B%E5%BC%8F-87b6a1690f0a)兩種手法完成。其中，docker 裡面是透過下方的程式碼執行的。這個程式碼比較特別的是，若有兩個 docker 同時在一個 Raspberry Pi 運作，dns 不能是一樣的，否則上傳資料時會有 error。另外，-v 的指令能將本地端的 config 檔案複製到 docker 檔案內部，讓開發者能透過本地端的 config.txt 做修改，進而影響到 docker 程式內部的設定

```shell
sudo docker run --net=host --dns=8.8.8.8 --privileged -v /home/pi/config:/config tonewworld/multi-sensor-image:latest
```

*P.S. 為了能讓 VMSM 的 docker 檔案能和原本 I2PDM 的 docker 並行，因此我在一份 shell 中，同時執行另外兩條 shell，讓實驗室學長 Dan 撰寫的 docker 和我撰寫的 docker 能一起執行*

## 未來建議
軟體上有幾點不足之處，如下所示：
* 可以改善 I2PDM 傳資料給 Arduino 的穩定度，讓 I2PDM 不會在短時間內收到重複的資料
* 若有一台 I2PDM 連接五台以上 VMSM 的需求，需要改寫`run_multiWSN.py`裡面的程式，添增一行 elif len(shadowName) == 6: 並照著前面的程式模板撰寫 elif 裡面的內容
