1、项目介绍

本次我们教程中的项目为STM32温湿度采集系统,该系统作为基础的物联网系统学习是再合适不过的选择,大家只要学会该系统的设计方法,就可以自己设计出其它各种物联网系统。本次我们系统实现采实现方案是通过 硬件-Onenet云平台-微信小程序三部分组成,具体可以看下述的系统架构介绍。

建议大家跟着本教程将温湿度采集系统完整的复刻一下,后续再做其它物联网系统就会非常的简单,对后续参加比赛、毕设、课设等都有很大的帮助。

关于系统的名称可以是以下不同的名称:基于Onenet平台的温湿度采集系统、STM32智能家居系统、STM32远程温湿度检测系统等。

本篇文章的所有内容都在视频中有讲到,只是为了方面大家学习,单独搞了一份电子版的教程。如果哪些地方写的不是很清楚,建议去看下视频。视频里会把该讲的内容都覆盖掉。

教程相关代码、工具、资料在微信公众号进行下载:

系统架构

目前该系统采用比较主流的物联网设计方案,主要有三部分组成。

1、硬件-STM32单片机

2、平台-Onenet云平台

3、APP-微信小程序

使用该方案去实现物联网系统,灵活性比较高,大家可以自由设计APP的界面和功能。并且做起来相对来说要简单一些,适合大家入门进行学习。

架构图.png

功能扩展

学习完该教程以后,各种各样的系统大家都可以尝试去做,不同的系统之间框架都是可以采用本教程框架,最大的区别就是每个系统使用到的传感器差异。建议大家跟着本教程将温湿度采集系统完整的复刻一下,后续再去做其它的系统就会比较简单。比如一些类似的物联网系统,大家在学完这个系统以后,都可以尝试做一下。后续我也会再挑几个系统给大家讲解一下。

1、STM32智能台灯系统

2、STM32智能家居系统

3、空气质量检测系统

4、智能农业灌溉系统

5、STM32烟雾报警系统

6、火灾检测系统

............

上面的系统万变不离其中,最关键的就是学会如何单片机和APP进行远程控制,其余的差异主要就是使用的传感器不一样,采集到的数据不一样。整体的框架都是相似的。


硬件搭建


单片机硬件系统主要有两种搭建方式,一种是PCB板搭建,另一种则是使用面包板搭建。(代码资料、硬件资料从公众号里获取链接下载)
1、PCB板搭建:

通过画系统的电路图,生成Gerber文件。然后发给PCB生产厂家进行打板。我们只需要在做好的PCB电路板上面将相关的模块和元器件进行焊接即可。这种方式需要有系统的原理图,做出来的系统比较美观(视频教程使用的方式就为PCB板,外观美观,并且集成CH340芯片,可以给单片机一键下载程序)。

PCB板打板成功后,只需要将元器件都给焊接上去就完成了板子的制作。具体需要买的元器件清单如下所示。后续我会将每个元器件的淘宝截图放在资料里面,大家对比着进行购买就行。



焊接完成后的PCB图下图所示,只需要将模块插到对应的板子上,就完成了板子的设计和制作。

2、面包板搭建:

使用面包板一样可以实现本系统功能,与PCB电路板相比,面包板搭建起来稍微简单一些,但是看起来不美观,并且有时候可能存在信号问题。但总体来讲,通过面包板搭建也是可以实现本项目系统功能的。

面包板搭建需要购买的元器件如下图所示,后续我也会将对应的模块淘宝截图给大家放在资料里面供参考


模块之间是如何进行连线的,大家可以直接看我们代码上是如何写的,代码里面都可以看到每个模块和单片机之间是怎么连接的。同样,大家也可以参考我们之前画PCB板时候的原理图,上面每个模块和单片机之间如何连接的也都有展示。使用杜邦线连接好的电路图如下所示。

系统功能


1、采集温度和湿度数据,在OLED显示屏上显示相关数值。


2、可以通过按键设置温度、湿度阈值,当温度或者湿度超过设定的阈值时,会触发声光报警功能。


3、APP可以远程控制单片机声光报警功能、LED灯开关功能。


4、单片机采集到的数据可以远程发送给APP,在APP上展示温度和湿度数据。

以上就是我们这个温湿度采集系统的具体功能,大家可以看到我们这个系统使用到的传感器不多,就一个温湿度检测传感器。主要是为了大家方便学习单片机和云平台以及APP这个整体的流程是怎么进行的,大家在学会这个流程以后。可以自己多添加一些传感器,比如说增加一个烟雾传感器,然后和温湿度数据一样的方式发送给APP显示即可。


2、Onenet云平台介绍

1、注册账号

登录Onenet平台官网,OneNET - 中国移动物联网开放平台,点击右上角登陆按钮进行注册账号。

onenet-1.jpg

注册完账号以后,进行登录,然后可以看到界面上显示“开发者中心”,进入开发者中心,进行物联网设备的创建。

onenet-2.jpg

进入开发者中心,可以看到下述界面。我们后续的设备的创建就在这个界面完成。

onenet-3.jpg

2、创建产品

产品是一组具有相同功能定义的设备集合,创建产品是使用平台的第一步,快速创建产品后可定义产品功能、添加对应设备、进行设备开发、软硬件设备调试,产品开发的全流程配置都在产品的基础上完成。创建产品时提供了标准方案和自定义方案两种开发方案,标准方案由平台定义好具体产品品类的物模型,自定义方案是由自己进行相关配置。

onenet-4.jpg

点击创建产品,在创建产品的时候有几个选项。

  • 产品种类

可以随便选,这个无关紧要。

  • 选择智能化方式

选择设备接入方式,不使用产品智能化方式

  • 产品名称、所属地址

自己随意命名和选择地址即可。

  • 接入协议

选择MQTT协议

  • 数据协议

OneJSON、透传/自定义格式:需定义物模型来进行功能定义,使用物模型功能点来组织设备数据上下行。

数据流格式:需定义数据流模板来进行功能定义,使用数据流与数据点来组织设备数据上下行。选择OneJSON比较规范一些。

  • 联网方式:WIFI

  • 开发方案:自定义方案

onenet-5.jpg

3、创建设备

产品创建完以后,在创建的产品下面,我们需要创建设备。后续单片机给云平台发送数据,就是发送到这个创建的设备上面。打开设备管理界面,点击创建设备。创建设备时所属产品要选择之前创建的产品,设备名称可以任意。点击确认,完成设备的创建。

onenet-6.jpg

4、创建物模型

设备创建完毕以后,还需要创建物模型,物模型是对设备的数字化抽象描述。简单来说就是设备的数据点在云平台上就是以物模型的形式展示的。比如温湿度采集系统,我们需要将单片机采集到的温度和湿度上传至云平台,创建对应的物模型,就可以显示上传的具体的温度和湿度数据。

在产品开发界面,选择自己创建好的产品,可以看到右侧有设置物模型,由于我们这个温湿度采集系统主要是将温度和湿度数据上传至小程序,并且能够通过小程序远程控制硬件上的蜂鸣器和报警LED灯。所以针对这个系统,我们需要创建4个数据点,后续如果大家想做其它的系统,可以添加更多的数据点。

onenet-10.jpg

5、关键参数

设备与云平台之间的连接是通过MQTT协议进行连接的,并且在接入Onenet云平台的时候,需要进行身份验证才能接入,身份验证是通过token进行验证的,这个token可以理解为是OneNET平台专属的一中安全验证方式,所以在后续硬件设备连接到云平台的时候,我们需要得到对应的token。

计算token我们只需要使用云平台提供的token工具就可以,在相应的地方填上对应的参数,即可生成设备的token。这是我创建的设备计算出来的token,大家根据自己创建的设备,需要修改对应的参数。

计算token的工具大家可以直接在Onenet云平台上下载,也可以直接下载我们教程资料,我把课程中使用到的一些工具都放在里面。

onenet-11.jpg

计算token的各个参数含义如下:

名称

参数说明

例子

res

products/{产品id}/devices/{设备名字}

products/yHtNI9iijM/devices/Test1

et

时间戳,该时间戳需要大于目前设备连接时的时间戳

2026-07-29 21:12:32的时间戳:1785330752

key

设备密钥

在创建的设备信息里面可获得:RkZJSVU3MHNFRTltQ3N4Y0J5Q1JpTUpwUzA4aWdHVW8=

method

签名方法

选择默认md5即可

version

参数组版本号,日期格式,目前仅支持"2018-10-31"

