第一章 智能家居(主控)的开发及代码分析

文章目录

一 工厂设计模式

二 串口开发

2.1 串口的初始化——115200波特率

2.2 将串口接收到的信息解析

三 socket 网络编程

3.1socket 初始化 ,香橙派作为服务端

 3.2 创建socket线程,启动线程

小结


概要

第一章 智能家居(主控)的开发及代码分析_第1张图片

 本章记录智能家居的代码开发及逻辑,主要分为四部分  

  1. 工厂设计模式
  2. 串口开发及数据发送与接收
  3. 网络编程Socket 

一 工厂设计模式

什么是工厂设计模式(C语言拟面向对象实现) 

这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
在工厂模式中,我们在创建对象时不会对客户接口来指向新创建的端暴露创建逻辑,并且是通过使用一个共同的对象。工厂模式的目的就根据不同的要求输出不同的产品。比如说,有一个生产的灯的设备工厂,它能生产厨房灯,也能生产汽车灯。

#include 
#include 

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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#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创建服务的端主要有四步:

  1. socket
  2. bind
  3. listen
  4. accep

服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后交互成功

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "IputCommand.h"
#include 
#include 
#include 
#include 


/* 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  技术细节

  1. 代码工厂设计模式,分为文件管理设备代码
  2. 利用线程,分线程处理不同的功能代码
  3. 使用结构体记录设备信息

4.2 失误总结

  1. 指针使用完,释放内存,造成内存泄露,造成断错误
  2. 香橙派默认波特率为115200,而与子系统通信使用的波特率为9600,造成数据接收不准确
  3. 代码中if()else嵌套过多,代码难看,最后用swich 与 字符串比对strcmp()解决,减少代码量
  4. 设备结构体未进行初始化就对其进行操作,造成段错误

如需要具体代码或源码,留言即可 

你可能感兴趣的:(Linux智能家居,linux,智能家居,笔记,arm开发)