【嵌入式Linux】基于ArmLinux的智能垃圾分類系統項目

目錄

  • 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攝像頭測試章節。

操作如下:

  1. 首先將 USB 攝像頭插入到 Orange Pi 開發板的 USB 接口中
  2. 然后通過 lsmod 命令可以看到內核自動加載了下面的模塊
lsmod | grep uvcvideo | grep -v grep
//結果
uvcvideo 106496 0
  1. 通過 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,請以實際看到的為準。

  1. 使用 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這個用戶名稱
    
  2. 使用 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 服務

  1. 在某目錄下創建mjpg.sh腳本文件,寫入以下內容并且保存關閉
#!/bin/bashcd /home/orangepi/mjpg-streamer/mjpg-streamer-experimental/
./start.sh
  1. 進入/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
  1. 重啟香橙派,并查看該服務是否已經自動啟動
    在這里插入圖片描述

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 環境準備

  1. 將語音模塊接在UART5的位置

在這里插入圖片描述

  1. 在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
  1. 同時將USB攝像頭接到香橙派上
  2. 確認已經運行了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 代碼實現

  1. 首先創建garbage目錄,將garbage.c、garbage.h、 garbage.py三個文件拷貝進來。
  2. 參照之前博文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;
}
  1. 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
  1. 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 代碼實現

流程圖:

在這里插入圖片描述

具體代碼實現:

  1. 增加用于實現開光蓋(驅動舵機)的源碼文件(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);
}
  1. 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
  1. 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顯示或者添加網絡控制變得非常復雜,而且執行一次識別開關蓋的流程非常長。因此,調整下代碼架構,增加并發功能、提升代碼的可擴展性和執行效率。

  1. 代碼大致流程圖如下:
  2. 修改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客戶端得斷開情形無非就兩種情況:

  1. 客戶端能夠發送狀態給服務器;正常斷開,強制關閉客戶端等,客戶端能夠做出反應。
  2. 客戶端不能發送狀態給服務器;突然斷網,斷電,客戶端卡死等,客戶端根本沒機會做出反應,服務器更不了解客戶端狀態,導致服務器異常等待。

為了解決上述問題,引入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;
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/73281.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/73281.shtml
英文地址,請注明出處:http://en.pswp.cn/web/73281.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【商城實戰(63)】配送區域與運費設置全解析

【商城實戰】專欄重磅來襲&#xff01;這是一份專為開發者與電商從業者打造的超詳細指南。從項目基礎搭建&#xff0c;運用 uniapp、Element Plus、SpringBoot 搭建商城框架&#xff0c;到用戶、商品、訂單等核心模塊開發&#xff0c;再到性能優化、安全加固、多端適配&#xf…

字節跳動實習生主導開發強化學習算法,助力大語言模型性能突破

目錄 禹棋贏的背景與成就 主要成就 DAPO算法的技術細節 算法優勢 禹棋贏的研究歷程 關鍵時間節點 字節跳動的“Top Seed人才計劃” 計劃特點 小編總結 在大模型時代&#xff0c;經驗不再是唯一的衡量標準&#xff0c;好奇心、執行力和對新技術的敏銳洞察力成為推動技術…

Rust + 時序數據庫 TDengine:打造高性能時序數據處理利器

引言&#xff1a;為什么選擇 TDengine 與 Rust&#xff1f; TDengine 是一款專為物聯網、車聯網、工業互聯網等時序數據場景優化設計的開源時序數據庫&#xff0c;支持高并發寫入、高效查詢及流式計算&#xff0c;通過“一個數據采集點一張表”與“超級表”的概念顯著提升性能…

使用LangChain實現基于LLM和RAG的PDF問答系統

目錄 前言一.大語言模型(LLM)1. 什么是LLM&#xff1f;2. LLM 的能力與特點 二、增強檢索生成(RAG)三. 什么是 LangChain&#xff1f;1. LangChain 的核心功能2. LangChain 的優勢3. LangChain 的應用場景4. 總結 四.使用 LangChain 實現基于 PDF 的問答系統 前言 本文將介紹 …

群核科技持續虧損近18億:營銷費用偏高,市場份額優勢面臨挑戰

《港灣商業觀察》施子夫 2025年開年&#xff0c;DeepSeek的爆火讓大眾將目光聚焦到了“杭州六小龍”。其中&#xff0c;杭州群核信息技術有限公司&#xff08;以下簡稱&#xff0c;群核科技&#xff09;因系“六小龍”中首家啟動上市的公司而被外界更多關注。 在此次遞表港交…

java版嘎嘎快充玉陽軟件互聯互通中電聯云快充協議充電樁鐵塔協議汽車單車一體充電系統源碼uniapp

演示&#xff1a; 微信小程序&#xff1a;嘎嘎快充 http://server.s34.cn:1888/ 系統管理員 admin/123456 運營管理員 yyadmin/Yyadmin2024 運營商 operator/operator2024 系統特色&#xff1a; 多商戶、汽車單車一體、互聯互通、移動管理端&#xff08;開發中&#xff09; 另…

音視頻學習(三十):fmp4

FMP4&#xff08;Fragmented MP4&#xff09;是 MP4&#xff08;MPEG-4 Part 14&#xff09;的擴展版本&#xff0c;它支持流式傳輸&#xff0c;并被廣泛應用于DASH&#xff08;Dynamic Adaptive Streaming over HTTP&#xff09;和HLS&#xff08;HTTP Live Streaming&#xf…

26考研——圖_圖的存儲(6)