2018-10-31

3、MQTT.fx使用

MQTT.fx 是一款专为 MQTT 协议设计的图形化调试工具,主要用于物联网开发。我们可以将MQTT.fx当作我们的设备端,先通过MQTT.fx学习如何通过MQTT协议连接到云平台,并且和云平台之间完成数据的交互。学习完这个流程以后,单片机硬件与云平台之间的数据交互和使用这个工具是一样的,不同的是我们需要把这些流程通过代码的方式实现在单片机上实现与云平台之间的数据交互。

我们先使用这个工具模拟一下和Onenet云平台的通信,大家也可以跳过这章学习,直接看单片机代码设计部分。这部分讲解主要是为了让大家对如何使用Onenet平台更加的了解。软件我也会放在资料里面,大家可以直接安装使用。

1、连接云平台

点击软件上齿轮,创建新的连接设备。

MQTT1.jpg

将打开的窗口按照如下进行填写,红圈圈起来的需要修改该成自己的设备名称、产品id和token。其它的参数保持一致即可。点击Apply,回到软件的主界面。

  • Client ID:设备名称

  • User Name:产品id

  • Password:token

MQTT2.jpg

点击Connect以后,右侧会出现小绿灯的标准,说明连接成功。如果出现小红灯,则说明没有连接成功,看是否是哪些参数没有设置正确。

MQTT3.jpg

MQTT连接成功以后,我们去Onenet云平台,可以看到在Onenet平台上创建的设备显示在线。此时表明我们已经使用MQTT.FX工具成功的连接到了Onenet平台。

MQTT4.jpg

2、属性上报

设备与云平台之间的交互是通过MQTT协议进行的,MQTT协议中有个比较重要的东西叫做Topic(主题),设备和云平台之间的数据的交互就是通过订阅相关的主题进行的。

MQTT5.jpg

在产品开发的界面下面,可以看到有很多的物模型Topic,不同的Topic有不同的功能和权限。

比如$sys/yHtNI9iijM/{device-name}/thing/property/post。这个topic的功能就是设备向云平台发送数据,它的权限是“发布”,这个权限是针对硬件设备来说的,比如想通过硬件发送给数据给云平台,那么这个topic的权限一定是“发布”,如果是硬件想接收云平台发送过来的数据,那么这个权限就是“订阅”。

通过MQTT.FX,订阅$sys/yHtNI9iijM/{device-name}/thing/property/post该topic,我们就可以将设备采集到的数据上传到云平台,比如我们将温度数据上传至云平台,只需要将对应的数据通过该topic发送至云平台即可。点击Publish,就可以成功将数据上传至Onenet平台创建的设备上面。

MQTT6.jpg

我们可以看到窗口里面发送的数据不仅仅有温度数据,还有id、params这些参数。大家一定要按照这个规范来。这些参数都是OneJson数据格式。

id:消息id号,用户自定义,String类型的数字,长度限制不超过13位。这个大家可以随便填写,主要是为了标识这条数据。

params:具体需要传输的数据,比如想给云平台上传温度数据,首先在云平台上设备创建了温度的物模型,那我们温度的标识是Temp,所以我们给温度赋值40,上传到Onenet云平台就是如下格式。

{
  "id": "123",
  "params": {
    "Temp": {
      "value": 40
    }
  }
}

如果同时还想上传湿度数据,我们找到定义物模型适合的湿度所对应的标识

{
  "id": "123",
  "params": {
    "Temp": {
      "value":40
    },
    "Hum":{
      "value":20
    }
  }
}

我们一开始在Onenet平台一共创建了4个物模型,除了温度和湿度,还有蜂鸣器和LED状态。如果我们想把蜂鸣器的状态也上传到Onenet云平台,类似温度和湿度数据一样。需要注意的是,我们在创建蜂鸣器和LED物模型的时候,使用的布尔类型,所以只能发送true和false。

{
  "id": "123",
  "params": {
    "Temp": {
      "value":40
    },
    "Hum":{
      "value":20
    },
    "Alarm":{
      "value": true
    }
  }
}

发送完以后,我们可以在Onenet云平台上看到设备下面的四个属性,发现上面已经是我们刚刚使用MQTT.fx工具发送的数值。说明我们刚刚已经发送成功了。如果大家点击发送以后,Onenet云平台上的数据没有变化,大家重点检测一下自己发送数据的格式和参数是否正确。

我们已经通过MQTT.FX知道如何向Onenet平台发送数据,只要连接上Onenet平台,并且向刚刚上述所说的Topic主题发送Json数据就完成了数据的发送。那聪明的小伙伴一定也知道,如果我们将单片机替换成MQTT.FX工具,是不是这个流程也是一模一样的呢?只不过是将刚刚哪些在MQTT.FX软件上的操作,转换为代码的方式让单片机去操作!

2、数据接收

在上面我们讲了如何通过MQTT.FX工具向Onenet云平台发送数据。接下来我们再讲一下MQTT.FX如何接收到Onenet平台发送过来的数据。

在上述那么多topic中,有一个topic的功能是设置直连设备属性,这个topic的功能就是云平台可以下发相关的数据给设备,设备只需要订阅这个topic,就可以拿到云平台下发的数据。$sys/yHtNI9iijM/{device-name}/thing/property/set

MQTT8.jpg

使用MQTT.FX工具订阅该主题,我们先使用云平台上面的模拟器模拟APP给设备发送数据,可以看到,当我们通过应用模拟器给设备发送温度数据时,只要MQTT.FX工具订阅了这个主题,那么就会接收到模拟器发送过来的数据。

MQTT12.jpg

我们可以在创建的设备下面找到这个设备调试,上面有个应用模拟器,通过这个应用模拟器,我们可以直接给设备下发数据。我们选择蜂鸣器,选择发送数据true,可以看到调试日志上面显示发送一串数据。此时如果MQTT.fx订阅了刚刚那个Topic,则就可以接收到Onenet平台发送的这个数据。

MQTT13.jpg

上述操作无论是MQTT.FX向云平台发送数据还是接收云平台发送的数据,都是通过使用相关的主题进行交互的。从而完成MQTT工具与云平台之间的数据交互。其实后续我们使用单片机和云平台之间的交互,方法和流程是一模一样的,只需要将上述的操作写到程序里面,让单片机通过程序自动去连接相应的主题,发送数据和接收数据即可。

4、工程代码框架

1、打开工程

开发STM32单片机程序,必不可少的工具就是keil,该工具如果大家没有的话,可以自行去网上下载安装一下。网上针对安装该软件的视频教程特别的多,安装完成以后,就可以查看和编译代码工程了。

接下来介绍一下我们这个系统硬件工程代码一个整体框架,让大家先能够大致明白工程中的每个文件是用来干什么的,后续我们应该修改哪些文件去实现相应的功能。keil软件安装完成以后,就可以进入代码工程的Project文件夹下,可以看到有个总的工程文件,打开该文件就可以看到我们的代码了。

代码介绍1.JPG

2、工程文件介绍

打开工程以后,我们可以看到工程左侧有很多文件,先简单介绍一下这些文件都是用来干什么的。

代码介绍2.jpg

  • STARTUP: 该目录下只有一个文件,作为单片机启动文件,该文件是由汇编语言编写,是上电之后执行的第一个程序。主要是初始化堆栈指针等内容,然后调用C语言main函数。

  • CMSIS:该目录下的文件主要是实现了操作内核外设寄存器的一些函数,我们其实很少用到,不需要修改。

  • FWLB:里面放的是ST 公司针对每个 STM32 外设而编写的库函数文件。我们调用的库函数的实现就是在这些文件中编写的。

  • Main:最主要的就是main.c文件,后续代码执行包括功能的实现,都是在这个main.c文件里进行的。

  • BSP:基本的功能代码,单片机的一些驱动代码,比如串口、定时器模块、按键、等功能的实现都放在这个文件夹下面。

  • APP:主要是一些传感器模块的驱动代码,比如温湿度传感器模块的代码、蜂鸣器功能的代码都在这个文件夹下。

  • Wifi:单片机与Onenet平台进行数据交互的实现都在这个目录中的文件中实现。

将不同功能的.c文件放在不同的目录下仅仅是为了整个工程框架看起来更加的简洁和清晰,比如说将实现与云平台交互的.c文件都放在Wifi目录下,大家一看就知道这个目录下的.c文件是和网络相关的。其实不同的.c文件也都可以全部都放在一个目录下,对其功能不影响,大家理解即可。

