最效果展示
演示視頻鏈接:基于樹莓派實現(xiàn)的智能家居_嗶哩嗶哩_bilibilihttps://www.bilibili.com/video/BV1Tr421n7BM/?spm_id_from=333.999.0.0
(PS:房屋模型的搭建是靠紙板箱和淘寶買的家居模型,戶型參考了留學(xué)時短租的公寓~)?
前言
到目前為止,對于linux的嵌入式軟件開發(fā),從底層到上層都有了一定的認(rèn)識。這個項目的初衷就是整合知識并以工廠模式的架構(gòu)開發(fā)項目。
功能實現(xiàn)
- 實現(xiàn)了socket服務(wù)器遠(yuǎn)程控制臥室,餐廳,廁所,客廳4盞燈的開啟和關(guān)閉
- 實現(xiàn)了語音控制臥室,餐廳,廁所,客廳4盞燈的開啟和關(guān)閉
- 實現(xiàn)了當(dāng)溫度超過閾值的時候進(jìn)行火災(zāi)報警,并且可以語音關(guān)閉警報
- 實現(xiàn)了進(jìn)門前結(jié)合語音,OLED和攝像頭的人臉識別
- 實現(xiàn)了實時的遠(yuǎn)程視頻監(jiān)控
- 實現(xiàn)OLED屏幕的實時溫濕度顯示
開發(fā)環(huán)境 & 實現(xiàn)思路
- 開發(fā)板:樹莓派3B+
- 開發(fā)語言:C
- 編程工具:Source Insight 3
工廠設(shè)計
對于這個項目的實現(xiàn),采用上節(jié)學(xué)到的工廠模式來設(shè)計,從而提升整體代碼的穩(wěn)定性和可拓展性。
軟件設(shè)計模式 --- 類,對象和工廠模式的引入-CSDN博客
?閱讀功能需求后,結(jié)合工廠模式的思路可以先設(shè)計兩個工廠:指令工廠 和 設(shè)備工廠
- 指令工廠:存儲需要使用到的指令
- 設(shè)備工廠:存儲需要使用到的設(shè)備
工廠模式的主要的考量有兩點(diǎn):
1. 工廠的類
struct device //設(shè)備工廠
{
char device_name[64]; //設(shè)備名稱
int status;
int (*init)(); //初始化函數(shù)
int (*open)(); //打開設(shè)備的函數(shù)
int (*close)(); //關(guān)閉設(shè)備的函數(shù)
int (*read_status)(); //查看設(shè)備狀態(tài)的函數(shù)
struct device *next;
};
struct cmd //指令工廠
{
char cmd_name[64]; //指令名稱
//char cmd_log[1024]; //指令日志
int (*init)(int port, char *IP, char *UART, int BAUD); //初始化函數(shù)
int (*accept)(int fd); //接受函數(shù)
int (*cmd_handler)(struct device *phead, int fd); //處理指令的函數(shù)
struct cmd *next;
};
2. 工廠的對象
實現(xiàn)思路Q&A
Q:如何實現(xiàn)socket服務(wù)器的遠(yuǎn)程控制?
A:使用之前學(xué)習(xí)的socket知識,創(chuàng)建一個服務(wù)端一個客戶端,服務(wù)端負(fù)責(zé)創(chuàng)建套接字并綁定,然后阻塞監(jiān)聽;客戶端負(fù)責(zé)建立連接后發(fā)送指令。指令在服務(wù)端通過指令工廠中socket對象的cmd_handler函數(shù)進(jìn)行分析并作出相關(guān)動作。最后在main函數(shù)中使用一個線程不斷阻塞等待新客戶端的加入;使用另一個線程不斷阻塞接受客戶端傳來的指令并分析。
參考我之前的博文:
(??相關(guān)的API講解)
Linux socket網(wǎng)絡(luò)編程概述 和 相關(guān)API講解_linux 網(wǎng)絡(luò)編程-CSDN博客
(??具體如何使用API的實戰(zhàn),父子進(jìn)程版)
基于Linux并結(jié)合socket網(wǎng)絡(luò)編程的ftp服務(wù)器的實現(xiàn)-CSDN博客
(??具體如何使用API的實戰(zhàn),多線程版)
使用香橙派并基于Linux實現(xiàn)最終版智能垃圾桶項目 --- 上_linux 打印扔垃圾桶-CSDN博客
Q:如何實現(xiàn)語音控制的操作?
A:使用之前學(xué)習(xí)的SU-03T,在其官網(wǎng)對指令進(jìn)行編輯和燒錄,然后通過串口來和樹莓派進(jìn)行通信。同樣通過指令工廠中語音控制對象的cmd_handler函數(shù)來進(jìn)行分析并作出相關(guān)動作。最后在main函數(shù)開啟一個線程不斷通過串口阻塞讀取語音模塊的指令并分析。
參考我之前的博文:
(??如何設(shè)置官網(wǎng)指令并燒錄&通過電平變化來控制語音模塊的實例)
語音小車---6 + 最終整合_unioneupdatetool-CSDN博客
(??關(guān)于在多線程環(huán)境下通過串口通信來控制語音模塊的實例)
使用香橙派并基于Linux實現(xiàn)最終版智能垃圾桶項目 --- 下_香橙派 項目-CSDN博客
Q:如何實現(xiàn)火災(zāi)報警?
A:使用之前學(xué)習(xí)的溫濕度傳感器DHT11和蜂鳴器,通過閱讀DHT11的手冊,在設(shè)備工廠中實現(xiàn)其激活和讀取狀態(tài)的函數(shù);在指令工廠中,調(diào)用剛剛實現(xiàn)的函數(shù)結(jié)合手冊實現(xiàn)溫濕度的獲取。最后在main函數(shù)中開啟一個線程不斷判斷當(dāng)前的溫度來決定是否驅(qū)動蜂鳴器。
參考我之前的博文:
(??DHT11的介紹和如何通過手冊驅(qū)動DHT11的實例)
溫濕度傳感器 DHT11_dht11溫濕度傳感器 庫從哪里下載-CSDN博客
(??別人實現(xiàn)的,通過樹莓派驅(qū)動DHT11的例程)
樹莓派驅(qū)動DH11溫濕度傳感器_如何使用zynq驅(qū)動dh11-CSDN博客
Q:如何實現(xiàn)OLED屏幕顯示?
參考我之前的博文:
(??關(guān)于樹莓派驅(qū)動OLED屏幕)
使用樹莓派 結(jié)合Python Adafruit驅(qū)動OLED屏幕 顯示實時視頻-CSDN博客
Q:如何實現(xiàn)人臉識別?
A:使用一枚之前用過的USB攝像頭HBV-W202012HD V33,接入樹莓派,在設(shè)備工廠為其實現(xiàn)拍照等功能。然后接入阿里云的人臉識別方案,當(dāng)收到對應(yīng)的人臉識別語音指令時,在指令工廠的語音模塊對象下的cmd_handler函數(shù)中添加人臉識別的代碼,并根據(jù)結(jié)果通過串口回傳給語音模塊播報結(jié)果。
參考我之前的博文:
(??香橙派中驅(qū)動攝像頭,并調(diào)用阿里云物品識別的實例)
使用香橙派并基于Linux實現(xiàn)最終版智能垃圾桶項目 --- 下_香橙派 項目-CSDN博客
(??樹莓派中驅(qū)動攝像頭,并調(diào)用阿里云人臉識別的實例)
基于阿里云平臺 通過樹莓派實現(xiàn) 1:1人臉識別-CSDN博客
硬件接線
整體的接線情況如下:
注意!如果外設(shè)和單片機(jī)采用了不同供電,且外設(shè)和單片機(jī)存在信息交互,那么就必須共地!!?
?
預(yù)備工作
在有了大概的思路和硬件接線完畢完成后,要進(jìn)行兩個重要的預(yù)備工作:
攝像頭的接入和mpjg-streamer的自動后臺運(yùn)行
由于這個項目在運(yùn)行時只要涉及人臉識別就需要用到攝像頭拍照,并且需要實現(xiàn)實時的監(jiān)控畫面,所以先將USB攝像頭接入并讓mpjg-streamer在每次樹莓派開機(jī)的時候自動運(yùn)行就很有必要了。
實現(xiàn)其實很簡單,可以參考我的這篇博文:
樹莓派接入USB攝像頭并使用fswebcam和mjpg-streamer進(jìn)行測試_在樹莓派ros2中安裝usb攝像頭驅(qū)動-CSDN博客
語音模塊SU-03T的指令編輯和燒寫
這一步雖然叫預(yù)備工作,但是在實際開發(fā)中隨著項目的完善肯定要多次修改和燒寫,但是為了邏輯清晰所以將這一步歸為預(yù)備工作,僅展示最后的效果。
同時再次提醒,只要涉及到SU-03T的串口輸入輸出,就要下載固件而不是SDK!??!
關(guān)于網(wǎng)站和具體細(xì)節(jié),請移步至上面的相關(guān)鏈接
引腳配置
命令設(shè)置
自定義配置
實現(xiàn)效果
1. 開機(jī)播報“海豹助手幫你實現(xiàn)智能居住體驗"
1. 當(dāng)說出“你好小豹”可以喚醒模塊,模塊回復(fù)“海豹在”或“有什么可以幫到你”
2. 當(dāng)超過10s沒有指令或說出“退下”時,模塊會進(jìn)入休眠模式,并回復(fù)“有需要再叫我”
3. 當(dāng)說出“打開/關(guān)閉 客廳/臥室/餐廳/廁所 燈”時,模塊回復(fù)“收到”,并根據(jù)當(dāng)前燈的狀態(tài)打開/關(guān)閉 相應(yīng)的燈或回復(fù)“燈本來就開/關(guān)著哦”
4.?當(dāng)說出“打開/關(guān)閉 所有燈”時,模塊回復(fù)“收到”,并打開/關(guān)閉所有燈
5.?當(dāng)說出“關(guān)閉警報”時,模塊回復(fù)“已關(guān)閉,但為了您的安全請隨時說出‘?恢復(fù)警報?’來恢復(fù)報警功能!”,并關(guān)閉警報
6.??當(dāng)說出“恢復(fù)警報”時,模塊回復(fù)“火災(zāi)警報已經(jīng)恢復(fù)工作”,并恢復(fù)警報
7.? 當(dāng)說出“人臉識別”時,開始人臉識別,并根據(jù)識別結(jié)果回復(fù)“識別成功”或“識別失敗”
代碼開發(fā)
在剛剛提到,代碼編寫的主要工具是“Source Insight”,所以主要的代碼編寫就在windows下;寫完發(fā)送到樹莓派測試
①代碼預(yù)創(chuàng)建
- 首先創(chuàng)建一個名為“smart_home”文件夾用于保存項目所有相關(guān)文件,并在其中創(chuàng)建一個“si”文件夾用于保存source insight工程:
- 在“smart_home”下創(chuàng)建會使用的.c和.h文件:
(以下是最終版的結(jié)果,實際開發(fā)過程中這一步先創(chuàng)建可能會需要的文件,后面隨著實現(xiàn)慢慢的添加和修改)
?一共23個代碼文件
- 打開source insight,創(chuàng)建一個新工程并將代碼全部包含進(jìn)來并同步:
(具體步驟見上篇博文)
最終效果:
此時就可以開始正式編程了!
②代碼編寫
2.1 指令工廠cmd_fac.h和設(shè)備工廠dvice_fac.h的編寫:
這一步主要是根據(jù)上一節(jié)工廠模式的思路來實現(xiàn),具體有哪些函數(shù)根據(jù)代碼的編寫再反過來修改
cmd_fac.h:
#ifndef __CMDFAC_H__
#define __CMDFAC_H__
#include <wiringPi.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include "device_fac.h"
#include "find_link.h"
#include "mjm_uart_tool.h"
#include "face_cmp.h"
struct cmd
{
char cmd_name[64]; //指令名稱
//char cmd_log[1024]; //指令日志
int (*init)(int port, char *IP, char *UART, int BAUD); //初始化函數(shù)
int (*accept)(int fd); //接受函數(shù)
int (*cmd_handler)(struct device *phead, int fd);
struct cmd *next;
};
struct cmd* putSocketInLink(struct cmd *head);
struct cmd* putVoiceInLink(struct cmd *head);
struct cmd* putFireInLink(struct cmd *head);
#endif
device_fac.h:
#ifndef __DEVICEFAC_H__
#define __DEVICEFAC_H__
#include <wiringPi.h>
#include <stddef.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
struct device
{
char device_name[64]; //設(shè)備名稱
int status;
int (*init)(); //初始化函數(shù)
int (*open)(); //打開設(shè)備的函數(shù)
int (*close)(); //關(guān)閉設(shè)備的函數(shù)
int (*read_status)(); //查看設(shè)備狀態(tài)的函數(shù)
struct device *next;
};
struct device* putLight_bedroomInLink(struct device *head);
struct device* putLight_diningroomInLink(struct device *head);
struct device* putLight_livingroomInLink(struct device *head);
struct device* putLight_washroomInLink(struct device *head);
struct device* putDhtInLink(struct device *head);
struct device* putBeeperInLink(struct device *head);
struct device* putCameraInLink(struct device *head);
#endif
?2.2?串口通訊mjm_uart_tool.c/.h的編寫:
串口的代碼使用我之前基于wiringPi庫自己實現(xiàn)的函數(shù),詳見:
樹莓派的的串口通信協(xié)議-CSDN博客
但是關(guān)于“serialSendstring”函數(shù)和“serialGetstring”函數(shù)需要進(jìn)行一些修改,其原因就是SU-03T(語音模塊)在規(guī)定串口輸入的時候有固定要求的幀頭幀尾格式:
//注意,這個通過串口發(fā)送字符串的函數(shù),其中的read函數(shù)的第三個參數(shù)不能使用strlen
//因為發(fā)送給語音模塊的數(shù)據(jù)有固定的幀頭幀尾,都是16進(jìn)制數(shù)不包含結(jié)束符
//所以如果使用了strlen的話,就無法成功的發(fā)送
//所以為這個函數(shù)加一個len參數(shù)
void serialSendstring (const int fd, const unsigned char *s, int len)
//void serialSendstring (const int fd, const char *s)
{
int ret;
ret = write (fd, s, len);
//ret = write (fd, s, strlen(s));
if (ret < 0)
printf("Serial Puts Error\n");
}
int serialGetstring (const int fd, unsigned char *buffer)
//int serialGetstring (const int fd, char *buffer)
{
int n_read;
n_read = read(fd, buffer,32);
return n_read;
}
主要修改有兩點(diǎn):
- “serialSendstring”函數(shù)增加一個參數(shù)len,用于指示發(fā)送數(shù)據(jù)的具體長度
- 兩個函數(shù)的第二個參數(shù)都加上“unsigned”,因為不加的話數(shù)據(jù)長度可能會超出普通char的范圍(127)
mjm_uart_tool.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) ; // 10mS
return fd ;
}
//注意,這個通過串口發(fā)送字符串的函數(shù),其中的read函數(shù)的第三個參數(shù)不能使用strlen
//因為發(fā)送給語音模塊的數(shù)據(jù)有固定的幀頭幀尾,都是16進(jìn)制數(shù)不包含結(jié)束符
//所以如果使用了strlen的話,就無法成功的發(fā)送
//所以為這個函數(shù)加一個len參數(shù)
void serialSendstring (const int fd, const unsigned char *s, int len)
//void serialSendstring (const int fd, const char *s)
{
int ret;
ret = write (fd, s, len);
//ret = write (fd, s, strlen(s));
if (ret < 0)
printf("Serial Puts Error\n");
}
int serialGetstring (const int fd, unsigned char *buffer)
//int serialGetstring (const int fd, char *buffer)
{
int n_read;
n_read = read(fd, buffer,32);
return n_read;
}
int serialDataAvail (const int fd)
{
int result ;
if (ioctl (fd, FIONREAD, &result) == -1)
return -1 ;
return result ;
}
mjm_uart_tool.h:
#ifndef __UART_H__
#define __UART_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);
int serialDataAvail (const int fd);
#endif
2.3 在工廠鏈表中查找對象的代碼find_link.c/.h的編寫:
這部分的代碼實現(xiàn)的功能就是封裝“在工廠鏈表中查找特定對象”的函數(shù),其實現(xiàn)思路就是根據(jù)對象的名字來在鏈表中進(jìn)行遍歷
find_link.c:
#include "find_link.h"
struct device* findDEVICEinLink(char *name, struct device *phead)
{
struct device *p = phead;
while(p != NULL){
if(strcmp(p->device_name,name)==0){
return p;
}
p = p->next;
}
return NULL;
}
struct cmd* findCMDinLink(char *name, struct cmd *phead)
{
struct cmd *p = phead;
while(p != NULL){
if(strcmp(p->cmd_name,name)==0){
return p;
}
p = p->next;
}
return NULL;
}
find_link.h:
#ifndef __FINDLINK_H__
#define __FINDLINK_H__
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include "device_fac.h"
#include "cmd_fac.h"
struct device* findDEVICEinLink(char *name, struct device *phead);
struct cmd* findCMDinLink(char *name, struct cmd *phead);
#endif
2.4 OLED顯示代碼oled_show.c/.h & python代碼的編寫:
這部分的代碼在之前給出的鏈接里已經(jīng)大致實現(xiàn),其核心思路就是先用python調(diào)用Adafruit_Python_SSD1306庫實現(xiàn)清屏,顯示溫濕度和顯示圖片的代碼,再用C語言調(diào)用python封裝這三個函數(shù),最后獲得可以清屏,顯示溫濕度和顯示圖片的C函數(shù)
oled_camera.py:
def init():
# Raspberry Pi pin configuration:
RST = 24
# Note the following are only used with SPI:
DC = 23
SPI_PORT = 0
SPI_DEVICE = 0
# 128x64 display with hardware I2C:
disp = Adafruit_SSD1306.SSD1306_128_64(rst=RST)
# Initialize library.
disp.begin()
# Clear display.
disp.clear()
disp.display()
def display():
# Raspberry Pi pin configuration:
RST = 24
# Note the following are only used with SPI:
DC = 23
SPI_PORT = 0
SPI_DEVICE = 0
# 128x32 display with hardware I2C:
disp = Adafruit_SSD1306.SSD1306_128_64(rst=RST)
# Initialize library.
disp.begin()
img = Image.open('/home/pi/mjm_code/smart_home/face.png')
img_resized = img.resize((128, 64),Image.LANCZOS)
image = img_resized.convert('1')
# Display image.
disp.image(image)
disp.display()
def tmphumi(tmp, humi):
# Raspberry Pi pin configuration:
RST = 24
# Note the following are only used with SPI:
DC = 23
SPI_PORT = 0
SPI_DEVICE = 0
# 128x32 display with hardware I2C:
disp = Adafruit_SSD1306.SSD1306_128_64(rst=RST)
# Initialize library.
disp.begin()
# Create blank image for drawing.
# Make sure to create image with mode '1' for 1-bit color.
width = disp.width
height = disp.height
image = Image.new('1', (width, height))
# Get drawing object to draw on image.
draw = ImageDraw.Draw(image)
# Load default font.
font = ImageFont.load_default()
str1 = f"tmperature: {tmp} C"
str2 = f"humidity: {humi} %"
# Write two lines of text.
draw.text((20,20), str1, font=font, fill=255)
draw.text((20,40), str2, font=font, fill=255)
disp.image(image)
disp.display()
#測試用
if __name__ == '__main__':
init()
#display()
tmphumi(25,50)
oled_show.c:
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <Python.h>
#include "oled_show.h"
void oled_init(void)
{
Py_Initialize();
PyObject *sys = PyImport_ImportModule("sys");
PyObject *path = PyObject_GetAttrString(sys, "path");
PyList_Append(path, PyUnicode_FromString("."));
}
void oled_final(void)
{
Py_Finalize();
}
void oled_show_init(void) //清屏
{
PyObject *pModule = PyImport_ImportModule("oled_camera");
if (!pModule)
{
PyErr_Print();
printf("Error: failed to load module\n");
goto FAILED_MODULE;
}
PyObject *pFunc = PyObject_GetAttrString(pModule, "init");
if (!pFunc)
{
PyErr_Print();
printf("Error: failed to load function\n");
goto FAILED_FUNC;
}
PyObject *pValue = PyObject_CallObject(pFunc, NULL);
if (!pValue)
{
PyErr_Print();
printf("Error: function call failed\n");
goto FAILED_VALUE;
}
FAILED_RESULT:
Py_DECREF(pValue);
FAILED_VALUE:
Py_DECREF(pFunc);
FAILED_FUNC:
Py_DECREF(pModule);
FAILED_MODULE:
}
void oled_show(void) //顯示圖片
{
PyObject *pModule = PyImport_ImportModule("oled_camera");
if (!pModule)
{
PyErr_Print();
printf("Error: failed to load module\n");
goto FAILED_MODULE;
}
PyObject *pFunc = PyObject_GetAttrString(pModule, "display");
if (!pFunc)
{
PyErr_Print();
printf("Error: failed to load function\n");
goto FAILED_FUNC;
}
PyObject *pValue = PyObject_CallObject(pFunc, NULL);
if (!pValue)
{
PyErr_Print();
printf("Error: function call failed\n");
goto FAILED_VALUE;
}
FAILED_RESULT:
Py_DECREF(pValue);
FAILED_VALUE:
Py_DECREF(pFunc);
FAILED_FUNC:
Py_DECREF(pModule);
FAILED_MODULE:
}
void oled_tmphumi(int tmp, int humi) //顯示溫濕度
{
PyObject *pModule = PyImport_ImportModule("oled_camera"); //加載python文件
if (!pModule)
{
PyErr_Print();
printf("Error: failed to load module\n");
goto FAILED_MODULE; //goto的意思就是如果運(yùn)行到這里就直接跳轉(zhuǎn)到FAILED_MODULE
}
PyObject *pFunc = PyObject_GetAttrString(pModule, "tmphumi"); //加載python文件中的對應(yīng)函數(shù)
if (!pFunc)
{
PyErr_Print();
printf("Error: failed to load function\n");
goto FAILED_FUNC;
}
//創(chuàng)建一個字符串作為參數(shù)
PyObject *pArgs = Py_BuildValue("(i,i)",tmp,humi); //(i,i)代表有兩個int的元組
PyObject *pValue = PyObject_CallObject(pFunc, pArgs);
if (!pValue)
{
PyErr_Print();
printf("Error: function call failed\n");
goto FAILED_VALUE;
}
FAILED_RESULT:
Py_DECREF(pValue);
FAILED_VALUE:
Py_DECREF(pFunc);
FAILED_FUNC:
Py_DECREF(pModule);
FAILED_MODULE:
}
oled_show.h:
#ifndef __oled__H
#define __oled__H
void oled_init(void);
void oled_final(void);
void oled_show_init(void); //清屏
void oled_show(void); //顯示圖片
void oled_tmphumi(int tmp, int humi); //顯示溫濕度
#endif
2.5?人臉識別代碼face_cmp.c/.h & python代碼的編寫:
這部分的代碼在之前給出的鏈接里已經(jīng)大致實現(xiàn),其核心思路就是先用python調(diào)用阿里云的1:1人臉識別,再用C語言調(diào)用python,最后獲得可以進(jìn)行人臉識別的C函數(shù)
face.py:
# -*- coding: utf-8 -*-
# 引入依賴??# 最低SDK版本要求:facebody20191230的SDK版本需大于等于4.0.8
# 可以在此倉庫地址中引用最新版本SDK:https://pypi.org/project/alibabacloud-facebody20191230/
# pip install alibabacloud_facebody20191230
import os
import io
from urllib.request import urlopen
from alibabacloud_facebody20191230.client import Client
from alibabacloud_facebody20191230.models import CompareFaceAdvanceRequest
from alibabacloud_tea_openapi.models import Config
from alibabacloud_tea_util.models import RuntimeOptions
def face_detect():
config = Config(
# 創(chuàng)建AccessKey ID和AccessKey Secret,請參考https://help.aliyun.com/document_detail/175144.html?? # 如果您用的是RAM用戶的AccessKey,還需要為RAM用戶授予權(quán)限AliyunVIAPIFullAccess,請參考https://help.aliyun.com/document_detail/145025.html?? # 從環(huán)境變量讀取配置的AccessKey ID和AccessKey Secret。運(yùn)行代碼示例前必須先配置環(huán)境變量??
access_key_id=os.environ.get('ALIBABA_CLOUD_ACCESS_KEY_ID'),
access_key_secret=os.environ.get('ALIBABA_CLOUD_ACCESS_KEY_SECRET'),
# 訪問的域?? endpoint='facebody.cn-shanghai.aliyuncs.com',
# 訪問的域名對應(yīng)的region
region_id='cn-shanghai'
)
runtime_option = RuntimeOptions()
compare_face_request = CompareFaceAdvanceRequest()
#場景一:文件在本地
streamA = open(r'/home/pi/mjm_code/smart_home/mjm.png', 'rb') #預(yù)存的照??
compare_face_request.image_urlaobject = streamA
streamB = open(r'/home/pi/mjm_code/smart_home/face.png', 'rb') #待測試的照片
compare_face_request.image_urlbobject = streamB
try:
# 初始化Client
client = Client(config)
response = client.compare_face_advance(compare_face_request, runtime_option)
# 獲取整體結(jié)果
#print(response.body)
# 單獨(dú)打印置信??
confidence = response.body.to_map()['Data']['Confidence'] #to_map()函數(shù)很重要,不要忘記
score = int(confidence)
#print(score)
return score
except Exception as error:
# 獲取整體報錯信息
print(error)
# 獲取單個字段
print(error.code)
# tips: 可通過error.__dict__查看屬性名??
# 關(guān)閉?? streamA.close()
streamB.close()
if __name__ == '__main__':
face_detect()
face_cmp.c:
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <Python.h>
#include "face_cmp.h"
void face_init(void)
{
Py_Initialize();
PyObject *sys = PyImport_ImportModule("sys");
PyObject *path = PyObject_GetAttrString(sys, "path");
PyList_Append(path, PyUnicode_FromString("."));
}
void face_final(void)
{
Py_Finalize();
}
int face_score(void) //python下face_detect函數(shù)返回的是已經(jīng)經(jīng)過提取和取證過的置信度score,是個int型
{
PyObject *pModule = PyImport_ImportModule("face"); //加載python文件
if (!pModule)
{
PyErr_Print();
printf("Error: failed to load module\n");
goto FAILED_MODULE; //goto的意思就是如果運(yùn)行到這里就直接跳轉(zhuǎn)到FAILED_MODULE
}
PyObject *pFunc = PyObject_GetAttrString(pModule, "face_detect"); //加載python文件中的對應(yīng)函數(shù)
if (!pFunc)
{
PyErr_Print();
printf("Error: failed to load function\n");
goto FAILED_FUNC;
}
PyObject *pValue = PyObject_CallObject(pFunc, NULL);
if (!pValue)
{
PyErr_Print();
printf("Error: function call failed\n");
goto FAILED_VALUE;
}
int result = 0;
if (!PyArg_Parse(pValue, "i", &result)) //ace_detect函數(shù)返回的是已經(jīng)經(jīng)過提取和取證過的置信度score,是個int型,用‘i’表示
{
PyErr_Print();
printf("Error: parse failed");
goto FAILED_RESULT;
}
/* 如果函數(shù)返回的是字符串,上面的PyArg_Parse則需要用‘s’來表示,且下面注釋的代碼非常重要,因為字符串名代表了其首地址,所以不能直接復(fù)制而是需要使用strncpy函數(shù)?。?!
category = (char *)malloc(sizeof(char) * (strlen(result) + 1) ); //開辟一個新的字符串常量。+1是為了留出空間給\0
memset(category, 0, (strlen(result) + 1)); //初始化字符串
strncpy(category, result, (strlen(result) + 1)); //將result的結(jié)果復(fù)制給新的字符串
*/
FAILED_RESULT:
Py_DECREF(pValue);
FAILED_VALUE:
Py_DECREF(pFunc);
FAILED_FUNC:
Py_DECREF(pModule);
FAILED_MODULE:
return result;
}
face_cmp.h:
#ifndef __face__H
#define __face__H
void face_init(void);
void face_final(void);
int face_score(void);
#endif
2.6?4盞燈的代碼light_xxx.c的編寫:
作為設(shè)備工廠的對象,燈代碼的編寫就是選擇性的實現(xiàn)設(shè)備工廠的類,并且4盞燈的代碼除了wiringPi對應(yīng)的引腳和名字不同之外,幾乎沒有任何差別。
light_XXXX.c:
#include "device_fac.h"
#define lightXXXX X //根據(jù)硬件接線來
int light_XXXX_init()
{
pinMode (lightXXXX, OUTPUT);
digitalWrite (lightXXXX, HIGH) ;
}
int light_XXXX_open()
{
digitalWrite (lightXXXX, LOW) ;
}
int light_XXXX_close()
{
digitalWrite (lightXXXX, HIGH) ;
}
int light_XXXX_status()
{
return digitalRead(lightXXXX);
}
struct device light_XXXX = {
.device_name = "light_XXXX",
.init = light_XXXX_init,
.open = light_XXXX_open,
.close = light_XXXX_close,
.read_status = light_XXXX_read_status,
};
struct device* putLight_XXXXInLink(struct device *head)
{
struct device *p = head;
if(p == NULL){
head = &light_XXXX;
}else{
light_XXXX.next = head;
head = &light_XXXX;
}
return head;
}
2.7?溫濕度傳感器dht11.c的編寫:
作為設(shè)備工廠的對象,dht11代碼的編寫就是選擇性的實現(xiàn)設(shè)備工廠的類:
dht11.c:
#include "device_fac.h"
#define dht 4
int dht_start()
{
pinMode(dht, OUTPUT); //起始拉高電平
digitalWrite(dht, 1);
delay(1000);
pinMode(dht, OUTPUT); //拉低超過18ms
digitalWrite(dht, 0);
delay(21);
digitalWrite(dht, 1); //拉高電平,等響應(yīng)
pinMode(dht, INPUT);
delayMicroseconds(28);
}
int dht_read_status()
{
return digitalRead(dht);
}
struct device dht11 = {
.device_name = "dht",
.open = dht_start,
.read_status = dht_read_status,
};
struct device* putDhtInLink(struct device *head)
{
struct device *p = head;
if(p == NULL){
head = &dht11;
}else{
dht11.next = head;
head = &dht11;
}
return head;
}
2.8?蜂鳴器beeper.c的編寫:
作為設(shè)備工廠的對象,蜂鳴器代碼的編寫就是選擇性的實現(xiàn)設(shè)備工廠的類,蜂鳴器的實現(xiàn)和4盞燈極其類似,直接看代碼:
beeper.c:
#include "device_fac.h"
#define io 5
int beep_init()
{
pinMode (io, OUTPUT);
digitalWrite (io, HIGH) ;
}
int beep_open()
{
digitalWrite (io, LOW) ; //蜂鳴器響
}
int beep_close()
{
digitalWrite (io, HIGH) ; //蜂鳴器不響
}
struct device beeper = {
.device_name = "beeper",
.init = beep_init,
.open = beep_open,
.close = beep_close,
};
struct device* putBeeperInLink(struct device *head)
{
struct device *p = head;
if(p == NULL){
head = &beeper;
}else{
beeper.next = head;
head = &beeper;
}
return head;
}
2.9?攝像頭camera.c的編寫:
作為設(shè)備工廠的對象,攝像頭代碼的編寫就是選擇性的實現(xiàn)設(shè)備工廠的類:
camera.c:
#include "device_fac.h"
int camera_takePic() //返回1成功拍到照片,返回0拍照失敗
{
system("wget http://192.168.2.56:8080/?action=snapshot -O /home/pi/mjm_code/smart_home/face.png"); //拍照
delay(10);//給一點(diǎn)時間讓照片拍出來
if(0 == access("/home/pi/mjm_code/smart_home/face.png", F_OK)){ //如果照片成功拍到了
return 1;
}else{
return 0;
}
}
int camera_removePic()
{
return remove("/home/pi/mjm_code/smart_home/face.png");
}
struct device camera = {
.device_name = "camera",
.open = camera_takePic,
.close = camera_removePic,
};
struct device* putCameraInLink(struct device *head)
{
struct device *p = head;
if(p == NULL){
head = &camera;
}else{
camera.next = head;
head = &camera;
}
return head;
}
2.10?socket控制socket_ctl.c的編寫:
作為指令工廠的對象,socket控制就是選擇性的實現(xiàn)指令工廠的類:
socket_ctl.c:
#include "cmd_fac.h"
char *HELP = "welcome to smart home! Here are some cmd instructions:\n\'oll\'---open livingroom light\n\'cll\'---close livingroom light\n\'odl\'---open diningroom light\n\'cdl\'---close diningroom light\n\'obl\'---open bedroom light\n\'cbl\'---close bedroom light\n\'owl\'---open washroom light\n\'cwl\'---close washroom light\n\'quit\'---disconnect\ntype \'help\' to review all the command\n";
int conn_sockfd;
int answer_success(int fd)
{
int ret = 0;
ret = write(fd,"operation success",18);
if(ret == -1){
perror("write1");
return -1;
}else{
return 0;
}
}
int answer_fail(int fd)
{
int ret = 0;
ret = write(fd,"already open/close",19);
if(ret == -1){
perror("write2");
return -1;
}else{
return 0;
}
}
int handler(int fd, char readbuf[128], struct device *phead)
{
struct device *device_pfind = NULL;
int ret;
int i = 0;
char str[128]; //將讀到的數(shù)據(jù)備份在這里
strcpy(str,readbuf); //由于字符串的首地址是字符串的名字,所以此時相當(dāng)于傳入的地址,所有對字符串的操作都會影響它,所以需要進(jìn)行備份,先備份再對備份的數(shù)據(jù)進(jìn)行數(shù)據(jù)處理就不會影響原數(shù)據(jù)了
if(strcmp((char *)str,"obl")==0){ //收到打開臥室燈的指令
device_pfind = findDEVICEinLink("light_bedroom",phead);
if(device_pfind->read_status()){//如果臥室燈關(guān)著
device_pfind->open();
answer_success(fd);
}else{//如果臥室燈開著
answer_fail(fd);
}
}else if(strcmp((char *)str,"cbl")==0){ //收到關(guān)閉臥室燈的指令
device_pfind = findDEVICEinLink("light_bedroom",phead);
if(!device_pfind->read_status()){//如果臥室燈開著
device_pfind->close();
answer_success(fd);
}else{//如果臥室燈關(guān)著
answer_fail(fd);
}
}else if(strcmp((char *)str,"odl")==0){ //收到打開廚房燈的指令
device_pfind = findDEVICEinLink("light_diningroom",phead);
if(device_pfind->read_status()){//如果廚房燈關(guān)著
device_pfind->open();
answer_success(fd);
}else{//如果廚房燈開著
answer_fail(fd);
}
}else if(strcmp((char *)str,"cdl")==0){ //收到關(guān)閉廚房燈的指令
device_pfind = findDEVICEinLink("light_diningroom",phead);
if(!device_pfind->read_status()){//如果廚房燈開著
device_pfind->close();
answer_success(fd);
}else{//如果廚房燈關(guān)著
answer_fail(fd);
}
}else if(strcmp((char *)str,"oll")==0){ //收到打開客廳燈的指令
device_pfind = findDEVICEinLink("light_livingroom",phead);
if(device_pfind->read_status()){//如果客廳燈關(guān)著
device_pfind->open();
answer_success(fd);
}else{//如果客廳燈開著
answer_fail(fd);
}
}else if(strcmp((char *)str,"cll")==0){ //收到關(guān)閉客廳燈的指令
device_pfind = findDEVICEinLink("light_livingroom",phead);
if(!device_pfind->read_status()){//如果客廳燈開著
device_pfind->close();
answer_success(fd);
}else{//如果客廳燈關(guān)著
answer_fail(fd);
}
}else if(strcmp((char *)str,"owl")==0){ //收到打開廁所燈的指令
device_pfind = findDEVICEinLink("light_washroom",phead);
if(device_pfind->read_status()){//如果廁所燈關(guān)著
device_pfind->open();
answer_success(fd);
}else{//如果廁所燈開著
answer_fail(fd);
}
}else if(strcmp((char *)str,"cwl")==0){ //收到關(guān)閉廁所燈的指令
device_pfind = findDEVICEinLink("light_washroom",phead);
if(!device_pfind->read_status()){//如果廁所燈開著
device_pfind->close();
answer_success(fd);
}else{//如果廁所燈關(guān)著
answer_fail(fd);
}
}else if(strcmp((char *)str,"quit")==0){
ret = write(fd,"Bye",4);
if(ret == -1){
perror("write5");
return -1;
}else{
return 0;
}
}else if(strcmp((char *)str,"help")==0){
ret = write(fd,HELP,512);
if(ret == -1){
perror("write4");
return -1;
}else{
return 0;
}
}else{
return -1;
}
}
int socket_init(int port, char *IP, char *UART, int BAUD)
{
int sockfd;
int ret = 0;
int len = sizeof(struct sockaddr_in);
struct sockaddr_in my_addr;
sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd == -1){
perror("socket");
return -1;
}else{
printf("socket success, sockfd = %d\n",sockfd);
}
//bind
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(port);//host to net (2 bytes) //此處原本是atoi(port),但考慮到port本來就是int,所以不用使用atoi
inet_aton(IP,&my_addr.sin_addr); //char* format -> net format
ret = bind(sockfd, (struct sockaddr *)&my_addr, len);
if(ret == -1){
perror("bind");
return -1;
}else{
printf("bind success\n");
}
//listen
ret = listen(sockfd,10);
if(ret == -1){
perror("listen");
return -1;
}else{
printf("listening...\n");
}
return sockfd;
}
int socket_accept(int sockfd) //return 1代表連接成功;return 0代表連接錯誤
{
int ret = 0;
int len = sizeof(struct sockaddr_in);
struct sockaddr_in client_addr;
//accept
conn_sockfd = accept(sockfd,(struct sockaddr *)&client_addr,&len);
if(conn_sockfd == -1){
perror("accept");
return -1;
}else{
printf("accept success, client IP = %s\n",inet_ntoa(client_addr.sin_addr));
fflush(stdout);
ret = write(conn_sockfd,HELP,512);
if(ret == -1){
perror("write3");
return -2;
}else{
return 1; //return 給main里的conn_flag
}
}
}
//socket實現(xiàn)的handler函數(shù)(下面這個)沒有使用第二個參數(shù)fd
//這是因為我發(fā)現(xiàn)把conn_sockfd傳進(jìn)來會導(dǎo)致recv函數(shù)不認(rèn)識這個標(biāo)識符
//但我不太清楚為什么會這樣,因為我用這種方法傳遞其他fd就不會報錯
int socket_receiveANDhandle(struct device *phead, int fd)
{
int ret;
char readbuf[128];
memset(&readbuf,0,sizeof(readbuf));
ret = recv(conn_sockfd, &readbuf, sizeof(readbuf), 0);
if(ret == 0){ //如果recv函數(shù)返回0表示連接已經(jīng)斷開
printf("client has quit\n");
fflush(stdout);
close(conn_sockfd);
return -1;
}else if(ret == -1){
perror("recv");
return 0; //這個值會return 給 main中的conn_flag。此時打印一遍錯誤信息就會結(jié)束,如果不把conn_flag置0,在一個客戶端退出另一個客戶端還未接入時就會不停的打印錯誤信息
//pthread_exit(NULL); //此處不能退出,因為因為這樣如果有一個客戶端接入并退出后這個線程就會退出,為了保證一個客戶端退出后,另一個客戶端還可以接入并正常工作,此處僅顯示錯誤信息而不退出
}
ret = handler(conn_sockfd, readbuf, phead);
if(ret == -1){
printf("socket_cmd_handler error!\n");
}
printf("\nclient: %s\n",readbuf);
fflush(stdout);
return 1; //這句很重要,正常情況下要保持conn_flag為1
}
struct cmd sockt = {
.cmd_name = "socket",
.init = socket_init,
.accept = socket_accept,
.cmd_handler = socket_receiveANDhandle,
};
struct cmd* putSocketInLink(struct cmd *head)
{
struct cmd *p = head;
if(p == NULL){
head = &sockt;
}else{
sockt.next = head;
head = &sockt;
}
return head;
}
2.11?火災(zāi)控制fire_ctl.c的編寫:
作為指令工廠的對象,火災(zāi)控制就是選擇性的實現(xiàn)指令工廠的類:
fire_ctl.c:
#include "cmd_fac.h"
int readDataFromDHT(struct device *phead, int fd) //此處的第二個參數(shù)fd用來指示返回的是溫度還是濕度
{
unsigned char crc, i;
unsigned long data = 0;
struct device *device_pfind = NULL;
device_pfind = findDEVICEinLink("dht",phead);
device_pfind->open();
if (!device_pfind->read_status()){ //主機(jī)接收到從機(jī)發(fā)送的響應(yīng)信號(低電平)
while(!device_pfind->read_status()); //主機(jī)接收到從機(jī)發(fā)送的響應(yīng)信號(高電平)
for (i = 0; i < 32; i++){
while(device_pfind->read_status()); //數(shù)據(jù)位開始的54us低電平
while(!device_pfind->read_status()); //數(shù)據(jù)位開始的高電平就開始
delayMicroseconds(50); //等50us,此時電平高為1,低為0
(data) *= 2; //進(jìn)位
if (device_pfind->read_status())
{
(data)++;
}
}
for (i = 0; i < 8; i++){
while(device_pfind->read_status());
while(!device_pfind->read_status());
delayMicroseconds(50);
crc *= 2;
if (device_pfind->read_status())
{
crc++;
}
}
//return 1;
}else{
//return 0;
}
if(fd == 0){
return ((data >> 8) & 0xff); //將溫度的整數(shù)位返回
}else if(fd == 1){
return ((data >> 24) & 0xff); //將濕度的整數(shù)位返回
}
//溫度小數(shù)位:data & 0xff
//濕度小數(shù)位:(data >> 16) & 0xff
}
struct cmd fire = {
.cmd_name = "fire",
.cmd_handler = readDataFromDHT,
};
struct cmd* putFireInLink(struct cmd *head)
{
struct cmd *p = head;
if(p == NULL){
head = &fire;
}else{
fire.next = head;
head = &fire;
}
return head;
}
2.12?語音控制voice_ctl.c的編寫:
作為指令工廠的對象,語音控制就是選擇性的實現(xiàn)指令工廠的類:
voice_ctl.c:
#include "cmd_fac.h"
#define threhold 70
int v_answer(int fd, int cmd)
{
unsigned char buffer[6]= {0xAA, 0X55, 0X00, 0X00, 0X55, 0XAA};
int ret = 0;
if(cmd == 1){ //回復(fù) 成功打開
buffer[2] = 0X02;
buffer[3] = 0X01;
}else if(cmd == 2){ //回復(fù) 成功關(guān)閉
buffer[2] = 0X04;
buffer[3] = 0X03;
}else if(cmd == 3){ //回復(fù) 燈本來就開著哦
buffer[2] = 0X03;
buffer[3] = 0X02;
}else if(cmd == 4){ //回復(fù) 燈本來就關(guān)著哦
buffer[2] = 0X05;
buffer[3] = 0X04;
}else if(cmd == 5){ //回復(fù) 識別成功
buffer[2] = 0X06;
buffer[3] = 0X05;
}else if(cmd == 6){ //回復(fù) 識別失敗
buffer[2] = 0X07;
buffer[3] = 0X06;
}
serialSendstring (fd, buffer, 6);
}
int voice_init(int port, char *IP, char *UART, int BAUD)
{
int serial_fd;
serial_fd = myserialOpen (UART, BAUD);
if(serial_fd < 0){
perror("serial:");
return -1;
}else{
return serial_fd;
}
}
int voice_accept(int serialfd)
{
int ret;
ret = serialDataAvail (serialfd);
if(ret != -1){
return ret;
}else{
perror("serial_DataAvail:");
return -1;
}
}
int voice_receiveANDhandle(struct device *phead, int fd)
{
struct device *device_pfind = NULL;
char readbuf[32] = {'\0'};
int re;
int score;//人臉識別結(jié)果
int val = 0;
int flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
int len = serialGetstring (fd,readbuf) ;
if(len <0){
perror("serialGetstring:");
}
if(strcmp(readbuf,"opli") == 0){ //收到打開客廳燈的指令
device_pfind = findDEVICEinLink("light_livingroom",phead);
if(device_pfind->read_status()){//如果客廳燈關(guān)著
device_pfind->open();
v_answer(fd,1);
}else{//如果客廳燈開著
v_answer(fd,3);
}
}else if(strcmp(readbuf,"clli") == 0){ //收到關(guān)閉客廳燈的指令
device_pfind = findDEVICEinLink("light_livingroom",phead);
if(!device_pfind->read_status()){//如果客廳燈開著
device_pfind->close();
v_answer(fd,2);
}else{//如果客廳燈關(guān)著
v_answer(fd,4);
}
}else if(strcmp(readbuf,"opbe") == 0){ //收到打開臥室燈的指令
device_pfind = findDEVICEinLink("light_bedroom",phead);
if(device_pfind->read_status()){//如果臥室燈關(guān)著
device_pfind->open();
v_answer(fd,1);
}else{//如果臥室燈開著
v_answer(fd,3);
}
}else if(strcmp(readbuf,"clbe") == 0){ //收到關(guān)閉臥室燈的指令
device_pfind = findDEVICEinLink("light_bedroom",phead);
if(!device_pfind->read_status()){//如果臥室燈開著
device_pfind->close();
v_answer(fd,2);
}else{//如果臥室燈關(guān)著
v_answer(fd,4);
}
}else if(strcmp(readbuf,"opdi") == 0){ //收到打開廚房燈的指令
device_pfind = findDEVICEinLink("light_diningroom",phead);
if(device_pfind->read_status()){//如果廚房燈關(guān)著
device_pfind->open();
v_answer(fd,1);
}else{//如果廚房燈開著
v_answer(fd,3);
}
}else if(strcmp(readbuf,"cldi") == 0){ //收到關(guān)閉廚房燈的指令
device_pfind = findDEVICEinLink("light_diningroom",phead);
if(!device_pfind->read_status()){//如果廚房燈開著
device_pfind->close();
v_answer(fd,2);
}else{//如果廚房燈關(guān)著
v_answer(fd,4);
}
}else if(strcmp(readbuf,"opwa") == 0){ //收到打開廁所燈的指令
device_pfind = findDEVICEinLink("light_washroom",phead);
if(device_pfind->read_status()){//如果廁所燈關(guān)著
device_pfind->open();
v_answer(fd,1);
}else{//如果廁所燈開著
v_answer(fd,3);
}
}else if(strcmp(readbuf,"clwa") == 0){ //收到關(guān)閉廁所燈的指令
device_pfind = findDEVICEinLink("light_washroom",phead);
if(!device_pfind->read_status()){//如果廁所燈開著
device_pfind->close();
v_answer(fd,2);
}else{//如果廁所燈關(guān)著
v_answer(fd,4);
}
}else if(strcmp(readbuf,"opal") == 0){ //收到打開所有燈的指令
device_pfind = findDEVICEinLink("light_washroom",phead);
device_pfind->open();
device_pfind = findDEVICEinLink("light_diningroom",phead);
device_pfind->open();
device_pfind = findDEVICEinLink("light_bedroom",phead);
device_pfind->open();
device_pfind = findDEVICEinLink("light_livingroom",phead);
device_pfind->open();
}else if(strcmp(readbuf,"clal") == 0){ //收到關(guān)閉所有燈的指令
device_pfind = findDEVICEinLink("light_washroom",phead);
device_pfind->close();
device_pfind = findDEVICEinLink("light_diningroom",phead);
device_pfind->close();
device_pfind = findDEVICEinLink("light_bedroom",phead);
device_pfind->close();
device_pfind = findDEVICEinLink("light_livingroom",phead);
device_pfind->close();
}else if(strcmp(readbuf,"gbjb") == 0){ //收到關(guān)閉警報指令
return 3;
}else if(strcmp(readbuf,"hfjb") == 0){ //收到恢復(fù)警報指令
return 2;
}else if(strcmp(readbuf,"rlsb") == 0){ //收到人臉識別指令
device_pfind = findDEVICEinLink("camera",phead);
re = device_pfind->open(); //拍照
if(re == 1){ //拍照成功
oled_show_init(); //OLED清屏
oled_show(); //顯示拍出的照片
score = face_score(); //進(jìn)行人臉識別,獲取置信度分?jǐn)?shù)
printf("score = %d\n",score);
fflush(stdout);
if(score >= threhold){//識別成功
v_answer(fd,5);
val = 4;
}else{//識別失敗
v_answer(fd,6);
val = 5;
}
re = device_pfind->close(); //刪除照片
if(re != 0){
printf("pic remove fail!\n");
fflush(stdout);
}
}else{ //拍照失敗
v_answer(fd,6);
val = 5;
}
score = 0;
return val; //return 4說明 成功,return 5說明失敗
}
}
struct cmd voice = {
.cmd_name = "voice",
.init = voice_init,
.accept = voice_accept,
.cmd_handler = voice_receiveANDhandle,
};
struct cmd* putVoiceInLink(struct cmd *head)
{
struct cmd *p = head;
if(p == NULL){
head = &voice;
}else{
voice.next = head;
head = &voice;
}
return head;
}
2.13?main函數(shù)的編寫:
main函數(shù)的核心思路就是利用以上所有代碼提供的函數(shù)接口來完成項目的總體邏輯:
main函數(shù)共有4個線程:
- socket等待連接的線程
- socket連接成功后接受數(shù)據(jù)的線程
- 語音控制&人臉識別線程
- 火災(zāi)報警線程
main.c:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include "device_fac.h"
#include "cmd_fac.h"
#include "find_link.h"
#include "face_cmp.h"
#include "oled_show.h"
#define port 8888 //端口號
#define IP "192.168.2.56" //IP地址
#define UART "/dev/ttyAMA0" //串口驅(qū)動文件
#define BAUD 115200 //波特率
#define FIRE_TMP 30 //火災(zāi)報警溫度
struct device *device_phead = NULL;
struct cmd *cmd_phead = NULL;
int sockfd;
int serialfd;
//int conn_sockfd;
int conn_flag = 0;
int ret;
//char readbuf[128];
int voice_return_flag;
int tmp;
int humi;
pthread_mutex_t mutex;
void *thread1(void *arg) //socket等待連接的線程
{
struct cmd *cmd_pfind_th1 = NULL;
while(1){
cmd_pfind_th1 = findCMDinLink("socket",cmd_phead);
if(cmd_pfind_th1!=NULL){
//accept
conn_flag = cmd_pfind_th1->accept(sockfd); //conn_flag保證了接收連接成功后才可以開始接收數(shù)據(jù)
if(conn_flag != 1 ){
printf("s_accept error!\n");
fflush(stdout);
}
}else{
printf("thread1:can't find 'socket' in link!\n");
fflush(stdout);
}
}
pthread_exit(NULL);
}
void *thread2(void *arg) //socket連接成功后接受數(shù)據(jù)的線程
{
struct cmd *cmd_pfind_th2 = NULL;
while(1){
while(conn_flag == 1){
cmd_pfind_th2 = findCMDinLink("socket",cmd_phead);
if(cmd_pfind_th2!=NULL){
conn_flag = cmd_pfind_th2->cmd_handler(device_phead,0);//receive msg form client and handle cmd
if(conn_flag == -1){
break;//說明客戶端已退出,退出內(nèi)層while,等待下一個客戶端接入
}
}else{
printf("thread2:can't find 'socket' in link!\n");
fflush(stdout);
}
}
}
pthread_exit(NULL);
}
void *thread3(void *arg) //語音控制&人臉識別線程
{
struct cmd *cmd_pfind_th3 = NULL;
while(1){
cmd_pfind_th3 = findCMDinLink("voice",cmd_phead);
if(cmd_pfind_th3!=NULL){
while(cmd_pfind_th3->accept(serialfd)){ //當(dāng)串口接收到信息時,即當(dāng)語音模塊發(fā)送信息時
pthread_mutex_lock(&mutex); //上鎖
voice_return_flag = cmd_pfind_th3->cmd_handler(device_phead,serialfd);
//voice的cmd_handler函數(shù)的返回值:
//返回2:接收到“恢復(fù)警報”指令
//返回3:接收到“關(guān)閉警報”指令
//返回4:接收到“人臉識別”指令且識別成功
//返回5:接收到“人臉識別”指令且識別失敗
pthread_mutex_unlock(&mutex); //解鎖
}
}else{
printf("thread3:can't find 'voice' in link!\n");
fflush(stdout);
}
}
pthread_exit(NULL);
}
void *thread4(void *arg) //火災(zāi)報警線程
{
struct cmd *cmd_pfind_th4 = NULL;
struct device *device_pfind_th4 = NULL;
while(1){
//delay(1000);//不用delay因為線程間本來就是競爭關(guān)系,加上一共有多個線程,哪怕不delay也不會很快速的運(yùn)行
cmd_pfind_th4 = findCMDinLink("fire",cmd_phead);
if(cmd_pfind_th4!=NULL){
tmp = cmd_pfind_th4->cmd_handler(device_phead,0);//檢測溫度
humi = cmd_pfind_th4->cmd_handler(device_phead,1);//檢測濕度
printf("current temperature:%d\n",tmp); //不斷打印當(dāng)前的溫度,同時充當(dāng)心跳包
fflush(stdout);
pthread_mutex_lock(&mutex); //上鎖
oled_show_init(); //清屏
int tmp1 = tmp; //保留tmp的值,至于為什么要保留存疑,如果不保留之后報警就會失效
oled_tmphumi(tmp1,humi); //顯示在OLED上
pthread_mutex_unlock(&mutex); //解鎖
if(tmp > FIRE_TMP && voice_return_flag!=3){//如果溫度大于XX度且用戶希望警報打開
device_pfind_th4 = findDEVICEinLink("beeper",device_phead);//此處不需要再判斷device_pfind是否為空,因為main函數(shù)在初始化的時候判斷過了
device_pfind_th4->open();
}else{ //否則就關(guān)閉警報
device_pfind_th4 = findDEVICEinLink("beeper",device_phead);//此處不需要再判斷device_pfind是否為空,因為main函數(shù)在初始化的時候判斷過了
device_pfind_th4->close();
}
}else{
printf("thread4:can't find 'fire' in link!\n");
fflush(stdout);
}
}
pthread_exit(NULL);
}
int main()
{
pthread_t t1_id;
pthread_t t2_id;
pthread_t t3_id;
pthread_t t4_id;
struct device *device_pfind = NULL;
struct cmd *cmd_pfind = NULL;
wiringPiSetup(); //初始化wiringPi庫
//指令工廠初始化
cmd_phead = putSocketInLink(cmd_phead);
cmd_phead = putVoiceInLink(cmd_phead);
cmd_phead = putFireInLink(cmd_phead);
//設(shè)備工廠初始化
device_phead = putLight_bedroomInLink(device_phead);
device_phead = putLight_diningroomInLink(device_phead);
device_phead = putLight_livingroomInLink(device_phead);
device_phead = putLight_washroomInLink(device_phead);
device_phead = putDhtInLink(device_phead);
device_phead = putBeeperInLink(device_phead);
device_phead = putCameraInLink(device_phead);
device_pfind = findDEVICEinLink("light_livingroom",device_phead);
if(device_pfind != NULL){
device_pfind->init();
}else{
printf("main:can't find 'livingroom' in link!\n");
}
device_pfind = findDEVICEinLink("light_diningroom",device_phead);
if(device_pfind != NULL){
device_pfind->init();
}else{
printf("main:can't find 'diningroom' in link!\n");
}
device_pfind = findDEVICEinLink("light_bedroom",device_phead);
if(device_pfind != NULL){
device_pfind->init();
}else{
printf("main:can't find 'bedroom' in link!\n");
}
device_pfind = findDEVICEinLink("light_washroom",device_phead);
if(device_pfind != NULL){
device_pfind->init();
}else{
printf("main:can't find 'washroom' in link!\n");
}
device_pfind = findDEVICEinLink("beeper",device_phead);
if(device_pfind != NULL){
device_pfind->init();
}else{
printf("main:can't find 'beeper' in link!\n");
}
//對于dht,唯一需要的初始化就是在通電后延時1秒越過不穩(wěn)定狀態(tài)
delay(1000);
//camera不需要初始化
//socket初始化
cmd_pfind = findCMDinLink("socket",cmd_phead);
if(cmd_pfind != NULL){
sockfd = cmd_pfind->init(port,IP,NULL,0);
if(sockfd == -1){
printf("socket init fail!\n");
}
}else{
printf("main:can't find 'socket' in link!\n");
}
//語音模塊初始化
cmd_pfind = findCMDinLink("voice",cmd_phead);
if(cmd_pfind != NULL){
serialfd = cmd_pfind->init(0,NULL,UART,BAUD);
if(serialfd == -1){
printf("main:voice init fail!\n");
}
}else{
printf("main:can't find 'voice' in link!\n");
}
//人臉識別初始化
face_init();
//OLED初始化
oled_init();
//互斥鎖初始化
ret = pthread_mutex_init(&mutex, NULL);
if(ret != 0){
printf("mutex create error\n");
}
//socket控制線程
ret = pthread_create(&t1_id,NULL,thread1,NULL);
if(ret != 0){
printf("thread1 create error\n");
}
ret = pthread_create(&t2_id,NULL,thread2,NULL);
if(ret != 0){
printf("thread2 create error\n");
}
//語音控制&人臉識別線程
ret = pthread_create(&t3_id,NULL,thread3,NULL);
if(ret != 0){
printf("thread3 create error\n");
}
//火災(zāi)報警線程
ret = pthread_create(&t4_id,NULL,thread4,NULL);
if(ret != 0){
printf("thread4 create error\n");
}
pthread_join(t1_id,NULL);
pthread_join(t2_id,NULL);
pthread_join(t3_id,NULL);
pthread_join(t4_id,NULL);
//釋放python解釋器
face_final();
oled_final();
return 0;
}
以上所有代碼均屬于服務(wù)端
以下代碼屬于客戶端
2.14 socke客戶端 client.c的編寫:
客戶端的編寫大量參考之前寫的socket客戶端,詳見上面的相關(guān)鏈接:
client.c:
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <linux/in.h>
#include <string.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#define port 8888
#define IP "192.168.2.56"
int main()
{
int sockfd;
int ret;
int n_read;
int n_write;
char readbuf[512];
char msg[128];
int fd; //fifo
char fifo_readbuf[20] = {0};
char *fifo_msg = "quit";
pid_t fork_return;
/*if(argc != 3){
printf("param error!\n");
return 1;
}*/
struct sockaddr_in server_addr;
memset(&server_addr,0,sizeof(struct sockaddr_in));
//socket
sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd == -1){
perror("socket");
return 1;
}else{
printf("socket success, sockfd = %d\n",sockfd);
}
//connect
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);//host to net (2 bytes)
inet_aton(IP,&server_addr.sin_addr);
ret = connect(sockfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr_in));
if(ret == -1){
perror("connect");
return 1;
}else{
printf("connect success!\n");
}
//fifo
if(mkfifo("./fifo",S_IRWXU) == -1 && errno != EEXIST)
{
perror("fifo");
}
//fork
fork_return = fork();
if(fork_return > 0){//father keeps writing msg
while(1){
//write
memset(&msg,0,sizeof(msg));
//printf("\ntype msg:");
scanf("%s",(char *)msg);
n_write = write(sockfd,&msg,strlen(msg));
if(msg[0]=='q' && msg[1]=='u' && msg[2]=='i' && msg[3]=='t'){
printf("quit detected!\n");
fd = open("./fifo",O_WRONLY);
write(fd,fifo_msg,strlen(fifo_msg));
close(fd);
close(sockfd);
wait(NULL);
break;
}
if(n_write == -1){
perror("write");
return 1;
}else{
printf("%d bytes msg sent\n",n_write);
}
}
}else if(fork_return < 0){
perror("fork");
return 1;
}else{//son keeps reading
while(1){
fd = open("./fifo",O_RDONLY|O_NONBLOCK);
lseek(fd, 0, SEEK_SET);
read(fd,&fifo_readbuf,20);
//printf("read from fifo:%s\n",fifo_readbuf);
if(fifo_readbuf[0]=='q' && fifo_readbuf[1]=='u' && fifo_readbuf[2]=='i' && fifo_readbuf[3]=='t'){
exit(1);
}
//read
memset(&readbuf,0,sizeof(readbuf));
n_read = read(sockfd,&readbuf,512);
if(n_read == -1){
perror("read");
return 1;
}else{
printf("\nserver: %s\n",readbuf);
}
}
}
return 0;
}
③注意事項
3.1 .h文件的格式
由于使用工廠模式,涉及到很多頭文件的調(diào)用,所以為了避免重復(fù)調(diào)用的錯誤,在.h文件中使用條件編譯非常重要,具體格式如下:
#ifndef __XXXXX_H__
#define __XXXXX_H__
//頭文件內(nèi)容
#endif
3.2? 關(guān)于cmd_pfind和device_pfind
cmd_pfind和device_pfind不能設(shè)置為全局變量,而應(yīng)該設(shè)置為局部變量
因為如果設(shè)置為全局變量,那么在多個線程里都會使用它們來定位需要的函數(shù),如果一個線程剛定義,另一個線程也定義了,可能會造成混亂,所以為了不讓它們成為臨界資源,要設(shè)置為局部變量。
Q:如果設(shè)置為全局變量,并且加鎖會怎么樣?
A:依然不行。在本代碼中,socket的accept函數(shù)和recv函數(shù);語音模塊的serialgetstring函數(shù)都會阻塞,這將導(dǎo)致阻塞時永遠(yuǎn)無法解鎖,所以不能用鎖。
3.3? 關(guān)于人臉識別和OLED顯示
人臉識別位于語音控制的線程中,如果說出“人臉識別”就會調(diào)用人臉識別的程序,同時還會調(diào)用OLED的程序來顯示照片;而在火災(zāi)報警線程中每隔一段時間也會調(diào)用OLED的程序來顯示溫度和濕度。這就導(dǎo)致了:如果在人臉識別調(diào)用OLED程序創(chuàng)建PYobject的同時火災(zāi)報警線程也正好調(diào)用OLED程序來創(chuàng)建PYobject,這就會導(dǎo)致段錯誤。
為了避免段錯誤,設(shè)置一個互斥鎖,使得人臉識別的過程中,暫時讓火災(zāi)報警程序阻塞,這樣就不會造成段錯誤,而且人臉識別通常只有幾秒,所以不會過久的阻塞火災(zāi)報警程序,不會影響安全性。文章來源:http://www.zghlxwxcb.cn/news/detail-844281.html
并且,最重要的是,由于使用了鎖,在語音控制線程里的cmd_handler里調(diào)用的read函數(shù)必須更改為非阻塞的模式!否則一旦在上鎖后阻塞住就會造成死鎖!文章來源地址http://www.zghlxwxcb.cn/news/detail-844281.html
//將fd修改為非阻塞的方式
int flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
//然后再使用fd來read就不會阻塞了
④代碼的編譯&運(yùn)行&關(guān)閉
?編譯語句
gcc *.c -I /usr/include/python3.11/ -l python3.11 -lwiringPi -o smart_home
運(yùn)行語句?
./smart_home
關(guān)閉程序方法
ps -ef|grep smart_home
kill 進(jìn)程編號
到了這里,關(guān)于基于樹莓派實現(xiàn) --- 智能家居的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!