一、前言
1.1 项目介绍
【1】项目开发背景
随着物联网技术的快速发展,智能家居逐渐成为现代生活的重要组成部分。智能插座作为智能家居的基础设备之一,不仅能够实现传统插座的基本功能,还能通过集成多种传感器和通信模块,实现对电器设备的远程控制、能耗监测和环境数据采集等功能。基于这一背景,设计一款基于STM32的智能插座,能够满足用户对电能管理、环境监测和远程控制的需求,具有重要的现实意义。
在电能管理方面,传统的插座无法提供详细的用电信息,用户难以了解电器的实际能耗情况。而智能插座通过集成电压、电流、功率等参数的采集功能,能够实时监测电器的用电状态,帮助用户优化用电行为,降低能源消耗。此外,电能计量功能的加入,使得用户可以精确掌握每个电器的用电量,为节能环保提供数据支持。
在环境监测方面,智能插座集成了温湿度传感器,能够实时采集周围环境的温度和湿度信息。这些数据不仅可以帮助用户了解室内环境的变化,还可以与其他智能家居设备联动,实现自动调节空调、加湿器等设备,提升居住舒适度。
为了实现远程控制和数据展示,智能插座通过ESP8266-WIFI模块与OneNet物联网服务器通信,利用MQTT协议传输数据。用户可以通过微信小程序实时查看插座的运行状态、电能数据和环境信息,并能够通过小程序远程控制插座的开关。同时,微信小程序还支持历史数据的存储和展示,用户可以通过折线图查看每个参数的历史变化趋势,便于分析和决策。
基于STM32设计的智能插座不仅具备传统插座的基本功能,还通过集成多种传感器和通信模块,实现了电能管理、环境监测和远程控制等智能化功能。该项目的开发为用户提供更加便捷、智能的用电体验,同时为节能环保和智能家居的发展提供技术支持。
当前项目使用的相关软件工具、模块源码已经上传到网盘:https://ccnr8sukk85n.feishu.cn/wiki/QjY8weDYHibqRYkFP2qcA9aGnvb?from=from_copylink
【2】设计实现的功能
当前项目使用的相关软件工具、模块源码已经上传到网盘:
https://ccnr8sukk85n.feishu.cn/wiki/QjY8weDYHibqRYkFP2qcA9aGnvb?from=from_copylink当前项目使用的相关软件工具、模块源码已经上传到网盘:
https://ccnr8sukk85n.feishu.cn/wiki/QjY8weDYHibqRYkFP2qcA9aGnvb?from=from_copylink
(1)支持电压采集:实时监测插座输入电压,电压量程为0~264V,能够准确反映电路中的电压变化。
(2)支持电流采集:实时监测插座输入电流,电流量程为10mA~20A,能够精确测量电器的工作电流。
(3)支持有功功率、无功功率采集:通过电压和电流的实时数据,计算有功功率和无功功率,帮助用户了解电器的实际能耗情况。
(4)支持电能信息采集:实时采集电能数据,包括累计用电量,为用户提供精确的电能计量功能。
(5)支持单向交流电、直流电采集:能够兼容单向交流电和直流电的采集,适用于不同类型的电器设备。
(6)支持环境温、湿度信息采集:通过SHT30传感器实时监测插座周围环境的温度和湿度,为用户提供环境数据。
(7)支持功率因数采集(仅对交流电):实时计算交流电的功率因数,帮助用户了解电路的效率。
(8)支持电压频率采集(仅对交流电):实时监测交流电的频率,确保电器设备在稳定的频率下工作。
(9)支持微信小程序显示:通过WIFI与OneNet物联网服务器通信,利用HTTP协议传输数据,用户可以通过微信小程序实时查看插座的运行状态、电能数据和环境信息。
(10)支持历史数据存储与展示:微信小程序利用MySQL数据库保存历史数据,用户可以通过折线图查看每个参数的历史变化趋势,并支持按日期查询历史数据。
(11)支持智能插座运行状态检测:实时监测插座的开关状态和运行情况,确保设备正常工作。
(12)支持电能计量:精确记录电器的用电量,为用户提供详细的用电报告,帮助优化用电行为。
(13)支持远程控制:通过微信小程序远程控制插座的开关,实现对电器设备的智能化管理。
(14)支持OLED本地显示:通过0.96寸OLED显示屏实时显示本地采集的全部信息,包括电压、电流、功率、电能、温湿度等数据,方便用户随时查看。
(15)支持WIFI通信:通过ESP8266-WIFI模块与OneNet物联网服务器通信,利用MQTT协议传输数据,实现数据的远程传输和控制。
【3】项目硬件模块组成
(1)主控芯片模块:采用STM32F103RCT6作为核心控制器,负责数据处理、逻辑控制以及与各模块的通信。
(2)电压电流采集模块:采用JSY-MK-1031单向交直流采集模块,通过串口与主控芯片通信,实时采集电压、电流、功率、电能等参数。
(3)温湿度传感器模块:采用SHT30传感器,实时采集环境温度和湿度数据,并通过I2C接口与主控芯片通信。
(4)OLED显示模块:采用0.96寸OLED显示屏,用于本地显示采集的电压、电流、功率、电能、温湿度等信息,方便用户实时查看。
(5)WIFI通信模块:采用ESP8266-WIFI模块,通过串口与主控芯片通信,实现与OneNet物联网服务器的数据交互,支持MQTT协议传输数据。
(6)电源模块:采用5V 2A的稳压电源,为整个系统提供稳定可靠的电源供应。
(7)继电器控制模块:用于控制插座的开关状态,支持远程控制电器的通断电。
(8)按键模块:提供本地操作接口,用户可以通过按键手动控制插座的开关状态或切换显示内容。
(9)指示灯模块:用于指示插座的运行状态(如开关状态、WIFI连接状态等),方便用户快速了解设备的工作情况。
(10)PCB主板模块:集成所有硬件模块,提供电路连接和信号传输的支持,确保各模块之间的稳定通信。
(11)外壳模块:用于保护内部电路和模块,同时提供插座的物理接口,方便用户插入电器设备。
1.2 设计思路
本项目的设计思路围绕实现智能插座的多功能集成和智能化管理展开,通过硬件和软件的协同设计,满足用户对电能管理、环境监测和远程控制的需求。
在硬件设计方面,以STM32F103RCT6作为核心控制器,负责协调各模块的工作。电压、电流、功率等电能参数的采集通过JSY-MK-1031模块实现,该模块能够兼容单向交流电和直流电的采集,并通过串口与主控芯片通信。环境温湿度数据通过SHT30传感器采集,利用I2C接口与主控芯片交互。为了提供本地显示功能,采用0.96寸OLED显示屏,实时显示电压、电流、功率、电能、温湿度等信息,方便用户随时查看。WIFI通信模块采用ESP8266,通过串口与主控芯片通信,实现与OneNet物联网服务器的数据交互,支持MQTT协议传输数据。此外,电源模块为系统提供稳定的5V供电,继电器模块用于控制插座的开关状态,按键和指示灯模块则提供本地操作和状态指示功能。
在软件设计方面,STM32端的代码采用C语言开发,通过寄存器编程方式实现对各硬件模块的控制和数据采集。软件设计主要包括数据采集、数据处理、通信协议实现和本地显示等功能。数据采集模块负责定时读取电压、电流、功率、电能、温湿度等数据,并进行初步处理。数据处理模块对采集的数据进行计算和校准,确保数据的准确性和可靠性。通信协议模块实现与ESP8266的串口通信,并通过MQTT协议将数据上传至OneNet物联网服务器。本地显示模块则通过OLED显示屏实时展示采集的数据。
在物联网平台和微信小程序的设计中,OneNet物联网服务器作为数据中转站,接收来自智能插座的数据并存储。微信小程序通过HTTP协议与OneNet服务器通信,获取实时数据和历史数据,并通过图表形式展示。小程序还支持远程控制功能,用户可以通过小程序发送指令,控制插座的开关状态。历史数据存储采用MySQL数据库,支持按日期查询和折线图展示,方便用户分析用电行为和环境变化趋势。
在系统集成和测试阶段,通过硬件和软件的联调,确保各模块之间的协同工作。测试内容包括数据采集的准确性、通信的稳定性、远程控制的功能性以及历史数据的存储和展示效果。通过不断优化和调试,确保智能插座的稳定运行和用户体验。
本项目的设计思路以硬件为基础,软件为核心,物联网平台为桥梁,微信小程序为交互界面,通过模块化的设计和系统化的集成,实现了智能插座的多功能集成和智能化管理,为用户提供便捷、智能的用电体验。
1.3 系统功能总结
| 功能分类 | 功能描述 |
| 电能管理 | |
| 1. 电压采集 | 实时监测插座输入电压,量程为0~264V,准确反映电路中的电压变化。 |
| 2. 电流采集 | 实时监测插座输入电流,量程为10mA~20A,精确测量电器的工作电流。 |
| 3. 有功功率采集 | 通过电压和电流数据,实时计算有功功率,帮助用户了解电器的实际能耗。 |
| 4. 无功功率采集 | 实时计算无功功率,反映电路中的能量损耗情况。 |
| 5. 电能信息采集 | 实时采集电能数据,包括累计用电量,为用户提供精确的电能计量功能。 |
| 6. 功率因数采集 | 实时计算交流电的功率因数(仅对交流电),帮助用户了解电路的效率。 |
| 7. 电压频率采集 | 实时监测交流电的频率(仅对交流电),确保电器设备在稳定的频率下工作。 |
| 环境监测 | |
| 8. 温度采集 | 通过SHT30传感器实时监测插座周围环境的温度。 |
| 9. 湿度采集 | 通过SHT30传感器实时监测插座周围环境的湿度。 |
| 远程控制与显示 | |
| 10. 微信小程序显示 | 通过WIFI与OneNet物联网服务器通信,利用HTTP协议传输数据,用户可实时查看插座状态、电能数据和环境信息。 |
| 11. 历史数据存储 | 微信小程序利用MySQL数据库保存历史数据,支持按日期查询和折线图展示。 |
| 12. 远程控制 | 通过微信小程序远程控制插座的开关状态,实现对电器设备的智能化管理。 |
| 本地显示与操作 | |
| 13. OLED本地显示 | 通过0.96寸OLED显示屏实时显示本地采集的全部信息,包括电压、电流、功率、电能、温湿度等数据。 |
| 14. 按键操作 | 提供本地按键接口,用户可通过按键手动控制插座的开关状态或切换显示内容。 |
| 15. 指示灯显示 | 通过指示灯显示插座的运行状态(如开关状态、WIFI连接状态等)。 |
| 通信与数据传输 | |
| 16. WIFI通信 | 通过ESP8266-WIFI模块与OneNet物联网服务器通信,支持MQTT协议传输数据。 |
| 系统状态监测 | |
| 17. 运行状态检测 | 实时监测插座的开关状态和运行情况,确保设备正常工作。 |
| 其他功能 | |
| 18. 电能计量 | 精确记录电器的用电量,为用户提供详细的用电报告,帮助优化用电行为。 |
1.4 模块的技术详情介绍
【1】ESP8266-WIFI模块
ESP8266-WIFI模块是一款功能强大的WiFi通信模块,广泛应用于物联网(IoT)设备中。它由Espressif Systems公司设计,并且由于其低功耗、较小的体积和较低的成本,成为了许多智能硬件项目的首选通信模块。ESP8266不仅具备WiFi无线连接功能,还具有较高的处理能力和丰富的接口资源,使其成为实现无线通信的理想选择。
ESP8266模块内置了一个完整的WiFi栈,可以直接作为WiFi客户端或AP(Access Point)工作,支持无线网络的连接、数据传输等多种功能。在设计中,ESP8266支持多种网络协议,最常见的是TCP/IP协议,允许设备与外部服务器或移动应用进行数据交换。其强大的功能使得它不仅可以连接到传统的路由器,还可以直接创建一个WiFi热点,支持设备与手机或其他WiFi设备的通信。
在硬件设计中,ESP8266模块的另一大优势是其与微控制器的高兼容性。模块通常通过串口(UART)与主控芯片进行通信,因此可以方便地与各种微控制器(如STM32、Arduino等)连接,进行数据传输。在本项目中,ESP8266模块通过串口与STM32单片机相连接,负责将本地采集的健康数据上传到Android手机APP中。数据传输采用的是TCP/IP协议,保证了通信的稳定性和可靠性。
ESP8266模块的低功耗特性也使其适用于嵌入式系统和移动设备中,尤其是在电池供电的项目中,能够有效延长设备的使用时间。通过合理的睡眠模式和待机模式,ESP8266能够在无需频繁通信时降低功耗,从而优化系统的能源使用。
ESP8266-WIFI模块因其高集成度、低成本、强大的WiFi功能及广泛的兼容性,成为了物联网领域中一个不可或缺的通信模块。它不仅为各种嵌入式系统提供了便捷的无线网络连接方案,还极大简化了开发过程,使得开发者能够专注于核心功能的实现。本项目采用ESP8266模块,便捷地实现了STM32与Android手机APP之间的实时数据传输,为健康检测仪提供了可靠的无线通信能力。
【2】SHT30温湿度检测模块
SHT30温湿度检测模块是一款高精度、低功耗的数字温湿度传感器,广泛应用于环境监测、智能家居、工业控制等领域。该模块由瑞士Sensirion公司生产,采用先进的CMOSens®技术,能够提供可靠的温度和湿度测量数据。
SHT30模块基于I2C通信接口,与主控芯片(如STM32)的连接简单方便。其工作电压范围为2.4V至5.5V,适合多种嵌入式系统的供电需求。模块的湿度测量范围为0%至100%RH,温度测量范围为-40°C至125°C,能够满足大多数环境监测场景的需求。SHT30的湿度测量精度为±2%RH,温度测量精度为±0.2°C,具有较高的测量准确性和稳定性。
在功耗方面,SHT30模块表现出色,平均电流消耗仅为0.6mA,适合低功耗应用场景。此外,模块支持多种测量模式和采样频率,用户可以根据实际需求选择不同的配置,以平衡测量精度和功耗。例如,在高精度模式下,模块可以提供更精确的测量数据,但功耗相对较高;而在低功耗模式下,模块可以显著降低能耗,适合电池供电的应用。
SHT30模块还具有出色的长期稳定性,其内部集成了校准存储器,能够自动补偿温度和湿度的漂移,确保长时间使用的测量准确性。此外,模块还具备CRC校验功能,能够检测数据传输过程中的错误,提高通信的可靠性。
在实际应用中,SHT30模块通常与主控芯片通过I2C接口连接,主控芯片发送指令启动测量,模块完成测量后返回温度和湿度数据。主控芯片可以对这些数据进行进一步处理或上传至物联网平台。例如,在智能插座项目中,SHT30模块用于实时监测插座周围环境的温度和湿度,帮助用户了解室内环境的变化,并与其他智能家居设备联动,实现自动调节空调、加湿器等设备的功能。
SHT30温湿度检测模块以其高精度、低功耗、易集成的特点,成为环境监测领域的理想选择。其可靠性和稳定性使其在智能家居、工业控制、气象监测等应用中表现出色,为用户提供了准确的环境数据支持。
【3】0.96寸OLED显示屏
0.96寸OLED显示屏是一种小型化、高分辨率的显示模块,广泛应用于嵌入式系统、智能设备、便携式仪器等领域。该显示屏采用有机发光二极管(OLED)技术,具有自发光、高对比度、低功耗等特点,适合在低光照环境下使用。
0.96寸OLED显示屏的分辨率通常为128x64像素,能够显示清晰的文字、图形和图标。由于其像素点自发光,无需背光模块,因此在显示黑色时完全不发光,能够实现极高的对比度。这使得OLED显示屏在显示效果上优于传统的LCD显示屏,尤其是在暗环境下,显示内容更加鲜明和清晰。
在接口方面,0.96寸OLED显示屏通常支持I2C和SPI两种通信协议,用户可以根据实际需求选择合适的接口方式。I2C接口占用引脚少,适合资源有限的主控芯片;而SPI接口传输速度快,适合需要频繁刷新显示内容的场景。无论是哪种接口,OLED显示屏的驱动都相对简单,主控芯片只需发送指令和数据即可控制显示内容。
0.96寸OLED显示屏的工作电压一般为3.3V至5V,与大多数嵌入式系统的供电电压兼容。其功耗较低,尤其是在显示静态内容时,能够显著降低能耗,适合电池供电的应用场景。此外,OLED显示屏的响应速度极快,能够实现流畅的动态显示效果,适合需要快速刷新显示内容的应用。
在实际应用中,0.96寸OLED显示屏通常用于显示实时数据、状态信息、菜单界面等。例如,在智能插座项目中,OLED显示屏用于实时显示电压、电流、功率、电能、温湿度等采集数据,方便用户随时查看设备的工作状态和环境信息。由于其体积小、显示效果好,OLED显示屏还可以集成到各种便携式设备中,如智能手表、健康监测设备等。
0.96寸OLED显示屏以其高分辨率、高对比度、低功耗和灵活的接口方式,成为嵌入式系统中理想的显示解决方案。其优异的显示效果和易用性使其在智能家居、工业控制、消费电子等领域得到广泛应用,为用户提供了直观、清晰的信息展示方式。
二、搭建开发环境
2.1 接入OneNet
帮助文档地址:https://open.iot.10086.cn/doc/v5/fuse/detail/1418
【1】查询设备数据点
使用这个接口的时候,要注意。你的设备是不是创建的数据流类型。 创建产品的时候会让你选择的。 是数据流还是OneJSON
接口功能
根据产品id和设备名称查询,时间范围参数,查询设备历史数据点信息
备注
仅支持数据流、IPSO数据协议,设备能正常下发上报
接口地址
http(s)://iot-api.heclouds.com/datapoint/history-datapoints
API说明
请求方式:GET
http query 请求参数:
| 参数 | 类型 | 是否必选 | 描述 |
| datastream_id | string | 否 | 数据流ID,多个id之间用逗号分开,缺省时取数据流或数据流模板100条为缺省数据流id条件 |
| product_id | string | 是 | 产品ID,平台生成唯一ID |
| device_name | string | 是 | 设备名称 |
| imei | string | 否 | lwm2m协议时传,设备imei |
| start | string | 否 | 提取数据点的开始时间,精确到秒,示例:2015-01-10T08:00:35 |
| end | string | 否 | 提取数据点的结束时间,精确到秒,示例:2015-01-10T08:00:35 |
| duration | int | 否 | 查询时间区间,单位为秒 |
| limit | int | 否 | 限定本次请求最多返回的数据点数,默认100,范围为(0,6000] |
| cursor | string | 否 | 指定本次请求继续从cursor位置开始提取数据 |
| sort | enum | 否 | 时间排序方式,DESC:倒序,ASC:升序,默认为DESC |
返回数据
| 参数名称 | 类型 | 描述 |
| code | int | 调用成功或失败时,返回的code码 |
| msg | string | 调用成功或失败时,返回的msg信息 |
| request_id | string | 调用API生成的请求标识 |
| data | - | 调用成功时,返回的业务数据 |
| data.count | int | 本次返回的数据点数量 |
| data.cursor | string | 本次请求若未能返回所有数据,则会返回cursor参数,用户可以携带cursor参数进行再次请求,获取剩下的数据 |
| data.datastreams | array-json | 设备数据流信息的json数组,见datastreams描述 |
datastreams描述表
| 参数名称 | 格式 | 说明 |
| id | string | 数据流名称 |
| datapoints | array-json | 数据点信息的json数组,见datapoints描述表 |
datapoints描述表
| 参数名称 | 格式 | 说明 |
| at | string | 数据记录时间 |
| value | string/int/json... | 数据点的值 |
特别说明
- 1. 当start,end时间均为空
i) limit
不传值或传值等于1时:排序为升序则查询最早一个数据点(30天内),否则查询最新数据点(最新缓存数据)ii) limit
传值大于1时:排序为升序则查询最早x条数据点,否则查询最新x条数据点(30天内,x=limit)
示例
请求示例
GET http(s)://iot-api.heclouds.com/datapoint/history-datapoints
?product_id=XhONWQ5zV5&device_name=mqtts-dev&datastream_id=ds&start=2017-01-01T00:00:00&limit=100
响应示例
{
"data":{
"cursor":"25971_564280_1448961152173",
"count":5,
"datastreams":[
{
"datapoints":[
{
"at":"2015-12-01 17:10:24.981",
"value":"35"
},
{
"at":"2015-12-01 17:10:53.406",
"value":"38"
},
...
],
"id":"3200_0_5501"
},
...
]
},
"request_id": "a25087f46df04b69b29e90ef0acfd115",
"msg": "succ",
"code": 0
}
在Qt项目包含网络模块,在.pro文件中添加:
QT += network
演示了如何使用Qt发出HTTP GET请求:
#include <QCoreApplication>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QUrl>
#include <QDebug>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QNetworkAccessManager manager;
QUrl url("https://open.iot.10086.cn/api/your-endpoint"); // 替换为实际URL
QNetworkRequest request(url);
// 设置请求头
request.setRawHeader("Authorization", "Bearer YOUR_ACCESS_TOKEN"); // 示例: 使用Bearer Token
QNetworkReply *reply = manager.get(request);
QObject::connect(reply, &QNetworkReply::finished, [&]() {
if (reply->error() == QNetworkReply::NoError) {
QString response_data = reply->readAll();
qDebug() << "Response data:" << response_data;
} else {
qDebug() << "Error:" << reply->errorString();
}
reply->deleteLater();
a.quit(); // 结束应用程序
});
return a.exec();
}
【2】MQTT命令下发
使用这个接口的时候,要注意。你的设备是不是创建的数据流类型。 创建产品的时候会让你选择的。 是数据流还是OneJSON
接口功能
根据产品ID和设备名,向Mqtt设备下发字符串命令,设备响应后返回设备命令执行状态。可设置5-30秒的超时时间。
备注
仅支持Mqtt数据流协议设备,需要设备在线。
接口地址
https://iot-api.heclouds.com/datapoint/synccmds
API说明
请求方式:POST
http query 请求参数:
| 参数 | 类型 | 是否必选 | 描述 |
| product_id | string | 是 | 产品ID,平台生成唯一ID |
| device_name | string | 是 | 设备名称 |
| timeout | string | 是 | 同步API最长等待时间,取值范围 5-30, 单位秒 |
http body 请求参数
| 参数 | 类型 | 是否必须 | 描述 |
| 自定义 | string | 是 | 命令字符串,大小限制20KB |
返回数据
| 参数名称 | 类型 | 描述 |
| code | int | 调用成功或失败时,返回的code码 |
| msg | string | 调用成功或失败时,返回的msg信息 |
| request_id | string | 调用API生成的请求标识 |
| data | - | 调用成功时,返回的业务数据 |
| cmd_uuid | string | 命令ID |
| cmd_resp | string | 设备应答内容,base64编码格式 |
示例
请求示例
POST http(s)://iot-api.heclouds.com/datapoint/synccmds?product_id=B7EEW578EbRg5Y4K&device_name=device3&timeout=30
Content-type: application/json
{
"id":"45461" //自定义参数
}
响应示例
{
"data": {
"cmd_uuid": "f9115090-8ef1-4b0c-aaf4-0678754f575a",
"cmd_resp": "dGhpcyUyMGlzJTIwY29tbWFuZC1yZXNwb25zZSUyMGNvbnRlbnQ="
},
"request_id": "a25087f46df04b69b29e90ef0acfd115",
"msg": "succ",
"code": 0
}
【3】API鉴权
平台需要对API调用方进行资源权限校验,使用API时,需要在请求Header中携带统一的安全鉴权信息。
不同情形下的鉴权方式
平台支持三种鉴权方式:用户鉴权、产品鉴权、项目鉴权
添加设备后未转移
1、设备未转移,但绑定了项目时,鉴权支持【用户鉴权】、【项目鉴权】。
2、设备未转移,且没有绑定任何项目时,鉴权支持【用户鉴权】、【产品鉴权】。
添加设备后进行设备转移:
1、用户在平台尝试“设备转移”时,需要先解除项目绑定,才能进行转移操作。
2、转移后,再次绑定项目时,支持【用户鉴权】、【项目鉴权】。
3、转移后,没有绑定项目时,仅支持【用户鉴权】。
安全鉴权机制
安全鉴权authorization由多个参数构成,每个参数均采用key = value的形式表示,并用&作为分隔符:
authorization: version=2022-05-01&res=userid%2F{userId}&et={expireTime}&method=sha1&sign={sign}
Token生成工具
-
- • 为便于开发者开发,OneNET提供了Token生成工具,填写字段后可以快速生成安全鉴权信息。前往下载
https://open.iot.10086.cn/doc/v5/fuse/detail/1487
参数说明
| 序 号 | 参数 | 类型 | 说明 | 示例 |
| 1 | version | string | 签名算法版本 | 目前仅支持 2022-05-01 |
| 2 | res | string | 访问资源信息 | 支持主用户、产品、项目三种方式: 1)主用户访问res为:userid/{userid}, userid为平台用户id,access_key为主用户access_key,参数在个人「账号信息」、「访问权限」中查看。 2)产品访问res为:products/{productid},access_key为产品access_key,进入「产品开发」菜单的「产品列表」中,点击「操作」列下的「产品开发」链接,进入「产品信息页面」查看。 3)项目访问res为:projects/{projectid},access_key为项目key,进入「应用开发」-「项目管理」菜单的「我的项目」列表中,点击「操作」列下的「进入项目管理」链接,进入「项目信息页面」,在「项目概况」的「项目信息」中查看。 注* 以上提到的不同access_key获取方式在下文中会有详细描述 |
| 3 | et | string | 访问过期时间 | 10位秒级时间戳,1537255523 表示:北京时间 2018-09-18 15:25:23 |
| 4 | method | string | 签名方法 | 目前支持md5、sha1、sha256 |
| 5 | sign | string | 签名结果字符串 | version、res、et、method参数计算生成 |
accessKey参数获取
主用户accessKey
产品accessKey
项目accessKey
sign的生成算法为:
sign = base64(hmac_<method>(base64decode(accessKey), utf-8(StringForSignature)))
- • accessKey为平台分配的访问密钥(用户访问权限页面查看),如果访问资源以主用户形式访问, 使用主用户的accessKey;如果访问资源以产品方式访问,使用产品的accessKey,且只能对产品下的设备进行操作;如果访问资源以项目方式访问,使用项目的accessKey,且只能对项目下的设备进行操作。• accessKey参与计算前应先进行base64decode操作。• 用于计算签名的字符串 StringForSignature按照et、method、res、version的顺序,以"n"作为分隔符进行排列,如下所示:
StringForSignature = et + "n" + method + "n" + res + "n" + version
参数编码
authorization中key=value的形式的value部分需要经过URL编码,需要进行编码的特殊符号如下:
| 序号 | 符号 | 编码 |
| 1 | + | %2B |
| 2 | 空格 | %20 |
| 3 | / | %2F |
| 4 | ? | %3F |
| 5 | % | %25 |
| 6 | # | %23 |
| 7 | & | %26 |
| 8 | = | %3D |
authorization生成示例
nodejs代码示例
'use strict';
const crypto = require('crypto');
/**
* authorization生成函数
*
* @param {String} method - hash method
* @param {String} res - resource
* @param {String} accessKey - access key
* @param {Number} et - effective time
* @return {String} - authorization
*/
function generateAuthorization(method, res, accessKey, et) {
const version = '2022-05-01';
const et = Math.ceil((Date.now() + et) / 1000); // token有效时间
const base64Key = Buffer.from(accessKey, 'base64'); // accessKey base64编码
const StringForSignature = et + 'n' + method + 'n' + res + 'n' + version;
const sign = encodeURIComponent(crypto.createHmac(method, base64Key).update(StringForSignature).digest('base64'));
const encodeRes = encodeURIComponent(res);
return `version=${version}&res=${encodeRes}&et=${et}&method=${method}&sign=${sign}`;
}
const method = 'sha1';
const accessKey = 'mjgvkTCYTBF6DguxMmm+aV9EkDp2CYfL5jzRTph5Th6KhU8gqZz/cBivPTA7tfY5';
const res = 'userid/130037';
const et = 3600 * 1000; // 有效时间:1小时
const authorization = generateAuthorization(method, res, accessKey, et);
python代码示例
import base64
import hmac
import time
from urllib.parse import quote
def token(user_id,access_key):
version = '2022-05-01'
res = 'userid/%s' % user_id
# 用户自定义token过期时间
et = str(int(time.time()) + 3600)
# 签名方法,支持md5、sha1、sha256
method = 'sha1'
# 对access_key进行decode
key = base64.b64decode(access_key)
# 计算sign
org = et + 'n' + method + 'n' + res + 'n' + version
sign_b = hmac.new(key=key, msg=org.encode(), digestmod=method)
sign = base64.b64encode(sign_b.digest()).decode()
# value 部分进行url编码,method/res/version值较为简单无需编码
sign = quote(sign, safe='')
res = quote(res, safe='')
# token参数拼接
token = 'version=%s&res=%s&et=%s&method=%s&sign=%s' % (version, res, et, method, sign)
return token
if __name__ == '__main__':
user_id = '37715'
access_key = 'mjgvkTCYTBF6DguxMmm+aV9EkDp2CYfL5jzRTph5Th6KhU8gqZz/cBivPTA7tfY5'
print(token(user_id,access_key))
Java代码示例
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
public class Token {
public static String assembleToken(String version, String resourceName, String expirationTime, String signatureMethod, String accessKey)
throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException {
StringBuilder sb = new StringBuilder();
String res = URLEncoder.encode(resourceName, "UTF-8");
String sig = URLEncoder.encode(generatorSignature(version, resourceName, expirationTime, accessKey, signatureMethod), "UTF-8");
sb.append("version=")
.append(version)
.append("&res=")
.append(res)
.append("&et=")
.append(expirationTime)
.append("&method=")
.append(signatureMethod)
.append("&sign=")
.append(sig);
return sb.toString();
}
public static String generatorSignature(String version, String resourceName, String expirationTime, String accessKey, String signatureMethod)
throws NoSuchAlgorithmException, InvalidKeyException {
String encryptText = expirationTime + "n" + signatureMethod + "n" + resourceName + "n" + version;
String signature;
byte[] bytes = HmacEncrypt(encryptText, accessKey, signatureMethod);
signature = Base64.getEncoder().encodeToString(bytes);
return signature;
}
public static byte[] HmacEncrypt(String data, String key, String signatureMethod)
throws NoSuchAlgorithmException, InvalidKeyException {
//根据给定的字节数组构造一个密钥,第二参数指定一个密钥算法的名称
SecretKeySpec signinKey = null;
signinKey = new SecretKeySpec(Base64.getDecoder().decode(key),
"Hmac" + signatureMethod.toUpperCase());
//生成一个指定 Mac 算法 的 Mac 对象
Mac mac = null;
mac = Mac.getInstance("Hmac" + signatureMethod.toUpperCase());
//用给定密钥初始化 Mac 对象
mac.init(signinKey);
//完成 Mac 操作
return mac.doFinal(data.getBytes());
}
public enum SignatureMethod {
SHA1, MD5, SHA256;
}
public static void main(String[] args) throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException {
String version = "2022-05-01";
String resourceName = "userid/12321";
//用户自定义token过期时间
String expirationTime = System.currentTimeMillis() / 1000 + 3600 + "";
String signatureMethod = SignatureMethod.SHA1.name().toLowerCase();
String accessKey = "KuF3NT/jUBJ62LNBB/A8XZA9CqS3Cu79B/ABmfA1UCw=";
String token = assembleToken(version, resourceName, expirationTime, signatureMethod, accessKey);
System.out.println("Authorization:" + token);
}
}
go代码示例
func (s *SignService) GenerateSign(params_map map[string]string, access_key string) (string, error){
signature_str := params_map["et"] + "n" + params_map["method"] + "n" + params_map["res"] + "n" + params_map["version"]
hmac_str := ""
switch strings.ToLower(params_map["method"]) {
case "sha1":
hmac_str = tools.HmacSha1(signature_str, tools.Base64Decode(access_key))
case "sha256":
hmac_str = tools.HmacSha256(signature_str, tools.Base64Decode(access_key))
case "md5":
hmac_str = tools.HmacMd5(signature_str, tools.Base64Decode(access_key))
default:
return "",errors.New("签名参数错误!")
}
sign :=tools.Base64Encode(hmac_str)
return sign,nil
}
func Base64Encode(message string) string {
return base64.StdEncoding.EncodeToString([]byte(message))
}
func Base64Decode(message string) string {
decode_str, err := base64.StdEncoding.DecodeString(message)
if err != nil {
return ""
}
return string(decode_str)
}
func HmacSha256(data string, secret string) string {
h := hmac.New(sha256.New, []byte(secret))
h.Write([]byte(data))
return string(h.Sum(nil))
}
func HmacMd5(data string, secret string) string {
h := hmac.New(md5.New, []byte(secret))
h.Write([]byte(data))
return string(h.Sum(nil))
}
func HmacSha1(data string, secret string) string {
h := hmac.New(sha1.New, []byte(secret))
h.Write([]byte(data))
return string(h.Sum(nil))
}
php代码示例
class SafetyAuth
{
private $_version = '2022-05-01';//版本
private $_method = 'sha1';//加密方法,还可以用md5、sha256
private $_access_key;//访问密钥
private $_res ;//资源参数
private $_et;//到期时间戳
private $_expiration;//有效期,有效时间
public function __construct($access_key, $expiration,$res){
$this->_expiration = $expiration;
$this->_access_key = $access_key;
$this->_res = $res;
}
//生成sign
private function _makeSign() {
date_default_timezone_set('PRC');
$this->_et =time() + $this->_expiration;
$string_for_signature = $this->_et . "n" . $this->_method . "n" . $this->_res . "n" . $this->_version;
$b64_decode_acckey = base64_decode($this->_access_key);
$hmac_key = hash_hmac($this->_method, $this->_strToUtf8($string_for_signature), $b64_decode_acckey, true);
$sign = base64_encode($hmac_key);
return $sign;
}
private function _strToUtf8($str) {
$encode = mb_detect_encoding($str, array("ASCII",'UTF-8',"GB2312","GBK",'BIG5'));
if ($encode == 'UTF-8') {
return $str;
} else {
return mb_convert_encoding($str, 'UTF-8', $encode);
}
}
//生成token
public function makeToken() {
$sign = $this->_makeSign();
$token = sprintf("version=2022-05-01&res=%s&et=%s&method=sha1&sign=%s", urlencode($this->_res), $this->_et, urlencode($sign));
return $token;
}
}
2.2 开发环境搭建
开发工具下载:https://developers.weixin.qq.com/miniprogram/dev/devtools/stable.html
2.3 静态网页展示
以下是一个展示智能插座功能的静态HTML页面设计,采用科技感配色(深蓝色调):
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>智能插座监控系统</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
:root {
--bg-color: #0a192f;
--accent-blue: #64ffda;
--accent-gray: #8892b0;
}
body {
background-color: var(--bg-color);
color: #ffffff;
font-family: 'Segoe UI', sans-serif;
margin: 0;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
.dashboard-header {
text-align: center;
padding: 20px;
border-bottom: 2px solid var(--accent-blue);
}
.data-cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
margin: 20px 0;
}
.card {
background: rgba(17, 34, 64, 0.8);
border-radius: 10px;
padding: 20px;
backdrop-filter: blur(5px);
border: 1px solid var(--accent-blue);
box-shadow: 0 0 15px rgba(100, 255, 218, 0.1);
}
.chart-container {
height: 400px;
margin: 20px 0;
}
.status-indicator {
display: flex;
align-items: center;
gap: 10px;
}
.status-led {
width: 15px;
height: 15px;
border-radius: 50%;
background: #00ff00;
animation: pulse 1.5s infinite;
}
@keyframes pulse {
0% { box-shadow: 0 0 0 0 rgba(0, 255, 0, 0.7); }
70% { box-shadow: 0 0 0 10px rgba(0, 255, 0, 0); }
100% { box-shadow: 0 0 0 0 rgba(0, 255, 0, 0); }
}
.param-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 15px;
}
.param-item {
display: flex;
justify-content: space-between;
padding: 10px;
background: rgba(255, 255, 255, 0.05);
border-radius: 5px;
}
</style>
</head>
<body>
<div class="container">
<div class="dashboard-header">
<h1>智能插座监控系统</h1>
<div class="status-indicator">
<div class="status-led"></div>
<span>运行状态:正常</span>
</div>
</div>
<!-- 实时数据展示 -->
<div class="data-cards">
<div class="card">
<h3>电气参数</h3>
<div class="param-grid">
<div class="param-item">
<span>电压</span>
<span>220.3 V</span>
</div>
<div class="param-item">
<span>电流</span>
<span>5.6 A</span>
</div>
<div class="param-item">
<span>有功功率</span>
<span>1234 W</span>
</div>
<div class="param-item">
<span>功率因数</span>
<span>0.98</span>
</div>
<div class="param-item">
<span>频率</span>
<span>50.0 Hz</span>
</div>
<div class="param-item">
<span>累计电能</span>
<span>56.8 kWh</span>
</div>
</div>
</div>
<div class="card">
<h3>环境参数</h3>
<div class="param-grid">
<div class="param-item">
<span>温度</span>
<span>28.5 ℃</span>
</div>
<div class="param-item">
<span>湿度</span>
<span>45% RH</span>
</div>
</div>
</div>
</div>
<!-- 历史数据图表 -->
<div class="card">
<h3>历史数据趋势</h3>
<div class="chart-container">
<canvas id="historyChart"></canvas>
</div>
<div style="text-align: right; margin-top: 10px;">
<input type="date" id="chartDate" value="2024-03-20">
<button style="background: var(--accent-blue); border: none; padding: 5px 15px; border-radius: 3px;">
查询
</button>
</div>
</div>
<!-- 设备功能列表 -->
<div class="card" style="margin-top: 20px;">
<h3>设备功能清单</h3>
<ul style="columns: 2;">
<li>电压采集 (0-264V)</li>
<li>电流采集 (10mA-20A)</li>
<li>有功/无功功率采集</li>
<li>电能计量</li>
<li>交直流电检测</li>
<li>温湿度监测</li>
<li>功率因数检测</li>
<li>频率检测</li>
<li>Web远程监控</li>
<li>运行状态检测</li>
</ul>
</div>
</div>
<script>
// 示例图表数据
const ctx = document.getElementById('historyChart').getContext('2d');
new Chart(ctx, {
type: 'line',
data: {
labels: Array(24).fill().map((_,i) => `${i}:00`),
datasets: [{
label: '电压 (V)',
data: Array(24).fill().map(() => Math.random()*20 + 220),
borderColor: '#64ffda',
tension: 0.3
},{
label: '电流 (A)',
data: Array(24).fill().map(() => Math.random()*5),
borderColor: '#ff6384',
tension: 0.3
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
grid: { color: 'rgba(255,255,255,0.1)' }
},
x: {
grid: { color: 'rgba(255,255,255,0.1)' }
}
}
}
});
</script>
</body>
</html>
这个页面包含以下主要特点:
- 1. 视觉设计:
- • 深蓝色科技感背景(#0a192f)• 霓虹蓝绿色(#64ffda)作为强调色• 半透明玻璃拟态效果卡片• 动态状态指示灯
- 2. 功能展示:
- • 实时电气参数显示(电压、电流、功率等)• 环境参数显示(温湿度)• 历史数据趋势图表(使用Chart.js)• 日期选择功能• 设备功能清单• 设备运行状态指示
- 3. 交互元素:
- • 响应式布局(自动适应不同屏幕尺寸)• 动态数据图表• 数据卡片悬浮效果• 日期选择器
- 4. 技术实现:
- • CSS Grid布局• CSS自定义属性• 图表可视化(Chart.js)• 简单的动画效果
五、微信小程序设计思路
以下是微信小程序的设计代码,用于从OneNet物联网平台获取设备端上传的数据,并展示电压、电流、功率、电能、温湿度等信息。小程序界面设计简洁,支持实时数据展示和历史数据查询。
5.1 微信小程序代码结构
1. 目录结构
/pages
/index
index.js
index.wxml
index.wxss
/history
history.js
history.wxml
history.wxss
/app.js
/app.json
/app.wxss
2. app.json 配置文件
{
"pages": [
"pages/index/index",
"pages/history/history"
],
"window": {
"navigationBarTitleText": "智能插座监控",
"navigationBarBackgroundColor": "#2d8cf0",
"navigationBarTextStyle": "white"
},
"tabBar": {
"list": [
{
"pagePath": "pages/index/index",
"text": "实时数据",
"iconPath": "images/home.png",
"selectedIconPath": "images/home-active.png"
},
{
"pagePath": "pages/history/history",
"text": "历史数据",
"iconPath": "images/history.png",
"selectedIconPath": "images/history-active.png"
}
]
}
}
3. index.wxml 实时数据页面
<view class="container">
<view class="card">
<text class="title">电压</text>
<text class="value">{{voltage}} V</text>
</view>
<view class="card">
<text class="title">电流</text>
<text class="value">{{current}} A</text>
</view>
<view class="card">
<text class="title">功率</text>
<text class="value">{{power}} W</text>
</view>
<view class="card">
<text class="title">电能</text>
<text class="value">{{energy}} kWh</text>
</view>
<view class="card">
<text class="title">温度</text>
<text class="value">{{temperature}} °C</text>
</view>
<view class="card">
<text class="title">湿度</text>
<text class="value">{{humidity}} %</text>
</view>
</view>
4. index.wxss 实时数据页面样式
.container {
padding: 20px;
}
.card {
background-color: #fff;
border-radius: 10px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.title {
font-size: 16px;
color: #666;
}
.value {
font-size: 24px;
color: #333;
font-weight: bold;
}
5. index.js 实时数据页面逻辑
Page({
data: {
voltage: 0,
current: 0,
power: 0,
energy: 0,
temperature: 0,
humidity: 0
},
onLoad() {
this.getDataFromOneNet();
setInterval(() => {
this.getDataFromOneNet();
}, 5000); // 每5秒更新一次数据
},
getDataFromOneNet() {
const apiKey = 'your-onenet-api-key'; // 替换为你的OneNet API Key
const deviceId = 'your-device-id'; // 替换为你的设备ID
const url = `https://api.heclouds.com/devices/${deviceId}/datapoints`;
wx.request({
url: url,
method: 'GET',
header: {
'api-key': apiKey
},
success: (res) => {
if (res.statusCode === 200) {
const data = res.data.data;
this.setData({
voltage: data.voltage,
current: data.current,
power: data.power,
energy: data.energy,
temperature: data.temp,
humidity: data.humi
});
}
},
fail: (err) => {
console.error('获取数据失败', err);
}
});
}
});
6. history.wxml 历史数据页面
<view class="container">
<picker mode="date" start="2023-01-01" end="2023-12-31" bindchange="onDateChange">
<view class="date-picker">
<text>选择日期:{{selectedDate}}</text>
</view>
</picker>
<view class="chart-container">
<canvas canvas-id="lineChart" class="chart"></canvas>
</view>
</view>
7. history.wxss 历史数据页面样式
.container {
padding: 20px;
}
.date-picker {
background-color: #fff;
border-radius: 10px;
padding: 20px;
margin-bottom: 20px;
text-align: center;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.chart-container {
width: 100%;
height: 400px;
}
8. history.js 历史数据页面逻辑
const util = require('../../utils/util.js');
Page({
data: {
selectedDate: util.formatDate(new Date()),
historyData: []
},
onLoad() {
this.getHistoryData(this.data.selectedDate);
},
onDateChange(e) {
const selectedDate = e.detail.value;
this.setData({ selectedDate });
this.getHistoryData(selectedDate);
},
getHistoryData(date) {
const apiKey = 'your-onenet-api-key'; // 替换为你的OneNet API Key
const deviceId = 'your-device-id'; // 替换为你的设备ID
const url = `https://api.heclouds.com/devices/${deviceId}/datapoints?start=${date}T00:00:00&end=${date}T23:59:59`;
wx.request({
url: url,
method: 'GET',
header: {
'api-key': apiKey
},
success: (res) => {
if (res.statusCode === 200) {
this.setData({ historyData: res.data.data });
this.drawChart();
}
},
fail: (err) => {
console.error('获取历史数据失败', err);
}
});
},
drawChart() {
const ctx = wx.createCanvasContext('lineChart');
const data = this.data.historyData;
// 绘制折线图
ctx.setStrokeStyle('#2d8cf0');
ctx.setLineWidth(2);
ctx.beginPath();
data.forEach((item, index) => {
const x = (index / (data.length - 1)) * 300;
const y = (1 - item.voltage / 264) * 200; // 假设电压范围为0-264V
if (index === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
});
ctx.stroke();
ctx.draw();
}
});
9. utils/util.js 工具函数
function formatDate(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
module.exports = {
formatDate
};
5.2 功能说明
1.实时数据页面:
-
-
- • 从OneNet获取设备上传的实时数据(电压、电流、功率、电能、温湿度)。• 每5秒更新一次数据。• 数据以卡片形式展示,界面简洁清晰。
-
2. 历史数据页面:
-
-
- • 支持选择日期,查询指定日期的历史数据。• 使用折线图展示电压变化趋势。• 数据通过Canvas绘制,支持动态更新。
-
3. 界面设计:
-
- • 采用卡片式布局,突出显示关键数据。• 使用蓝色主题色,界面风格统一。
5.3 总结
该微信小程序实现了从OneNet物联网平台获取数据并展示的功能,支持实时数据监控和历史数据查询。代码结构清晰,界面设计简洁,适合智能插座项目的需求。
四、STM32设备端设计思路
4.1 整体代码设计思路
(1)初始化阶段:
- • 初始化系统时钟、GPIO、串口、I2C、SPI等外设。• 初始化各子模块(如SHT30、JSY-MK-1031、ESP8266、OLED等)。• 配置中断(如果需要)。
(2)主循环阶段:
- • 定时采集电压、电流、功率、电能、温湿度等数据。• 将采集的数据显示在OLED屏幕上。• 通过ESP8266模块将数据上传至OneNet物联网服务器。• 检测用户按键输入,实现本地控制功能(如开关插座)。• 实时监测系统状态,确保各模块正常工作。
(3)模块化设计:
- • 各子模块的功能封装成独立的函数,便于维护和扩展。• 主循环中通过调用这些函数实现数据的采集、处理和传输。
(4)低功耗优化:
- • 在空闲时进入低功耗模式,降低系统功耗。• 定时唤醒进行数据采集和传输。
4.2 main.c 代码示例
#include "stm32f10x.h"
#include "sht30.h"
#include "jsy_mk_1031.h"
#include "esp8266.h"
#include "oled.h"
#include "key.h"
#include "relay.h"
#include <stdio.h>
#include <string.h>
// 定义全局变量存储采集的数据
float voltage = 0.0; // 电压
float current = 0.0; // 电流
float power = 0.0; // 功率
float energy = 0.0; // 电能
float temperature = 0.0; // 温度
float humidity = 0.0; // 湿度
// 系统初始化函数
void System_Init(void) {
// 初始化系统时钟
RCC_Configuration();
// 初始化GPIO
GPIO_Configuration();
// 初始化串口(用于调试和ESP8266通信)
USART_Init(115200);
// 初始化I2C(用于SHT30和OLED)
I2C_Configuration();
// 初始化SPI(如果需要)
SPI_Configuration();
// 初始化各子模块
SHT30_Init();
JSY_MK_1031_Init();
ESP8266_Init();
OLED_Init();
KEY_Init();
Relay_Init();
// 初始化定时器(用于定时采集数据)
TIM_Configuration();
}
// 数据采集函数
void Data_Acquisition(void) {
// 采集电压、电流、功率、电能
voltage = JSY_MK_1031_GetVoltage();
current = JSY_MK_1031_GetCurrent();
power = JSY_MK_1031_GetPower();
energy = JSY_MK_1031_GetEnergy();
// 采集温湿度
SHT30_ReadData(&temperature, &humidity);
}
// 数据显示函数
void Data_Display(void) {
char buffer[64];
// 清屏
OLED_Clear();
// 显示电压
snprintf(buffer, sizeof(buffer), "Voltage: %.2f V", voltage);
OLED_ShowString(0, 0, buffer);
// 显示电流
snprintf(buffer, sizeof(buffer), "Current: %.2f A", current);
OLED_ShowString(0, 16, buffer);
// 显示功率
snprintf(buffer, sizeof(buffer), "Power: %.2f W", power);
OLED_ShowString(0, 32, buffer);
// 显示电能
snprintf(buffer, sizeof(buffer), "Energy: %.2f kWh", energy);
OLED_ShowString(0, 48, buffer);
// 显示温湿度
snprintf(buffer, sizeof(buffer), "Temp: %.2f C", temperature);
OLED_ShowString(64, 0, buffer);
snprintf(buffer, sizeof(buffer), "Humi: %.2f %%", humidity);
OLED_ShowString(64, 16, buffer);
}
// 数据上传函数
void Data_Upload(void) {
char json[128];
// 构造JSON数据
snprintf(json, sizeof(json),
"{"voltage":%.2f,"current":%.2f,"power":%.2f,"energy":%.2f,"temp":%.2f,"humi":%.2f}",
voltage, current, power, energy, temperature, humidity);
// 通过ESP8266上传数据到OneNet
ESP8266_SendData(json);
}
// 主函数
int main(void) {
// 系统初始化
System_Init();
// 主循环
while (1) {
// 数据采集
Data_Acquisition();
// 数据显示
Data_Display();
// 数据上传
Data_Upload();
// 检测按键输入
if (KEY_GetState() == KEY_PRESSED) {
Relay_Toggle(); // 切换继电器状态
}
}
}
// 定时器中断服务函数(用于定时采集数据)
void TIM2_IRQHandler(void) {
if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) {
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
// 触发数据采集
Data_Acquisition();
}
}
4.3 代码设计思路说明
(1)模块化设计:
• 每个功能模块(如数据采集、显示、上传等)都封装成独立的函数,便于维护和扩展。
• 例如,Data_Acquisition负责采集数据,Data_Display负责显示数据,Data_Upload负责上传数据。
(2)定时采集与实时显示:
-
- • 使用定时器中断定时触发数据采集,确保数据的实时性。• 主循环中调用Data_Display函数,将采集的数据实时显示在OLED屏幕上。
(3)数据上传:
- • 通过ESP8266模块将数据封装成JSON格式,上传至OneNet物联网服务器。• 数据上传频率可以根据实际需求调整。
(4)用户交互:
- • 通过按键检测实现本地控制功能(如开关插座)。• 按键状态检测在主循环中进行,确保响应用户操作。
(5)低功耗优化:
- • 在数据采集和上传完成后,可以进入低功耗模式,降低系统功耗。
4.4 总结
该main.c代码实现了智能插座的核心功能,包括数据采集、显示、上传和用户交互。通过模块化设计和定时器中断,确保了系统的实时性和稳定性。代码结构清晰,便于后续功能扩展和维护。
3191