文件:

上面主要介绍了每个文件夹下都存放了什么样的文件。接下来重点介绍一下BSP、APP、Wifi目录下的文件。这些文件是我们实现功能的基础。

BSP:

bsp_delay.c: 延迟函数实现文件,这个里面通过定时器的方式实现ms级的延迟。比如你想实现LED灯一秒亮一下,就可以通过这个函数去实现。

bsp_key.c: 按键扫描实现,我们板子上有三个按键,如何判断按键是否被按下,就是在这个文件里面实现的相关函数。

bsp_timer: 定时器实现文件,定时器功能在单片机中比较重要,通过定时器我们可以去做很多的事情。比如说你想每隔1S实现一个变量Value加1,就可以通过定时器去定时,每过1s,首先Value加1。

bsp_usart.c: 串口功能实现,单片机和wifi模块之间的通信就需要使用到串口功能。

bsp_led.c: LED灯驱动文件。

APP:

1、bsp_dht11.c:DHT11温湿度传感器驱动文件。

DHT11温湿度传感器可以同时检测温度和湿度。它与单片机直接的交互协议是单总线协议,通过一根线单片机就可以和DHT11进行数据交流。DHT11上电之后就会自动检测环境中的温度和湿度,单片机只需要通过通信协议从DHT11获取温湿度的数据即可。

2、bsp_oled.c:OLED显示屏驱动函数。

该模块与单片机的通讯为IIC协议。模拟IIC指的就是用单片机引脚高低电平的变化,来模拟IIC的时序信号传送数据。硬件IIC则可以直接配置单片机相应的寄存器自动产生通讯信号,我们代码中主要采用了模拟IIC的方式来驱动OLED。单片机与OLED显示屏之间通过IIC协议进行数据的交互,从而正确的显示单片机发送给显示屏的数据。

具体关于该OLED显示屏的介绍,可以参考OELD显示模块 | 江航电子

3、bsp_Alarm.c:声光报警功能的实现。

该文件主要功能是实现驱动蜂鸣器和LED灯,实现声光报警的功能。

Wifi:

1、esp8266.c:该文件实现wifi模块的初始化,单片机和wifi模块的数据交互功能。

2、MqttKit.c: MQTT协议实现函数,单片机与Onenet平台之间是通过MQTT协议进行交互的,所以该文件主要是关于MQTT协议的实现。一般不用去修改它,只需要调用其相关的功能函数即可。

3、onenet.c:单片机与Onenet平台之间的数据交换功能实现,比如实现单片机订阅主题,和发送数据到平台,相关的功能函数都是在该文件实现的。

4、cJSON.c: Json格式转换文件,之前有说过单片机与云平台之间数据交换的格式为Json格式,那这个地方就是对数据的一个解析,或者说可以将接收到数据转换为JSON格式进行处理。

大家需要注意的是,在单片机开发的过程中。比如想实现一个模块的功能,这个模块的驱动文件一般有两个,分别为xx.c和xx.h文件,.c文件主要是函数功能的实现,.h文件则主要是变量的定义等。通过 ​.H 和 .C 文件的分离,开发者能够构建清晰、模块化的代码结构,显著提升开发效率和系统可靠性。这一设计模式是嵌入式开发中应对复杂性和可维护性挑战的核心方法论。

代码工程整体上比较简洁,如果后续大家想在这个工程的基础上去实现其它的功能。比如说想使用光敏电阻传感器去采集环境亮度。只需要将光敏电阻传感器实现的驱动文件(一般都有.C和.H文件)添加到我们的工程。然后在main.c函数里面去调用相关的功能函数即可。如果这部分大家不是很了解的话。建议是可以先花费几个小时学一下单片机的基础课程,比如说(野火、正点原子等)。B站上也有相关的视频,但是我感觉视频内容实在是太多了,我一开始学习的时候也没有耐心完全看完看懂,只是看个大概就行吗,大家不需要完全学会。只需要将一些入门的章节学习一下即可。

5、数据的采集显示

本章要实现的功能:

  • 单片机能够采集到温度和湿度数据,并且将温度数据和湿度数据通过OLED灯展示出来。

main.c文件是单片机运行的主文件,单片机上电以后初始化完,就会去执行main函数,所以我们后续想要实现的所有功能,都需要在main函数中去调用,单片机才会正确的调用。

具体代码如下,相关讲解请参考B站视频上对应的章节。

#include <stdio.h>
#include "stm32f10x.h"
#include "bsp_delay.h"
#include "bsp_key.h"
#include "bsp_oled.h"
#include "bsp_dht11.h"
#include "bsp_Alarm.h"
#include "bsp_usart.h"
#include "esp8266.h"
#include "onenet.h"
DHT11_Data_TypeDef DHT11_Data;
char oled_Temp[16];
char oled_Hum[16];
/*
---------------------------------------------------------------------------------------------------------
*	函 数 名: Bsp_init
*	功能说明: 各个模块的初始化函数(串口、OELD、按键等)
*	参    数:无
*	返 回 值: 无
---------------------------------------------------------------------------------------------------------
*/
void Bsp_init()
{
        Delay_Init();
        Key_Init(); 
        OLED_Init();    
        DHT11_Init();
        Alarm_Init();    
        LED_Init();    
        Usart_Init(); //串口初始化
}

void Oled_Show()
{
		OLED_ShowCH(5,0,"温湿度检测系统");
		if(DHT11_Read_TempAndHumidity(&DHT11_Data) == 1)	
		{
  				sprintf(oled_Temp,"Temp:%d.%d℃", DHT11_Data.temp_int,DHT11_Data.temp_deci);
  				OLED_ShowCH(20,3,(u8*)oled_Temp);
  				sprintf(oled_Hum,"Hum:%d%%", DHT11_Data.humi_int);
  				OLED_ShowCH(20,5,(u8*)oled_Hum);
		}	
}

int main()
{
		Bsp_init();
		while(1)
		{
				Oled_Show();
		}
}

6、阈值设置与界面切换

本章实现的功能:

1、通过按键可以实现OLED界面的切换,该系统中设计了三个界面。分别是主界面、温度阈值调节界面、湿度阈值调节界面。可以通过按键去让OLED显示屏显示不同的界面,并且可以调节温度和湿度的阈值。当温度或者湿度数据大于阈值的时候,会触发声光报警功能。

界面1:主界面

主界面.jpg

界面2:温度阈值设置界面

温度.jpg

界面3:湿度阈值设置界面

我们板子上一共使用了3个按键,按键1用来切换界面。按键2是增加阈值,按键3是减少阈值。通过这三个按键,就可以实现温湿度阈值的设置和界面的切换功能。

#include <stdio.h>
#include "stm32f10x.h"
#include "bsp_delay.h"
#include "bsp_key.h"
#include "bsp_oled.h"
#include "bsp_dht11.h"
#include "bsp_Alarm.h"
#include "bsp_led.h"
#include "bsp_usart.h"
#include "esp8266.h"
#include "onenet.h"

DHT11_Data_TypeDef DHT11_Data;
char oled_Temp[16],oled_TempThr[16];
char oled_Hum[16],oled_HumThr[16];
uint8_t key_value = 0;
uint8_t Temp_Thr = 30;
uint8_t Hum_Thr = 60;

typedef enum{
		
	MAIN_MENU, //主菜单界面
	TEMP_SET,  //温度阈值设置界面
	HUM_SET,   //湿度阈值设置界面
}DisplayState;


DisplayState currentState = MAIN_MENU;

void Bsp_init()
{
		DHT11_Init();
		OLED_Init();
		Delay_Init();
		Key_Init();
		Alarm_Init();
}

void Oled_show()
{
		 OLED_ShowCH(5,0,"温湿度采集系统");
	
		 if(DHT11_Read_TempAndHumidity(&DHT11_Data) == 1)
		 {
			 sprintf(oled_Temp,"Temp:%d.%d",DHT11_Data.temp_int,DHT11_Data.temp_deci);
			 OLED_ShowCH(20,3,(char*)oled_Temp);
			 sprintf(oled_Hum,"Hum:%d%%",DHT11_Data.humi_int);
			 OLED_ShowCH(20,5,(char*)oled_Hum);			
		 }
		
}
void Oled_show1()
{
		OLED_ShowCH(30,0,"温度阈值");
		if(key_value == 2)
		{
				if(Temp_Thr < 100)
				{
					Temp_Thr++;
				}
		}
		else if(key_value == 3)
		{
				if(Temp_Thr > 0)
				{
					Temp_Thr--;
				}			
		}
		sprintf(oled_TempThr,"Temp:%d",Temp_Thr);
		OLED_ShowCH(30,4,(u8*)oled_TempThr);		
}

