智慧家居
开发文档:
OneNet开发文档
系统架构设计
首先我想先通过一个图片,来说明项目整体框架
硬件部分
1.按键与LED实现开关功能
LED驱动代码
#include "led.h"
uint8_t led_sta=0;
void led_init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_13;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOC,&GPIO_InitStructure);
GPIO_SetBits(GPIOC,GPIO_Pin_13);
}
void led_turn(void)
{
if(GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_13)==1)
{
GPIO_ResetBits(GPIOC,GPIO_Pin_13);
led_sta=1;
}
else
{
GPIO_SetBits(GPIOC,GPIO_Pin_13);
led_sta=0;
}
}
void led_off(void)
{
GPIO_SetBits(GPIOC,GPIO_Pin_13);
led_sta=0;
}
void led_on(void)
{
GPIO_ResetBits(GPIOC,GPIO_Pin_13);
led_sta=1;
}
Key驱动代码
疑点解析:
- 我这里用的是外部中断实现的
- 上拉输入就是默认是高电平1,比如我有个按键,按下就是0,如果是下拉输入,默认是0,按下是1
EXTI9_5_IRQHandler(void)
是触发中断要执行的功能
#include "key.h"
void key_init(void)
{
GPIO_InitTypeDef my_key_init;
EXTI_InitTypeDef my_exti_init;
NVIC_InitTypeDef my_nvic_init;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//开启gpio口时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//开启外部中断AFIO时钟
//gpio初始化
my_key_init.GPIO_Mode=GPIO_Mode_IPU;//上拉输入
my_key_init.GPIO_Speed=GPIO_Speed_50MHz;
my_key_init.GPIO_Pin=GPIO_Pin_6;
GPIO_Init(GPIOA,&my_key_init);
//AFIO初始化
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource6);
//EXTI初始化
my_exti_init.EXTI_Line = EXTI_Line6; // 选择中断线 6,对应 GPIO 的第 6 号引脚
my_exti_init.EXTI_LineCmd = ENABLE; // 使能这条中断线
my_exti_init.EXTI_Mode = EXTI_Mode_Interrupt; // 中断模式(不是事件)
my_exti_init.EXTI_Trigger = EXTI_Trigger_Falling;// 触发方式:下降沿触发
EXTI_Init(&my_exti_init); // 调用库函数初始化
//NVIC初始化
//设置优先级
my_nvic_init.NVIC_IRQChannel=EXTI9_5_IRQn;
my_nvic_init.NVIC_IRQChannelCmd=ENABLE;
my_nvic_init.NVIC_IRQChannelPreemptionPriority=2;
my_nvic_init.NVIC_IRQChannelSubPriority=2;
NVIC_Init(&my_nvic_init);
}
void EXTI9_5_IRQHandler(void)
{
if (EXTI_GetITStatus(EXTI_Line6) != RESET) // 判断是否真的是 EXTI6 触发的中断
{
if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6) == 0)
{
DelayMs(20); // 简单消抖
while(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6) == 0);
DelayMs(20);
led_turn(); // 执行你的功能函数
}
EXTI_ClearITPendingBit(EXTI_Line6); // 清除中断标志,避免重复进入中断
}
}
OLED驱动我采用的是江科大的驱动代码,这里不写出了
2.温度与光照传感器模块
这里主要是
adc
初始化、adc
转化温度光照值
#include "waishe.h"
#include <math.h>
#define R1 10000.0f // NTC分压电阻阻值 10kΩ
#define B 3950.0f // NTC B值
#define R0 10000.0f // NTC 25℃时阻值
#define T0 298.15f // 25℃开尔文温度
#define R2 10000.0f // LDR分压电阻阻值 10kΩ
#define ADC_REF 3.3f // ADC参考电压
#define ADC_MAX 4095.0f // 12位ADC最大值
void waishe_init(void)
{
GPIO_InitTypeDef gpio;
ADC_InitTypeDef adc;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE);
gpio.GPIO_Mode = GPIO_Mode_AIN;
gpio.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
GPIO_Init(GPIOA, &gpio);
//adc初始化
ADC_DeInit(ADC1);
adc.ADC_Mode = ADC_Mode_Independent;
adc.ADC_ScanConvMode = DISABLE;
adc.ADC_ContinuousConvMode = DISABLE;
adc.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
adc.ADC_DataAlign = ADC_DataAlign_Right;
adc.ADC_NbrOfChannel = 1;
ADC_Init(ADC1, &adc);
ADC_Cmd(ADC1, ENABLE);
ADC_ResetCalibration(ADC1);
while (ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while (ADC_GetCalibrationStatus(ADC1));
}
uint16_t Read_ADC_Channel(uint8_t channel)
{
ADC_RegularChannelConfig(ADC1, channel, 1, ADC_SampleTime_239Cycles5);
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
while (!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));
return ADC_GetConversionValue(ADC1);
}
// 计算NTC温度,返回摄氏度
float calculate_ntc_temperature(uint16_t adc_val)
{
float voltage;
float resistance;
float temp_k;
voltage = adc_val * ADC_REF / ADC_MAX;
if (voltage == 0) return -273.15f; // 防止除零错误
resistance = (voltage * R1) / (ADC_REF - voltage);
temp_k = 1.0f / ( (1.0f / T0) + (1.0f / B) * log(resistance / R0) );
return temp_k - 273.15f; // 转摄氏度
}
float calculate_ldr_light_level(uint16_t adc_val)
{
float voltage;
float resistance;
float light_level;
voltage = adc_val * ADC_REF / ADC_MAX;
if (voltage == 0) return 0.0f; // 防止除零
resistance = (ADC_REF - voltage) * R2 / voltage;
light_level = 100000.0f / resistance;
if (light_level > 100.0f) light_level = 100.0f;
if (light_level < 0.0f) light_level = 0.0f;
return light_level;
}
void show_info(float *temperature, float *light)
{
uint16_t temp_val = Read_ADC_Channel(ADC_Channel_0); // PA0: NTC
uint16_t light_val = Read_ADC_Channel(ADC_Channel_1); // PA1: LDR
*temperature = calculate_ntc_temperature(temp_val);
*light = calculate_ldr_light_level(light_val);
OLED_ShowString(1, 1, "Tem:");
OLED_ShowNum(1, 5, (int)(*temperature), 2);
OLED_ShowString(2, 1, "Light:");
OLED_ShowNum(2, 7, (100-(int)(*light)%100), 2);
OLED_ShowString(2, 9, "%");
if (GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_13) == 0)
{
OLED_ShowString(3, 1, "Led_status:On "); // 注意后面空格
}
else
{
OLED_ShowString(3, 1, "Led_status:Off ");
}
}
3.usart通讯模块
Usart1
用于ch340通讯
/*
************************************************************
* 函数名称: Usart1_Init
*
* 函数功能: 串口1初始化
*
* 入口参数: baud:设定的波特率
*
* 返回参数: 无
*
* 说明: TX-PA9 RX-PA10
************************************************************
*/
void Usart1_Init(unsigned int baud)
{
GPIO_InitTypeDef gpio_initstruct;
USART_InitTypeDef usart_initstruct;
NVIC_InitTypeDef nvic_initstruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
//PA9 TXD
gpio_initstruct.GPIO_Mode = GPIO_Mode_AF_PP;
gpio_initstruct.GPIO_Pin = GPIO_Pin_9;
gpio_initstruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &gpio_initstruct);
//PA10 RXD
gpio_initstruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
gpio_initstruct.GPIO_Pin = GPIO_Pin_10;
gpio_initstruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &gpio_initstruct);
usart_initstruct.USART_BaudRate = baud;
usart_initstruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //无硬件流控
usart_initstruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //接收和发送
usart_initstruct.USART_Parity = USART_Parity_No; //无校验
usart_initstruct.USART_StopBits = USART_StopBits_1; //1位停止位
usart_initstruct.USART_WordLength = USART_WordLength_8b; //8位数据位
USART_Init(USART1, &usart_initstruct);
USART_Cmd(USART1, ENABLE); //使能串口
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); //使能接收中断
nvic_initstruct.NVIC_IRQChannel = USART1_IRQn;
nvic_initstruct.NVIC_IRQChannelCmd = ENABLE;
nvic_initstruct.NVIC_IRQChannelPreemptionPriority = 0;
nvic_initstruct.NVIC_IRQChannelSubPriority = 2;
NVIC_Init(&nvic_initstruct);
}
Usart2
用于esp
和服务器通讯
/*
************************************************************
* 函数名称: Usart2_Init
*
* 函数功能: 串口2初始化
*
* 入口参数: baud:设定的波特率
*
* 返回参数: 无
*
* 说明: TX-PA2 RX-PA3
************************************************************
*/
void Usart2_Init(unsigned int baud)
{
GPIO_InitTypeDef gpio_initstruct;
USART_InitTypeDef usart_initstruct;
NVIC_InitTypeDef nvic_initstruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
//PA2 TXD
gpio_initstruct.GPIO_Mode = GPIO_Mode_AF_PP;
gpio_initstruct.GPIO_Pin = GPIO_Pin_2;
gpio_initstruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &gpio_initstruct);
//PA3 RXD
gpio_initstruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
gpio_initstruct.GPIO_Pin = GPIO_Pin_3;
gpio_initstruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &gpio_initstruct);
usart_initstruct.USART_BaudRate = baud;
usart_initstruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //无硬件流控
usart_initstruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //接收和发送
usart_initstruct.USART_Parity = USART_Parity_No; //无校验
usart_initstruct.USART_StopBits = USART_StopBits_1; //1位停止位
usart_initstruct.USART_WordLength = USART_WordLength_8b; //8位数据位
USART_Init(USART2, &usart_initstruct);
USART_Cmd(USART2, ENABLE); //使能串口
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE); //使能接收中断
nvic_initstruct.NVIC_IRQChannel = USART2_IRQn;
nvic_initstruct.NVIC_IRQChannelCmd = ENABLE;
nvic_initstruct.NVIC_IRQChannelPreemptionPriority = 0;
nvic_initstruct.NVIC_IRQChannelSubPriority = 0;
NVIC_Init(&nvic_initstruct);
}
串口函数
/*
************************************************************
* 函数名称: Usart_SendString
*
* 函数功能: 串口数据发送
*
* 入口参数: USARTx:串口组
* str:要发送的数据
* len:数据长度
*
* 返回参数: 无
*
* 说明:
************************************************************
*/
void Usart_SendString(USART_TypeDef *USARTx, unsigned char *str, unsigned short len)
{
unsigned short count = 0;
for(; count < len; count++)
{
USART_SendData(USARTx, *str++); //发送数据
while(USART_GetFlagStatus(USARTx, USART_FLAG_TC) == RESET); //等待发送完成
}
}
/*
************************************************************
* 函数名称: UsartPrintf
*
* 函数功能: 格式化打印
*
* 入口参数: USARTx:串口组
* fmt:不定长参
*
* 返回参数: 无
*
* 说明:
************************************************************
*/
void UsartPrintf(USART_TypeDef *USARTx, char *fmt,...)
{
unsigned char UsartPrintfBuf[296];
va_list ap;
unsigned char *pStr = UsartPrintfBuf;
va_start(ap, fmt);
vsnprintf((char *)UsartPrintfBuf, sizeof(UsartPrintfBuf), fmt, ap); //格式化
va_end(ap);
while(*pStr != 0)
{
USART_SendData(USARTx, *pStr++);
while(USART_GetFlagStatus(USARTx, USART_FLAG_TC) == RESET);
}
}
/*
************************************************************
* 函数名称: USART1_IRQHandler
*
* 函数功能: 串口1收发中断
*
* 入口参数: 无
*
* 返回参数: 无
*
* 说明:
************************************************************
*/
void USART1_IRQHandler(void)
{
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中断
{
USART_ClearFlag(USART1, USART_FLAG_RXNE);
}
}
4.esp8266驱动
为了便于后续了解外设各个功能,我这里拆解一下esp的驱动代码
#define REV_OK 0 //接收完成标志 #define REV_WAIT 1 //接收未完成标志
1.初始化
void ESP8266_Init(void)
{
ESP8266_Clear();
UsartPrintf(USART_DEBUG, "1. AT\r\n"); //u1负责打印到串口助手
while(ESP8266_SendCmd("AT\r\n", "OK"))
DelayXms(500);
UsartPrintf(USART_DEBUG, "2. CWMODE\r\n");
while(ESP8266_SendCmd("AT+CWMODE=1\r\n", "OK"))
DelayXms(500);
UsartPrintf(USART_DEBUG, "3. AT+CWDHCP\r\n");
while(ESP8266_SendCmd("AT+CWDHCP=1,1\r\n", "OK"))
DelayXms(500);
UsartPrintf(USART_DEBUG, "4. CWJAP\r\n");
while(ESP8266_SendCmd(ESP8266_WIFI_INFO, "GOT IP"))
DelayXms(500);
UsartPrintf(USART_DEBUG, "5. ESP8266 Init OK\r\n");
}
2.发送esp
执行命令
Bool ESP8266_SendCmd(char *cmd, char *res)
{
unsigned char timeOut = 200;
Usart_SendString(USART2, (unsigned char *)cmd, strlen((const char *)cmd));
while(timeOut--)
{
if(ESP8266_WaitRecive() == REV_OK) //如果收到数据
{
if(strstr((const char *)esp8266_buf, res) != NULL) //如果检索到关键词
{
ESP8266_Clear(); //清空缓存
return 0;
}
}
DelayXms(10);
}
return 1;
}
3.清空缓存
void ESP8266_Clear(void)
{
memset(esp8266_buf, 0, sizeof(esp8266_buf));
esp8266_cnt = 0;
}
4.等待响应
Bool ESP8266_WaitRecive(void)
{
if(esp8266_cnt == 0) //如果接收计数为0 则说明没有处于接收数据中,所以直接跳出,结束函数
return REV_WAIT;
if(esp8266_cnt == esp8266_cntPre) //如果上一次的值和这次相同,则说明接收完毕
{
esp8266_cnt = 0; //清0接收计数
return REV_OK; //返回接收完成标志
}
esp8266_cntPre = esp8266_cnt; //置为相同
return REV_WAIT; //返回接收未完成标志
}
5.esp发送数据给服务器
AT+CIPSEND=%d\r\n
这是发送数据给服务器的指令,所以下面串口发送的数据不是给esp而是服务器
void ESP8266_SendData(unsigned char *data, unsigned short len)
{
char cmdBuf[32];
ESP8266_Clear(); //清空接收缓存
sprintf(cmdBuf, "AT+CIPSEND=%d\r\n", len); //发送命令
if(!ESP8266_SendCmd(cmdBuf, ">")) //收到‘>’时可以发送数据
{
Usart_SendString(USART2, data, len); //发送设备连接请求数据
}
}
6.获取平台返回数据
unsigned char *ESP8266_GetIPD(unsigned short timeOut)
{
char *ptrIPD = NULL;
do
{
if(ESP8266_WaitRecive() == REV_OK) //如果接收完成
{
ptrIPD = strstr((char *)esp8266_buf, "IPD,"); //搜索“IPD”头
if(ptrIPD == NULL) //如果没找到,可能是IPD头的延迟,还是需要等待一会,但不会超过设定的时间
{
//UsartPrintf(USART_DEBUG, "\"IPD\" not found\r\n");
}
else
{
ptrIPD = strchr(ptrIPD, ':'); //找到':'
if(ptrIPD != NULL)
{
ptrIPD++;
return (unsigned char *)(ptrIPD);
}
else
return NULL;
}
}
DelayXms(5); //延时等待
} while(timeOut--);
return NULL; //超时还未找到,返回空指针
}
通信部分
1.mqtt
这里我们要用到MQTT.fx
这一个客户端去进行测试,客户端就是在模拟我们设备(这个项目),去和服务器通讯
你作为客户端程序:
- 想要连服务器 → 调
MQTT_PacketConnect()
组一个 CONNECT 报文,通过 TCP 发送。 - 想要订阅主题 → 调
MQTT_PacketSubscribe()
组一个 SUBSCRIBE 报文,发给服务器。 - 想要发消息 → 调
MQTT_PacketPublish()
组一个 PUBLISH 报文,发给服务器。
服务器返回的数据(二进制 MQTT 报文):
- 收到后先用
MQTT_UnPacketRecv()
或者具体的解包函数,比如MQTT_UnPacketConnectAck()
、MQTT_UnPacketPublish()
来解析。 - 解析出来的结果就是你能在代码里直接用的内容(比如连接是否成功、主题是什么、消息内容是什么)。
小程序部分
先介绍一下
JavaScript 和 JSON 是两个不同的东西:
JavaScript
是一种编程语言,用来写逻辑、操作网页、调用 API、控制流程等。
例如:
let name = "Tom"; function sayHello() { console.log("Hello, " + name); } sayHello();
👉 这是 JavaScript 代码,能执行。
JSON (JavaScript Object Notation)
是一种 数据格式,长得很像 JavaScript 的对象语法,但只能用来存数据,不能执行逻辑。
常用在前后端通信、配置文件里。
例如:
{ "name": "Tom", "age": 18, "isStudent": true }
👉 这是 JSON,只能存数据,不能像 JavaScript 那样写函数或逻辑。
关系
JSON 的语法最早就是从 JavaScript 对象语法演变出来的。
但 JSON 独立于语言,Python、Java、C、Go 等都能读写 JSON。
JavaScript 可以很方便地处理 JSON:
let obj = JSON.parse('{"name":"Tom"}'); // JSON → JS 对象 console.log(obj.name); let str = JSON.stringify({name: "Tom"}); // JS 对象 → JSON console.log(str);
👉 所以:
- JavaScript = 一门编程语言
- JSON = 一种数据交换格式
Vue 组件的 <script>
,功能是:
👉 定时从 OneNET 平台获取设备数据(温度、光照、LED 状态),并能控制 LED 开关。
1. 导入部分
const { createCommonToken } = require('@/key.js')
- 从
key.js
文件里引入了createCommonToken
函数。 - 这个函数用来生成 鉴权 token,请求 OneNET 平台 API 时要带上。
2. data 数据
data() {
return {
temp: '', // 温度
light: '', // 光照
led: true, // led状态(布尔值)
token: '', // 鉴权 token
}
}
这些变量绑定到页面 <template>
里,用来显示和交互。
3. 生命周期钩子
onLoad() {
const params = {
author_key: 'xxx',
version: '2022-05-01',
user_id: '460751',
}
this.token = createCommonToken(params);
},
- 页面加载时执行。
- 传入密钥、版本号、用户 ID,生成 token,保存到
this.token
。
onShow() {
this.fetchDevData();
setInterval(() => {
this.fetchDevData();
}, 3000)
},
- 页面显示时执行。
- 先调用一次
fetchDevData()
获取设备数据。 - 然后每隔 3 秒刷新一次数据,实现 实时监控。
4. methods 方法
(1) 获取设备数据
fetchDevData() {
uni.request({
url: 'https://iot-api.heclouds.com/thingmodel/query-device-property',
method: 'GET',
data: {
product_id: 'ECHECArR1s',
device_name: 't1'
},
header: {
'authorization': this.token // 自定义请求头信息
},
success: (res) => {
console.log(res.data);
this.led = res.data.data[0].value === 'true';
this.light = res.data.data[1].value;
this.temp = res.data.data[2].value;
}
})
}
- 调用 OneNET 设备属性查询接口,获取
t1
设备的数据。 - 带上
authorization
头部(token)。 - 成功后解析返回数据,更新页面的 LED、光照、温度。
this.led = res.data.data[0].value === 'true';
→ 解析出 LED 状态并转成布尔值。this.light = res.data.data[1].value;
→ 光照数值。this.temp = res.data.data[2].value;
→ 温度数值。
(2) 控制 LED 开关
on_led(event) {
let value = event.detail.value;
uni.request({
url: 'https://iot-api.heclouds.com/thingmodel/set-device-property',
method: 'POST',
data: {
product_id: 'ECHECArR1s',
device_name: 't1',
params: {
"led": value
}
},
header: {
'authorization': this.token
},
success: () => {
console.log();
}
})
}
- 当用户在页面点击
<switch>
控件时触发。 - 获取开关状态
event.detail.value
(true / false)。 - 调用 OneNET 设置设备属性接口,修改 LED 的状态。
- 请求体里的
params: {"led": value}
告诉服务器要开还是关。
5. 总体流程
- 生成 token → 用于鉴权。
- 页面加载 → 保存 token。
- 页面显示 → 每 3 秒调用一次
OneNET
查询接口,获取设备属性(温度、光照、LED)。 - 用户切换开关 → 调用
OneNET
设置接口,控制设备的 LED 灯。
内容还需更新改进,感谢Onenet平台提供的开发文档以及demo,后续可以在此基础添加新的功能