概要
本章记录智能家居的代码开发及逻辑,主要分为四部分
- 工厂设计模式
- 串口开发及数据发送与接收
- 网络编程Socket
一 工厂设计模式
什么是工厂设计模式(C语言拟面向对象实现)
这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
在工厂模式中,我们在创建对象时不会对客户接口来指向新创建的端暴露创建逻辑,并且是通过使用一个共同的对象。工厂模式的目的就根据不同的要求输出不同的产品。比如说,有一个生产的灯的设备工厂,它能生产厨房灯,也能生产汽车灯。
#include <wiringPi.h>
#include <stdio.h>
struct Devices
{
char deviceName[128];
int ststus; //设备状态
int (*open)( int pinNum); //打开设备 pinNum 为IO口的引脚
int (*close)( int pinNum); //关闭设备
int (*deviceInit)( int pinNum); //端口号:
int pinNum; //引脚号
int (*readstatus)( int pinNum); //读状态
int (*changestatus)(int status); //改变后的状态
struct Devices *next; //设备的下一个节点
};
struct Devices *addBathroomLightToDeviceLink(struct Devices *phead);
struct Devices *addlivingroomLightToDeviceLink(struct Devices *phead);
struct Devices *addcanteenLightToDeviceLink(struct Devices *phead);
struct Devices *addupstairLightToDeviceLink(struct Devices *phead);
struct Devices *addFireToDeviceLink(struct Devices *phead);
设备结构体有了之后,那怎么创建设备对象,下方代码为设备1厨房灯的节点创建及相关服务函数的实现,如canteenLightOpen(),canteenLightInit()等函数
#include "controldevices.h"
int canteenLightOpen( int pinNum){
digitalWrite(pinNum,LOW); //厨房灯开
}
int canteenLightClose( int pinNum){
digitalWrite(pinNum,HIGH); //厨房灯关
}
/* 厨房灯初始化函数*/
int canteenLightInit( int pinNum){
printf(" canteen 初始化成功\n");
pinMode (pinNum,OUTPUT); //设置为输出模式
digitalWrite(pinNum,HIGH);
}
struct Devices canteenlight={
.pinNum = 2, //引脚号
.deviceName = "cant", //设备名
.close = canteenLightClose, //关厨房灯
.open = canteenLightOpen, //开厨房灯
.deviceInit = canteenLightInit, //厨房灯初始化
// .changestatus=bathroomLightStatus
};
struct Devices *addcanteenLightToDeviceLink(struct Devices *phead) //将厨房灯加入链表中
{
if(NULL == phead){
return &canteenlight; //如果链表头为空,则该结构体为链表头
}else{
canteenlight.next = phead; //头插法
phead = &canteenlight;
}
};
设备的节点和相关函数实现后,怎么在项目中运用呢
其实也很简单,先初始化设备的结构体
struct Devices *Can =NULL; //厨房结构体
struct Devices *pdevicesHead = NULL; //头节点
在主函数中将厨房灯灯加入设备节点
pdevicesHead = addcanteenLightToDeviceLink(pdevicesHead);
//将厨房灯的节点加入项目的链表节点中
最后进行设备初始化
Can = findDeviceName("cant",pdevicesHead); //找到设备名,返回相关设备的结构体
Can->deviceInit(Can->pinNum); //设备初始化
上述是项目的部分代码,如果需要源码,则看章尾
二 串口开发
2.1 串口的初始化——115200波特率
指令工厂结构体配置
//指令工厂结构体,负责开发通信相关
struct Inputcommander
{
char commanderName[128]; //获取指令
char commander[32]; //
int (*Init)(struct Inputcommander *voicer,char *ipAdress,char *port);//socket初始化
int (*getcommand)(struct Inputcommander *voicer); //获取指令
int (*sendcommand)(struct Inputcommander *voicer); //发送指令
struct Inputcommander *next;
char deviceName[128]; //设备名称
char cdmand[128]; //
char sendcdmand[128]; //发送指令
char port[12]; //端口号
char ipAddress[32]; //IP地址
};
struct Inputcommander *addVoiceControlToIputCommanderLink(struct Inputcommander *phead);
struct Inputcommander *addSocketControlToIputCommanderLink(struct Inputcommander *phead);
串口的基础化配置及串口发送(int voiceSendCommand(struct Inputcommander *voicer))与接收(int voiceGetCommand(struct Inputcommander *voicer))等函数
#include <sys/time.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <pthread.h>
#include <wiringPi.h>
#include <wiringSerial.h>
#include <stdlib.h>
#include <unistd.h>
#include "IputCommand.h"
/* 串口初始化 */
int voiceInit(struct Inputcommander *voicer,char *ipAdress,char *port){
int fd;
if((fd = serialOpen(voicer->deviceName, 115200)) < 0) //配置9600波特率
{
fprintf (stderr, "Unable to open serial device: %s\n", strerror (errno)) ; //初始化失败
}
voicer->fd = fd;
return voicer->fd;
}
/* 串口获取指令 */
int voiceGetCommand(struct Inputcommander *voicer){
int nread;
nread = read(voicer->fd,voicer->cdmand,sizeof(voicer->cdmand)); //接收指令
if(nread == 0){
printf("over time\n");
}
return nread;
}
/* 串口接受消息 */
int voiceSendCommand(struct Inputcommander *voicer){
int ret;
ret = write (voicer->fd,voicer->sendcdmand,sizeof(voicer->sendcdmand));
if (ret < 0)
printf("Serial Puts Error\n");
return ret;
}
/* 语音模块的结构体初始化 */
struct Inputcommander voiceControl={
.commanderName="voice",
.deviceName="/dev/ttyS5", //全志H616使用tty5进行串口通信
.cdmand={'\0'},
.Init=voiceInit, //语音模块初始化
.getcommand = voiceGetCommand, //语音模块的获取指令函数
.sendcommand = voiceSendCommand, //语音模块的发送指令函数
.log={'\0'},
.next=NULL
};
/* 将串口指令节点加入指令工厂中 */
struct Inputcommander *addVoiceControlToIputCommanderLink(struct Inputcommander *phead) //结构体加入链表
{
if(phead == NULL){
return &voiceControl;
}else{
voiceControl.next = phead;
phead = &voiceControl;
}
};
2.2 将串口接收到的信息解析
语音线程
线程解析:创建一个串口的结构体指针,初始化voiceInit(),然后写一个循环,专门用于接收串口信息,如果接收到串口的·数据,则将数据参数传给msg_handler(),让该函数对数据进行解析以及对硬件操作
/* 语音结构体 */
void *voice_thread()
{
struct Inputcommander *voiceHandler; //串口结构体
struct Devices *tmp = NULL;
int nread; //串口接收到的字节数
voiceHandler = voice_Init(); //语音初始化
while(1){
memset(voiceHandler->cdmand,'\0',sizeof(voiceHandler->cdmand));
nread = voiceHandler->getcommand(voiceHandler);
if(nread == 0){//串口无信息
puts("No Data From Voice\n");
}else{//串口有信息,打印出来
printf("do divce contrl:%s\n",voiceHandler->cdmand);
msg_handler(voiceHandler->cdmand); //将获取到的消息进行识别,并执行相对应的操作
}
}
}
msg_handler()函数
将串口的数据传给get_cmd(),get_cmd ()通过识别后返回相对应的句柄,msg_handler()函数再根据返回的句柄做出相对应的操作
//识别指令,返回句柄
void msg_handler(char *msg)
{
int ret = get_cmd(msg); //swich指令句柄
printf("%d \n",ret);
switch (ret)
{
/************灯*************灯**************灯*********/
case OPEN1:
Liv->open(Liv->pinNum); Liv->ststus = 0;
break;
case OPEN2:
Can->open(Can->pinNum); Can->ststus = 0;
break;
case OPEN3:
Ups->open(Ups->pinNum); Ups->ststus = 0;
break;
case OPEN4:
Bath->open(Bath->pinNum); Bath->ststus = 0;
break;
case CLOSE1:
Liv->close(Liv->pinNum); Liv->ststus = 1;
break;
case CLOSE2:
Can->close(Can->pinNum); Can->ststus = 1;
break;
case CLOSE3:
Ups->close(Ups->pinNum); Ups->ststus = 1;
break;
case CLOSE4:
Bath->close(Bath->pinNum); Bath->ststus = 1;
break;
/************灯*************灯**************灯*********/
}
get_cmd()函数及相关的宏
get_cmd(char *buf) 函数功能,将串口接收到的数据进行解析,如果字符串匹配,则返回相应的宏,如果没用相对应的字符串则返回-1
#define OPEN1 0
#define OPEN2 1
#define OPEN3 2
#define OPEN4 3
#define CLOSE1 4
#define CLOSE2 5
#define CLOSE3 6
#define CLOSE4 7
#define FIND 8
#define H 9
#define T 10
int get_cmd(char *buf)
{
//printf("get_cmd buf = %s\n",buf);
if(strcmp("open1",buf) == 0) return OPEN1;
if(strcmp("open2",buf) == 0) return OPEN2;
if(strcmp("open3",buf) == 0) return OPEN3;
if(strcmp("open4",buf) == 0) return OPEN4.
if(strcmp("close1",buf) == 0) return CLOSE1;
if(strcmp("close2",buf) == 0) return CLOSE2;
if(strcmp("close3",buf) == 0) return CLOSE3;
if(strcmp("close4",buf) == 0) return CLOSE4;
if(strcmp("find",buf) == 0) return FIND;
if(strncmp("H",buf,1) == 0) return H;
if(strncmp("T",buf,1) == 0) return T;
return -1;
}
三 socket 网络编程
既然是智能家居的项目,如果不能网络通信,那肯定不行的,所以第三章就是将主控香橙派和操作面板实现socke的TCP/IP。
3.1socket 初始化 ,香橙派作为服务端
socket创建服务的端主要有四步:
- socket
- bind
- listen
- accep
服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后交互成功
#include <sys/time.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <pthread.h>
#include <wiringPi.h>
#include <wiringSerial.h>
#include <stdlib.h>
#include <unistd.h>
#include "IputCommand.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
/* socke初始化 */
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));
//1socket
s_fd = socket(AF_INET , SOCK_STREAM ,0);//参数1 IPV4参数2TCP
if(s_fd == -1){
perror("socket");
exit(-1);
}
//2bind
s_addr.sin_family = AF_INET; //网络协议agreement
s_addr.sin_port = htons(atoi(port)); //port
inet_aton(ipAdress,&s_addr.sin_addr); //ip site
bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
//3.listen
listen(s_fd,10);
socketMes->sfd=s_fd;
printf("socket Server listening ...\n");
return s_fd;
}
/* 等待客户端的接入,读取客户端的信息 */
int socketGetCommand(struct Inputcommander *socketMes){
int c_fd; //socket连接成功返回的文件描述符
int n_read = 0; //读取到客户端的数据的字节数
struct sockaddr_in c_addr;
memset(&c_addr,0,sizeof(struct sockaddr_in));
int clen = sizeof(struct sockaddr_in); //4.accept
/* 4一直阻塞到有客户端连接 */
c_fd = accept(socketMes->sfd,(struct sockaddr *)&c_addr,&clen);
if(c_fd == -1){
perror("accept");
}
printf("get connect %s\n",inet_ntoa(c_addr.sin_addr));
n_read = read(c_fd,socketMes->commander,32);
if(n_read == -1){
perror("read");
}else{
printf("get Message:%d, %s\n",n_read,socketMes->commander);
}
return n_read;
}
struct Inputcommander socketControl = {
.commanderName = "socketServer",
.port = "8888",
.ipAddress = "192.168.102.145",
.cdmand = {'\0'},
.Init = socketInit,
.getcommand = socketGetCommand,
.log = {'\0'},
.next = NULL
};
struct Inputcommander *addSocketControlToIputCommanderLink(struct Inputcommander *phead)
{
if(phead == NULL){
return &socketControl;
}else{
socketControl.next=phead;
phead = &socketControl;
}
};
3.2 创建socket线程,启动线程
pthread_t socketThread; //socke线程
pthread_create(&socketThread,NULL,socket_thread,NULL);//调用socket_thread
socket线程
进入线程,创建结构体,初始化socke指令工厂,然后while循环一直等待循环等待客户端接入,接入客户端后,启动读写操作的线程
//socket连接客户端的线程
void *socket_thread(){
pthread_t readThread;
int n_read = 0;
int clen = sizeof(struct sockaddr_in);
struct sockaddr_in c_addr;
memset(&c_addr,0,sizeof(struct sockaddr_in));
socketHandler = findCommandName("socketServer",commanderHead);
if(socketHandler == NULL){ //没找到这个线程初始化
printf("find socket error\n");
//return NULL;
}else{
printf("%s init success\n",socketHandler->commanderName);
}
socketHandler->Init(socketHandler,ipAdress,port);
while(1){
c_fd = accept(socketHandler->sfd,(struct sockaddr *)&c_addr,&clen);
pthread_create(&readThread,NULL,read_thread,NULL);//创建新的进程
}
}
读写线程
获取客户端发送过来的数据,然后进行解析操作,最好做出相对应的操作(和上述串口通信收到数据后差不多)
//socket server 的读取数据的线程
void *read_thread(){
int n_read = 0;
struct Devices *tmp=NULL;
while(1){
memset(socketHandler->cdmand,'\0',sizeof(socketHandler->cdmand));
n_read = read(c_fd,socketHandler->cdmand,sizeof(socketHandler->cdmand));
if(-1 == n_read){ /*读取失败,返回-1*/
perror("read:?");
}else{
if(0 != n_read){
printf("get Message:%dByte %s\n",n_read,socketHandler->cdmand);
msg_handler(socketHandler->cdmand);
}
}
}
}
四 小结
4.1 技术细节
- 代码工厂设计模式,分为文件管理设备代码
- 利用线程,分线程处理不同的功能代码
- 使用结构体记录设备信息
4.2 失误总结
- 指针使用完,释放内存,造成内存泄露,造成断错误
- 香橙派默认波特率为115200,而与子系统通信使用的波特率为9600,造成数据接收不准确
- 代码中if()else嵌套过多,代码难看,最后用swich 与 字符串比对strcmp()解决,减少代码量
- 设备结构体未进行初始化就对其进行操作,造成段错误
如需要具体代码或源码,留言即可