void Oled_show2()
{
		OLED_ShowCH(30,0,"湿度阈值");
		if(key_value == 2)
		{
				if(Hum_Thr < 100)
				{
					Hum_Thr++;
				}
		}
		else if(key_value == 3)
		{
				if(Hum_Thr > 0)
				{
					Hum_Thr--;
				}			
		}
		sprintf(oled_HumThr,"Hum:%d",Hum_Thr);
		OLED_ShowCH(30,4,(u8*)oled_HumThr);		
}

void Oled_Switch()
{
	 key_value = Key_Scan(0);
	 if(key_value == 1)
	 {
			currentState = (currentState + 1) % 3;	
			OLED_Clear();
	 }

	 switch(currentState)
	 {
			case MAIN_MENU:
					Oled_show();
					break;
			case TEMP_SET:
					Oled_show1();
					break;
			case HUM_SET:	
					Oled_show2();
					break; 
	 }
}

void Alarm_Statue()
{
		if(DHT11_Data.temp_int > Temp_Thr || DHT11_Data.humi_int > Hum_Thr)
		{
            Alarm_ON();
		}
		else
		{
            Alarm_OFF();
		}
	
}
int main()
{
		Bsp_init();
		while(1)
		{
			Oled_Switch();
			Alarm_Statue();
		}
}

7、设备连接云平台

在前面的学习过程中,我们通过MQTT.fx工具已经实现了将数据上传到Onenet平台和接收Onenet平台发送过来的数据,接下来我们需要将单片机替换MQTT.fx工具,实现通过单片机向Onenet平台发送数据和接收Onenet平台发送过来的数据。

1、函数介绍

  • esp8266.c文件

函数名

功能

ESP8266_Clear

清空接收到的缓存

ESP8266_WaitRecive

等待接收数据完成

ESP8266_SendCmd

发送命令

ESP8266_SendData

发送数据

ESP8266_GetIPD

获取平台返回的数据

ESP8266_Init

初始化ESP8266

USART2_IRQHandler

串口2接收中断函数

  • onenet.c

OneNet_DevLink

连接Onenet平台

OneNet_Subscribe

设备订阅主题

OneNet_Publish

设备发布主题

OneNet_RevPro

检测平台返回的数据进行解析。

  • MqttKit.c

Mqttkit.c文件里面的函数都是涉及到MQTT协议的的处理函数,单片机采集到的温湿度数据是不能够直接上发给Onenet平台,需要将这些数据封装成MQtt协议包、有包头、数据长度、有效数据等等。这部分内容可以先不去了解,不影响系统整体功能的实现。如果大家对这部分内容感兴趣,可以先学一下MQTT协议具体的组成,然后回过头来看这些代码,就知道每个函数的作用和功能是干什么的。

  • cJSON.c

单片机与Onenet云平台之间的交互数据格式是Json格式,这个文件里面我们主要使用到的功能就是解析从 OneNet 平台下发的JSON字符串中去提取关键字段并触发相应操作。

2、参数配置

  • esp8266.c文件

修改wifi账号密码和Onenet服务器地址。

ESP8266_WIFI_INFO:填入需要连接的wifi的账号和密码。

ESP8266_ONENET_INFO:Onenet平台MQtt服务器地址。默认mqtts.heclouds.com:1883。不用修改。

连接1.jpg

  • onenet.c文件

PROID:产品ID,在Onenet云平台创建完产品以后,信息里面会有该值。

TOKEN:鉴权信息,通过token工具生成。在这个工具上面填入信息。生成token。关于res、et这些数据怎么填写,前面已经描述过,此处不再描述。

DEVID:设备名称。

连接2.jpg

3、代码编写

#include <stdio.h>
#include "stm32f10x.h"
#include "bsp_delay.h"
#include "bsp_key.h"
#include "bsp_oled.h"
#include "bsp_dht11.h"
#include "bsp_Alarm.h"
#include "bsp_led.h"
#include "bsp_usart.h"
#include "esp8266.h"
#include "onenet.h"

DHT11_Data_TypeDef DHT11_Data;
char oled_Temp[16],oled_TempThr[16];
char oled_Hum[16],oled_HumThr[16];
uint8_t key_value = 0;
uint8_t Temp_Thr = 30;
uint8_t Hum_Thr = 60;

typedef enum{
		
	MAIN_MENU, //主菜单界面
	TEMP_SET,  //温度阈值设置界面
	HUM_SET,   //湿度阈值设置界面
}DisplayState;


DisplayState currentState = MAIN_MENU;

/*
---------------------------------------------------------------------------------------------------------
*	函 数 名: Bsp_init
*	功能说明: 各个模块的初始化函数(串口、OELD、按键等)
*	参    数:无
*	返 回 值: 无
---------------------------------------------------------------------------------------------------------
*/
void Bsp_init()
{
		DHT11_Init();
		OLED_Init();
		Delay_Init();
		Key_Init();
		Alarm_Init();
		Usart_Init();
		LED_Init();
}
/*
---------------------------------------------------------------------------------------------------------
*	函 数 名:  Oled_Show
*	功能说明: OLED显示屏显示温湿度数据信息
*	参    数:无
*	返 回 值: 无
---------------------------------------------------------------------------------------------------------
*/

void Oled_show()
{
		 OLED_ShowCH(5,0,"温湿度采集系统");
	
		 if(DHT11_Read_TempAndHumidity(&DHT11_Data) == 1)
		 {
			 sprintf(oled_Temp,"Temp:%d.%d",DHT11_Data.temp_int,DHT11_Data.temp_deci);
			 OLED_ShowCH(20,3,(char*)oled_Temp);
			 sprintf(oled_Hum,"Hum:%d%%",DHT11_Data.humi_int);
			 OLED_ShowCH(20,5,(char*)oled_Hum);			
		 }
		
}
/*
---------------------------------------------------------------------------------------------------------
*	函 数 名:  Oled_Show1
*	功能说明: OLED显示屏显示设置温度阈值界面
*	参    数:无
*	返 回 值: 无
---------------------------------------------------------------------------------------------------------
*/
void Oled_show1()
{
		OLED_ShowCH(30,0,"温度阈值");
		if(key_value == 2)
		{
				if(Temp_Thr < 100)
				{
					Temp_Thr++;
				}
		}
		else if(key_value == 3)
		{
				if(Temp_Thr > 0)
				{
					Temp_Thr--;
				}			
		}
		sprintf(oled_TempThr,"Temp:%d",Temp_Thr);
		OLED_ShowCH(30,4,(u8*)oled_TempThr);		
}
/*
---------------------------------------------------------------------------------------------------------
*	函 数 名:  Oled_Show2
*	功能说明: OLED显示屏显示设置湿度阈值界面
*	参    数:无
*	返 回 值: 无
---------------------------------------------------------------------------------------------------------
*/
void Oled_show2()
{
		OLED_ShowCH(30,0,"湿度阈值");
		if(key_value == 2)
		{
				if(Hum_Thr < 100)
				{
					Hum_Thr++;
				}
		}
		else if(key_value == 3)
		{
				if(Hum_Thr > 0)
				{
					Hum_Thr--;
				}			
		}
		sprintf(oled_HumThr,"Hum:%d",Hum_Thr);
		OLED_ShowCH(30,4,(u8*)oled_HumThr);		
}

/*
---------------------------------------------------------------------------------------------------------
*	函 数 名: Oled_Switch()
*	功能说明: OLED界面切换函数
*	参    数:无
*	返 回 值: 无
---------------------------------------------------------------------------------------------------------
*/
void Oled_Switch()
{
	 key_value = Key_Scan(0);
	 if(key_value == 1)
	 {
			currentState = (currentState + 1) % 3;	
			OLED_Clear();
	 }

	 switch(currentState)
	 {
			case MAIN_MENU:
					Oled_show();
					break;
			case TEMP_SET:
					Oled_show1();
					break;
			case HUM_SET:	
					Oled_show2();
					break; 
	 }
}

void Alarm_Statue()
{
		if(DHT11_Data.temp_int > Temp_Thr || DHT11_Data.humi_int > Hum_Thr)
		{
				Alarm_ON();
		}
		else
		{
				Alarm_OFF();
		}
	
}

