项目材料:小房子模型,树莓派,Wemos D1,继电器组,继电器,语音模块LDV7,连接线,电池电源。
项目效果:可以通过网络远程控制树莓派服务器,让树莓派用wiringPi库控制不同针脚(GPIO)输出电压,让小房子搭建的别墅模型里不同位置灯的开关。
因为是做测试项目,所以在继电器组的口子用完后,就在加了一个单独的继电器控制屋外的泳池灯,是通过远程发送控制指令达到树莓派网络服务器,在树莓派解析指令后,在启动一个客户端服务器,给Wemos发控制指令,让Wemos对发来的控制指令解析后,在做最后的具体执行开关灯。
设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。
工厂模式:
在开始编写代码时,沿用了设计模式里的工厂模式。
这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
设备工厂的设备数据对象
struct Devices
{
char deviceName[128];
int status;
int pinNum;
int (*open)(int pinNum);
int (*close)(int pinNum);
int (*deviceInit)(int pinNum);
int (*readStatus)();
int (*changeStatus)(int status);
struct Devices *next;
};
配置设备:
struct InputCommandr voiceContrl =
{
.commandName = "voice",
.deviceName = "/dev/ttyAMA0",
.command = {'\0'},
.init = voiceInit,
.getCommand = voiceGetCommand,
.log = {'\0'},
.next = NULL
};
指令工厂的指令数据对象
struct InputCommandr
{
char commandName[128];
char deviceName[128];
char command[32];
int (*init)(struct InputCommandr *voices, char *ipAderss, char *port);
int (*getCommand)(struct InputCommandr *voices);
char log[1024];
int fd;
char port[12];
char ipAddress[32];
int sfd;
struct InputCommandr *next;
};
配置指令:
struct InputCommandr socketContrl =
{
.commandName = "socketServer",
.command = {'\0'},
.init = socketInit,
.getCommand = socketGetCommand,
.log = {'\0'},
.port = "8890",
.ipAddress = "192.168.1.220",
.next = NULL
};
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <wiringPi.h>
//定义需要被用到的设备数据表
struct Devices
{
char deviceName[128];
int status;
int pinNum;
int (*open)(int pinNum);
int (*close)(int pinNum);
int (*deviceInit)(int pinNum);
int (*readStatus)();
int (*changeStatus)(int status);
struct Devices *next;
};
//声明添加设备模块配置信息的函数,好被主程序调用
struct Devices* addBathroomLightToDeviceLink(struct Devices *phand);
struct Devices* addUpstairsLightToDeviceLink(struct Devices* phand);
struct Devices* addRestaurantLightToDeviceLink(struct Devices* phand);
struct Devices* addRoomLightToDeviceLink(struct Devices* phand);
struct Devices* addFireToDeviceLink(struct Devices* phand);
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wiringPi.h>
#include <wiringSerial.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
//配置好解析指令的宏定义
#define WSD 1 //打开卧室灯
#define KTD 2 //打开客厅灯
#define CFD 3 //打开厨房灯
#define CSD 4 //打开厕所灯
#define YCD 5 //打开泳池灯
#define ALLON 6 //打开所有灯
#define WS 7 //关闭卧室灯
#define KT 8 //关闭客厅灯
#define CF 9 //关闭厨房灯
#define CS 10 //关闭厕所灯
#define YC 11 //关闭泳池灯
#define ALLOFF 12 //关闭所有灯
//定义控制继电器组引脚的宏定义
#define SWI1 23 //厕所
#define SWI2 25 //卧室
#define SWI3 24 //客厅
#define SWI4 22 //厨房
//定义需要被用到的设备数据表
struct InputCommandr
{
char commandName[128];
char deviceName[128];
char command[32];
int (*init)(struct InputCommandr *voices, char *ipAderss, char *port);
int (*getCommand)(struct InputCommandr *voices);
char log[1024];
int fd;
char port[12];
char ipAddress[32];
int sfd;
struct InputCommandr *next;
};
//声明添加指令模块配置信息的函数,好被主程序调用
struct InputCommandr* addVoiceContrlInputCommandLink(struct InputCommandr* phand);
struct InputCommandr* addSocketContrlInputCommandLink(struct InputCommandr* phand);
//连接设备工厂,指令工厂的头文件
#include "contrlDevices.h"
#include "inputCommand.h"
//定义指令工厂,设备工厂文件头的全局变量,便于函数调用
struct InputCommandr* pcommandHand = NULL;
struct Devices* pdevicesHand = NULL;
struct InputCommandr* socketHandler = NULL;
int c_fd;
//查找设备工厂对应的设备结构体数据函数
struct Devices *findDeviceByName(char *name,struct Devices *phand)
{
struct Devices *tmp = phand;
if(phand == NULL){
return NULL;
}else{
while(tmp !=NULL){
if(strcmp(name,tmp->deviceName) == 0){
return tmp;
}
tmp = tmp->next;
}
return NULL;
}
}
//查找指令工厂对应的指令结构体数据的函数
struct InputCommandr *findCommandByName(char *name,struct InputCommandr *phand)
{
struct InputCommandr *tmp = phand;
if(phand == NULL){
return NULL;
}else{
while(tmp !=NULL){
if(strcmp(name,tmp->commandName) == 0){
return tmp;
}
tmp = tmp->next;
}
return NULL;
}
}
//给Wemos发指令信息函数
void writeWemosSignal(char* command)
{
int s_fd=socket(AF_INET, SOCK_STREAM,0);//配置通信模式
if (s_fd==-1)
{
perror("socket");
exit(-1);
}
struct sockaddr_in addr;
memset(&addr,0,sizeof(struct sockaddr_in));
addr.sin_family=AF_INET;
addr.sin_port=htons(atoi("8898"));
inet_aton("192.168.1.104",&addr.sin_addr);
if(connect(s_fd,(struct sockaddr *)&addr,sizeof(struct sockaddr))==-1)//连接wemos版
{
perror("connect");
exit(-1);
}
write(s_fd,command,strlen(command));//向wemos版发送信息
close(s_fd);
}
//解析收到的指令
int get_cmd_type(char *cmd)
{
if(strcmp("ONCS",cmd) == 0){ return CSD;} //打开厕所灯
if(strcmp("ONWS",cmd) == 0){ return WSD;} //打开卧室灯
if(strcmp("ONKT",cmd) == 0){ return KTD;} //打开客厅灯
if(strcmp("ONCF",cmd) == 0){ return CFD;} //打开厨房灯
if(strcmp("ONYC",cmd) == 0){ return YCD;} //打开泳池灯
if(strcmp("ALLON",cmd) == 0){ return ALLON;} //打开所有灯
if(strcmp("OFFCS",cmd) == 0){ return CS;} //关闭厕所灯
if(strcmp("OFFWS",cmd) == 0){ return WS;} //关闭卧室灯
if(strcmp("OFFKT",cmd) == 0){ return KT;} //关闭客厅灯
if(strcmp("OFFCF",cmd) == 0){ return CF;} //关闭厨房灯
if(strcmp("OFFYC",cmd) == 0){ return YC;} //关闭泳池灯
if(strcmp("ALLOFF",cmd) == 0){ return ALLOFF;} //关闭所有灯
if(strstr(cmd,"ONCS") != NULL){ return CSD;} //打开厕所灯
if(strstr(cmd,"ONWS") != NULL){ return WSD;} //打开卧室灯
if(strstr(cmd,"ONKT") != NULL){ return KTD;} //打开客厅灯
if(strstr(cmd,"ONCF") != NULL){ return CFD;} //打开厨房灯
if(strstr(cmd,"ONYC") != NULL){ return YCD;} //打开泳池灯
if(strstr(cmd,"ALLON") != NULL){ return ALLON;} //打开所有灯
if(strstr(cmd,"ALLOFF") != NULL){ return ALLOFF;} //关闭所有灯
if(strcmp("quit",cmd) == 0){ return 13;} //退出
return 100;
}
//对解析后的指令做相对应的功能执行
void msg_handler(char* command)
{
int ret = get_cmd_type(command);
if(ret < 7 ){
switch(ret){
case CSD:
digitalWrite(SWI1, LOW);
break;
case WSD:
digitalWrite(SWI2, LOW);
break;
case KTD:
digitalWrite(SWI3, LOW);
break;
case CFD:
digitalWrite(SWI4, LOW);
break;
case YCD:
socketHandler->command[0] = '1';
writeWemosSignal(socketHandler->command);
break;
case ALLON:
digitalWrite(SWI1, LOW);
digitalWrite(SWI2, LOW);
digitalWrite(SWI3, LOW);
digitalWrite(SWI4, LOW);
socketHandler->command[0] = '1';
writeWemosSignal(socketHandler->command);
break;
}
}
else if(ret > 6 && ret <= 12){
switch(ret){
case CS:
digitalWrite(SWI1, HIGH);
break;
case WS:
digitalWrite(SWI2, HIGH);
break;
case KT:
digitalWrite(SWI3, HIGH);
break;
case CF:
digitalWrite(SWI4, HIGH);
break;
case YC:
socketHandler->command[0] = '0';
writeWemosSignal(socketHandler->command);
break;
case ALLOFF:
digitalWrite(SWI1, HIGH);
digitalWrite(SWI2, HIGH);
digitalWrite(SWI3, HIGH);
digitalWrite(SWI4, HIGH);
socketHandler->command[0] = '0';
writeWemosSignal(socketHandler->command);
break;
}
}
else if(ret == 13){
printf("quit!\n");
close(c_fd);
close(socketHandler->sfd);
exit(0);
}
else{
printf("command error!");
pthread_exit(NULL);
}
}
//线程函数,可以实时监测语音模块传过来的数据,便于跟服务器远程模块区分执行
void *voice_thread(void *datas)
{
pthread_t voiceWriteThread;
int nread = 0;
struct InputCommandr* voiceHandler = NULL;
voiceHandler = findCommandByName("voice",pcommandHand);
if(voiceHandler == NULL){
printf("find voiceHandler 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->commandName);
}
while(1){
nread = voiceHandler->getCommand(voiceHandler);
if(nread == 0){
printf("nodata form voice\n");
}else{
printf("do divece contrl:%s",voiceHandler->command);
msg_handler(voiceHandler->command);
}
}
}
}
//读取信息后,执行线程函数,可以对每一个连接的客户端做指令执行
void *read_thread(void *datas)
{
int n_read = 0;
memset(socketHandler->command,'\0', sizeof(socketHandler->command));
n_read = read(c_fd, socketHandler->command, sizeof(socketHandler->command));
if(n_read == -1){
perror("socketRead");
}else if(n_read > 0){
printf("\nget:%d,%s\n",n_read,socketHandler->command);
msg_handler(socketHandler->command);
}else{
printf("client quit\n");
}
}
//线程函数,可以实时监测远程连接传过来的数据,便于跟语音模块区分执行
void *socket_thread(void *datas)
{
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",pcommandHand);
if(socketHandler == NULL){
printf("find socketHandler error\n");
pthread_exit(NULL);
}else{
printf("%s init success\n",socketHandler->commandName);
}
socketHandler->init(socketHandler,NULL,NULL);
while(1){
c_fd = accept(socketHandler->sfd, (struct sockaddr *)&c_addr, &clen); //实时检测连接的客户端
if(c_fd == -1){
perror("accept");
}
pthread_create(&readThread, NULL, read_thread, NULL); //给每一个连接到的客户端去做对应指令的执行
}
}
int main()
{
pthread_t voiceThread;
pthread_t socketThread;
if(wiringPiSetup() == -1){
return -1;
}//初始化wiringPi库
//1.指令工厂初始化
pcommandHand = addVoiceContrlInputCommandLink(pcommandHand);
pcommandHand = addSocketContrlInputCommandLink(pcommandHand);
//2.设备控制工厂初始化
pdevicesHand = addBathroomLightToDeviceLink(pdevicesHand);
pdevicesHand = addUpstairsLightToDeviceLink(pdevicesHand);
pdevicesHand = addRestaurantLightToDeviceLink(pdevicesHand);
pdevicesHand = addRoomLightToDeviceLink(pdevicesHand);
//3.线程池的建立
//3.1.语音线程
pthread_create(&voiceThread, NULL, voice_thread, NULL);
//3.2.socket线程
pthread_create(&socketThread, NULL, socket_thread, NULL);
pthread_join(voiceThread, NULL);
pthread_join(socketThread, NULL);
return 0;
}
#include "inputCommand.h"
//从打开的串口中读取数据,写入到语音模块指令数据结构体中
int voiceGetCommand(struct InputCommandr *voices)
{
int nread = 0;
memset(voices->command,'\0', sizeof(voices->command));
nread = read(voices->fd,voices->command, sizeof(voices->command));
if(nread == 0){
printf("usart for voice read over time\n");
}else{
return nread;
}
}
//打开串口,把描述符放到语音模块指令数据结构体中
int voiceInit(struct InputCommandr *voices, char *ipAderss, char *port)
{
int fd;
if((fd = serialOpen(voices->deviceName,9600)) == -1){
exit(-1);
}
voices->fd = fd;
return fd;
}
//配置语音模块指令工厂的初始信息
struct InputCommandr voiceContrl =
{
.commandName = "voice",
.deviceName = "/dev/ttyAMA0",
.command = {'\0'},
.init = voiceInit,
.getCommand = voiceGetCommand,
.log = {'\0'},
.next = NULL
};
//把配置好的语音模块指令数据结构体添加到指令工厂中
struct InputCommandr* addVoiceContrlInputCommandLink(struct InputCommandr* phand)
{
if(phand == NULL){
return &voiceContrl;
}else{
voiceContrl.next = phand;
phand = &voiceContrl;
return phand;
}
}
#include "inputCommand.h"
int socketGetCommand(struct InputCommandr *voices)
{
}
//开起一个服务器函数
int socketInit(struct InputCommandr *socketMesg, char *ipAderss, char *port)
{
int s_fd;
struct sockaddr_in s_addr;
memset(&s_addr, 0, sizeof(struct sockaddr_in));
//1.socket
s_fd = socket(AF_INET, SOCK_STREAM, 0);
if(s_fd == -1){
perror("socket");
exit(-1);
}
//2.bind
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(atoi(socketMesg->port));
inet_aton(socketMesg->ipAddress, &s_addr.sin_addr);
bind(s_fd, (struct sockaddr *)&s_addr, sizeof(struct sockaddr_in));
//3.listen
listen(s_fd, 10);
printf("socket server listening .....\n");
socketMesg->sfd = s_fd;
//初始化继电器组设备使用的引脚
pinMode(SWI1, OUTPUT);
pinMode(SWI2, OUTPUT);
pinMode(SWI3, OUTPUT);
pinMode(SWI4, OUTPUT);
//给引脚输入高电压,让其继电器组关闭
digitalWrite(SWI1, HIGH);
digitalWrite(SWI2, HIGH);
digitalWrite(SWI3, HIGH);
digitalWrite(SWI4, HIGH);
}
//配置服务器模块指令工厂的初始信息
struct InputCommandr socketContrl =
{
.commandName = "socketServer",
.command = {'\0'},
.init = socketInit,
.getCommand = socketGetCommand,
.log = {'\0'},
.port = "8890",
.ipAddress = "192.168.1.105",
.next = NULL
};
//把配置好的服务器模块指令数据结构体添加到指令工厂中
struct InputCommandr* addSocketContrlInputCommandLink(struct InputCommandr* phand)
{
if(phand == NULL){
return &socketContrl;
}else{
socketContrl.next = phand;
phand = &socketContrl;
return phand;
}
}
#include "contrlDevices.h"
//功能模块
int bathroomLightopen(int pinNum)
{
digitalWrite(pinNum, LOW);
}
int bathroomLightclose(int pinNum)
{
digitalWrite(pinNum, HIGH);
}
int bathroomLightDeviceInit(int pinNum)
{
pinMode(pinNum, OUTPUT);
digitalWrite(pinNum, HIGH);
}
int bathroomLightStatus(int status)
{
}
//配置浴室灯模块的设备基本信息
struct Devices bathroomLight =
{
.deviceName = "bathroomLight",
.pinNum = 25,
.open = bathroomLightopen,
.close = bathroomLightclose,
.deviceInit = bathroomLightDeviceInit,
.changeStatus = bathroomLightStatus
};
//添加设备到设备工厂
struct Devices* addBathroomLightToDeviceLink(struct Devices* phand)
{
if(phand == NULL){
return &bathroomLight;
}else{
bathroomLight.next = phand;
phand = &bathroomLight;
return phand;
}
}
#include "contrlDevices.h"
int restaurantLightopen(int pinNum)
{
digitalWrite(pinNum, LOW);
}
int restaurantLightclose(int pinNum)
{
digitalWrite(pinNum, HIGH);
}
int restaurantLightDeviceInit(int pinNum)
{
pinMode(pinNum, OUTPUT);
digitalWrite(pinNum, HIGH);
}
int restaurantLightStatus(int status)
{
}
struct Devices restaurantLight =
{
.deviceName = "restaurantLight",
.pinNum = 23,
.open = restaurantLightopen,
.close = restaurantLightclose,
.deviceInit = restaurantLightDeviceInit,
.changeStatus = restaurantLightStatus
};
struct Devices* addRestaurantLightToDeviceLink(struct Devices* phand)
{
if(phand == NULL){
return &restaurantLight;
}else{
restaurantLight.next = phand;
phand = &restaurantLight;
return phand;
}
}
#include "contrlDevices.h"
int roomLightopen(int pinNum)
{
digitalWrite(pinNum, LOW);
}
int roomLightclose(int pinNum)
{
digitalWrite(pinNum, HIGH);
}
int roomLightDeviceInit(int pinNum)
{
pinMode(pinNum, OUTPUT);
digitalWrite(pinNum, HIGH);
}
int roomLightStatus(int status)
{
}
struct Devices roomLight =
{
.deviceName = "roomLight",
.pinNum = 22,
.open = roomLightopen,
.close = roomLightclose,
.deviceInit = roomLightDeviceInit,
.changeStatus = roomLightStatus
};
struct Devices* addRoomLightToDeviceLink(struct Devices* phand)
{
if(phand == NULL){
return &roomLight;
}else{
roomLight.next = phand;
phand = &roomLight;
return phand;
}
}
#include "contrlDevices.h"
int upstairsLightopen(int pinNum)
{
digitalWrite(pinNum, LOW);
}
int upstairsLightclose(int pinNum)
{
digitalWrite(pinNum, HIGH);
}
int upstairsLightDeviceInit(int pinNum)
{
pinMode(pinNum, OUTPUT);
digitalWrite(pinNum, HIGH);
}
int upstairsLightStatus(int status)
{
}
struct Devices upstairsLight =
{
.deviceName = "upstairsLight",
.pinNum = 24,
.open = upstairsLightopen,
.close = upstairsLightclose,
.deviceInit = upstairsLightDeviceInit,
.changeStatus = upstairsLightStatus
};
struct Devices* addUpstairsLightToDeviceLink(struct Devices* phand)
{
if(phand == NULL){
return &upstairsLight;
}else{
upstairsLight.next = phand;
phand = &upstairsLight;
return phand;
}
}
使用Arduino平台做代码编写,通过安装USB串口驱动在把烧录到Wemos中
#include <ESP8266WiFi.h>
char ssid[] = "yang";
char passwd[] = "12345678";
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地址192.168.200.110
delay(500);
}
int port = 8898; //端口号
WiFiServer server(port); // 设置服务器端口号
void setup() {
Serial.begin(115200);
initWifiSta();
pinMode(D5, OUTPUT);
digitalWrite(D5, HIGH);
}
void loop() {
char cmd;
WiFiClient client = server.available();
server.begin();//服务初始化
while (client.connected()) {
while (client.available() > 0) {
cmd = client.read(); //从客户端读取数据
Serial.println(cmd);
switch (cmd) {
case '1':
digitalWrite(D5,LOW);
break;
case '0':
digitalWrite(D5,HIGH);
break;
}
}
}
}
语音模块LDV72
由一片stc11单片机和LD3320组成,我用的这个语音模块有五个针脚,分别是GND,RXD,TXD,3.3V,5V。此模块的工作电压是5v(使用手册说的是5v,但是我实际使用的结果是烧写代码用的5v,与arduino相连用的3.3v。如果连接arduino的5v就无法正常工作),代码的烧写需要用到USB转TTL与电脑端相连。(注意语音模块的RXD与TXD要跟USB转TTL模块的RXD,TXD反接)用keil编译代码后再用串口助手进行烧写。
(注:跟我一样在语音模块上没有复位按钮的需要在烧写代码时拔插GND口的线进行从新上电才能烧写进代码)
树莓派 | 语音模块YS-LDV7 |
---|---|
5V | 5V |
GND | GND |
RXD | TXD |
TXD | RXD |
语音模块与树莓派串口调试代码
#include <stdio.h>
#include <wiringPi.h>
#include <wiringSerial.h>
#include<unistd.h>
#include <string.h>
int main() {
int fd;
char cmd[128] = {'\0'};
int nread;
wiringPiSetup();
fd = serialOpen("/dev/ttyAMA0",9600);
if(fd == -1){
perror("why");
printf("%d\n",fd);
}
while(1){
nread = read(fd,cmd, sizeof(cmd));
if(nread > 0 ){
if(strlen(cmd) == 0){
printf("连接超时!\n"); continue;
}
printf("getData:%dbyte,contex:%s\n",nread,cmd);
memset(cmd,'\0',sizeof(cmd)/sizeof(char));
}
}
return 0;
}
根据说明书,通过一些基本的语音测试,去修改语音模块的代码
设计模式介绍参考 ↩︎
语音模块参考 ↩︎