目錄
- 1. 功能需求
- 2. Python基礎
- 2.1 特點
- 2.2 Python基礎知識
- 2.3 dict嵌套簡單說明
- 3. C語言調用Python
- 3.1 搭建編譯環境
- 3.2 直接調用python語句
- 3.3 調用無參python函數
- 3.4 調用有參python函數
- 4. 阿里云垃圾識別方案
- 4.1 接入阿里云
- 4.2 C語言調用阿里云Python接口
- 5. 香橙派使用攝像頭
- 補充設置開機自啟動 mjpg-streamer 服務
- 6. 語音模塊的配置
- 7. VSCode安裝并通過SSH連接全志開發板
- 8. 語音模塊和阿里云結合
- 8.1 環境準備
- 8.2 代碼實現
- 9. 增加垃圾桶及開關蓋功能
- 9.1 環境準備
- 9.2 代碼實現
- 10. 項目代碼優化
- 11. 增加OLED顯示功能
- 11. 增加網絡控制功能
- 11.1 實現需求
- 11.2 TCP 心跳機制解決Soket異常斷開問題
- 11.3 C語言實現TCP KeepAlive功能
- 11.4 網絡控制功能代碼實現
1. 功能需求
- 語音接入控制垃圾分類識別,并觸發垃圾桶的開關蓋
- 回顧二階段的Socket編程,實現Sockect發送指令遠程控制垃圾分類識別,并觸發垃圾桶的開關蓋
- 圖像識別垃圾分類功能
- 語音播報垃圾物品類型
- OLED顯示垃圾物品類型
- 根據垃圾類型開關不同類型垃圾桶
圖像處理使用阿里SDK支持Python和Java接口,目的是引入C語言的Python調用,感受大廠做的算法bug,此接口是人工智能接口,阿里云識別模型是通過訓練后的模型,精準度取決于訓練程度,人工智能范疇,在常規嵌入式設備負責執行居多,說白了就是嵌入式設備負責數據采集,然后轉發給人工智能識別后,拿到結果進行執行器動作。
2. Python基礎
環境搭建:(備注:在香橙派 3.0.6版本的鏡像里已經默認自帶了python3.10的版本,不需要安裝,只需要后續安裝下python3 dev即可。后續統一采用Orangepizero2_3.0.6_ubuntu_jammy_desktop_xfce_linux5.16.17的系統鏡像)
Python是一種動態解釋型的編程語言。Python可以在Windows、UNIX、MAC等多種操作系統上
使用,也可以在Java、.NET開發平臺上使用。
2.1 特點
- Python使用C語言開發,但是Python不再有C語言中的指針等復雜的數據類型。
- Python具有很強的面向對象特性,而且簡化了面向對象的實現。它消除了保護類型、抽象類、接口等面向對象的元素。
- Python代碼塊使用空格或制表符縮進的方式分隔代碼。
- Python僅有31個保留字,而且沒有分號、begin、end等標記。
- Python是強類型語言,變量創建后會對應一種數據類型,出現在統一表達式中的不同類型的變量需要做 類型轉換。
2.2 Python基礎知識
簡單的基礎知識推薦閱讀www.runoob.com官網的Python3課程內容
地址如下:
https://www.runoob.com/python3/python3-reg-expressions.html
2.3 dict嵌套簡單說明
字典(Dictionary)是Python里非常常見的一種數據結構,如果是在其他語言里,一般稱做map。是由鍵(key)和值(value)成對組成,鍵和值中間以冒號":“隔開,鍵值對之間用”,“隔開,整個字典由大括號”{}"括起來。
格式如下:
dict = {key1 : value1, key2 : value2 }
如:
tinydict = {'name': 'runoob', 'likes': 123, 'url': 'www.runoob.com'}
這里面,鍵一般是唯一的,如果重復了, 最后的一個鍵值對(Key:value)會替換前面的。 而且鍵可以用數字,字符串或元組充當,用列表不行。而且值就不需要唯一,而且形式多樣,比如可以以列表或者dict的形式出現。
dict的使用非常靈活, 甚至可以和列表組合使用, 列表里能嵌套列表,也能嵌套字典。同樣的,字典里能嵌套字典,字典里也能嵌套列表。 如下面這個例子:
garbage_dict = {'Data': {'Elements': [{'Category': '干垃圾', 'CategoryScore':
0.8855999999999999, 'Rubbish': '', 'RubbishScore': 0.0}], 'Sensitive': False},
'RequestId': '1AB9E813-3781-5CA2-95A0-1EA334E80663'}
這個例子里的dict內容是就是一個嵌套的結構,也就是說,它包含了其他的dict或列表作為值。我們可以用以下的方式來理解它:
- 'Elements’對應的值是一個列表,它包含了一個元素,也就是另一個內層的dict。
- 這個內層的dict有四個鍵:‘Category’、‘CategoryScore’、‘Rubbish’和’RubbishScore’。
- ‘Category’對應的值是一個字符串,表示垃圾分類的類別,例如’干垃圾’。
- 'CategoryScore’對應的值是一個浮點數,表示垃圾分類的置信度,例如0.8856。
- ‘Rubbish’對應的值是一個字符串,表示垃圾的具體名稱,例如’'(空字符串)。
- 'RubbishScore’對應的值是一個浮點數,表示垃圾名稱的置信度,例如0.0。
- 'Sensitive’對應的值是一個布爾值,表示是否涉及敏感信息,例如False。
- ‘RequestId’對應的值是一個字符串,表示請求的唯一標識符,例如’1AB9E813-3781-5CA2-95A0-1EA334E80663’。
想要取到內層垃圾的類型,可以這樣取:
str = garbage_dict['Data']['Elements'][0]['Category']
3. C語言調用Python
3.1 搭建編譯環境
通過C語言調用Python代碼,需要先安裝libpython3的 dev依賴庫(不同的ubuntu版本下,python版本可能會有差異, 比如ubuntu 22.04里是libpython3.10-dev)。
首先可以通過以下命令驗證是否是否已經存在python3的dev包
dpkg -l | grep libpython3
正常會有類似如下的輸出,出現"libpython3"和 “dev”,如libpython3.10-dev即可:
pg@pg-Default-string:~$ dpkg -l | grep libpython
ii libpython3-dev:amd64 3.10.6-1~22.04
amd64 header files and a static library for Python (default)
ii libpython3-stdlib:amd64 3.10.6-1~22.04
amd64 interactive high-level object-oriented language (default
python3 version)
ii libpython3.10:amd64 3.10.12-1~22.04.2
amd64 Shared Python runtime library (version 3.10)
ii libpython3.10-dev:amd64 3.10.12-1~22.04.2
amd64 Header files and a static library for Python (v3.10)
ii libpython3.10-minimal:amd64 3.10.12-1~22.04.2
amd64 Minimal subset of the Python language (version 3.10)
ii libpython3.10-stdlib:amd64 3.10.12-1~22.04.2
amd64 Interactive high-level object-oriented language (standard
library, version 3.10)
如果沒有, 可以通過apt命令安裝相關的dev包:
sudo apt install libpython3.10-dev
3.2 直接調用python語句
先看這么一個簡單的例子:
#include "Python.h"
int main()
{Py_Initialize(); // 初始化PyRun_SimpleString("print ('funny')");Py_Finalize(); //釋放資源
}
然要編譯和運行這個程序,可以使用以下命令(假設使用的是gcc編譯器和Python 3.10版本):
gcc simpledemo.c -o simpledemo -I /usr/include/python3.10 -l python3.10
./simpledemo
gcc后面鏈接分別為鏈接庫文件目錄和鏈接動態庫
輸出:
funny
上面代碼的解釋:
- 首先包含Python.h頭文件,這是Python API的頭文件,用于訪問Python對象和函數
- 其次在程序開始時使用Py_Initialize()函數初始化Python解釋器。這樣可以在C程序中執行Python代碼
- 然后使用PyRun_SimpleString()函數執行一段簡單的Python代碼,例如打印"funny"。需要傳遞一個字符串作為參數,表示要執行的Python代碼,如print (‘funny’)這么一個Python代碼字符串。
函數說明:
int PyRun_SimpleString(const char *command)
這是針對下面 PyRun_SimpleStringFlags() 的簡化版接口,將 PyCompilerFlags* 參數設為 NULL;
傳參就是python執行語句。
- 最后在程序結束時使用Py_Finalize()函數關閉Python解釋器,并釋放資源。
3.3 調用無參python函數
現在把某語句放到nopara.py的文件的函數里, 如下:
#nopara.py文件
def say_funny():print('funny')
接下來用C語言進行調用,一般調用的流程是這樣子的:
#if 0
1、包含Python.h頭文件,以便使用Python API。
2、使用void Py_Initialize()初始化Python解釋器,
3、使用PyObject *PyImport_ImportModule(const char *name)和PyObject
*PyObject_GetAttrString(PyObject *o, const char *attr_name)獲取sys.path對象,并利用int PyList_Append(PyObject *list, PyObject *item)將當前路徑.添加到sys.path中,以便加載
當前的Python模塊(Python文件即python模塊)。
4、使用PyObject *PyImport_ImportModule(const char *name)函數導入Python模塊,并檢查是否
有錯誤。
5、使用PyObject *PyObject_GetAttrString(PyObject *o, const char *attr_name)函數獲取
Python函數對象,并檢查是否可調用。
6、使用PyObject *PyObject_CallObject(PyObject *callable, PyObject *args)函數調用
Python函數,并獲取返回值。
7、使用void Py_DECREF(PyObject *o)函數釋放所有引用的Python對象。
8、結束時調用void Py_Finalize()函數關閉Python解釋器。
相關的函數參數說明參考網站(網站左上角輸入函數名即可開始搜索):
https://docs.python.org/zh-cn/3/c-api/import.html
#endif
根據上面的流程寫出的示例代碼如下:
#include <Python.h>int main()
{Py_Initialize(); // 初始化// 將當前路徑添加到sys.path中PyObject *sys = PyImport_ImportModule("sys");PyObject *path = PyObject_GetAttrString(sys, "path");PyList_Append(path, PyUnicode_FromString("."));// 導入nopara模塊/Python文件名PyObject *pModule = PyImport_ImportModule("nopara");if (!pModule){PyErr_Print();printf("ERROR: failed to load nopara.py\n");return 1;}// 獲取say_funny函數對象PyObject *pFunc = PyObject_GetAttrString(pModule, "say_funny");if (!pFunc || !PyCallable_Check(pFunc)){PyErr_Print();printf("ERROR: function say_funny not found or not callable\n");return 1;}// 調用say_funny函數并獲取返回值PyObject *pValue = PyObject_CallObject(pFunc, NULL);if (!pValue){PyErr_Print();printf("ERROR: function call failed\n");return 1;}// 釋放所有引用的Python對象Py_DECREF(pValue);Py_DECREF(pFunc);Py_DECREF(pModule);// 關閉Python解釋器Py_Finalize();return 0;
}
然要編譯和運行這個程序,可以使用以下命令(假設使用的是gcc編譯器和Python 3.10版本):
gcc -o nopara nopara.c -I /usr/include/python3.10/ -l python3.10
./nopara
輸出:
funny
3.4 調用有參python函數
C語言調用python有參函數,首先定義一個帶參數和返回值的函數:
def say_funny(category):print(category)return category
接下來用C語言進行調用,調用的流程無參函數的調用方式幾乎是一樣的是的:
#if 0
1、包含Python.h頭文件,以便使用Python API。
2、使用void Py_Initialize()初始化Python解釋器,
3、使用PyObject *PyImport_ImportModule(const char *name)和PyObject
*PyObject_GetAttrString(PyObject *o, const char *attr_name)獲取sys.path對象,并利用
int PyList_Append(PyObject *list, PyObject *item)將當前路徑.添加到sys.path中,以便加載
當前的Python模塊(Python文件即python模塊)。
4、使用PyObject *PyImport_ImportModule(const char *name)函數導入Python模塊,并檢查是否
有錯誤。
5、使用PyObject *PyObject_GetAttrString(PyObject *o, const char *attr_name)函數獲取
Python函數對象,并檢查是否可調用。
6、使用PyObject *Py_BuildValue(const char *format, ...)函數將C類型的數據結構轉換成
Python對象,作為Python函數的參數,沒有參數不需要調用
7、使用PyObject *PyObject_CallObject(PyObject *callable, PyObject *args)函數調用
Python函數,并獲取返回值。
8、使用int PyArg_Parse(PyObject *args, const char *format, ...)函數將返回值轉換為C類
型,并檢查是否有錯誤,沒有返回值時不需要調用。
9、使用void Py_DECREF(PyObject *o)函數釋放所有引用的Python對象。
10、結束時調用void Py_Finalize()函數關閉Python解釋器。
相關的函數參數說明參考網站(網站左上角輸入函數名即可開始搜索):
https://docs.python.org/zh-cn/3/c-api/import.html
#endif
無非多了兩步,第六步傳參:
6、使用PyObject *Py_BuildValue(const char *format, ...)函數創建一個Python元組或者對
象,作為Python函數的參數,沒有參數勢不需要調用
這里要注意的是,Py_BuildValue的第一個參數是類型轉換:C對應的Python的數據類型轉換對應的格式如下:
和第八步返回值的處理:
8、使用int PyArg_Parse(PyObject *args, const char *format, ...)函數將返回值轉換為C類
型,并檢查是否有錯誤,沒有返回值時不需要調用。
示例代碼如下:
#include <Python.h>
int main()
{Py_Initialize();// 將當前路徑添加到sys.path中PyObject *sys = PyImport_ImportModule("sys");PyObject *path = PyObject_GetAttrString(sys, "path");PyList_Append(path, PyUnicode_FromString("."));// 導入para模塊PyObject *pModule = PyImport_ImportModule("para");if (!pModule){PyErr_Print();printf("Error: failed to load nopara.py\n");}//獲取say_funny函數對象PyObject *pFunc = PyObject_GetAttrString(pModule, "say_funny");if (!pFunc){PyErr_Print();printf("Error: failed to load say_funny\n");}//創建一個字符串作為參數char *category = "comedy";PyObject *pArgs = Py_BuildValue("(s)", category);//調用say_funny函數并獲取返回值PyObject *pValue = PyObject_CallObject(pFunc, pArgs);if (!pValue){PyErr_Print();printf("Error: function call failed\n");}//將返回值轉換為C類型char *result = NULL;if (!PyArg_Parse(pValue, "s", &result)){PyErr_Print();printf("Error: parse failed\n");}//打印返回值printf("pValue=%s\n", result);//釋放所有引用的Python對象Py_DECREF(pValue);Py_DECREF(pFunc);Py_DECREF(pModule);//釋放所有引用的Python對象Py_Finalize();return 0;
}
然要編譯和運行這個程序,可以使用以下命令(假設使用的是gcc編譯器和Python 3.10版本):
gcc para.c -o para -I /usr/include/python3.10 -l python3.10
./para
輸出:
comedy
4. 阿里云垃圾識別方案
4.1 接入阿里云
在垃圾分類的項目中,我們采用阿里云視覺智能開發平臺的接口來做垃圾分類的識別方案,通過上傳
本地的拍照下的垃圾圖片,通過阿里提供的接口來識別出該垃圾是干垃圾、濕垃圾、回收垃圾還是有害垃圾。
對應官網地址如下:
https://vision.aliyun.com/
然后在上面的輸入框輸入“垃圾分類”:
可以跳轉到對應的垃圾分類的“免費開通"和”技術文檔頁面“:
https://vision.aliyun.com/experience/detail?
spm=a2c4g.11186623.0.0.6f1b55e2n5cBbZ&tagName=imagerecog&children=ClassifyingRubb ish
可以先選擇"技術文檔"查看下使用方法:
根據上面描述的指引,藍色為可點進去的詳細說明,完成注冊及運行環境的搭建。
重點步驟:
1. 開通阿里云賬號及圖像識別服務,用自己支付寶即可開通
2. 創建并獲取AccessKey ID和Secret
3. 在Linux或開發板上安裝所需的SDK
4. 根據示例代碼進行修改垃圾分類識別
3. 選擇免費開通,通過自己的支付寶或者賬號登錄即可
4. 登錄完成后,即可在產品控制臺->點擊獲取Acce Token獲取 對應的AccessKey ID和AccessKey
Secret
當然,在第一次獲取到AccessKey ID和AccessKey Secret,需要點擊創建AccessKey, 然后最好把
AccessKey.csv下載下來備份,不然會找不到AccessKey Secret就需要重新創建。
5. 在ubuntu 22.04或者全志開發板(orangepi 3.0.6)上安裝圖像識別(imagerecog)SDK
sudo apt install python3-pip
pip3 install alibabacloud_imagerecog20190930
6. 同時配置Linux環境,根據自己實際的ACCESS_KEY_ID和ACCESS_KEY_SECRET,下面的兩行寫入到家目錄下的.bashrc中
export ALIBABA_CLOUD_ACCESS_KEY_ID=“你的ID” #根據自己實際的ID填寫
export ALIBABA_CLOUD_ACCESS_KEY_SECRET="你的SECRET" #根據自己實際的SECRET填寫
也可以永久配置這兩行生效:
vi ~/.bashrc 和 /etc/profile #然后在末尾輸入上面兩行后保存
然后退出終端重新登錄下,此時再執行export,能看到這兩個Key的存在。
7. 抄襲”文件在本地或文件不在同一地域OSS“示例代碼,命名為garbage.py。
8. 同時將場景二注釋,場景一代碼打開,并輸入自己測試圖片的路徑,如下:
# -*- coding: utf-8 -*-
# 引入依賴包
# pip install alibabacloud_imagerecog20190930
import os
import io
from urllib.request import urlopen
from alibabacloud_imagerecog20190930.client import Client
from alibabacloud_imagerecog20190930.models import
ClassifyingRubbishAdvanceRequest
from alibabacloud_tea_openapi.models import Config
from alibabacloud_tea_util.models import RuntimeOptions
config = Config(
# 創建AccessKey ID和AccessKey Secret,請參考
https://help.aliyun.com/document_detail/175144.html。
# 如果您用的是RAM用戶的AccessKey,還需要為RAM用戶授予權限AliyunVIAPIFullAccess,請參考
https://help.aliyun.com/document_detail/145025.html
# 從環境變量讀取配置的AccessKey ID和AccessKey Secret。運行代碼示例前必須先配置環境變量。
access_key_id=os.environ.get('ALIBABA_CLOUD_ACCESS_KEY_ID'),
access_key_secret=os.environ.get('ALIBABA_CLOUD_ACCESS_KEY_SECRET'),
# 訪問的域名
endpoint='imagerecog.cn-shanghai.aliyuncs.com',
# 訪問的域名對應的region
region_id='cn-shanghai'
)
#場景一:文件在本地
img = open(r'/home/pg/test.jpg', 'rb')
#場景二:使用任意可訪問的url
#url = 'https://viapi-test-bj.oss-cn-beijing.aliyuncs.com/viapi-
3.0domepic/imagerecog/ClassifyingRubbish/ClassifyingRubbish1.jpg'
#img = io.BytesIO(urlopen(url).read())
classifying_rubbish_request = ClassifyingRubbishAdvanceRequest()
classifying_rubbish_request.image_urlobject = img
runtime = RuntimeOptions()
try:
# 初始化Client
client = Client(config)
response = client.classifying_rubbish_advance(classifying_rubbish_request,
runtime)
# 獲取整體結果
print(response.body)
except Exception as error:
# 獲取整體報錯信息
print(error)
# 獲取單個字段
print(error.code)
其中“/home/pg/test.jpg”為本地測試用圖片(根據在線文檔要求:圖像類型:JPEG、JPG、PNG,圖像大小:不大于3 MB,圖像分辨率:不限制圖像分辨率,但圖像分辨率太高可能會導致API識別超時,超時時間為5秒)
測試圖片如下:
9. 然后用python3 garbage.py命令測試運行
orangepi@orangepizero2:~$ python3 garbage.py
{'Data': {'Elements': [{'Category': '干垃圾', 'CategoryScore': 0.8855999999999999,
'Rubbish': '', 'RubbishScore': 0.0}], 'Sensitive': False}, 'RequestId':
'541130FA-92DE-5BCA-8BD4-14154F08BBF0'}
如上,測試成功,說明阿里云垃圾分類方案對接成功。
4.2 C語言調用阿里云Python接口
改造下garbage.py里面的代碼,封裝成一個函數,供后C程序調用
# -*- coding: utf-8 -*-
# 引入依賴包
# garbage.py
# pip install alibabacloud_imagerecog20190930
import os
import io
import json
from urllib.request import urlopen
from alibabacloud_imagerecog20190930.client import Client
from alibabacloud_imagerecog20190930.models import
ClassifyingRubbishAdvanceRequest
from alibabacloud_tea_openapi.models import Config
from alibabacloud_tea_util.models import RuntimeOptions
config = Config(# 創建AccessKey ID和AccessKey Secret,請參考https://help.aliyun.com/document_detail/175144.html。# 如果您用的是RAM用戶的AccessKey,還需要為RAM用戶授予權限AliyunVIAPIFullAccess,請參考https://help.aliyun.com/document_detail/145025.html# 從環境變量讀取配置的AccessKey ID和AccessKey Secret。運行代碼示例前必須先配置環境變量。access_key_id=os.environ.get('ALIBABA_CLOUD_ACCESS_KEY_ID'),access_key_secret=os.environ.get('ALIBABA_CLOUD_ACCESS_KEY_SECRET'),# 訪問的域名endpoint='imagerecog.cn-shanghai.aliyuncs.com',# 訪問的域名對應的regionregion_id='cn-shanghai'
)
def alibabacloud_garbage():#場景一:文件在本地img = open(r'/tmp/garbage.jpg', 'rb')#場景二:使用任意可訪問的url#url = 'https://viapi-test-bj.oss-cn-beijing.aliyuncs.com/viapi-3.0domepic/imagerecog/ClassifyingRubbish/ClassifyingRubbish1.jpg'#img = io.BytesIO(urlopen(url).read())classifying_rubbish_request = ClassifyingRubbishAdvanceRequest()classifying_rubbish_request.image_urlobject = imgruntime = RuntimeOptions()try:# 初始化Clientclient = Client(config)response = client.classifying_rubbish_advance(classifying_rubbish_request,runtime)print(response.body)return response.body.to_map()['Data']['Elements'][0]['Category']except Exception as error:print(type('獲取失敗'))return '獲取失敗'
參照3.4節有參python函數的做法, 實封裝并現以下代碼(garbage.c):
//garbage.c
void garbage_init(void)
{Py_Initialize();PyObject *sys = PyImport_ImportModule("sys");PyObject *path = PyObject_GetAttrString(sys, "path");PyList_Append(path, PyUnicode_FromString("."));
}
void garbage_final(void)
{Py_Finalize();
}
char *garbage_category(char *category)
{PyObject *pModule = PyImport_ImportModule("garbage");if (!pModule){PyErr_Print();printf("Error: failed to load garbage.py\n");goto FAILED_MODULE;}PyObject *pFunc = PyObject_GetAttrString(pModule, "alibabacloud_garbage");if (!pFunc){PyErr_Print();printf("Error: failed to load alibabacloud_garbage\n");goto FAILED_FUNC;}PyObject *pValue = PyObject_CallObject(pFunc, NULL);if (!pValue){PyErr_Print();printf("Error: function call failed\n");goto FAILED_VALUE;}char *result = NULL;if (!PyArg_Parse(pValue, "s", &result)){PyErr_Print();printf("Error: parse failed");goto FAILED_RESULT;}category = (char *)malloc(sizeof(char) * (strlen(result) + 1) );memset(category, 0, (strlen(result) + 1));strncpy(category, result, (strlen(result) + 1));FAILED_RESULT:Py_DECREF(pValue);FAILED_VALUE:Py_DECREF(pFunc);FAILED_FUNC:Py_DECREF(pModule);FAILED_MODULE:return category;
}
頭文件(garbage.h)如下:
//garbage.h
#ifndef __GARBAGE__H
#define __GARBAGE__Hvoid garbage_init(void);
void garbage_final(void);
char *garbage_category(char *category);#endif
測試代碼(garbagetest.c)如下:
//garbagetest.c
#include <stdio.h>
#include <stdlib.h>
#include "garbage.h"
int main()
{char *category = NULL;garbage_init();category = garbage_category(category);printf("category=%s\n", category);garbage_final();free(category);return 0;
}
然要編譯和運行這個程序,可以使用以下命令(假設使用的是gcc編譯器和Python 3.10版本):
gcc -o garbagetest garbagetest.c garbage.c garbage.h -I /usr/include/python3.10/
-l python3.10
./garbagetest
輸出:
category=干垃圾
5. 香橙派使用攝像頭
詳細可參考《OrangePi_Zero2_H616用戶手冊v4.0.pdf》 中的3.13.6 USB攝像頭測試章節。
操作如下:
- 首先將 USB 攝像頭插入到 Orange Pi 開發板的 USB 接口中
- 然后通過 lsmod 命令可以看到內核自動加載了下面的模塊
lsmod | grep uvcvideo | grep -v grep
//結果
uvcvideo 106496 0
- 通過 v4l2-ctl 命令可以看到 USB 攝像頭的設備節點信息為/dev/videox(x有可能是0 1或者2等數字)
sudo apt update
sudo apt install -y v4l-utils
v4l2-ctl --list-devices
//結果
USB 2.0 Camera (usb-sunxi-ehci-1):
/dev/video1
注意 v4l2 中的 l 是小寫字母 l,不是數字1。
另外 video 的序號不一定都是 video1,請以實際看到的為準。
-
使用 fswebcam 測試 USB 攝像頭
a. 安裝 fswebcam
sudo apt update sudo apt-get install -y fswebcam
b. 安裝完 fswebcam 后可以使用下面的命令來拍照
a) -d 選項用于指定 USB 攝像頭的設備節點
b) --no-banner 用于去除照片的水印
c) -r 選項用于指定照片的分辨率
d) -S 選項用設置于跳過前面的幀數
e) ./image.jpg 用于設置生成的照片的名字和路徑sudo fswebcam -d /dev/video1 --no-banner -r 1280x720 -S 5 ./image.jpg //注意這里的video1要根據實際的情況修改,
c. 在服務器版的 linux 系統中,拍完照后可以直接通過mobaxterm拖到電腦桌面看或者使用 scp 命令將拍好的圖片傳到Ubuntu PC 上鏡像觀看:
scp image.jpg orangepi@192.168.1.55:/home/orangepi/ //根據自己的實際家目錄修改pg這個用戶名稱
-
使用 mjpg-streamer 測試 USB 攝像頭
a. 下載 mjpg-streamer
a) Github 的下載地址:
git clone https://github.com/jacksonliam/mjpg-streamer
b) Gitee 的鏡像下載地址為:
git clone https://gitee.com/leeboby/mjpg-streamer
b. 安裝依賴的軟件包
a) Ubuntu 系統sudo apt-get install -y cmake libjpeg8-dev
c. 編譯安裝 mjpg-streamer
cd mjpg-streamer/mjpg-streamer-experimental make -j4 sudo make install
d. 然后輸入下面的命令啟動 mjpg_streamer
注意,video的序號不一定都是 video1,請以實際看到的為準。
export LD_LIBRARY_PATH=. sudo ./mjpg_streamer -i "./input_uvc.so -d /dev/video0 -u -f 30" -o "./output_http.so -w ./www"
e. 然后在和開發板同一局域網的 Ubuntu PC 或者 Windows PC 或者手機的瀏覽orange Pi器中輸入 【開發板的 IP地址:8080】就能看到攝像頭輸出的視頻了
f. 推薦使用 mjpg-streamer 來測試 USB 攝像頭,比 motion 流暢很多,使用mjpg-streamer 感覺不到任何卡頓
g.修改 start.sh腳本,將start.sh里的:./mjpg_streamer -i "./input_uvc.so" -o "./output_http.so -w ./www"
字段修改為:
./mjpg_streamer -i "./input_uvc.so -d /dev/video1 -u -f 30" -o "./output_http.so -w ./www" //注意這里的video1需要根據實際情況修改
這樣就可以通過執行./start.sh運行攝像頭了。
補充設置開機自啟動 mjpg-streamer 服務
- 在某目錄下創建mjpg.sh腳本文件,寫入以下內容并且保存關閉
#!/bin/bashcd /home/orangepi/mjpg-streamer/mjpg-streamer-experimental/
./start.sh
- 進入/etc/xdg/autostart目錄創建一個mjpg.desktop文件并寫入以下內容,保存關閉
cd /etc/xdg/autostart
vi mjpg.desktop
//內容
[Desktop Entry]
Name=mjpg
Exec=/home/orangepi/mjpg.sh
Type=Application
NoDisplay=true
- 重啟香橙派,并查看該服務是否已經自動啟動
6. 語音模塊的配置
本項目的語音模塊采用智能公元家的SU-03T模塊,進入官網創建產品即可傻瓜式配置
1. 首先更改P6,P7口為串口模式
2. 設置喚醒詞
3. 設置命令詞,語音播報垃圾類型的觸發方式為串口輸入,并設置喚醒詞的回復詞
4. 設置命令詞的控制,將串口輸入喚醒語音播報的設置和語音喚醒向串口發送數據的設置補充完整
這個是垃圾類型播報的喚醒與動作詳細設置
這個是語音喚醒并且向串口發送識別指令命令動作的詳細設置
5. 剩余內容根據自己愛好進行設置,最后點擊右上角生成SDK按鈕等待生成
注意:生成固件需要一段時間,下載完畢后要點擊下載固件,如果點擊下載SDK燒錄以后可能會出現一些問題(本人親身經歷找bug找了半天)
最后根據產品說明用燒錄軟件將固件燒錄入語音模塊,燒錄完成后可以利用串口調試助手進行測試功能的完整性。
7. VSCode安裝并通過SSH連接全志開發板
1. 進入VSCode官網 Visual Studio Code - Code Editing. Redefined,下載安裝包
2. 安裝Remote Development
3. 安裝中文插件
4. 配置字體為20
配置文件–>首選項->設置->Font Size為20
5. 配置遠程連接
6. 配置遠程目錄
連接成功后點擊打開文件夾并輸入文件目錄進入指定文件目錄,即可看到代碼文件
8. 語音模塊和阿里云結合
8.1 環境準備
- 將語音模塊接在UART5的位置
- 在orange pi 3.0.6上確認已經配置開啟了uart5:(overlays=uart5)
orangepi@orangepizero2:~/garbage$ cat /boot/orangepiEnv.txt
verbosity=1
bootlogo=false
console=both
disp_mode=1920x1080p60
overlay_prefix=sun50i-h616
rootdev=UUID=15a0010c-94e1-412f-b030-199e90c16cb1
rootfstype=ext4
overlays=uart5 i2c3
usbstoragequirks=0x2537:0x1066:u,0x2537:0x1068:u
- 同時將USB攝像頭接到香橙派上
- 確認已經運行了mjpg-streamer服務
orangepi@orangepizero2:~/garbage$ ps ax | grep mjpg
1704 ? S 0:00 /bin/bash /home/orangepi/mjpg.sh
1710 ? Sl 0:29 ./mjpg_streamer -i ./input_uvc.so -d /dev/video1 -u
-f 30 -o ./output_http.so -w ./www
5594 pts/0 S+ 0:00 grep --color=auto mjpg
orangepi@orangepizero2:~/garbage$
8.2 代碼實現
- 首先創建garbage目錄,將garbage.c、garbage.h、 garbage.py三個文件拷貝進來。
- 參照之前博文Linux原生串口開發代碼實現, 修改uartTool.h、uartTool.c:
//uartTool.h
#ifndef __UARTTOOL_H
#define __UARTTOOL_H
int myserialOpen (const char *device, const int baud);
void serialSendstring (const int fd, const unsigned char *s, int len);
int serialGetstring (const int fd, unsigned char *buffer);
#define SERIAL_DEV "/dev/ttyS5"
#define BAUD 115200
#endif
//uartTool.c
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdarg.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>#include "wiringSerial.h"int mySerialOpen (const char *device, const int baud)
{struct termios options ;speed_t myBaud ;int status, fd ;switch (baud){case 9600: myBaud = B9600 ; break ;case 115200: myBaud = B115200 ; break ;}if ((fd = open (device, O_RDWR | O_NOCTTY | O_NDELAY | O_NONBLOCK)) == -1)return -1 ;fcntl (fd, F_SETFL, O_RDWR) ;// Get and modify current options:tcgetattr (fd, &options) ;cfmakeraw (&options) ;cfsetispeed (&options, myBaud) ;cfsetospeed (&options, myBaud) ;options.c_cflag |= (CLOCAL | CREAD) ;options.c_cflag &= ~PARENB ;options.c_cflag &= ~CSTOPB ;options.c_cflag &= ~CSIZE ;options.c_cflag |= CS8 ;options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG) ;options.c_oflag &= ~OPOST ;options.c_cc [VMIN] = 0 ;options.c_cc [VTIME] = 100 ; // Ten seconds (100 deciseconds)tcsetattr (fd, TCSANOW, &options) ;ioctl (fd, TIOCMGET, &status);status |= TIOCM_DTR ;status |= TIOCM_RTS ;ioctl (fd, TIOCMSET, &status);usleep (10000) ; // 10mSreturn fd ;
}void serialSendString (const int fd, const unsigned char *s,int len)
{int ret;ret = write (fd, s, len);if (ret < 0)printf("Serial Puts Error\n");
}int serialGetString (const int fd,unsigned char *buffer)
{int n_read;n_read = read(fd,buffer,32);return n_read;
}
- garbage.h頭文件
#ifndef __GARBAGE__H
#define __GARBAGE__H
void garbage_init(void);
void garbage_final(void);
char *garbage_category(char *category);
//增加拍照指令和照片路徑宏定義
#define WGET_CMD "wget http://127.0.0.1:8080/?action=snapshot -O
/tmp/garbage.jpg"
#define GARBAGE_FILE "/tmp/garbage.jpg"
#endif
- main.c實現
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "uartTool.h"
#include "garbage.h"static int detect_process(const char *process_name) // 判斷進程是否在運行
{int n = -1;FILE *strm;char buf[128] = {0};sprintf(buf, "ps -ax | grep %s|grep -v grep", process_name);if ((strm = popen(buf, "r")) != NULL){if (fgets(buf, sizeof(buf), strm) != NULL){n = atoi(buf);}}else{return -1;}pclose(strm);return n;
}int main(int argc, char *argv[])
{int serial_fd = -1;int len = 0;int ret = -1;char *category = NULL;unsigned char buffer[6] = {0xAA, 0x55, 0x00, 0x00, 0x55, 0xAA};wiringPiSetup();garbage_init();ret = detect_process("mjpg_streamer");if (-1 == ret){printf("detect process failed\n");goto END;}serial_fd = mySerialOpen(SERIAL_DEV, BAUD);if (serial_fd == -1){goto END;}while (1){len = serialGetString(serial_fd, buffer);if (len > 0 && buffer[2] == 0x46){buffer[2] = 0x00;system(WGET_CMD);if (0 == access(GARBAGE_FILE, F_OK)){category = garbage_category(category);if (strstr(category, "干垃圾")){buffer[2] = 0x41;}else if (strstr(category, "濕垃圾")){buffer[2] = 0x42;}else if (strstr(category, "可回收垃圾")){buffer[2] = 0x43;}else if (strstr(category, "有害垃圾")){buffer[2] = 0x44;}else{buffer[2] = 0x45;}}else{buffer[2] = 0x45;}serialSendString(serial_fd, buffer, 6);buffer[2] = 0x00;remove(GARBAGE_FILE);}}close(serial_fd);
END:gargage_final();return 0;
}
9. 增加垃圾桶及開關蓋功能
實現功能:使用語音模塊和攝像頭在香橙派上做垃圾智能分類識別, 同時根據識別結果開關不同的垃圾桶的蓋子。
9.1 環境準備
在語音模塊和阿里云結合搭建環境的基礎上, 接上用于開關蓋的舵機(舵機模塊可以直接粘在垃
圾桶內側),當前代碼里僅用了1個舵機用于示例代碼的編寫,可以自行多購買4個垃圾桶和舵機用于區分4垃圾類型,接線位置如下:(我只用了Pin5,干垃圾開Pin5對應舵機,其余只發送信號)
實物圖:
9.2 代碼實現
流程圖:
具體代碼實現:
- 增加用于實現開光蓋(驅動舵機)的源碼文件(pwm.c)
#include <wiringPi.h>
#include <softPwm.h>
// 根據公式:PWMfreq = 1 x 10^6 / (100 x range) ,要得到PWM頻率為50Hz,則range為200,即周期分為200步,控制精度相比硬件PWM較低。
void pwm_write(int pwm_pin)
{pinMode(pwm_pin, OUTPUT);softPwmCreate(pwm_pin, 0, 200); // range設置周期分為200步, 周期20mssoftPwmWrite(pwm_pin, 10); // 1ms 45度delay(1000);softPwmStop(pwm_pin);
}
void pwm_stop(int pwm_pin)
{pinMode(pwm_pin, OUTPUT);softPwmCreate(pwm_pin, 0, 200); // range設置周期分為200步, 周期20mssoftPwmWrite(pwm_pin, 5); // 0.5ms 0度delay(1000);softPwmStop(pwm_pin);
}
- pwm.h代碼
#ifndef __PWM__H
#define __PWM__H#define PWM_GARBAGE 7
#define PWM_RECOVERABLE_GARBAGE 5void pwm_write(int pwm_pin);
void pwm_stop(int pwm_pin);#endif
- main.c里增加調用舵機的控制代碼
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "uartTool.h"
#include "garbage.h"
#include "pwm.h"static int detect_process(const char *process_name) // 判斷進程是否在運行
{int n = -1;FILE *strm;char buf[128] = {0};sprintf(buf, "ps -ax | grep %s|grep -v grep", process_name);if ((strm = popen(buf, "r")) != NULL){if (fgets(buf, sizeof(buf), strm) != NULL){n = atoi(buf);}}else{return -1;}pclose(strm);return n;
}int main(int argc, char *argv[])
{int serial_fd = -1;int len = 0;int ret = -1;char *category = NULL;unsigned char buffer[6] = {0xAA, 0x55, 0x00, 0x00, 0x55, 0xAA};wiringPiSetup();garbage_init();ret = detect_process("mjpg_streamer");if (-1 == ret){printf("detect process failed\n");goto END;}serial_fd = mySerialOpen(SERIAL_DEV, BAUD);if (serial_fd == -1){goto END;}while (1){len = serialGetString(serial_fd, buffer);if (len > 0 && buffer[2] == 0x46){buffer[2] = 0x00;system(WGET_CMD);if (0 == access(GARBAGE_FILE, F_OK)){category = garbage_category(category);if (strstr(category, "干垃圾")){buffer[2] = 0x41;}else if (strstr(category, "濕垃圾")){buffer[2] = 0x42;}else if (strstr(category, "可回收垃圾")){buffer[2] = 0x43;}else if (strstr(category, "有害垃圾")){buffer[2] = 0x44;}else{buffer[2] = 0x45;}}else{buffer[2] = 0x45;}serialSendString(serial_fd, buffer, 6);if (buffer[2] == 0x41){pwm_write(PWM_RECOVERABLE_GARBAGE);delay(2000);pwm_stop(PWM_RECOVERABLE_GARBAGE);}else if (buffer[2] != 0x45){pwm_write(PWM_GARBAGE);delay(2000);pwm_stop(PWM_GARBAGE);}buffer[2] = 0x00;remove(GARBAGE_FILE);}}close(serial_fd);
END:gargage_final();return 0;
}
10. 項目代碼優化
在之前實現的代碼中, 主函數是單線程執行的, 導致整個代碼的可擴展性非常差,比如想加OLED顯示或者添加網絡控制變得非常復雜,而且執行一次識別開關蓋的流程非常長。因此,調整下代碼架構,增加并發功能、提升代碼的可擴展性和執行效率。
- 代碼大致流程圖如下:
- 修改main.c代碼,調整整體main函數的代碼架構,利用多線程實現具體的功能(用到了線程里的條
件變量控制線程間的數據同步)如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <wiringPi.h>
#include <pthread.h>#include "uartTool.h"
#include "garbage.h"
#include "pwm.h"static int detect_process(const char *process_name) // 判斷進程是否在運行
{int n = -1;FILE *strm;char buf[128] = {0};sprintf(buf, "ps -ax | grep %s|grep -v grep", process_name);if ((strm = popen(buf, "r")) != NULL){if (fgets(buf, sizeof(buf), strm) != NULL){n = atoi(buf);}}else{return -1;}pclose(strm);return n;
}int serial_fd = -1;
pthread_cond_t cond;
pthread_mutex_t mutex;void *pget_voice(void *arg)
{unsigned char buffer[6] = {0xAA, 0x55, 0x00, 0x00, 0x55, 0xAA};int len = 0;if (serial_fd == -1){printf("%s|%s|%d: open serial failed\n",__FILE__,__func__,__LINE__);pthread_exit(0);}while(1){len = serialGetString(serial_fd, buffer);if (len > 0 && buffer[2] == 0x46){pthread_mutex_lock(&mutex);pthread_cond_signal(&cond);pthread_mutex_unlock(&mutex);}}pthread_exit(0);
}void *psend_voice(void *arg)
{unsigned char *buffer = (unsigned char *)arg;if (serial_fd == -1){printf("%s|%s|%d: open serial failed\n",__FILE__,__func__,__LINE__);pthread_exit(0);}pthread_detach(pthread_self());//忽略線程等待,自己釋放資源if(NULL != buffer){printf("0x%x\n",buffer[2]);serialSendString(serial_fd, buffer, 6);}pthread_exit(0);
}void *popen_trash(void *arg)
{unsigned char *buffer = (unsigned char *)arg;if (serial_fd == -1){printf("%s|%s|%d: open serial failed\n",__FILE__,__func__,__LINE__);pthread_exit(0);}printf("0x%x\n",buffer[2]);pthread_detach(pthread_self());//忽略線程等待,自己釋放資源if (buffer[2] == 0x41){pwm_write(PWM_RECOVERABLE_GARBAGE);delay(5000);pwm_stop(PWM_RECOVERABLE_GARBAGE);}else if (buffer[2] != 0x45){pwm_write(PWM_GARBAGE);delay(5000);pwm_stop(PWM_GARBAGE);}pthread_exit(0);
}void *pcategory(void *arg)
{unsigned char buffer[6] = {0xAA, 0x55, 0x00, 0x00, 0x55, 0xAA};char *category = NULL;pthread_t send_voice_tid,trash_tid;while(1){pthread_mutex_lock(&mutex);pthread_cond_wait(&cond,&mutex);pthread_mutex_unlock(&mutex);buffer[2] = 0x00;system(WGET_CMD);if (0 == access(GARBAGE_FILE, F_OK)){category = garbage_category(category);if (strstr(category, "干垃圾")){buffer[2] = 0x41;}else if (strstr(category, "濕垃圾")){buffer[2] = 0x42;}else if (strstr(category, "可回收垃圾")){buffer[2] = 0x43;}else if (strstr(category, "有害垃圾")){buffer[2] = 0x44;}else{buffer[2] = 0x45;}}else{buffer[2] = 0x45;}//開語音播報線程pthread_create(&send_voice_tid,NULL,psend_voice,(void *)buffer);//開垃圾桶線程pthread_create(&trash_tid,NULL,popen_trash,(void *)buffer);remove(GARBAGE_FILE);}pthread_exit(0);
}int main(int argc, char *argv[])
{int ret = -1;pthread_t get_voice_tid,category_tid;wiringPiSetup();garbage_init();ret = detect_process("mjpg_streamer");if (-1 == ret){printf("detect process failed\n");goto END;}serial_fd = mySerialOpen(SERIAL_DEV, BAUD);if (serial_fd == -1){goto END;}//開語音識別線程pthread_create(&get_voice_tid,NULL,pget_voice,NULL);//開阿里云交互線程pthread_create(&category_tid,NULL,pcategory,NULL);pthread_join(get_voice_tid,NULL);pthread_join(category_tid,NULL);pthread_mutex_destroy(&mutex);pthread_cond_destroy(&cond);close(serial_fd);
END:gargage_final();return 0;
}
此代碼中,實現了等待語音模塊發送識別指令線程和拍照發送給阿里云識別并修改指令串線程的同步和并發執行,還實現了語音播報和開垃圾桶兩個線程的并發執行。
11. 增加OLED顯示功能
關于硬件接線,將OLED屏幕分別接香橙派開發板的SDA,SCK,5V,GND四個接線柱。
增加myoled.c和myoled.h兩個文件
//oled.c
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <stdint.h>
#include "oled.h"
#include "font.h"
// 包含頭文件
#include "myoled.h"
#define FILENAME "/dev/i2c-3"static struct display_info disp;int oled_show(void *arg)
{unsigned char *buffer = (unsigned char *)arg;oled_putstrto(&disp, 0, 9 + 1, "This garbage is:");disp.font = font2;switch (buffer[2]){case 0x41:oled_putstrto(&disp, 0, 20, "dry waste");break;case 0x42:oled_putstrto(&disp, 0, 20, "wet waste");break;case 0x43:oled_putstrto(&disp, 0, 20, "recyclable waste");break;case 0x44:oled_putstrto(&disp, 0, 20, "hazardous waste");break;case 0x45:oled_putstrto(&disp, 0, 20, "recognition failed");break;}disp.font = font2;oled_send_buffer(&disp);return 0;
}
int myoled_init(void)
{int e;disp.address = OLED_I2C_ADDR;disp.font = font2;e = oled_open(&disp, FILENAME);e = oled_init(&disp);return e;
}
//myoled.h
#ifndef __MYOLED__H
#define __MYOLED__Hint myoled_init(void);
int oled_show(void *arg);#endif
修改main.c文件,加入oled屏幕顯示線程
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <wiringPi.h>
#include <pthread.h>#include "uartTool.h"
#include "garbage.h"
#include "pwm.h"
#include "myoled.h"static int detect_process(const char *process_name) // 判斷進程是否在運行
{int n = -1;FILE *strm;char buf[128] = {0};sprintf(buf, "ps -ax | grep %s|grep -v grep", process_name);if ((strm = popen(buf, "r")) != NULL){if (fgets(buf, sizeof(buf), strm) != NULL){n = atoi(buf);}}else{return -1;}pclose(strm);return n;
}int serial_fd = -1;
pthread_cond_t cond;
pthread_mutex_t mutex;void *pget_voice(void *arg)
{unsigned char buffer[6] = {0xAA, 0x55, 0x00, 0x00, 0x55, 0xAA};int len = 0;if (serial_fd == -1){printf("%s|%s|%d: open serial failed\n", __FILE__, __func__, __LINE__);pthread_exit(0);}while (1){len = serialGetString(serial_fd, buffer);if (len > 0 && buffer[2] == 0x46){pthread_mutex_lock(&mutex);pthread_cond_signal(&cond);pthread_mutex_unlock(&mutex);}}pthread_exit(0);
}void *psend_voice(void *arg)
{unsigned char *buffer = (unsigned char *)arg;if (serial_fd == -1){printf("%s|%s|%d: open serial failed\n", __FILE__, __func__, __LINE__);pthread_exit(0);}pthread_detach(pthread_self()); // 忽略線程等待,自己釋放資源if (NULL != buffer){serialSendString(serial_fd, buffer, 6);}pthread_exit(0);
}void *popen_trash(void *arg)
{unsigned char *buffer = (unsigned char *)arg;if (serial_fd == -1){printf("%s|%s|%d: open serial failed\n", __FILE__, __func__, __LINE__);pthread_exit(0);}pthread_detach(pthread_self()); // 忽略線程等待,自己釋放資源if (buffer[2] == 0x41){pwm_write(PWM_RECOVERABLE_GARBAGE);delay(3000);pwm_stop(PWM_RECOVERABLE_GARBAGE);}else if (buffer[2] != 0x45){pwm_write(PWM_GARBAGE);delay(3000);pwm_stop(PWM_GARBAGE);}pthread_exit(0);
}void *poled_show(void *arg)
{pthread_detach(pthread_self());myoled_init();oled_show(arg);pthread_exit(0);
}void *pcategory(void *arg)
{unsigned char buffer[6] = {0xAA, 0x55, 0x00, 0x00, 0x55, 0xAA};char *category = NULL;pthread_t send_voice_tid, trash_tid,oled_tid;while (1){pthread_mutex_lock(&mutex);pthread_cond_wait(&cond, &mutex);pthread_mutex_unlock(&mutex);buffer[2] = 0x00;system(WGET_CMD);if (0 == access(GARBAGE_FILE, F_OK)){category = garbage_category(category);if (strstr(category, "干垃圾")){buffer[2] = 0x41;}else if (strstr(category, "濕垃圾")){buffer[2] = 0x42;}else if (strstr(category, "可回收垃圾")){buffer[2] = 0x43;}else if (strstr(category, "有害垃圾")){buffer[2] = 0x44;}else{buffer[2] = 0x45;}}else{buffer[2] = 0x45;}// 開語音播報線程pthread_create(&send_voice_tid, NULL, psend_voice, (void *)buffer);// 開垃圾桶線程pthread_create(&trash_tid, NULL, popen_trash, (void *)buffer);//oled顯示線程pthread_create(&oled_tid, NULL, poled_show, (void *)buffer);remove(GARBAGE_FILE);}pthread_exit(0);
}int main(int argc, char *argv[])
{int ret = -1;pthread_t get_voice_tid, category_tid;wiringPiSetup();garbage_init();ret = detect_process("mjpg_streamer");if (-1 == ret){printf("detect process failed\n");goto END;}serial_fd = mySerialOpen(SERIAL_DEV, BAUD);if (serial_fd == -1){goto END;}// 開語音識別線程pthread_create(&get_voice_tid, NULL, pget_voice, NULL);// 開阿里云交互線程pthread_create(&category_tid, NULL, pcategory, NULL);pthread_join(get_voice_tid, NULL);pthread_join(category_tid, NULL);pthread_mutex_destroy(&mutex);pthread_cond_destroy(&cond);close(serial_fd);
END:gargage_final();return 0;
}
編譯運行
11. 增加網絡控制功能
11.1 實現需求
通過Socket遠程接入控制垃圾識別功能。
11.2 TCP 心跳機制解決Soket異常斷開問題
Socket客戶端得斷開情形無非就兩種情況:
- 客戶端能夠發送狀態給服務器;正常斷開,強制關閉客戶端等,客戶端能夠做出反應。
- 客戶端不能發送狀態給服務器;突然斷網,斷電,客戶端卡死等,客戶端根本沒機會做出反應,服務器更不了解客戶端狀態,導致服務器異常等待。
為了解決上述問題,引入TCP心跳包機制:
心跳包的實現,心跳包就是服務器定時向客戶端發送查詢信息,如果客戶端有回應就代表連接正常,
類似于linux系統的看門狗機制。心跳包的機制有一種方法就是采用TCP_KEEPALIVE機制,它是一種用于檢測TCP連接是否存活的機制,它的原理是在一定時間內沒有數據往來時,發送探測包給對方,如果對方沒有響應,就認為連接已經斷開。TCP_KEEPALIVE機制可以通過設置一些參數來調整,如探測時間間隔、探測次數等。
Linux內核提供了通過sysctl命令查看和配置TCP KeepAlive參數的方法。
- 查看當前系統的TCP KeepAlive參數
sysctl net.ipv4.tcp_keepalive_time
sysctl net.ipv4.tcp_keepalive_probes
sysctl net.ipv4.tcp_keepalive_intvl
- 修改TCP KeepAlive參數
sysctl net.ipv4.tcp_keepalive_time=3600
11.3 C語言實現TCP KeepAlive功能
對于Socket而言,可以在程序中通過socket選項開啟TCP KeepAlive功能,并配置參數。對應的Socket選項分別為 SO_KEEPALIVE 、 TCP_KEEPIDLE 、 TCP_KEEPCNT 、 TCP_KEEPINTVL。
int keepalive = 1; // 開啟TCP KeepAlive功能
int keepidle = 5; // tcp_keepalive_time 3s內沒收到數據開始發送心跳包
int keepcnt = 3; // tcp_keepalive_probes 每次發送心跳包的時間間隔,單位秒
int keepintvl = 3; // tcp_keepalive_intvl 每3s發送一次心跳包
setsockopt(client_socketfd, SOL_SOCKET, SO_KEEPALIVE, (void *)&keepalive,sizeof(keepalive));
setsockopt(client_socketfd, SOL_TCP, TCP_KEEPIDLE, (void *) &keepidle, sizeof(keepidle));
setsockopt(client_socketfd, SOL_TCP, TCP_KEEPCNT, (void *)&keepcnt, sizeof(keepcnt));
setsockopt(client_socketfd, SOL_TCP, TCP_KEEPINTVL, (void *)&keepintvl, sizeof(keepintvl));
11.4 網絡控制功能代碼實現
socket.h代碼實現如下:
#ifndef __SOCKET__H
#define __SOCKET__H#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <errno.h>#define IPADDR "192.168.0.10" //填寫自己實際的ip地址
#define IPPORT "8192"
#define BUF_SIZE 6int socket_init(const char *ipaddr, const char *port);#endif
socket.c代碼實現如下:
#include "socket.h"int socket_init(const char *ipaddr, const char *port)
{int s_fd = -1;int ret = -1;struct sockaddr_in s_addr;memset(&s_addr, 0, sizeof(struct sockaddr_in));// 1.sockets_fd = socket(AF_INET, SOCK_STREAM, 0);if (s_fd == -1){perror("socket");return -1;}s_addr.sin_family = AF_INET;s_addr.sin_port = htons(atoi(port));inet_aton(ipaddr, &s_addr.sin_addr);// 2. bindret = bind(s_fd, (struct sockaddr *)&s_addr, sizeof(struct sockaddr_in));if (-1 == ret){perror("bind");return -1;}// 3. listenret = listen(s_fd, 1); // 只監聽1個連接,排隊扔垃圾if (-1 == ret){perror("listen");return -1;}return s_fd;
}
main.c代碼如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <wiringPi.h>
#include <pthread.h>#include "uartTool.h"
#include "garbage.h"
#include "pwm.h"
#include "myoled.h"
#include "socket.h"static int detect_process(const char *process_name) // 判斷進程是否在運行
{int n = -1;FILE *strm;char buf[128] = {0};sprintf(buf, "ps -ax | grep %s|grep -v grep", process_name);if ((strm = popen(buf, "r")) != NULL){if (fgets(buf, sizeof(buf), strm) != NULL){n = atoi(buf);}}else{return -1;}pclose(strm);return n;
}int serial_fd = -1;
pthread_cond_t cond;
pthread_mutex_t mutex;void *pget_voice(void *arg)
{unsigned char buffer[6] = {0xAA, 0x55, 0x00, 0x00, 0x55, 0xAA};int len = 0;if (serial_fd == -1){printf("%s|%s|%d: open serial failed\n", __FILE__, __func__, __LINE__);pthread_exit(0);}while (1){len = serialGetString(serial_fd, buffer);if (len > 0 && buffer[2] == 0x46){pthread_mutex_lock(&mutex);pthread_cond_signal(&cond);pthread_mutex_unlock(&mutex);}}pthread_exit(0);
}void *psend_voice(void *arg)
{unsigned char *buffer = (unsigned char *)arg;if (serial_fd == -1){printf("%s|%s|%d: open serial failed\n", __FILE__, __func__, __LINE__);pthread_exit(0);}pthread_detach(pthread_self()); // 忽略線程等待,自己釋放資源if (NULL != buffer){serialSendString(serial_fd, buffer, 6);}pthread_exit(0);
}void *popen_trash(void *arg)
{unsigned char *buffer = (unsigned char *)arg;if (serial_fd == -1){printf("%s|%s|%d: open serial failed\n", __FILE__, __func__, __LINE__);pthread_exit(0);}pthread_detach(pthread_self()); // 忽略線程等待,自己釋放資源if (buffer[2] == 0x41){pwm_write(PWM_RECOVERABLE_GARBAGE);delay(3000);pwm_stop(PWM_RECOVERABLE_GARBAGE);}else if (buffer[2] != 0x45){pwm_write(PWM_GARBAGE);delay(3000);pwm_stop(PWM_GARBAGE);}pthread_exit(0);
}void *poled_show(void *arg)
{pthread_detach(pthread_self());myoled_init();oled_show(arg);pthread_exit(0);
}void *pcategory(void *arg)
{unsigned char buffer[6] = {0xAA, 0x55, 0x00, 0x00, 0x55, 0xAA};char *category = NULL;pthread_t send_voice_tid, trash_tid, oled_tid;while (1){pthread_mutex_lock(&mutex);pthread_cond_wait(&cond, &mutex);pthread_mutex_unlock(&mutex);buffer[2] = 0x00;system(WGET_CMD);if (0 == access(GARBAGE_FILE, F_OK)){category = garbage_category(category);if (strstr(category, "干垃圾")){buffer[2] = 0x41;}else if (strstr(category, "濕垃圾")){buffer[2] = 0x42;}else if (strstr(category, "可回收垃圾")){buffer[2] = 0x43;}else if (strstr(category, "有害垃圾")){buffer[2] = 0x44;}else{buffer[2] = 0x45;}}else{buffer[2] = 0x45;}// 開語音播報線程pthread_create(&send_voice_tid, NULL, psend_voice, (void *)buffer);// 開垃圾桶線程pthread_create(&trash_tid, NULL, popen_trash, (void *)buffer);// oled顯示線程pthread_create(&oled_tid, NULL, poled_show, (void *)buffer);remove(GARBAGE_FILE);}pthread_exit(0);
}void *pget_socket(void *arg)
{int s_fd = -1;int c_fd = -1;char buffer[6];int nread = -1;struct sockaddr_in c_addr;memset(&c_addr, 0, sizeof(struct sockaddr_in));s_fd = socket_init(IPADDR, IPPORT);printf("%s|%s|%d:s_fd=%d\n", __FILE__, __func__, __LINE__, s_fd);if (-1 == s_fd){pthread_exit(0);}sleep(3);int clen = sizeof(struct sockaddr_in);while (1){c_fd = accept(s_fd, (struct sockaddr *)&c_addr, &clen);int keepalive = 1; // 開啟TCP KeepAlive功能int keepidle = 5; // tcp_keepalive_time 3s內沒收到數據開始發送心跳包int keepcnt = 3; // tcp_keepalive_probes 發送3次int keepintvl = 3; // tcp_keepalive_intvl 每3s發送一次心跳包setsockopt(c_fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&keepalive,sizeof(keepalive));setsockopt(c_fd, SOL_TCP, TCP_KEEPIDLE, (void *)&keepidle, sizeof(keepidle));setsockopt(c_fd, SOL_TCP, TCP_KEEPCNT, (void *)&keepcnt, sizeof(keepcnt));setsockopt(c_fd, SOL_TCP, TCP_KEEPINTVL, (void *)&keepintvl, sizeof(keepintvl));printf("%s|%s|%d: Accept a connection from %s:%d\n", __FILE__, __func__,__LINE__, inet_ntoa(c_addr.sin_addr), ntohs(c_addr.sin_port));if (c_fd == -1){perror("accept");continue;}while (1){memset(buffer, 0, sizeof(buffer));nread = recv(c_fd, buffer, sizeof(buffer), 0); // n_read = read(c_fd,buffer, sizeof(buffer));printf("%s|%s|%d:nread=%d, buffer=%s\n", __FILE__, __func__,__LINE__, nread, buffer);if (nread > 0){if (strstr(buffer, "open")){pthread_mutex_lock(&mutex);pthread_cond_signal(&cond);pthread_mutex_unlock(&mutex);}}else if (0 == nread || -1 == nread){break;}}close(c_fd);}pthread_exit(0);
}int main(int argc, char *argv[])
{int ret = -1;pthread_t get_voice_tid, category_tid,get_socket_tid;;wiringPiSetup();garbage_init();ret = detect_process("mjpg_streamer");if (-1 == ret){printf("detect process failed\n");goto END;}serial_fd = mySerialOpen(SERIAL_DEV, BAUD);if (serial_fd == -1){goto END;}// 開語音識別線程pthread_create(&get_voice_tid, NULL, pget_voice, NULL);// 開阿里云交互線程pthread_create(&category_tid, NULL, pcategory, NULL);//開網絡線程pthread_create(&get_socket_tid, NULL, pget_socket, NULL);pthread_join(get_voice_tid, NULL);pthread_join(category_tid, NULL);pthread_join(get_socket_tid, NULL);pthread_mutex_destroy(&mutex);pthread_cond_destroy(&cond);close(serial_fd);
END:gargage_final();return 0;
}