int main()
{
		Bsp_init();
		OLED_ShowCH(30,4,"网络连接中...");	
		ESP8266_Init();
		while(OneNet_DevLink())//连接Onenet平台,如果失败等待500ms继续尝试。
		{
			DelayXms(500);
		}
		OLED_Clear();		
		OLED_ShowCH(30,4,"连接成功");
		DelayXms(3000);
		OLED_Clear();					
		while(1)
		{
				Oled_Switch();
				Alarm_Statue();
			
		}
}

连接云平台主要调用到的函数为OneNet_DevLink(),这个函数的实现也是比较简单的。主要功能是:将我们前面更改的产品id、鉴权token、设备名称等信息封装成MQTT协议包。向Onenet平台上创建的设备发送连接请求。如果产品id、鉴权token、设备名称这些参数正确的话,Onenet就会返回连接成功的响应。此时就已经完成了单片机硬件与Onenet平台上的设备的连接。

8、数据上发云平台

1、数据转换

在连接到云平台的设备以后,要想将采集到的温湿度等数据发送给Onenet平台,首先需要做一层封装。在Onenet平台创建设备的时候,我们创建的是JSON数据格式交互的设备。因此我们需要将采集到的温湿度数据封装成JSON格式发送给Onenet平台。新建一个函数JsonValue(),将需要上报的数据在这个函数里实现JSON格式的封装。

下面这段代码就是将温度和湿度数据封装成JSON格式,放在PUBLIS_BUF数组中,然后将PUBLIS_BUF数组中的内容发送给云平台。

void JsonValue()
{
	uint8_t Temp = DHT11_Data.temp_int;
	uint8_t Hum = DHT11_Data.humi_int;
	
	memset(PUBLIS_BUF, 0, sizeof(PUBLIS_BUF));
	
	sprintf(PUBLIS_BUF,"{\"id\":\"123\",\"params\":{\"Temp\":{\"value\":%d},\"Hum\":{\"value\":%d} }}",
					DHT11_Data.temp_int,DHT11_Data.humi_int);	
}

其中,PUSBLIS_BUF里面存储的数据格式转换为JSON格式如下面所示。


{
  "id": "123",
  "params": {
    "Temp": {
      "value": 25
    },
    "Hum": {
      "value": 60
    }
  }
}

上述我们只是将温度和湿度数据封装成JSON格式发送给Onenet平台,大家可以参考上面的方法上传更多的数据。比如在我们创建设备的时候还创建了Alarm和Led物模型,因此这两个数据也可以封装成Json格式发送给Onenet平台。由于在我们这个系统中,是通过微信小程序远程控制单片机进行报警和开关LED,所以不需要单片机将这两个数据上报给Onenet云平台。

如果想上传Alarm和LED灯的状态,只需要在sprint函数里面进行封装即可。可以看到由于Alarm和LED在创建的时候是布尔类型,所以赋值给它们的只能是”false“和”true“。否则云平台识别不到该数据。

	
	sprintf(PUBLIS_BUF,"{\"id\":\"123\",\"params\":{\"Alarm\":{\"value\":%s},\"Temp\":{\"value\":%d},\"Led\":{\"value\":%s},\"Hum\":{\"value\":%d}}}","false",DHT11_Data.temp_int,"true",DHT11_Data.humi_int);	

2、发布主题

数据封装完以后,就可以将JSON格式的数据上传到云平台,关键代码如下,将下述代码放在main函数里面执行即可。这段代码的含义就是每5s调用一次OneNet_Publish(devPubTopic, PUBLIS_BUF)函数,将PUBLIS_BUF数据发送到云平台,其中devPubTopic值的是Topic主题。单片机向这个主题发送数据,Onenet平台才能够接收到数据并在物模型界面显示出来。因此我们需要对devPubTopic进行宏定义。这个操作在代码里初始化变量的时候就已经赋值好了。

const char devPubTopic[] = "$sys/yHtNI9iijM/Test1/thing/property/post"; (注:yHtNI9iijM是产品ID,Test1是设备名称)如果是连接自己创建的Onenet平台,需要将这两个参数替换成自己的即可。

				if(++TimeCount >= 100)
				{
					JsonValue();
					OneNet_Publish(devPubTopic, PUBLIS_BUF);
					ESP8266_Clear();
					TimeCount = 0;
					DelayXms(50);	
				}					
		

完成上述操作,将代码下载到单片机上,就可以使单片机连接到Onenet平台,并且在Onenet平台上设备里面可以看到单片机上传到的数据。此刻即完成了单片机将数据上传至云平台的操作。

9、设备接收数据

在前面已经实现了单片机连接到Onenet云平台和向Onenet云平台发送数据。接下来需要实现的功能就是接收Onenet平台发送过来的数据。我们最终实现的效果是通过微信小程序发送数据控制单片机,但实际上微信小程序是先将数据发送给Onenet云平台,然后云平台内部会进行流转,再将数据发送给单片机。

1、订阅主题

设备接收数据和设备发送数据类似,由于信息的交互是使用的MQTT协议,所以都是通过Topic主题进行数据交互的。在设备发送给Onenet云平台的时候我们是向一个主题发送数据,那接收Onenet平台发送过来的数据,我们就要订阅一个主题。

const char *devSubTopic[] = {"$sys/yHtNI9iijM/Test1/thing/property/set"};

同样如果大家用的自己的Onenet平台设备。需要将yHtNI9iijM和Test1替换成自己创建的产品ID和设备名称。

int main()
{
		Bsp_init();
		OLED_ShowCH(30,2,"网络连接中...");	
		ESP8266_Init();
		while(OneNet_DevLink())//连接Onenet平台,如果失败等待500ms继续尝试。
		{
			DelayXms(500);
		}
		OLED_Clear();		
		OLED_ShowCH(30,2,"连接成功");
		DelayXms(3000);
		OLED_Clear();
		/*订阅Topic主题*/
		OneNet_Subscribe(devSubTopic, 1);				
		while(1)
		{
				Oled_Switch();
				Alarm_Statue();
			
				if(++TimeCount >= 100)/*Oled_Switch大概需要耗时10ms,每5s进一次这个逻辑*/
				{
					JsonValue();
					OneNet_Publish(devPubTopic, PUBLIS_BUF);
					ESP8266_Clear();
					TimeCount = 0;
				}				
				
				dataPtr = ESP8266_GetIPD(2);
				if(dataPtr != NULL)
					OneNet_RevPro(dataPtr);						
			
		}
}

上述代码中,通过OneNet_Subscribe(devSubTopic, 1);接口就实现了订阅一个主题,订阅成功以后,在while循环里面会不断的检索有没有收到Onenet平台发送过来的数据包。如果收到数据包,则dataPtr就不为NULL,就会执行OneNet_RevPro(dataPtr); 函数。

2、解析数据

单片机接收到Onenet云平台发送过来的数据,需要对其进行解析才能够知道发送过来的命令到底是什么,OneNet_RevPro(dataPtr)函数实现的数据的解析,所以需要重点理解一下OneNet_RevPro(dataPtr)函数的功能和作用。

OneNet_RevPro(dataPtr);函数里面实现的功能也比较简单,就是将接收到的数据包进行解析。Onenet平台会根据不同的操作发送给单片机不同命令的数据包。Onenet平台发送给单片机发送数据时,MQTT控制报文类型为MQTT_PKT_PUBLISH。因此当我们通过Onenet平台发送给单片机数据时,单片机解析到的数据包为MQTT_PKT_PUBLISH类型。

接收数据1.jpg

在OneNet_RevPro(dataPtr)接口中的MQTT_PKT_PUBLISH下,总体的代码流程为:

