在58同城做網(wǎng)站怎么樣如何發(fā)布自己的html網(wǎng)站
最效果展示
演示視頻鏈接:基于樹莓派實(shí)現(xiàn)的智能家居_嗶哩嗶哩_bilibilihttps://www.bilibili.com/video/BV1Tr421n7BM/?spm_id_from=333.999.0.0
(PS:房屋模型的搭建是靠紙板箱和淘寶買的家居模型,戶型參考了留學(xué)時(shí)短租的公寓~)?
前言
到目前為止,對(duì)于linux的嵌入式軟件開發(fā),從底層到上層都有了一定的認(rèn)識(shí)。這個(gè)項(xiàng)目的初衷就是整合知識(shí)并以工廠模式的架構(gòu)開發(fā)項(xiàng)目。
功能實(shí)現(xiàn)
- 實(shí)現(xiàn)了socket服務(wù)器遠(yuǎn)程控制臥室,餐廳,廁所,客廳4盞燈的開啟和關(guān)閉
- 實(shí)現(xiàn)了語音控制臥室,餐廳,廁所,客廳4盞燈的開啟和關(guān)閉
- 實(shí)現(xiàn)了當(dāng)溫度超過閾值的時(shí)候進(jìn)行火災(zāi)報(bào)警,并且可以語音關(guān)閉警報(bào)
- 實(shí)現(xiàn)了進(jìn)門前結(jié)合語音,OLED和攝像頭的人臉識(shí)別
- 實(shí)現(xiàn)了實(shí)時(shí)的遠(yuǎn)程視頻監(jiān)控
- 實(shí)現(xiàn)OLED屏幕的實(shí)時(shí)溫濕度顯示
開發(fā)環(huán)境 & 實(shí)現(xiàn)思路
- 開發(fā)板:樹莓派3B+
- 開發(fā)語言:C
- 編程工具:Source Insight 3
工廠設(shè)計(jì)
對(duì)于這個(gè)項(xiàng)目的實(shí)現(xiàn),采用上節(jié)學(xué)到的工廠模式來設(shè)計(jì),從而提升整體代碼的穩(wěn)定性和可拓展性。
軟件設(shè)計(jì)模式 --- 類,對(duì)象和工廠模式的引入-CSDN博客
?閱讀功能需求后,結(jié)合工廠模式的思路可以先設(shè)計(jì)兩個(gè)工廠:指令工廠 和 設(shè)備工廠
- 指令工廠:存儲(chǔ)需要使用到的指令
- 設(shè)備工廠:存儲(chǔ)需要使用到的設(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. 工廠的對(duì)象
實(shí)現(xiàn)思路Q&A
Q:如何實(shí)現(xiàn)socket服務(wù)器的遠(yuǎn)程控制?
A:使用之前學(xué)習(xí)的socket知識(shí),創(chuàng)建一個(gè)服務(wù)端一個(gè)客戶端,服務(wù)端負(fù)責(zé)創(chuàng)建套接字并綁定,然后阻塞監(jiān)聽;客戶端負(fù)責(zé)建立連接后發(fā)送指令。指令在服務(wù)端通過指令工廠中socket對(duì)象的cmd_handler函數(shù)進(jìn)行分析并作出相關(guān)動(dòng)作。最后在main函數(shù)中使用一個(gè)線程不斷阻塞等待新客戶端的加入;使用另一個(gè)線程不斷阻塞接受客戶端傳來的指令并分析。
參考我之前的博文:
(👇相關(guān)的API講解)
Linux socket網(wǎng)絡(luò)編程概述 和 相關(guān)API講解_linux 網(wǎng)絡(luò)編程-CSDN博客
(👇具體如何使用API的實(shí)戰(zhàn),父子進(jìn)程版)
基于Linux并結(jié)合socket網(wǎng)絡(luò)編程的ftp服務(wù)器的實(shí)現(xiàn)-CSDN博客
(👇具體如何使用API的實(shí)戰(zhàn),多線程版)
使用香橙派并基于Linux實(shí)現(xiàn)最終版智能垃圾桶項(xiàng)目 --- 上_linux 打印扔垃圾桶-CSDN博客
Q:如何實(shí)現(xiàn)語音控制的操作?
A:使用之前學(xué)習(xí)的SU-03T,在其官網(wǎng)對(duì)指令進(jìn)行編輯和燒錄,然后通過串口來和樹莓派進(jìn)行通信。同樣通過指令工廠中語音控制對(duì)象的cmd_handler函數(shù)來進(jìn)行分析并作出相關(guān)動(dòng)作。最后在main函數(shù)開啟一個(gè)線程不斷通過串口阻塞讀取語音模塊的指令并分析。
參考我之前的博文:
(👇如何設(shè)置官網(wǎng)指令并燒錄&通過電平變化來控制語音模塊的實(shí)例)
語音小車---6 + 最終整合_unioneupdatetool-CSDN博客
(👇關(guān)于在多線程環(huán)境下通過串口通信來控制語音模塊的實(shí)例)
使用香橙派并基于Linux實(shí)現(xiàn)最終版智能垃圾桶項(xiàng)目 --- 下_香橙派 項(xiàng)目-CSDN博客
Q:如何實(shí)現(xiàn)火災(zāi)報(bào)警?
A:使用之前學(xué)習(xí)的溫濕度傳感器DHT11和蜂鳴器,通過閱讀DHT11的手冊(cè),在設(shè)備工廠中實(shí)現(xiàn)其激活和讀取狀態(tài)的函數(shù);在指令工廠中,調(diào)用剛剛實(shí)現(xiàn)的函數(shù)結(jié)合手冊(cè)實(shí)現(xiàn)溫濕度的獲取。最后在main函數(shù)中開啟一個(gè)線程不斷判斷當(dāng)前的溫度來決定是否驅(qū)動(dòng)蜂鳴器。
參考我之前的博文:
(👇DHT11的介紹和如何通過手冊(cè)驅(qū)動(dòng)DHT11的實(shí)例)
溫濕度傳感器 DHT11_dht11溫濕度傳感器 庫從哪里下載-CSDN博客
(👇別人實(shí)現(xiàn)的,通過樹莓派驅(qū)動(dòng)DHT11的例程)
樹莓派驅(qū)動(dòng)DH11溫濕度傳感器_如何使用zynq驅(qū)動(dòng)dh11-CSDN博客
Q:如何實(shí)現(xiàn)OLED屏幕顯示?
參考我之前的博文:
(👇關(guān)于樹莓派驅(qū)動(dòng)OLED屏幕)
使用樹莓派 結(jié)合Python Adafruit驅(qū)動(dòng)OLED屏幕 顯示實(shí)時(shí)視頻-CSDN博客
Q:如何實(shí)現(xiàn)人臉識(shí)別?
A:使用一枚之前用過的USB攝像頭HBV-W202012HD V33,接入樹莓派,在設(shè)備工廠為其實(shí)現(xiàn)拍照等功能。然后接入阿里云的人臉識(shí)別方案,當(dāng)收到對(duì)應(yīng)的人臉識(shí)別語音指令時(shí),在指令工廠的語音模塊對(duì)象下的cmd_handler函數(shù)中添加人臉識(shí)別的代碼,并根據(jù)結(jié)果通過串口回傳給語音模塊播報(bào)結(jié)果。
參考我之前的博文:
(👇香橙派中驅(qū)動(dòng)攝像頭,并調(diào)用阿里云物品識(shí)別的實(shí)例)
使用香橙派并基于Linux實(shí)現(xiàn)最終版智能垃圾桶項(xiàng)目 --- 下_香橙派 項(xiàng)目-CSDN博客
(👇樹莓派中驅(qū)動(dòng)攝像頭,并調(diào)用阿里云人臉識(shí)別的實(shí)例)
基于阿里云平臺(tái) 通過樹莓派實(shí)現(xiàn) 1:1人臉識(shí)別-CSDN博客
硬件接線
整體的接線情況如下:
注意!如果外設(shè)和單片機(jī)采用了不同供電,且外設(shè)和單片機(jī)存在信息交互,那么就必須共地!!?
?
預(yù)備工作
在有了大概的思路和硬件接線完畢完成后,要進(jìn)行兩個(gè)重要的預(yù)備工作:
攝像頭的接入和mpjg-streamer的自動(dòng)后臺(tái)運(yùn)行
由于這個(gè)項(xiàng)目在運(yùn)行時(shí)只要涉及人臉識(shí)別就需要用到攝像頭拍照,并且需要實(shí)現(xiàn)實(shí)時(shí)的監(jiān)控畫面,所以先將USB攝像頭接入并讓mpjg-streamer在每次樹莓派開機(jī)的時(shí)候自動(dòng)運(yùn)行就很有必要了。
實(shí)現(xiàn)其實(shí)很簡單,可以參考我的這篇博文:
樹莓派接入U(xiǎn)SB攝像頭并使用fswebcam和mjpg-streamer進(jìn)行測(cè)試_在樹莓派ros2中安裝usb攝像頭驅(qū)動(dòng)-CSDN博客
語音模塊SU-03T的指令編輯和燒寫
這一步雖然叫預(yù)備工作,但是在實(shí)際開發(fā)中隨著項(xiàng)目的完善肯定要多次修改和燒寫,但是為了邏輯清晰所以將這一步歸為預(yù)備工作,僅展示最后的效果。
同時(shí)再次提醒,只要涉及到SU-03T的串口輸入輸出,就要下載固件而不是SDK!!!
關(guān)于網(wǎng)站和具體細(xì)節(jié),請(qǐng)移步至上面的相關(guān)鏈接
引腳配置
命令設(shè)置
自定義配置
實(shí)現(xiàn)效果
1. 開機(jī)播報(bào)“海豹助手幫你實(shí)現(xiàn)智能居住體驗(yàn)"
1. 當(dāng)說出“你好小豹”可以喚醒模塊,模塊回復(fù)“海豹在”或“有什么可以幫到你”
2. 當(dāng)超過10s沒有指令或說出“退下”時(shí),模塊會(huì)進(jìn)入休眠模式,并回復(fù)“有需要再叫我”
3. 當(dāng)說出“打開/關(guān)閉 客廳/臥室/餐廳/廁所 燈”時(shí),模塊回復(fù)“收到”,并根據(jù)當(dāng)前燈的狀態(tài)打開/關(guān)閉 相應(yīng)的燈或回復(fù)“燈本來就開/關(guān)著哦”
4.?當(dāng)說出“打開/關(guān)閉 所有燈”時(shí),模塊回復(fù)“收到”,并打開/關(guān)閉所有燈
5.?當(dāng)說出“關(guān)閉警報(bào)”時(shí),模塊回復(fù)“已關(guān)閉,但為了您的安全請(qǐng)隨時(shí)說出‘?恢復(fù)警報(bào)?’來恢復(fù)報(bào)警功能!”,并關(guān)閉警報(bào)
6.??當(dāng)說出“恢復(fù)警報(bào)”時(shí),模塊回復(fù)“火災(zāi)警報(bào)已經(jīng)恢復(fù)工作”,并恢復(fù)警報(bào)
7.? 當(dāng)說出“人臉識(shí)別”時(shí),開始人臉識(shí)別,并根據(jù)識(shí)別結(jié)果回復(fù)“識(shí)別成功”或“識(shí)別失敗”
代碼開發(fā)
在剛剛提到,代碼編寫的主要工具是“Source Insight”,所以主要的代碼編寫就在windows下;寫完發(fā)送到樹莓派測(cè)試
①代碼預(yù)創(chuàng)建
- 首先創(chuàng)建一個(gè)名為“smart_home”文件夾用于保存項(xiàng)目所有相關(guān)文件,并在其中創(chuàng)建一個(gè)“si”文件夾用于保存source insight工程:
- 在“smart_home”下創(chuàng)建會(huì)使用的.c和.h文件:
(以下是最終版的結(jié)果,實(shí)際開發(fā)過程中這一步先創(chuàng)建可能會(huì)需要的文件,后面隨著實(shí)現(xiàn)慢慢的添加和修改)
?一共23個(gè)代碼文件
- 打開source insight,創(chuàng)建一個(gè)新工程并將代碼全部包含進(jìn)來并同步:
(具體步驟見上篇博文)
最終效果:
此時(shí)就可以開始正式編程了!
②代碼編寫
2.1 指令工廠cmd_fac.h和設(shè)備工廠dvice_fac.h的編寫:
這一步主要是根據(jù)上一節(jié)工廠模式的思路來實(shí)現(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庫自己實(shí)現(xiàn)的函數(shù),詳見:
樹莓派的的串口通信協(xié)議-CSDN博客
但是關(guān)于“serialSendstring”函數(shù)和“serialGetstring”函數(shù)需要進(jìn)行一些修改,其原因就是SU-03T(語音模塊)在規(guī)定串口輸入的時(shí)候有固定要求的幀頭幀尾格式:
//注意,這個(gè)通過串口發(fā)送字符串的函數(shù),其中的read函數(shù)的第三個(gè)參數(shù)不能使用strlen
//因?yàn)榘l(fā)送給語音模塊的數(shù)據(jù)有固定的幀頭幀尾,都是16進(jìn)制數(shù)不包含結(jié)束符
//所以如果使用了strlen的話,就無法成功的發(fā)送
//所以為這個(gè)函數(shù)加一個(gè)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ù)增加一個(gè)參數(shù)len,用于指示發(fā)送數(shù)據(jù)的具體長度
- 兩個(gè)函數(shù)的第二個(gè)參數(shù)都加上“unsigned”,因?yàn)椴患拥脑挃?shù)據(jù)長度可能會(huì)超出普通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) ; // 10mSreturn fd ;
}//注意,這個(gè)通過串口發(fā)送字符串的函數(shù),其中的read函數(shù)的第三個(gè)參數(shù)不能使用strlen
//因?yàn)榘l(fā)送給語音模塊的數(shù)據(jù)有固定的幀頭幀尾,都是16進(jìn)制數(shù)不包含結(jié)束符
//所以如果使用了strlen的話,就無法成功的發(fā)送
//所以為這個(gè)函數(shù)加一個(gè)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 在工廠鏈表中查找對(duì)象的代碼find_link.c/.h的編寫:
這部分的代碼實(shí)現(xiàn)的功能就是封裝“在工廠鏈表中查找特定對(duì)象”的函數(shù),其實(shí)現(xiàn)思路就是根據(jù)對(duì)象的名字來在鏈表中進(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)大致實(shí)現(xiàn),其核心思路就是先用python調(diào)用Adafruit_Python_SSD1306庫實(shí)現(xiàn)清屏,顯示溫濕度和顯示圖片的代碼,再用C語言調(diào)用python封裝這三個(gè)函數(shù),最后獲得可以清屏,顯示溫濕度和顯示圖片的C函數(shù)
oled_camera.py:
def init(): # Raspberry Pi pin configuration:RST = 24# Note the following are only used with SPI:DC = 23SPI_PORT = 0SPI_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 = 23SPI_PORT = 0SPI_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 = 23SPI_PORT = 0SPI_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.widthheight = disp.heightimage = 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()#測(cè)試用
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文件中的對(duì)應(yīng)函數(shù)if (!pFunc){PyErr_Print();printf("Error: failed to load function\n");goto FAILED_FUNC;}//創(chuàng)建一個(gè)字符串作為參數(shù)PyObject *pArgs = Py_BuildValue("(i,i)",tmp,humi); //(i,i)代表有兩個(gè)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__Hvoid 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?人臉識(shí)別代碼face_cmp.c/.h & python代碼的編寫:
這部分的代碼在之前給出的鏈接里已經(jīng)大致實(shí)現(xiàn),其核心思路就是先用python調(diào)用阿里云的1:1人臉識(shí)別,再用C語言調(diào)用python,最后獲得可以進(jìn)行人臉識(shí)別的C函數(shù)
face.py:
# -*- coding: utf-8 -*-
# 引入依賴�?# 最低SDK版本要求:facebody20191230的SDK版本需大于等于4.0.8
# 可以在此倉庫地址中引用最新版本SDK:https://pypi.org/project/alibabacloud-facebody20191230/
# pip install alibabacloud_facebody20191230import 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 RuntimeOptionsdef face_detect():config = Config(# 創(chuàng)建AccessKey ID和AccessKey Secret,請(qǐng)參考https://help.aliyun.com/document_detail/175144.html�? # 如果您用的是RAM用戶的AccessKey,還需要為RAM用戶授予權(quán)限AliyunVIAPIFullAccess,請(qǐng)參考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',# 訪問的域名對(duì)應(yīng)的regionregion_id='cn-shanghai')runtime_option = RuntimeOptions()compare_face_request = CompareFaceAdvanceRequest()#場(chǎng)景一:文件在本地streamA = open(r'/home/pi/mjm_code/smart_home/mjm.png', 'rb') #預(yù)存的照�? compare_face_request.image_urlaobject = streamAstreamB = open(r'/home/pi/mjm_code/smart_home/face.png', 'rb') #待測(cè)試的照片compare_face_request.image_urlbobject = streamBtry:# 初始化Clientclient = 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 scoreexcept Exception as error:# 獲取整體報(bào)錯(cuò)信息print(error)# 獲取單個(gè)字段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,是個(gè)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文件中的對(duì)應(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,是個(gè)int型,用‘i’表示{PyErr_Print();printf("Error: parse failed");goto FAILED_RESULT;}/* 如果函數(shù)返回的是字符串,上面的PyArg_Parse則需要用‘s’來表示,且下面注釋的代碼非常重要,因?yàn)樽址砹似涫椎刂?#xff0c;所以不能直接復(fù)制而是需要使用strncpy函數(shù)!!!category = (char *)malloc(sizeof(char) * (strlen(result) + 1) ); //開辟一個(gè)新的字符串常量。+1是為了留出空間給\0memset(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__Hvoid face_init(void);
void face_final(void);
int face_score(void);#endif
2.6?4盞燈的代碼light_xxx.c的編寫:
作為設(shè)備工廠的對(duì)象,燈代碼的編寫就是選擇性的實(shí)現(xiàn)設(shè)備工廠的類,并且4盞燈的代碼除了wiringPi對(duì)應(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è)備工廠的對(duì)象,dht11代碼的編寫就是選擇性的實(shí)現(xiàn)設(shè)備工廠的類:
dht11.c:
#include "device_fac.h"#define dht 4int dht_start()
{pinMode(dht, OUTPUT); //起始拉高電平digitalWrite(dht, 1); delay(1000); pinMode(dht, OUTPUT); //拉低超過18msdigitalWrite(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è)備工廠的對(duì)象,蜂鳴器代碼的編寫就是選擇性的實(shí)現(xiàn)設(shè)備工廠的類,蜂鳴器的實(shí)現(xiàn)和4盞燈極其類似,直接看代碼:
beeper.c:
#include "device_fac.h"#define io 5int 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è)備工廠的對(duì)象,攝像頭代碼的編寫就是選擇性的實(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)時(shí)間讓照片拍出來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的編寫:
作為指令工廠的對(duì)象,socket控制就是選擇性的實(shí)現(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); //由于字符串的首地址是字符串的名字,所以此時(shí)相當(dāng)于傳入的地址,所有對(duì)字符串的操作都會(huì)影響它,所以需要進(jìn)行備份,先備份再對(duì)備份的數(shù)據(jù)進(jìn)行數(shù)據(jù)處理就不會(huì)影響原數(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,所以不用使用atoiinet_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代表連接錯(cuò)誤
{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實(shí)現(xiàn)的handler函數(shù)(下面這個(gè))沒有使用第二個(gè)參數(shù)fd
//這是因?yàn)槲野l(fā)現(xiàn)把conn_sockfd傳進(jìn)來會(huì)導(dǎo)致recv函數(shù)不認(rèn)識(shí)這個(gè)標(biāo)識(shí)符
//但我不太清楚為什么會(huì)這樣,因?yàn)槲矣眠@種方法傳遞其他fd就不會(huì)報(bào)錯(cuò)
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; //這個(gè)值會(huì)return 給 main中的conn_flag。此時(shí)打印一遍錯(cuò)誤信息就會(huì)結(jié)束,如果不把conn_flag置0,在一個(gè)客戶端退出另一個(gè)客戶端還未接入時(shí)就會(huì)不停的打印錯(cuò)誤信息 //pthread_exit(NULL); //此處不能退出,因?yàn)橐驗(yàn)檫@樣如果有一個(gè)客戶端接入并退出后這個(gè)線程就會(huì)退出,為了保證一個(gè)客戶端退出后,另一個(gè)客戶端還可以接入并正常工作,此處僅顯示錯(cuò)誤信息而不退出 }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的編寫:
作為指令工廠的對(duì)象,火災(zāi)控制就是選擇性的實(shí)現(xiàn)指令工廠的類:
fire_ctl.c:
#include "cmd_fac.h"int readDataFromDHT(struct device *phead, int fd) //此處的第二個(gè)參數(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)信號(hào)(低電平)while(!device_pfind->read_status()); //主機(jī)接收到從機(jī)發(fā)送的響應(yīng)信號(hào)(高電平)for (i = 0; i < 32; i++){while(device_pfind->read_status()); //數(shù)據(jù)位開始的54us低電平while(!device_pfind->read_status()); //數(shù)據(jù)位開始的高電平就開始delayMicroseconds(50); //等50us,此時(shí)電平高為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的編寫:
作為指令工廠的對(duì)象,語音控制就是選擇性的實(shí)現(xiàn)指令工廠的類:
voice_ctl.c:
#include "cmd_fac.h"#define threhold 70int 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ù) 識(shí)別成功buffer[2] = 0X06;buffer[3] = 0X05;}else if(cmd == 6){ //回復(fù) 識(shí)別失敗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;//人臉識(shí)別結(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)閉警報(bào)指令return 3;}else if(strcmp(readbuf,"hfjb") == 0){ //收到恢復(fù)警報(bào)指令return 2;}else if(strcmp(readbuf,"rlsb") == 0){ //收到人臉識(shí)別指令device_pfind = findDEVICEinLink("camera",phead);re = device_pfind->open(); //拍照if(re == 1){ //拍照成功oled_show_init(); //OLED清屏oled_show(); //顯示拍出的照片score = face_score(); //進(jìn)行人臉識(shí)別,獲取置信度分?jǐn)?shù)printf("score = %d\n",score);fflush(stdout);if(score >= threhold){//識(shí)別成功v_answer(fd,5);val = 4;}else{//識(shí)別失敗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ù)接口來完成項(xiàng)目的總體邏輯:
main函數(shù)共有4個(gè)線程:
- socket等待連接的線程
- socket連接成功后接受數(shù)據(jù)的線程
- 語音控制&人臉識(shí)別線程
- 火災(zāi)報(bào)警線程
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 //端口號(hào)
#define IP "192.168.2.56" //IP地址
#define UART "/dev/ttyAMA0" //串口驅(qū)動(dòng)文件
#define BAUD 115200 //波特率
#define FIRE_TMP 30 //火災(zāi)報(bào)警溫度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){//acceptconn_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 cmdif(conn_flag == -1){break;//說明客戶端已退出,退出內(nèi)層while,等待下一個(gè)客戶端接入}}else{printf("thread2:can't find 'socket' in link!\n");fflush(stdout);}}}pthread_exit(NULL);
}void *thread3(void *arg) //語音控制&人臉識(shí)別線程
{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)串口接收到信息時(shí),即當(dāng)語音模塊發(fā)送信息時(shí)pthread_mutex_lock(&mutex); //上鎖voice_return_flag = cmd_pfind_th3->cmd_handler(device_phead,serialfd);//voice的cmd_handler函數(shù)的返回值://返回2:接收到“恢復(fù)警報(bào)”指令//返回3:接收到“關(guān)閉警報(bào)”指令//返回4:接收到“人臉識(shí)別”指令且識(shí)別成功//返回5:接收到“人臉識(shí)別”指令且識(shí)別失敗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)報(bào)警線程
{struct cmd *cmd_pfind_th4 = NULL;struct device *device_pfind_th4 = NULL;while(1){//delay(1000);//不用delay因?yàn)榫€程間本來就是競(jìng)爭關(guān)系,加上一共有多個(gè)線程,哪怕不delay也不會(huì)很快速的運(yùn)行cmd_pfind_th4 = findCMDinLink("fire",cmd_phead);if(cmd_pfind_th4!=NULL){tmp = cmd_pfind_th4->cmd_handler(device_phead,0);//檢測(cè)溫度humi = cmd_pfind_th4->cmd_handler(device_phead,1);//檢測(cè)濕度printf("current temperature:%d\n",tmp); //不斷打印當(dāng)前的溫度,同時(shí)充當(dāng)心跳包fflush(stdout); pthread_mutex_lock(&mutex); //上鎖oled_show_init(); //清屏int tmp1 = tmp; //保留tmp的值,至于為什么要保留存疑,如果不保留之后報(bào)警就會(huì)失效oled_tmphumi(tmp1,humi); //顯示在OLED上pthread_mutex_unlock(&mutex); //解鎖if(tmp > FIRE_TMP && voice_return_flag!=3){//如果溫度大于XX度且用戶希望警報(bào)打開device_pfind_th4 = findDEVICEinLink("beeper",device_phead);//此處不需要再判斷device_pfind是否為空,因?yàn)閙ain函數(shù)在初始化的時(shí)候判斷過了device_pfind_th4->open(); }else{ //否則就關(guān)閉警報(bào)device_pfind_th4 = findDEVICEinLink("beeper",device_phead);//此處不需要再判斷device_pfind是否為空,因?yàn)閙ain函數(shù)在初始化的時(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");}//對(duì)于dht,唯一需要的初始化就是在通電后延時(shí)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");}//人臉識(shí)別初始化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");}//語音控制&人臉識(shí)別線程ret = pthread_create(&t3_id,NULL,thread3,NULL);if(ret != 0){printf("thread3 create error\n");}//火災(zāi)報(bào)警線程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; //fifochar 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));//socketsockfd = socket(AF_INET,SOCK_STREAM,0);if(sockfd == -1){perror("socket");return 1;}else{printf("socket success, sockfd = %d\n",sockfd);}//connectserver_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");}//fifoif(mkfifo("./fifo",S_IRWXU) == -1 && errno != EEXIST){perror("fifo");}//forkfork_return = fork();if(fork_return > 0){//father keeps writing msgwhile(1){//writememset(&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);}//readmemset(&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;
}
③注意事項(xiàng)
3.1 .h文件的格式
由于使用工廠模式,涉及到很多頭文件的調(diào)用,所以為了避免重復(fù)調(diào)用的錯(cuò)誤,在.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è)置為局部變量
因?yàn)槿绻O(shè)置為全局變量,那么在多個(gè)線程里都會(huì)使用它們來定位需要的函數(shù),如果一個(gè)線程剛定義,另一個(gè)線程也定義了,可能會(huì)造成混亂,所以為了不讓它們成為臨界資源,要設(shè)置為局部變量。
Q:如果設(shè)置為全局變量,并且加鎖會(huì)怎么樣?
A:依然不行。在本代碼中,socket的accept函數(shù)和recv函數(shù);語音模塊的serialgetstring函數(shù)都會(huì)阻塞,這將導(dǎo)致阻塞時(shí)永遠(yuǎn)無法解鎖,所以不能用鎖。
3.3? 關(guān)于人臉識(shí)別和OLED顯示
人臉識(shí)別位于語音控制的線程中,如果說出“人臉識(shí)別”就會(huì)調(diào)用人臉識(shí)別的程序,同時(shí)還會(huì)調(diào)用OLED的程序來顯示照片;而在火災(zāi)報(bào)警線程中每隔一段時(shí)間也會(huì)調(diào)用OLED的程序來顯示溫度和濕度。這就導(dǎo)致了:如果在人臉識(shí)別調(diào)用OLED程序創(chuàng)建PYobject的同時(shí)火災(zāi)報(bào)警線程也正好調(diào)用OLED程序來創(chuàng)建PYobject,這就會(huì)導(dǎo)致段錯(cuò)誤。
為了避免段錯(cuò)誤,設(shè)置一個(gè)互斥鎖,使得人臉識(shí)別的過程中,暫時(shí)讓火災(zāi)報(bào)警程序阻塞,這樣就不會(huì)造成段錯(cuò)誤,而且人臉識(shí)別通常只有幾秒,所以不會(huì)過久的阻塞火災(zāi)報(bào)警程序,不會(huì)影響安全性。
并且,最重要的是,由于使用了鎖,在語音控制線程里的cmd_handler里調(diào)用的read函數(shù)必須更改為非阻塞的模式!否則一旦在上鎖后阻塞住就會(huì)造成死鎖!
//將fd修改為非阻塞的方式int flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);//然后再使用fd來read就不會(huì)阻塞了
④代碼的編譯&運(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)程編號(hào)