智能家居代码架构—简单工厂模式
设计模式:
代码设计经验的总结,稳定、拓展性更强,是一系列编程思想。有23种,代码更容易被他人理解、保证代码可靠性、程序的重用性。设计模式通常描述了一组相互紧密作用的类与对象。
算法不是设计模式,因为算法致力于解决问题而非设计问题。
类和对象:
类是一种用户定义的引用数据类型,也称类类型。结构体
对象:类的一种具象
工厂模式:
这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
//animal.h
#include
struct Animal{
char name[128];
int sex;
int age;
void *peat();
void *pbeat();
struct Animal *next;
}
struct Animal* putCatInLink(struct Animal *phread);
struct Animal* putDogInLink(struct Animal *phread);
//cat.c
#include "animal.h"
void catEat()
{
printf("cat bite fish\n");
}
void catBeat()
{
printf("cat catch person\n");
}
struct Animal cat = {
.name = "Tom",
.peat = catEat,
.pbeat = catBeat
};
struct Animal* putCatInLink(struct Animal *phread)
{
if(phread == NULL){
phread = &cat;
return phread;
}else{
cat.next = phread;
phead = &cat;
return phead;
}
}
//dog.c
#include "animal.h"
void dogEat()
{
printf("dog bite meat\n");
}
void dogBeat()
{
printf("dog bite person\n");
}
struct Animal dog = {
.name = "huang",
.peat = dogEat,
.pbeat = dogBeat
};
struct Animal* putDogInLink(struct Animal *phread)
{
if(phread == NULL){
phread = &dog;
return phread;
}else{
dog.next = phread;
phead = &dog;
return phead;
}
}
//main.c
#include "animal.h"
struct Animal* findName(char *str, struct Animal *phead)
{
struct Animal *tmp = head;
if(phead == NULL){
printf("NULL\n");
return NULL;
}else{
while(tmp != NULL){
if(strcmp(tmp->name,str) == 0){
return tmp;
}
tmp = tmp->next;
}
return NULL;
}
}
int main()
{
char buf[128] = {'\0'};
struct Animal *phead = NULL;
struct Animal *ptmp
phead = putCatInLink(phead);
phead = putDogInLink(phead);
while(1){
printf("input your choice: Tom huang\n");
scanf("%s", buf);
ptmp = findName(buf,phead);
if(ptmp != NULL){
ptmp->pbeat();
ptmp->peat();
}
memset(buf, '\0', sizeof(buf));
}
return 0;
}
//1.浴室灯 bathroom.c
#include "contrlDevices.h"
#include
int bathroomLightInit(int pinNum)//引脚初始化
{
pinMode(pinNum, OUTPUT);
digitalWrite(pinNum, HIGH);
}
int bathroomLightOpen(int pinNum)//开灯
{
digitalWrite(pinNum, LOW);
}
int bathroomLightClose(int pinNum)//关灯
{
digitalWrite(pinNum, HIGH);
}
struct Devices bathroomLight = {
.devicename = "bathroomLight",
.pinNum = 22,
.devicesInit = bathroomLightInit,
.open = bathroomLightOpen,
.close = bathroomLightClose,
.changeStatus = bathroomLightStatus
};
struct Devices* addBathroomLightToDeviceLink(struct Devices *phead)//头插法加入链表
{
if(phead == NULL){ //链表为空时
return &bathroomLight;
}
else{ //链表不为空时,bathroomLiguht为头插入
bathroomLight.next = phead;
phead = &bathroomLight;
}
}
//同理可以修改成卧室灯、二楼灯、餐厅灯等等。
//2.火灾报警 fire.c
#include "contrlDevices.h"
#include
int fireInit(int pinNum)//引脚初始化
{
pinMode(pinNum, INPUT);//火灾引脚输入,监测外部电平变化
digitalWrite(pinNum, HIGH);
}
int fireStatusRead(int pinNum)//获取引脚状态
{
return digitalRead(pinNum);
}
struct Devices fire = {
.devicename = "fire",
.pinNum = 25,
.devicesInit = fireInit,
.readStatus = fireStatusRead
};
struct Devices* addFireToDeviceLink(struct Devices *phead)//头插法加入链表
{
if(phead == NULL){ //链表为空时
return &fire;
}
else{ //链表不为空时,bathroomLiguht为头插入
fire.next = phead;
phead = &fire;
}
}
//3.设备的头文件。controlDevices.h
#include
#include
struct Devices
{
char devicename[128];//名字
int status;//状态,开还是关
int pinNum;//控制引脚
int(*open)(int pinNum);//函数指针,开灯
int(*close)(int pinNum);//关灯
int(*devicesInit)(int pinNum);//初始化
int(*readStatus)(int pinNum);//获取状态
int(*changeStatus)(int status);//改变状态
struct Devices *next;
};
struct Devices* addBathroomLightToDeviceLink(struct Devices *phead);//链表接口,传给main函数
struct Devices* addFireToDeviceLink(struct Devices *phead);
//4.语音控制 voiceControl.c
#include
#include
#include
#include
#include
#include
#include "inputCommand.h"
int voiceInit(struct InputCommander *voicer, char *ipAdress, char *port)//语音模块,串口初始化
{
int fd;
if((fd = serialOpen(voicer->devicename, 9600)) == -1)//初始化串口,波特率9600
{
exit(-1);
}
voicer->fd = fd;
return fd;
}
int voiceGetcommand(struct InputCommander *voicer)//读取语音,读取fd
{
int nread = 0;
memset(voicer->command,'\0', sizeof(voicer->command));
nread = read(voicer->fd, voicer->command, sizeof(voicer->command));
if(nread == 0){
printf("over time\n");
}else{
return nread;
}
}
struct InputCommander voiceContrl = {
.commandName = "voice",
.devicename = "/dev/ttyAMA0"//设备
.command = {'\0'},
.Init = voiceInit,
.getCommand = voiceGetcommand,
.log = {'\0'},
.next = NULL
};
struct InputCommander* addVoiceToInputCommandLink(struct InputCommander *phead)//头插法加入链表
{
if(phead == NULL){ //链表为空时
return &voiceContrl;
}
else{ //链表不为空时,bathroomLiguht为头插入
voiceContrl.next = phead;
phead = &voiceContrl;
}
}
//socket客户端 socketControl.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "inputCommand.h"
int socketInit(struct InputCommander *socketMes, char *ipAdress, char *port)//语音模块,串口初始化
{
int s_fd;
struct sockaddr_in s_addr;
memset(&s_addr,0,sizeof(struct sockaddr_in));
//1.建立套接字
s_fd = socket(AF_INET, SOCK_STREAM, 0);
if(s_fd == -1){
perror("socket");
exit(-1);
}
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(atoi(socketMes->port));
inet_aton(socketMes->ipAdress, &s_addr.sin_addr);
//2.绑定
bind(s_fd, (struct sockaddr *)&s_addr, sizeof(struct sockaddr_in));
//3.监听
listen(s_fd,10);
socketMes->sfd = s_fd;
return s_fd;
}
int socketGetcommand(struct InputCommander *socketMes)//读取语音,读取fd
{
int c_fd;
int n_read = 0;
struct sockaddr_in c_addr;
memset(&c_addr,0,sizeof(struct sockaddr_in));
int clen = sizeof(struct sockaddr_in);
c_fd = accept(socketMes->sfd, (struct sockaddr *)&c_addr, &clen);//等待连接,但是只连接一次
n_read = read(c_fd, socketMes->command, sizeof(socketMes->command));
if(n_read == -1){
perror("read");
}else if(n_read > 0){
printf("%d\n", n_read);
}else{
printf("client quit\n");
}
return n_read;
}
struct InputCommander socketContrl = {
.commandName = "socketServer",
.command = {'\0'},
.port = "8088",
.ipAdress = "192.168.0.123",
.Init = socketInit,
.getCommand = socketGetcommand,
.log = {'\0'},
.next = NULL
};
struct InputCommander* addSocketToInputCommandLink(struct InputCommander *phead)//头插法加入链表
{
if(phead == NULL){ //链表为空时
return &socketContrl;
}
else{ //链表不为空时,bathroomLiguht为头插入
socketContrl.next = phead;
phead = &socketContrl;
}
}
//主程序 mainPro.c
#include
#include
#include
#include
#include
#include
#include
#include "contrlDevices.h"
#include "inputCommand.h"
int c_fd;
struct InputCommander *pcommandHead = NULL;//指令工厂链表,全局变量
struct Devices *pdeviceHead = NULL;//设备工厂创建链表
struct InputCommander *socketHandler = NULL ;
struct Devices* findDeviceByName(char *name, struct Devices *phead)
{
struct Devices *tmp = phead;
if(phead == NULL){
return NULL;
}else{
while(tmp != NULL){
if(strcmp(tmp->devicename, name)){
return tmp
}
tmp = tmp->next;
}
return NULL;
}
}
struct InputCommander* findCommandByName(char *name, struct InputCommander *phead)
{
struct InputCommander *tmp = phead;
if(phead == NULL){
return NULL;
}else{
while(tmp != NULL){
if(strcmp(tmp->commandName, name)){
return tmp
}
tmp = tmp->next;
}
return NULL;
}
}
void *read_thread()
{
int n_read;
memset(socketHandler->command,'\0', sizeof(socketHandler->command));//缓存区初始化
n_read = read(c_fd, socketHandler->command, sizeof(socketHandler->command));//数据放在socketHandler->command
if(n_read == -1){
perror("read");
}else if(n_read > 0){
printf("%d, %s\n", n_read, socketHandler->command);
}else{
printf("client quit\n");
}
}
void *voice_thread(void* data)
{
int nread;
struct InputCommander *voiceHandler;
voiceHandler = findCommandByName("voice", pcommandHead);//寻找语音结构体,返回到voiceHandler
if(voiceHandler == NULL){//寻找失败
printf("find voice error\n");
pthread_exit(NULL);
}else{
if(voiceHandler->Init(voiceHandler,NULL,NULL) < 0){//初始化失败
printf("voice init error\n");
pthread_exit(NULL);
}else{
printf("%s init success\n", voiceHandler);
}
while(1){//不断读取语音数据
nread = voiceHandler->getCommand(voiceHandler);//调用获取指令函数,读数据
if(nread == 0){
printf("no data\n");
}else{
printf("data:%s\n", voiceHandler->command);//数据放在voiceHandler->command,打印语音指令
}
}
}
}
void *socket_thread(void* data)
{
int c_fd;
int n_read = 0;
pthread_t readThread;
struct sockaddr_in c_addr;
memset(&c_addr,0,sizeof(struct sockaddr_in));
int clen = sizeof(struct sockaddr_in);
socketHandler = findCommandByName("socketServer", pcommandHead);//寻找socket操作,返回到socketeHandler
if(voiceHandler == NULL){//寻找失败
printf("find voice error\n");
pthread_exit();
}
socketHandler->Init(socketHandler,NULL,NULL);
while(1){
c_fd = accept(socketHandler->sfd, (struct sockaddr *)&c_addr, &clen);//不断接收客户端数据
pthread_create(&readThread, NULL, read_thread, NULL);//创建新线程,读数据
}
}
int main()
{
char name [128];
struct Devices *tmp = NULL;
pthread_t voiceThread;
pthread_t socketThread;
if(wiringPiSetup() == -1){ //wiringPi库初始化
return -1;
}
//设备初始化
pdeviceHead = addBathroomLightToDeviceLink(pdeviceHead);//加入链表
//pdeviceHead = addupstairLightToDeviceLink(pdeviceHead);//二楼灯
//pdeviceHead = addRestaurantLightToDeviceLink(pdeviceHead);//厨房灯
//pdeviceHead = addLivingRoomLightToDeviceLink(pdeviceHead);//卧室灯
pdeviceHead = addFireToDeviceLink(pdeviceHead);//火灾报警器
//控制指令初始化
pcommandHead = addVoiceToInputCommandLink(pcommandHead);//语音串口
pcommandHead = addSocketToInputCommandLink(pcommandHead);//socket
//3.线程池
//3.1语音线程
pthread_create(&voiceThread, NULL, voice_thread, NULL);//调用voice_thread函数
//3.3socket线程
pthread_create(&socketThread, NULL, socket_thread, NULL);//调用socket_thread函数
pthread_join(voiceThread,NULL);
pthread_join(socketThread,NULL);
/* while(1){
printf("input your choice\n");
scanf("%s", name);
tmp = findDeviceByName(name, pdeviceHead);//在链表中寻找
if(tmp != NULL){
tmp->devicesInit(tmp->pinNum);
tmp->open(tmp->pinNum);
}
}*/
return 0;
}
树莓派利用pi Camera模块,通过mjpg-streamer软件获取视频,通过手机端或电脑端浏览实时视频。
配置环境:
sudo apt-get update #更新软件列表
sudo apt-get upgrade #更新软件
sudo apt-get install subversion #Subversion是一个自由开源的版本控制系统
sudo apt-get install libjpeg8-dev #JPEG支持库
sudo apt-get install imagemagick
sudo apt-get install libv4l-dev #4l是小写"L"
sudo apt-get install cmake #下载编译工具
1、git clone https://github.com/jacksonliam/mjpg-streamer.git
获取安装包
2、cd mjpg-streamer/mjpg-streamer-experimental
#进入下载目录后进入左侧路径
3、make all
编译
4、sudo make install
#安装
5、vi.start.sh 修改启动脚本
./mjpg_streamer -i “./input_raspicam.so -r 640x480 -f 10 -n” -o “./output_http.so -p 8080 -w /usr/local/www”
注释掉#sudo mjpg_streamer -i “./input_uvc.so -r 640x480 -f 10 -n” -o “./output_http.so -p 8080 -w /usr/local/www”
树莓派使用input_raspicam.so
6、sudo raspi-config 打开摄像头
Interfacing Options -> Camera
7、./start.sh
如下图所示,输出信息,说明成功!
6、在浏览器输入 http://IP地址:8080,回车 显示如下页面,点击页面左侧,Stream栏,显示监视画面
https://jingyan.baidu.com/article/47a29f2474a555c01523994c.html
翔云人脸对比识别服务,可快速对两张人脸图像进行识别比对,判断是否为同一个人的人脸。https://www.netocr.com/
Base64是网络上最常见的用于传输8Bit字节码的编码方式之一,Base64就是一种基于64个可打印字符来表示二进制数据的方法。Base64编码是从二进制到字符的过程,可用于在HTTP环境下传递较长的标识信息。采用Base64编码具有不可读性,需要解码后才能阅读。
#include
#include
#include
#include
#include
#include
size_t readBuf(void *ptr, size_t size, size_t nmemb, void *stream)
{
char buf[1024];
strncpy(buf, ptr, 1024);
printf("%s\n", buf);
}
char *getBase64(char *filePath)//封装函数,获取图片base64流,保存到文件tmpFile
{
char *bufPic;
char cmd[128] = {'\0'};
sprintf(cmd, "base64 %s > tmpFile", filePath);//调用Linux函数base64对图片base64流
system(cmd);
int fd = open("./tmpFile", O_RDWR);
int filelen = lseek(fd, 0, SEEK_END);//可读可写打开文件
lseek(fd, 0, SEEK_SET);
bufPic = (char *)malloc(filelen + 2);
memset(bufPic, '\0', filelen+2);
read(fd, bufPic, filelen);
close(fd);
system("rm -f tmpFile");
return bufPic;
}
int postUrl()//POST请求
{
CURL *curl;
CURLcode res;
char *postString;//野指针注意开辟空间
char *key = "用户ocrKey";
char *secret = "用户ocrSecrert";
int typeId = 21;
char *format = "xml";
char *buf1 = getBase64("./reba1.jpg");
char *buf2 = getBase64("./reba2.jpg");
/*system("base64 reba1.jpg > tmpFile1");//调用Linux函数base64对图片base64流
int fd = open("./tmpFile1", O_RDWR);//可读可写打开文件
int filelen = lseek(fd, 0, SEEK_END);
lseek(fd, 0, SEEK_SET);
char *buf1 = (char *)malloc(filelen+2);
memset(buf1, '\0', sizeof(buf1));
read(fd, buf1, filelen);
close(fd);
system("base64 reba1.jpg > tmpFile1");//调用Linux函数base64对图片base64流
int fd = open("./tmpFile1", O_RDWR);//可读可写打开文件
int filelen = lseek(fd, 0, SEEK_END);
lseek(fd, 0, SEEK_SET);
char *buf2 = (char *)malloc(filelen+2);
memset(buf2, '\0', sizeof(buf2));
read(fd, buf2, filelen);
close(fd);*/
int len = strlen(key)+strlen(secret)+strlen(buf1)+strlen(buf2)+124;
postString = (char *)malloc(len);
memset(postString, '\0', sizeof(postString));
sprintf(postString, "&img1=%s&img2=%s&key=%s&secret=%s&typeId=%d&format=%s", buf1,buf2,key,secret,21,format);//拼接内容
curl = curl_easy_init();
if (curl)
{
curl_easy_setopt(curl, CURLOPT_COOKIEFILE, "/tmp/cookie.txt"); // 指定cookie文件
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postString); // 指定post内容
//curl_easy_setopt(curl, CURLOPT_PROXY, "10.99.60.201:8080");
curl_easy_setopt(curl, CURLOPT_URL, "https://netocr.com/api/faceliu.do");// 指定url
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, readBuf);//调用回调函数
res = curl_easy_perform(curl);
//printf("OK: %d\n", res);//返回200成功
if(strstr(buf,"是")!=NULL){
printf("两张人像为同一人\n");
}
else if(strstr(buf,"否")!=NULL){
printf("两张人像不是同一人\n");
}
curl_easy_cleanup(curl);
}
fclose(fp);
return 1;
}
int main()
{
postUrl();
return 0;
}
1、总体设计思路
先通过wemos d1获得家电设备的红外编码,开机、关机等功能的红外编码信息。在手机上tcp工具端按下相应的开机、关机按键,wemos d1服务器端响应请求,控制红外发射管相对应的红外编码信息,来实现对家电设备的控制。
2、获取家电设备的红外编码信息
1)获取家电设备的红外编码信息电路连接
设备:wemos d1, 红外接收头(CHQ1838)、红外遥控器
三针脚红外接收头(CHQ1838)内部有光电放大和解调电路的芯片型接收头。
2)上传代码获取空调遥控器的红外编码信息
1.先安装IRremoteESP8266库文件,此文件可在github官网获得,可点击此连接下载安装:https://github.com/crankyoldgit/IRremoteESP8266
2.打开Arduino IDE选择示例程序如下图所示上传到开发板,此程序用来获取空调遥控器的红外编码信息
3.红外遥控器按下功能键,信号经过内部的编码电路,从红外发射管发出特定频率的红外信号,红外接收头接收到信号,经过内部的光电放大和解调电路,将解调出的信号传输到我们的wemos d1板,在串口监视器中获取红外发射管编码信息。(注意不同的红外遥控器所遵从的协议可能不一样,若不知道版本,可用此程序获取,将红外编码信息储存在一个数组内。)
3、红外发射
wemos D1板载网卡,支持AP(路由),STA(上网设备)模式。架设wemos D1位ftp服务器,通过手机tcp工具远程控制红外发射管发射相应的红外编码信息,来实现对家电设备的控制。
通过手机TCP工具远程网络控制家电设备
#include
#include
#inlcude <TRremoteESP8266.h>
#include
char *ssid="239"; //wifi热点名称
char *passwd="239239239"; //wifi密码
int port = 8888;//端口号
WiFiServer server(port);//设置服务器端口号
void initWifiSta(){
WiFi.mode(WIFI_STA); //设置STA模式
WiFi.begin(ssid,passwd); //连接网络
while(WiFi.status() != WL_CONNECTED){//判断是否连上热点
Serial.print(".");
delay(500);
}
Serial.println(WiFi.localIP());//通过串口打印wemos的IP地址
}
void setup(){
Serial.begin(115200);
initWifiSta();//接入Wifi
//pinMode(BEEP, OUTPUT);//蜂鸣器初始化
digitalWrite(BEEP,HIGH);
server.begin();//启动服务器
}
void loop(){
char cmd;
WiFiClient.client = server.available();//服务器初始化
while(client.connected()){
while(client.available() > 0){
cmd = client.read();
if(cmd = '1'){ //TCP工具输入1
//digitalWrite(BEEP, LOW);//蜂鸣器响
irsend.sendRaw(rawData1,299,38);
delay(1000);
}
if(cmd = '2'){ //TCP工具输入1
//digitalWrite(BEEP, LOW);//蜂鸣器响
irsend.sendRaw(rawData2,201,38);
delay(1000);
}
}
}
}
感谢:学习资料https://www.bilibili.com/video/BV11p4y1Y77e
本模块实际原理为 1 片 STC11 单片机+1 片 LD3320 组合形成的一款语音识别模块。
本模块的串口如下图所示, 从下至上的 5 根针分别为 GND、 RXD、 TXD、5V、 3.3V, 其中 RXD 和 TXD 为实际内部单片机的串口接收和发射端口, 故与外部串口设备连接时要注意 RXD 接 TXD 的交叉接法。 3.3V 的口为输出口,可用于外部设备的供电利用, 一般未用上直接忽略不用即可。
下图为本店提供的 USB 转 TTL 与语音模块的连接方法, 如与其他单片机或者其他设备的连接时注意串口电平需要为 5V, 与语音模块电平一致, 如电平不一致可加入串口电平转换模块否则将导致通信异常甚至损坏设备端口
1、使用的硬件平台:树莓派3B,CortexA53,博通BCM2837 和 Wemos D1开发板 ,ESP8266
软件平台:Linux,内核版本3.14
2、实现的功能是通过手机TCP工具,语音,生物识别等控制家电,对门锁,灯光,空调,电视等设备进行控制,开发回家模式,睡觉模式等应用场景。
3、项目软件架构采用简单工厂模式来设计,将tcp服务器,语音识别,人脸识别设计成链表的每个节点,形成控制端工厂,灯光,门锁,窗帘,空调等也设计成链表每个节点,形成设备端工厂。基于这种架构添加新功能的时候只要添加一个链表节点文件就可以,稳定性,拓展性都做的不错。
电视,空调的功能对接采用红外编解码单元,支持遥控器的学习和替代功能实现远程控制。
支持人脸识别开锁采用翔云人工智能平台实现人脸识别功能,让我熟悉了Linux、C语言的https编程,对第三方包,库文件的开发模式有了更好的研发经验。
语音处理是ld3320模块的二次开发,在keil环境中阅读厂家给的全部代码,然后找到识别词条相关的代码,对串口数据进行修改并整合到树莓派的串口通信中。
4、不管是设备端还是控制端,在实际调试过程当中又涉及临界资源的竞争,在多线程管理中配合线程锁来解决这个问题。
通过这个项目,对简单工厂模式,Linux操作系统的文件,进程,线程,网络以及Linux字符设备开发都有很大的收获。