MQTT数据包接收 → 解析PUBLISH报文 → 提取JSON数据 → 解析JSON → 控制外设 → 释放内存

		case MQTT_PKT_PUBLISH:														//接收的Publish消息
		
			result = MQTT_UnPacketPublish(cmd, &cmdid_topic, &topic_len, &req_payload, &req_len, &qos, &pkt_id);
			if(result == 0)
			{
				UsartPrintf(USART_DEBUG, "topic: %s, topic_len: %d, payload: %s, payload_len: %d\r\n",
																	cmdid_topic, topic_len, req_payload, req_len);
				
				// 对数据包req_payload进行JSON格式解析
				json = cJSON_Parse(req_payload);
				params_json = cJSON_GetObjectItem(json,"params");
				led_json = cJSON_GetObjectItem(params_json,"LED");
				Alarm_json = cJSON_GetObjectItem(params_json,"Alarm");
				if(led_json != NULL)
				{
					if(led_json->type == cJSON_True) 
					{
							LED_ON();
					}
					else 
					{
							LED_OFF();
					}
				}	
				if(Alarm_json != NULL)
				{
					if(Alarm_json->type == cJSON_True) 
					{
							Alarm_flag = 1;
							UsartPrintf(USART_DEBUG, "Alarm_flag = 1\r\n");									
					}
					else 
					{
							Alarm_flag = 0;
							UsartPrintf(USART_DEBUG, "Alarm_flag = 0\r\n");					
					}
				}	
				cJSON_Delete(json);
			}

JSON格式下成员变量。

typedef struct cJSON {
    struct cJSON *next;       // 同一层级的**下一个节点**(兄弟节点,用于链表遍历)
    struct cJSON *prev;       // 同一层级的**上一个节点**(兄弟节点)
    struct cJSON *child;      // 子节点(若当前节点是「对象 {}」或「数组 []」,则指向第一个子节点;否则为 NULL)
    int type;                 // 节点的**数据类型**(核心!标记是布尔、数值、字符串、对象、数组等)
    char *valuestring;        // 若 type 是 cJSON_String,存储**字符串内容**;其他类型通常为 NULL
    int valueint;             // 若 type 是 cJSON_Number 且表示「整数」,存储整数值;否则可能为 0(需注意类型匹配)
    double valuedouble;       // 若 type 是 cJSON_Number(无论整数/浮点数),存储**双精度浮点数值**
    char *string;             // 当前节点的**名称**(仅在 JSON 「对象 {}」中有效,对应键名,如 "LED")
} cJSON;

通过对MQTT数据包的解析,提取出对应的参数,比如说当Onenet想控制LED灯进行开关,Onenet平台上有一个应用模拟器,我们可以通过这个功能模拟手机APP给单片机发送数据。

接收数据2.jpg

当我们想通过云平台给单片机将LED灯打开时,选中LED-true,点击属性设置。可以看到调试日志上面会有数据打印出来,这段数据就会发送给单片机。

接收数据3.jpg 单片机将这个数据进行解析,查到params下有LED参数,然后LED下面的值是true。此时就知道Onenet平台发送的数据就是想控制LED灯打开,那我们代码上就去判断,如果接收到LED下的值为true,就写代码将LED灯打开,否则就关闭。就完成了Onenet平台远程控制单片机。

				// 对数据包req_payload进行JSON格式解析
				json = cJSON_Parse(req_payload);
				params_json = cJSON_GetObjectItem(json,"params");
				led_json = cJSON_GetObjectItem(params_json,"LED");
				if(led_json != NULL)
				{
					if(led_json->type == cJSON_True) 
					{
							LED_ON();
					}
					else 
					{
							LED_OFF();
					}
				}	

至此,我们已经完整的实现了单片机与Onenet平台之间进行数据的交互,能够实现单片机上发数据给Onenet平台和接收Onenet平台发送过来的数据。这个系统的最终目标是实现单片机与微信小程序进行数据的交互,那和微信小程序的交互是如何实现的呢?其实也很简单,只要微信小程序也和Onenet平台之间能够数据交互。那么Onenet平台就可以作为数据的中转站,完成单片机与微信小程序的数据交互。接下来我们就继续学习关于微信小程序的制作和使用以及与单片机之间的数据通信。

10、微信小程序介绍

在本次项目 中我们使用的手机端软件是微信小程序,使用微信小程序和开发手机APP能够实现的功能效果是一致的,但微信小程序的学习和上手要更快一些,开发语言和语法较为简单,能够比较快速的实现物联网系统的功能交互。

大家应该都知道网页的开发使用的开发语言是HTML + CSS + JS, 其中 HTML 是用来描述当前这个页面的结构,CSS 用来描述页面的样子,JS 通常是用来处理这个页面和用户的交互。

同样道理。在小程序中也有同样的角色,其中 WXML 充当的就是类似 HTML 的角色。WXSS充当的是CSS角色,JS与网页开发中的JS也是一样的。所以开发网页和开发微信小程序,开发内容是非常接近的。由于我们是做的物联网系统,APP在这个系统中主要的作用还是和单片机之间进行数据的交互,功能并不是很复杂,只需要写一个简单的界面,实现一些交互逻辑就行。在学习微信小程序的过程中,大家可以多问下AI,甚至直接让AI帮我们生成一个我们想要的界面都是可以的。但是我们还是需要懂一些基本的语法,才能在AI的基础上真正的完成我们想要的功能。

我们项目中微信小程序主要功能是显示单片机采集到的温湿度数据和能够通过按键控制单片机打开报警器或者LED灯,最终的设计界面如下。大家在后续做其它物联网系统的时候,可以自由发挥,设计流程都是一样的,只不过是修改一下界面和功能

为了方便大家更好的学习微信小程序,在这里推荐一些B站上一些比较好的微信小程序开发教程,大家如果有时间可以简单学习一下。当然,大家不必学微信小程序开发过程中太多复杂的内容,只需要将最基本的内容学习一下即可。

视频教程地址:【原创~2025微信小程序~完整项目】最新版微信小程序-零基础到企业级收费项目速成版【本人原创-高清纯享版-其它均为盗录低清版-支持正版-从我做起】_哔哩哔哩_bilibili 。大家只需要将该视频的前21个视频学习一下(大概不到1个小时的内容),开发出我们这个系统中的微信小程序的样子完全不在话下。

微信小程序开发官方参考文档:微信开放文档 / 开发

wx1.jpg

11、小程序工程创建

1、开发工具下载

在开发小程序之前,我们首先需要下载小程序的开发工具。在微信开发官方文档里可以进行软件的下载和安装,微信小程序开发工具有很多的版本,大家下载稳定版本即可。

https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html

wx.jpg

2、代码工程创建

打开微信开发者工具,会显示让你扫码登陆,使用微信扫码登陆。尽量不要使用游客模式进入,游客模式下有一些限制,比较麻烦一些。

wx2.jpg

扫码登陆以后,在小程序选项下,创建新的项目。

wx3.jpg

点击创建小程序以后,会出现下面的界面,需要注意的就是AppID这个选项,大家如果后续想把自己开发的小程序发布上线的话,需要去注册一个小程序账号,每个小程序账号下都会有自己的Appid,填写自己的Appid就可以在完成小程序开发以后,将小程序给发布出去。如果大家主要是开发小程序实现我们的物联网系统功能,直接使用测试号即可。

后端服务:选择不使用云服务。

模板选择:在全部分类下选择不使用模板。使用模板的话,有些模板代码比较多,还得进行删除,我们不使用模板直接开发,方便进行代码的修改。

信息填写完成后,点击创建。

wx4.jpg

wx5.jpg

3、工程目录介绍

微信小程序工程目标比较简洁,我们主要修改的就是pages下面的界面文件。每个文件的作用如下所示。

├── components                  【页面中使用的组件,可以先删除掉】
├── pages                       【页面文件目录】
│   ├── index                   【页面】
│       ├── index.js                【页面JS】
│       ├── index.json              【页面配置】
│       ├── index.wxml              【页面HTML】
│       └── index.wxss              【页面CSS】
├── eslintrc.js                     【语法检测】
├── app.js                      【全局JS】
├── app.json                    【全局配置】
├── app.wxss                    【全局CSS】
├── project.config.json         【开发者工具默认配置】
├── project.private.config.json 【开发者工具用户配置,在这里修改,优先用这个,可以删除】
├── .eslintrc.js                【ESlint语法检查配置】
├── sitemap.json                【微信收录页面】

其中我们主要需要修改的就是项目主配置文件和页面文件,工程中其它文件都不需要修改。

# 1 项目主配置文件,在项目根路径下,控制整个项目的
    -app.js    # 小程序入口文件,小程序启动,会执行这个js
    -app.json  # 小程序的全局配置:顶部的颜色,标题。。。
    -app.wxss  # 小程序全局样式:所有样式,全局生效        
    
# 2 页面文件
    -pages文件夹下,有一个个的文件夹,每个文件夹下有4个文件
    -xx.js     # 页面逻辑,js代码控制
    -xx.wxml   # 页面结构,布局。
    -xx.json   # 页面配置,当前页面顶部颜色,标题。。
    -xx.wxss   # 页面的样式,如果全局样式也有,以当前页面为准
    

