适读者:初学者
项目简介:用键盘输入指令,控制一盏灯的亮灭。
(可拓展为多盏灯)
硬件及外设:树莓派,继电器,灯泡
语言:C
技术要点:通过调wiringPi库的接口函数,来控制树莓派的引脚。直接调库的代码比较简单。此项目为了后续制作一整套的智能家居,采用了工厂模式的设计模式,用来提高代码的可拓展性以及可维护性,这是主要目的。
知识点:wiringPi库,工厂模式(程序框架);链表
2023年4月26日: 代码有需要修改的地方。命名太长,没有把函数抽出来,部分逻辑冗余...待修改
- 项目概念图
“设备工厂”里存储了一条设备相关的链表,你可以对链表进行新增节点,以实现的设备功能;
为方便调试,本篇只设置一个链表的节点,即浴室灯的节点。因此此节点的next指向NULL;
,
对于浴室灯这个对象,其结构体的内容里包含了控制相关的函数。(如初始化 initdevice。开关 open、close 等)
结构体的定义放在 controlDevices.h 里,
具体的实现代码封装在 Light_bathroom.c 里,
主程序为 mainpro.c 里。
源代码
controlDevices.h
#include <wiringPi.h>
#include <stdio.h>
/*
设备通用的结构体定义。
支持各种类型的设备使用,不仅限于灯。
*/
struct Devices
{
char devName[128];
int status;
int pinNum;
int (*open)(int pinNum);
int (*close)(int pinNum);
int (*initdevice)(int pinNum);
int (*readStatus)();
int (*changeStatus)(int status);
struct Devices* next;
};
/*
添加链表
把浴室灯这个设备,插入“设备工厂”的链表中;
后续从"设备工厂"的链表中直接提取这个节点来使用
*/
struct Devices* addBathroomLightToDeviceLink(struct Devices* pHead);
Light_bathroom.c
#include "controlDevices.h"
int initBathroomLight(int pinNum)
{
pinMode(pinNum,OUTPUT);
digitalWrite(pinNum,HIGH); //初始状态。表现为关灯
}
int openBathroomLight(int pinNum)
{
digitalWrite(pinNum,LOW); //表现为开灯
}
int closeBathroomLight(int pinNum)
{
digitalWrite(pinNum,HIGH); //表现为关灯
}
/*new一个对象——浴室灯。并对其针对性赋初值*/
struct Devices Dev_bathroomLight =
{
.devName = "BATHROOMLIGHT",
.pinNum = 22,
.open = openBathroomLight,
.close = closeBathroomLight,
.initdevice = initBathroomLight,
.changeStatus = changeBathroomLightStatus
};
// 友情提示:注意格式,等号、逗号、分号等必不可少!
/*插入链表*/
struct Devices* addBathroomLightToDeviceLink(struct Devices* pHead)
{
if (pHead == NULL)
{
return &Dev_bathroomLight; //先前没有节点,那么现在有了
}
else
{
Dev_bathroomLight.next = pHead;
pHead = &Dev_bathroomLight; //实现插入节点,并修改设备链表头部指针的指向
return pHead;
}
}
mainpro.c
#include "controlDevices.h"
#include <stdio.h>
#include <string.h>
struct Devices* findDevByName(char* name, struct Devices* pDevicesHead);
void control_BRLight(struct Devices* pDevicesHead);
int main()
{
// 检测树莓派是否正常上电。删掉也可
if (wiringPiSetup() == -1)
{
printf("wiringPiSetup error!\n");
return -1;
}
char name[128] = "BATHROOMLIGHT";
struct Devices* pDevicesHead;
pDevicesHead = NULL;
pDevicesHead = addBathroomLightToDeviceLink(pDevicesHead); //添加浴室灯进链表
//从设备工厂中找到指定的一个设备节点,即浴室灯的节点
//拓展1: 通过修改此处name,使用二维数组,可实现对不同的设备进行操作。其次是修改在ontrol_BRLight()这个函数,把里边的输入键盘及判断的逻辑抽离出来才可以拓展。
struct Devices* pSomeoneDev = findDevByName(&name, pDevicesHead);
// 控制浴室灯
if (pSomeoneDev != NULL)
{
pSomeoneDev->initdevice(pSomeoneDev->pinNum); //初始化引脚的模式
control_BRLight(pSomeoneDev); //控制浴室灯
}
return 0;
}
struct Devices* findDevByName(char* name, struct Devices* pDevicesHead)
{
struct Devices* pHead;
pHead = pDevicesHead; //备份一下头节点的地址,避免因后续移动指针而丢失。删掉也可,可直接用pDevicesHead
if (pDevicesHead == NULL) //如果链表为空就不找了。删掉这个分支也可
{
return pDevicesHead;
}
else
{
while (pHead != NULL)
{
if (strcmp((pHead->devName), "BATHROOMLIGHT") == 0) //找到了浴室灯这个节点
{
return pHead;
}
pHead = pHead->next;
}
printf("dev not find!"); //找不到浴室灯这个设备节点
return pDevicesHead;
}
}
void control_BRLight(struct Devices* pDevicesHead)
{
int lscmd;
while (1)
{
printf("请输入指令\n");
printf("2为开灯,20为关灯\n");
scanf("%d",&lscmd);
getchar(); //吸收回车键
//gets(&lscmd); //此处不推荐用gets,无法得到整数类型
printf("get = %d\n",lscmd);
/*找到用户的指令,并相应的动作*/
switch (lscmd)
{
case 2:
pDevicesHead->open(pDevicesHead->pinNum);
printf("浴室灯已打开(22pin = 0)\n");
printf("=====================");
break;
case 20:
pDevicesHead->close(pDevicesHead->pinNum);
printf("浴室灯已关闭(22pin = 1)\n");
printf("=====================");
break;
default:
printf("input error!\n plese input again\n======================\n\n");
break;
}
lscmd = 0; //初始化用户的指令,避免重复
delayMicroseconds(1000000); //树莓派设置1s的延时。删掉也可
}
}
项目心得
代码本身的难度不高,都是先前学习过的,比较熟悉,但是实际编译的时候感觉有些问题。
- 关于编辑器
笔者用的是source insight来编码的,简单的调试再用vim。
使用SI的时候,结构体的
- 关于编程熟练度
实际编写代码时,本来知道的格式要求,它还是可能犯错,可能是熟练度不够,要多写多记才能记得清楚吧。
记录的如下:
结构体:少分号,少逗号,少等号... (这几处地方一处都不能错,要观察细致)
switch:case里边没有用到break;
gets:需要用取地址符号&。且无法对整形数直接读取,读取的是键盘数;
scanf:需要吸收空格。gets不能直接读取整数型,只能读取字符型,如'a';
NULL:代码编译报错。.h文件没有包含标准头文件<stdio.h>;
指针赋值:插入链表的函数里,用一个等号(=)来给指针赋值,不小心用成了(==)。还是在点亮两盏灯的时候才发现..
这启示我们在分支多的情况下,即使程序跑通了,也不能证明别的分支也是正确的。