408答疑 文章目錄 二、圖的存儲圖的存儲相關概念鄰接矩陣存儲方式鄰接矩陣的定義頂點的度計算鄰接矩陣的特點鄰接矩陣的局限性 應用場景鄰接矩陣的冪次意義&#xff08;了解即可&#xff09; 鄰接表存儲方式鄰接表定義鄰接表結構鄰接表的特點 鄰接矩陣和鄰接表的適用性差異十字…

以高斯(GaussDB) 為例, 在cmd 命令行連接數據,操作數據庫,關閉數據庫的詳細步驟

以下是使用 Windows 命令行&#xff08;cmd&#xff09; 操作 GaussDB&#xff08;以 GaussDB(for openGauss) 社區版為例&#xff09; 的詳細步驟&#xff0c;涵蓋 連接數據庫、基本操作、關閉數據庫 的全流程&#xff1a; 1. 環境準備 前提條件&#xff1a; 安裝 GaussDB&a…

HAL庫定時器配置

定時器的開啟需要手動開啟&#xff0c;例如在driver_capature.c開啟&#xff0c;該文件主要寫了具體的函數實現&#xff0c;與driver_can.c一樣&#xff0c;同時還有回調函數等一些高級的自定義函數。 這段代碼是 STM32 HAL 庫中用于初始化 定時器 2 (TIM2) 的函數 MX_TIM2_In…

使用Python開發自動駕駛技術:車道線檢測模型

友友們好! 我是Echo_Wish,我的的新專欄《Python進階》以及《Python!實戰!》正式啟動啦!這是專為那些渴望提升Python技能的朋友們量身打造的專欄,無論你是已經有一定基礎的開發者,還是希望深入挖掘Python潛力的愛好者,這里都將是你不可錯過的寶藏。 在這個專欄中,你將會…

Modern C++面試題及參考答案

目錄 解釋右值引用的定義及其與左值引用的核心區別 std::move 的實現原理是什么?為什么它本身不執行移動操作? 移動構造函數與拷貝構造函數的調用場景有何不同? 實現一個支持移動語義的類需要遵循哪些原則? 完美轉發(Perfect Forwarding)的實現原理及 std::forward 的…

Thinkphp(TP)框架漏洞攻略

1.環境搭建 vulhub/thinkphp/5-rce docker-compose up -d 2.訪問靶場 遠程命令執行&#xff1a; ? sindex/think\app/invokefunction&functioncall_user_func_array&vars[0]system&vars[1] []whoami 遠程代碼執行&#xff1a; ? s/Index/\think\app/invokefunc…

QT筆記---JSON

QT筆記---JSON JSON1、JSON基本概念1.1、判斷.json文件工具 2、生成.json數據3、解析.json數據 JSON 在現代軟件開發中&#xff0c;數據的交換和存儲格式至關重要。JSON&#xff08;JavaScript Object Notation&#xff09;作為一種輕量級的數據交換格式&#xff0c;以其簡潔易…

Unity 使用 Protobuf(Pb2)二進制數據全流程工具詳解

前言 在Unity游戲開發中&#xff0c;高效、快速、安全地讀取配置數據是一項重要需求。本文介紹一種完整的解決方案——使用Protobuf二進制格式&#xff08;Pb2&#xff09;存儲和讀取游戲數據&#xff0c;并詳細分享實現全流程的Unity工具。 一、技術流程概覽 實現Unity讀取…

MySQL-----視圖與索引

目錄 視圖 1.視圖 2.操作 11.索引 1.定義 2.優缺點: 3.分類 4.索引的設計原則 5.索引的使用 作業 視圖 1.視圖 ?如果需要在原表中隱藏部分字段時&#xff0c;怎么辦&#xff1f; 視圖 &#x1f4d6;視圖: 是一個沒有存儲任何數據的表&#xff0c;可以對其CRUD視圖…

stm32-IIC

i^2c,iiCBus,集成電路總線&#xff0c;同步串行半雙工通信總線方式 sck:時鐘同步信號 SDA:發送數據 GND&#xff1a;接地 通信對象&#xff1a;芯片與芯片 主從應答方式&#xff1a; SDA&#xff1a;數據總線 SCL&#xff1a;時鐘總線 在硬件設計中&#xff1a; 上拉電阻&#…

`chromadb` 是什么

chromadb 是什么 chromadb 是一個開源的向量數據庫,它專門用于存儲、索引和查詢向量數據。在處理自然語言處理(NLP)、計算機視覺等領域的任務時,通常會將文本、圖像等數據轉換為向量表示,而 chromadb 可以高效地管理這些向量,幫助開發者快速找到與查詢向量最相似的向量數…

機器視覺工程師如何看機器視覺展會,有些機器視覺兄弟參加機器視覺展會,真的是參加了?重在參與?

作為機器視覺工程師,參加機器視覺展會不僅是了解行業前沿技術的窗口,也是拓展專業網絡、尋找解決方案的重要機會。以下是結合展會信息和工程師視角的綜合建議: 一、聚焦技術趨勢與創新應用 參與技術論壇與研討會 展會同期的技術論壇是獲取行業洞見的核心渠道。例如: 上海展…

Centos操作系統安裝及優化

Centos操作系統安裝及優化 零、環境概述 主機名 centos版本 cpu 內存 Vmware版本 ip地址 test CentOS Linux release 7.6.1810 (Core) 2C 2G 15.5.1 10.0.0.10 一、介質下載 1、7.6版本下載 CentOS7.6標準版下載鏈接: https://archive.kernel.org/centos-vault/7.6.1810/i…