在编写我们的正式代码之前,我们需要删除一些文件和代码,方面我们后续的代码编写。

1、将 目录下面的components  和eslintrc.js ,还有最后三个文件都删除掉,下面图片红色圈起来的都可以删除掉。在开发过程中我们暂且用不到。

2、打开app.json文件,将文件中的skyline渲染方式和window配置下的navigationstyle都删掉。如下图红色圈起来的代码。

删除文件和代码之后,就可以开启项目中代码的编写了。

12、界面开发基本语法

小程序界面设计主要是通过修改wxml和wxss文件,在我们这个项目中,不会过多的去展开讲各种语法,如果展开来讲的话内容就太多了。大家如果有需要可以自己找专门的开发微信小程序的视频进行学习,之前我也推荐了一些视频供大家去学习。在学习微信小程序的视频中,大家适当的学习一下就行了,不需要学习太多的语法和内容,学习的知识只要能够满足我们开发这个物联网系统中的小程序即可。那我们物联网系统中的小程序界面是比较简洁的,没有太多花里胡哨的东西,所以学起来还是比较轻松的。

下面主要把一些在我们项目中用到的一些css基本概念给大家简单介绍一下,微信小程序开发中使用到的各种组件或属性,大家可以在官方文档中进行搜索查看。


1、常用组件

在我们本次开发小程序过程中,主要就使用到了3个组件,view、text、image、button。

  • view:视图容器

  • text:文本组件

  • image:图片组件

  • button:按键组件

wx9.jpg

2、盒子模型

在 CSS 中,​盒子模型​​ 是构建所有页面布局的基础概念,它定义了元素如何占据空间以及与其他元素的交互方式。以下是盒子模型的核心解析:

每个组件(view、text)视为一个 ​矩形盒子,由以下四部分构成(从内到外):

  1. 内容区域(Content)​

  • 显示实际内容(文本、图片等),尺寸由 width 和 height 控制。

  1. 内边距(Padding)​

  • 内容与边框之间的空白区域,控制内容与边框的距离。

  • 语法:padding: 10px;(统一值)或 padding: 5px 10px;(上下/左右)。

  1. 边框(Border)​

  • 围绕内边距和内容的边界线,由 border-width、border-style、border-color 定义。

  • 示例:border: 1px solid #000;。

  1. 外边距(Margin)​

  • 元素与其他元素之间的透明空白区域,控制元素间距。

  • 语法:margin: 20px;(统一值)或 margin: 0 auto;(水平居中)

比如说我们在WXML文件中写了一个view组件,这个组件里的内容为一行字。此时该组件显示在微信小程序上面的样式则如下图模拟器中所示。我们如果想修改这个组件的样式,需要在wxss文件中进行修改。在wxss文件修改之前,我们需要给view组件一个class属性,然后在能够在wxss文件中通过这个class进行设置该view组件的样式。可以看到上述图片的右下角,在我们没有设置view组件的样式时,该组件的Padding、border等内容都是0。我们接下来在wxss文件中设置一下这些属性,看会发生什么样的变化。

wx10.jpg

在css属性里面,我们对view组件进行了样式的设定,大家可以看到view组件的盒子模型变成了右下角所示。padding、border、margin都有了数值。至于为什么右下角的大小数值和我们设置的不一样,主要是右下角数值的单位是px,而我们设置属性大小用的是rpx。推荐大家在设置宽高大小的 时候都用rpx。rpx是微信小程序特有的,根据屏幕宽度和长度动态调整,而px是固定像素。对应rpx来说,不管使用的手机屏幕宽多大,都统一为750rpx,如果将组件设置为375rpx,那么这个组件不管在多么宽的屏幕上,都会自适应这个屏幕的一半。

wx11.jpg 关于盒子模型的介绍,大家看完上述视频还是不理解的话,可以看下B站上相关内容的视频。

https://www.bilibili.com/video/BV1As4y1m7hw/?spm_id_from=333.788.videopod.sections&vd_source=f02385984140b818a4cdc72f8e253089


3、flex布局

Flex 布局(Flexible Box Layout,弹性盒布局)是 CSS3 引入的一种现代布局模型,旨在通过灵活的容器和子元素控制,实现高效、自适应的页面排版。其核心是通过 ​主轴(Main Axis)​​ 和 ​交叉轴(Cross Axis)​​ 两套坐标系管理元素排列,适用于响应式设计和复杂布局需求。

属性

作用

常用值

flex-direction

定义主轴方向

row:水平方向

column:垂直方向

flex-wrap

控制是否换行

wrap:换行

nowrap:不换行

justify-content

定义子元素在主轴上的对齐方式

center:居中对齐

space-between:两端对齐

align-items

定义子元素在交叉轴上的对齐方式

center:居中对齐

flex-start:交叉轴的前对齐方式

flex-end:交叉轴的后对齐方式

align-content

