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

概要

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

  1. 工厂设计模式
  2. 串口开发及数据发送与接收
  3. 网络编程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创建服务的端主要有四步:

  1. socket
  2. bind
  3. listen
  4. 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  技术细节

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

4.2 失误总结

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

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

  • 30
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
项目开发环境 Linux+Arm53+C语言   项目需求    功能类别 功能名称 描述 家电控制模块 1、空调控制模块 信息显示 显示当前室内温度 自动控制 设置空调在某温度下自动开、关  2、风扇控制模块 状态显示 风扇开、关 自动控制 设置风扇的档数 3、灯光控制模块 状态控制 灯光的开/关控制 亮度 正常、节能  4、窗帘控制模块 自动开/关 根据室外的亮度自动开启/关闭窗帘 手动开/关 读取当前窗帘状态,手动开启/关闭 监控模块  1、烟感监控 自动控制 超过限量烟雾浓渡报警  2、红外监控 自动控制 有人非法闯进报警  3、报警功能 自动控制 打开、关闭  4、GPRS模块 信息收发 当有报警信息的时候,系统会自动将警报信息发送到指定的手机号码。 信息设定 设定接收信息的手机号  5、访客视频对讲 状态显示 观察访客信息、自定义门锁开、关 娱乐模块  1、收音机 状态控制 开、关 自定义 更换频道 自动调节声音 增大、减小  2、家庭影院 灯光控制 亮度效果调节 自动调节声音 增大、减小  3、音频播放 状态控制 开、关 自动调节声音 增大、减小 自定义模式 选择播放、随机播放、顺序播放、列表循环  4、数码相册 状态控制 开、关 自定义模式 选择播放、随机播放、顺序播放、列表循环 场景切换  1、在家模式 状态控制 家居电器开关、灯光亮度、监控状态  2、离家模式    项目源码结构:     |--bin            脚本目录         |--tar.sh     项目打包脚本     |--data           数据目录     |--include        头文件         |--beep.h     蜂鸣器模块         |--bmp.h      BMP图模块         |--config.h   项目配置文件         |--file.h     文件操作模块         |--jpeg.h     JPG图模块         |--led.h      LED模块         |--mp3.h      音频模块         |--mplayer.h  音频库模块         |--res.h      资源配置文件         |--tslib.h    触摸模块         |--ui.h       UI模块         |--vedio.h    视频模块     |--lib            库文件     |--pic            资源图片         |--icon       应用图标         |--ui         UI文件     |--shortcut       项目效果图     |--ext            第三方库         |--driver     驱动         |--lib        第三方库     |--src            代码目录         |--main.c     主程序         |--Makfile    自动编译脚本 --------------------- 作者:qq_39188039 来源:CSDN 原文:https://blog.csdn.net/qq_39188039/article/details/83751526 版权声明:本文为博主原创文章,转载请附上博文链接!
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@ChenPi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值