多行时交叉轴的对齐方式(需配合

flex-wrap: wrap

space-around

flex-start

关于flex布局,b站上有个up主做的动画视频讲讲的非常的清晰,大家如果想了解的话,大家可以去学习一下。我这里就不展开讲解了。

flex弹性布局 动画详解系列 css科普教程_哔哩哔哩_bilibili

关于微信小程序的界面的制作,我个人认为只要理解了wxss、wxml是干什么的,知道都有哪些组件和怎么配置组件的样式,就可以尝试自己去实现界面了。

13、小程序界面制作

<view class="container">

  <!--第一个气泡:标题气泡-->
  <view class="title_bubble">
    <view class="bubble_text">
      <text class="text1">把握现在,就是创造未来</text>
      <text class="text2">请开启你的学习之旅</text>
    </view>
    <image class="bubble-icon" src="/image/帆船.png"></image>
  </view>

   <!--后四个气泡的容器-->
    <view class="bubble-group-container">
        <!--温度气泡-->
      <view class="function-bubble">
        <image class="icon" src="/image/Temp.png" />
        <view class="data-text">
          <view class="data-title">环境温度</view>
          <view class="data-value">1111</view>
        </view>
      </view>

        <!--湿度气泡-->
      <view class="function-bubble">
        <image class="icon" src="/image/Hum.png" />
        <view class="data-text">
          <view class="data-title">环境湿度</view>
          <view class="data-value">1111</view>
        </view>
      </view>

        <!--报警气泡-->
      <view class="function-bubble">
        <image class="icon" src="/image/Alarm.png" />
        <view class="data-text">
          <view class="data-title">报警器</view>
        </view>
        <switch class="toggle-switch"/>
      </view>

         <!--LED灯气泡-->
      <view class="function-bubble">
        <image class="icon" src="/image/Led.png" />
        <view class="data-text">
          <view class="data-title">LED灯</view>
        </view>
        <switch class="toggle-switch"/>
      </view>     
        
    </view>
</view>
.container{
  display: flex;
  flex-direction: column;
  align-items: center;
}
.bubble-icon{
  width: 120rpx;
  height: 100rpx;
}
.title_bubble{
  width: 600rpx;
  height: 100rx;
  background-color: #f7c21296;
  border-radius: 40rpx;
  padding: 60rpx 20rpx;
  margin: 80rpx;
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.bubble_text{
  display: flex;
  flex-direction: column;
}
.text1{
  font-size: 40rpx;
  font-weight: bold;
  text-indent: 30rpx;
  margin: 10rpx;
}
.text2{
  text-indent: 30rpx;
  margin: 10rpx;
}
.bubble-group-container
{
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
}
.function-bubble{
  width: 300rpx;
  height: 300rpx;
  background: #ffffff;
  border-radius: 30rpx;
  box-shadow: 0rpx 0rpx 30rpx rgba(0, 0, 0, 0.1);
  display: flex;
  flex-direction: column;
  align-items: center;
  margin: 30rpx;
  margin-top: 60rpx;

}
.icon{
  width: 120rpx;
  height: 120rpx;
  margin: 30rpx;
}
.data-text{
  text-align: right;
}
.data-title{
  font-size: 30rpx;
  color: #7f7f7f;
}
.data-value{
  font-size: 40rpx;
  font-weight: bold;
  color: #7f7f7f;
}


14、数据交互基本语法

1、Page函数

在微信小程序中,页面交互的代码写在页面的JS文件中,每个页面都需要通过Page()函数进行注册。需要注意的是,Page()函数只能写在微信小程序每个页面对应的JS文件中,并且每个页面只能注册一个。Page()函数的参数是一个对象,通过该对象可以指定页面初始数据、页面生命周期回调函数和页面事件处理函数。

上述代码在Page()函数的参数中定义了页面初始数据data、页面生命周期回调函数onLoad()和页面事件处理函数onPullDownRefresh()。了解了Page()函数的作用后,下面对该函数中的页面初始数据、页面生命周期回调函数和页面事件处理函数分别进行讲解。

  • 页面初始数据

页面初始数据是指页面第一次渲染时所用到的数据。下面演示如何定义页面初始数据,示例代码如下。

上述代码在data中定义了两个属性,分别是Temp和Hum,这两个属性为页面初始数据。如果说我们界面上有展示Temp和Hum的话,那么这两个值默认分别就是20和30。

  • 页面生命周期回调函数

在微信小程序中,页面的生命周期是指每个页面“加载→渲染→销毁”的过程,每个页面都有生命周期。如果想要在某个特定的时机进行特定的处理,则可以通过页面生命周期回调函数来完成。页面生命周期回调函数用于实现在特定的时间点执行特定的操作,随着页面生命周期的变化,页面生命周期回调函数会自动执行,常见的页面生命周期回调函数如下图所示。

  • 页面事件处理函数

在微信小程序中,用户可能会在页面上进行一些操作,例如上拉、下拉、滚动页面等,如何在发生这些操作的时候进行处理呢?可以通过页面事件处理函数来完成。

2、数据绑定

微信小程序数据的定义是在JS文件中实现的,将数据从页面中分离以后,如何将数据显示到页面中呢?这就需要将JS文件中的数据绑定到页面中。微信小程序提供了Mustache语法(又称为双大括号语法)用于实现数据绑定,可将data中的数据通过Mustache语法输出到页面上。下面来演示如何通过数据绑定将数据显示在页面中。首先打开pages/index/index.js文件,在data中定义Temp和Hum数据,具体代码如下。

Page({
  //页面初始数据
  data:{
    Temp:20,
    Hum:30
  }
})

接下来在pages/index/index.wxml文件中编写页面结构,具体代码如下。可以看到界面上显示了Temp和Hum的数据。这就完成了数据的绑定。

3、事件绑定

在微信小程序中,事件是视图层到逻辑层的通信方式,通过给组件绑定事件,可以监听用户的操作行为,然后在对应的事件处理函数中进行相应的业务处理。例如,为页面中的按钮绑定事件,当用户点击按钮时,就产生了事件。

首先在pages/index/index.wxml文件中为switch组件绑定事件,事件处理函数为show_log()函数,具体代码如下。

然后在js文件中实现该函数的功能,我们让这个函数在控制台打印”111“,大家可以看到,每当我点击一下界面上的开关按键,show_log函数就会执行一次,控制台就会输出字符串”111“。这就完成了事件的绑定操作。

4、this关键字

在微信小程序开发过程中,有时需要在函数中访问页面中定义的一些数据,或者调用页面中定义的一些函数,此时可以通过this关键字来实现。this关键字代表当前页面对象。

上述代码演示了如何在show_log函数中通过this关键字访问data中的Temp数据。程序运行后,在控制台中可以看到程序输出了this.data.Temp的值“20”。

5、SetData方法

在微信小程序开发过程中,虽然通过数据绑定可以将data中定义的数据渲染到页面,但是如果数据发生了变化,页面并不会同步更新数据。为了实现在数据变化时使页面同步更新,微信小程序提供了setData()方法,该方法可以立即改变data中的数据,并通过异步的方式将数据渲染到页面上。

在上述代码中,给开关组件绑定了事件changevalue,当点击开关的时候,将Temp的值设置成60。大家可以看到,Temp的初始值是20,当点击开关的时候,界面上显示的Temp值变成了60。


15、数据的接收和上传

这一章也算是我们本系统的最后一节内容了。本章结束后,大家就可以实现微信小程序和单片机之间的数据通信了,微信小程序上能够展示单片机采集到的温湿度数据,单片机也可以接收到微信小程序下发的控制命令。从而实现我们系统的一个完整的功能。

1、配置参数

在微信小程序实现和onenet云平台数据交互之前呢,我们需要先把一些初始化配置给写一下。我们单片机连接Onenet云平台的时候是通过MQTT协议进行连接的,当时使用到了产品id、设备名称、token、MQTT服务器地址等。同样的,微信小程序连接Onenet云平台同样需要这些参数。不同的地方在于,微信小程序与Onenet云平台之间的数据交互是通过HTTP协议进行的。

先把使用到的一些参数信息在JS文件中配置好,方面我们后续的使用。

data: {
  Temp:0,
  Hum:0
},
  
config: {
    authorization: "version=2018-10-31&res=products%2FyHtNI9iijM%2Fdevices%2FTest1&et=1782126077&method=md5&sign=pj16A1L5hxgFaeONZtSFSA%3D%3D", // 鉴权信息
    product_id: "yHtNI9iijM", // 产品ID
    device_name: "Test1", // 设备名称
    getinfo_url: 'https://iot-api.heclouds.com/thingmodel/query-device-property?product_id=yHtNI9iijM&device_name=Test1', //设备属性最新数据查询地址
    setinfo_url:'https://iot-api.heclouds.com/thingmodel/set-device-property'//设置设备属性
  },

2、数据的接收

微信小程序和Onenet云平台之间是通过HTTP协议进行交互的,小程序给我们提供了专门的函数去执行HTTP请求。因此呢,小程序从Onenet云平台获取数据,只需要通过wx.request方法去向云平台获取数据就行了。


新建一个获取数据的函数,这个函数里面就实现了微信小程序向Onenet云平台发送获取数据的请求。请求成功以后,会返回数据存储在这个e变量里面。其中我们需要的温度、湿度等信息,就存放在这个e变量里面, 我们将数据提取出来在界面上进行展示即可。

  Onenet_GetInfo()
  {
    wx.request({
      url: this.config.getinfo_url,
      header:{
        'authorization':this.config.authorization
      },
      method:"GET",
      success: (e) =>
      {
         console.log(e),
         this.setData({
         Temp:e.data.data[3].value,
         Hum:e.data.data[1].value
       })
      }
    })
  },

在调用这个函数之前,我们需要将小程序里面的”不校验合法域名“给勾选上,否则的话微信小程序没办法实现和onenet云平台完成数据的请求。

如果我们想实现每5秒钟微信小程序给云平台发送一此获取数据的请求,我们需要给这个函数加个定时器。让它每5s执行一次。

  onLoad(){
    setInterval(this.Onenet_GetInfo,5000)
  }

这样的话,就实现了微信小程序每5s钟向Onenet云平台发送一次请求,然后在界面上将温度和湿度的数据不断的进行更新。


3、数据的发送

同样的,微信小程序如果向下发数据给单片机的话。需要先把数据发送给Onenet云平台,然后Onenet云平台会把数据直接转发给单片机。从而实现微信小程序控制单片机的功能。

以控制Led灯为例,代码实现如下。控制蜂鸣器的代码类似,具体完整的代码可以下载代码工程进行查看。

  Onenet_SetAlarmInfo(event)
  {
    const is_checked = event.detail.value; // 获取开关状态
    wx.showToast({
      title: '操作成功', // 提示的文字内容
      icon: 'success', // 图标类型,使用成功图标
      duration: 1000 // 提示框自动隐藏的时间,单位是毫秒
    }), 
    wx.request({
      url: this.config.setinfo_url,
      header:{
        'authorization':this.config.authorization
      },
      method:"POST",
      data: {
        "product_id": this.config.product_id,
        "device_name": this.config.device_name,
        "params": {
          "Alarm": is_checked
        }     
      },            
    })
  },    
  Onenet_SetLedInfo(event)
  {
    const is_checked = event.detail.value; // 获取开关状态
    wx.showToast({
      title: '操作成功', // 提示的文字内容
      icon: 'success', // 图标类型,使用成功图标
      duration: 1000 // 提示框自动隐藏的时间,单位是毫秒
    }), 
    wx.request({
      url: this.config.setinfo_url,
      header:{
        'authorization':this.config.authorization
      },
      method:"POST",
      data: {
        "product_id": this.config.product_id,
        "device_name": this.config.device_name,
        "params": {
          "Led": is_checked
        }     
      },            
    })
  },