0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

智能农业监控系统:MQTT阿里云平台监测+内置Web网页控制+代码解析

王亚龙 来源:jf_04762332 作者:jf_04762332 2025-08-08 11:07 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

软硬件开源项目-智能农业监控系统:MQTT阿里云平台监测+内置Web网页控制+代码解析

智能农业监控系统:开源项目推荐

【下载地址】智能农业监控系统源码
本仓库提供了一套完整的智能农业监控系统源码,基于 W55MH32 以太网单片机实现三大核心功能:通过 ADC 采集土壤湿度与光照数据,基于阈值自动控制水泵灌溉;经 MQTT 协议对接阿里云,实现数据远程查看与设备控制;提供本地 Web 接口,支持实时监测与阈值调整。
项目仓库地址:https://gitee.com/shenzhen-weishi_3_0/W55MH32

完整显示视频bilibili|点击跳转

1 项目构思与核心目标

老家亲戚种大棚,灌溉总让人头疼:每天来回查湿度,凭经验浇水,忙时作物缺水蔫苗,雨天积水烂根,既费人力又浪费水,环境调节总跟不上作物需求。我便琢磨着做套简易系统解决这些问题。​

之前用过 WIZnet 的 W5500 芯片做以太网项目,对他们的芯片挺熟悉。听说新出了带 MCU 的 W55MH32 以太网芯片,就申请了开发板试试。拿到板子后,先连接传感器在自家盆栽做测试,确认硬件兼容和数据稳定后,正式搭建智能农业监管系统。这板子自带硬件 TCP/IP 引擎,外设接口丰富,刚好满足传感器连接和数据传输需求,官方还有阿里云连接例程,省了不少事。有之前的经验打底,用它连阿里云、搭局域网监控网页,心里挺有底。​我计划搭传感器与水泵联动模型:传感器采集土壤湿度、光照等数据,传云端后自动控制水泵启停。说干就干,画接线图、连传感器和继电器,优化 MQTT 逻辑接阿里云,还做了简易网页方便远程查看操作。调试后,数据采集和水泵自动启停功能都稳定实现了。​这个模型还在完善中,后续会优化硬件集成和代码逻辑,让运行更稳定耐用。现在整理开发细节记录下来,之后会开源代码和 PCB 文件,希望给想在田间用物联网技术的朋友做个参考,提供思路,让技术真正为农活添力。​

核心目标:

硬件层面:实现传感器数据采集与执行器(水泵)控制的稳定联动,确保环境数据实时性与设备响应可靠性。

软件层面:完成 MQTT 协议对接阿里云,实现设备与云端的双向数据传输,同时支持本地网页控制和环境数据查看。

应用层面:默认土壤湿度低于 30% 自动启动灌溉,高于 50% 自动停止,支持远程动态调整阈值,兼顾自动化与灵活性。

后续计划进一步将W55MH32以太网单片机芯片与光照传感器、土壤湿度传感器深度集成,同时匹配适配田间环境的防护外壳——既通过硬件整合提升系统稳定性,又借助外壳抵御大棚内的温湿度波动、粉尘等干扰,让设备在实际农业场景中更耐用、易部署。此外,还会公开完整的硬件原理图和PCB设计文件,方便有需要的朋友参考复用,降低技术落地的门槛,让这套方案能更便捷地应用到田间地头。

1.1 方案图示

wKgZPGiVaIOAGFKmAACJ-5yiZaI734.png

2 硬件选型与搭建

2.1 核心组件清单

主控:W55MH32L-EVB(216MHz主频、自带硬件TCP/IP引擎)。

传感器:土壤湿度传感器(模拟输出)、光照传感器(模拟输出)。

执行器:5V继电器模块、小型水泵。

辅助设备:外部 5V 电源、网线、路由器、杜邦线若干。

2.2 电路连接技巧

开发板引脚定义复杂,我采用"功能分组"法简化连接:

模拟量输入组:PA0接土壤湿度传感器,PA3接光照传感器(利用单片机ADC功能)。

数字输出组:PB10 接继电器IN引脚(控制信号)。

电源组:开发板5V输出给继电器供电,传感器独立接3.3V(避免干扰),继电器COM端接外接电源正极。

水泵:正极接继电器常开端,负极接外接电源负极。

特别注意继电器的"低电平有效"特性——初始化时需将PB10置高,通过拉低电平触发动作,这一点在后续软件设计中需重点匹配。

3 开发环境搭建

3.1 软件工具链

编译环境:Keil uVision5,版本大于V5.3(需安装W55MH32 系列芯片包)。

调试工具:WIZ UartTool串口助手,其他串口助手也可。

浏览器:用于打开网页查看。

云平台:阿里云物联网平台(需完成实名认证)。

源码:gitee仓库项目仓库地址|点击跳转

4 连接阿里云物联网平台

4.1 MQTT连接阿里云收发数据流程

4.1.1 准备阶段

注册与实名认证:用户需要在阿里云平台注册账号,并完成实名认证。

创建产品和添加物模型:登录阿里云物联网平台,创建产品并在产品下添加以下物模型功能。

wKgZO2iVaIWATUqbAAXq13gW2BI244.png

创建设备:在刚刚创建的产品下创建一个设备。

wKgZO2iVaIOAd6PxAAD0br08nQ0304.png

4.1.2 记录参数

连接参数:在刚刚创建的设备详情页中找到MQTT连接参数。

wKgZPGiVaISAMExlAAJql4u5KMM271.png

订阅主题:/sys/k1zh33h3hte/${deviceName}/thing/service/property/set(属性设置主题)

发布主题:/sys/k1zh33h3hte/${deviceName}/thing/event/property/post(上报消息主题)

注意:上面两个主题中的${deviceName}需要替换成设备名。

wKgZO2iVaIOAGGqWAAFrxzSq4T4795.png

4.1.3 连接、订阅和发布消息

接着我们可以使用上面记录的连接参数进行连接,当连接成功后,订阅上面的订阅主题。并通过发布主题上报物模型数据。

在阿里云平台,如果产品创建阶段选择的数据格式为Alink JSON格式时,接收和发送数据格式都会遵守下面这个格式:

{
   "method": "thing.event.property.post", 
   "id": "2241348", 
   "params": {
       "prop_float": 1.25, 
       "prop_int16": 4658, 
       "prop_bool": 1
   }, 
   "version": "1.0"
}

5 主要程序解析

5.1 main.c分析

1.系统初始化与硬件配置

完成基础硬件初始化(时钟、延时、串口、定时器等),配置ADC(模数转换)用于传感器数据采集,初始化继电器控制模块,并设置WIZnet以太网芯片的网络参数(MAC、IP、网关等)。

2.网络通信功能 实现双重网络通信能力:

MQTT协议:通过MQTT客户端(do_mqtt()和mqtt_post_properties())实现传感器数据的远程发布。

获取网页:通过loopback_tcps()提供TCP服务器功能,进行HTTP请求和响应处理。

3.传感器数据处理与控制

周期性(5秒间隔)通过process_sensors_and_control()读取传感器数据(湿度、光照强度)。

基于湿度阈值(高低阈值)实现继电器自动控制逻辑。

将实时数据(湿度、光照、继电器状态)通过MQTT发布到阿里平台同时刷新网页环境数据。

#include "bsp_adc.h"
#include "bsp_rcc.h"
#include "bsp_tim.h"
#include "bsp_uart.h"
#include "delay.h"
#include "do_mqtt.h"
#include "do_sensor.h"
#include "loopback.h"
#include "sv.h"
#include "wiz_interface.h"
#include "wizchip_conf.h"
#include < stdio.h >
#include < stdlib.h >
#include < string.h >

/* 全局实时传感器数据(在do_mqtt.c中定义) */
extern float g_humidity_value;         // 当前湿度读数
extern float g_light_intensity;        // 当前光照强度读数
extern uint8_t g_solenoid_valve_state; // 电磁阀状态(1:开启, 0:关闭)

/* 湿度阈值(与阿里云IoT模型对齐,范围0~100) */
int g_humidity_low_threshold = 30;  // 浇水的下限阈值
int g_humidity_high_threshold = 50; // 停止浇水的上限阈值

/* 函数原型 */
extern void mqtt_post_properties(void);

/* 套接字和缓冲区配置常量 */
#define SOCKET_TCP_ID 0
#define SOCKET_MQTT_ID 1
#define ETHERNET_BUF_MAX_SIZE (1024 * 2) // 以太网缓冲区最大大小
#define SENSOR_READ_INTERVAL 5000        // 传感器采样间隔(毫秒)

uint16_t g_tcp_listen_port = 8080;     // TCP服务器监听端口
wiz_NetInfo g_default_network_info = { // 默认网络配置
    .mac = {0x00, 0x08, 0xdc, 0x12, 0x22, 0x12},
    .ip = {192, 168, 1, 30},
    .gw = {192, 168, 1, 1},
    .sn = {255, 255, 255, 0},
    .dns = {8, 8, 8, 8},
    .dhcp = NETINFO_DHCP};

uint8_t g_ethernet_data_buf[ETHERNET_BUF_MAX_SIZE] = {
    0}; // 以太网数据缓冲区
static uint8_t s_mqtt_send_buf[ETHERNET_BUF_MAX_SIZE] = {
    0}; // MQTT发送缓冲区(静态)
static uint8_t s_mqtt_recv_buf[ETHERNET_BUF_MAX_SIZE] = {
    0}; // MQTT接收缓冲区(静态)

/* 声明systick_count,名称与bsp_tim.o中的引用匹配 */
volatile uint32_t systick_count =
    0; // 系统滴答计数器(毫秒)- bsp_tim使用的名称

/**
 * @brief 主程序循环
 * @details 处理MQTT通信、TCP回环和传感器处理
 */
int main(void) {
  rcc_clk_config();          // RCC时钟配置
  delay_init();              // 延时初始化
  console_usart_init(115200); // 控制台串口初始化,波特率115200
  tim3_init();               // TIM3定时器初始化

  printf("%s 传感器监控系统rn", _WIZCHIP_ID_);

  adc_dma_init(); // ADC DMA初始化
  sv_init();      // 电磁阀初始化

  wiz_toe_init();                // WIZ芯片TOE初始化
  wiz_phy_link_check();          // WIZ物理层连接检查
  network_init(g_ethernet_data_buf, &g_default_network_info); // 网络初始化
  mqtt_init(SOCKET_MQTT_ID, s_mqtt_send_buf, s_mqtt_recv_buf); // MQTT初始化
  printf("系统初始化完成。rn");
  
  while (1) {
    do_mqtt(); // 处理MQTT通信

    // 处理TCP回环
    loopback_tcps(SOCKET_TCP_ID, g_ethernet_data_buf, g_tcp_listen_port);

    // 按间隔读取传感器并发布数据
    if (systick_count >= SENSOR_READ_INTERVAL) {
      systick_count = 0;
      process_sensors_and_control(); // 处理传感器数据并控制设备
      mqtt_post_properties();        // 通过MQTT发布传感器数据
    }
  }
}

5.2 ADC采集

1.关键配置:

初始化ADC为连续扫描模式,同时启用DMA传输。

配置GPIO为模拟输入模式,对应传感器连接的引脚。

设置DMA以循环方式将 ADC 数据传输到缓冲区,无需CPU干预。

2.数据处理:

采样数据交替存储在s_adc_dma_buffer缓冲区中。

提供adc_get_average_value()函数,可获取指定通道的平均采样值,减少噪声影响。

#include "bsp_adc.h"
#include "w55mh32_adc.h"
#include "w55mh32_dma.h"
#include "w55mh32_gpio.h"
#include "w55mh32_rcc.h"


/* ADC DMA数据静态缓冲区(存储交替的通道读数) */
static uint16_t s_adc_dma_buffer[ADC_BUFFER_SIZE];

/**
 * @brief 初始化带DMA功能的ADC
 * @details 配置ADC通道(湿度和光照)、GPIO和DMA以实现连续采样
 */
void adc_dma_init(void) {
  ADC_InitTypeDef adc_init_struct;  // ADC初始化结构体
  GPIO_InitTypeDef gpio_init_struct; // GPIO初始化结构体
  DMA_InitTypeDef dma_init_struct;  // DMA初始化结构体

  // 使能ADC、GPIO和DMA的时钟
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE);
  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

  // 配置GPIO引脚为模拟输入(PA0: 湿度, PA3: 光照)
  gpio_init_struct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_3;
  gpio_init_struct.GPIO_Mode = GPIO_Mode_AIN;  // 模拟输入模式
  GPIO_Init(GPIOA, &gpio_init_struct);

  // 配置ADC为连续扫描模式
  ADC_StructInit(&adc_init_struct);  // 初始化ADC结构体为默认值
  adc_init_struct.ADC_Mode = ADC_Mode_Independent;  // 独立模式
  adc_init_struct.ADC_ScanConvMode = ENABLE;  // 使能扫描模式
  adc_init_struct.ADC_ContinuousConvMode = ENABLE;  // 使能连续转换模式
  adc_init_struct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;  // 无外部触发
  adc_init_struct.ADC_DataAlign = ADC_DataAlign_Right;  // 数据右对齐
  adc_init_struct.ADC_NbrOfChannel = 2;  // 2个通道(湿度+光照)
  ADC_Init(ADC1, &adc_init_struct);

  // 配置ADC通道和采样时间
  ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1,
                           ADC_SampleTime_55Cycles5);  // 湿度通道(PA0)
  ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 2,
                           ADC_SampleTime_55Cycles5);  // 光照通道(PA3)

  // 配置DMA用于ADC数据传输
  DMA_DeInit(DMA1_Channel1);  // 重置DMA通道1配置
  dma_init_struct.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;  // 外设基地址(ADC数据寄存器)
  dma_init_struct.DMA_MemoryBaseAddr = (uint32_t)s_adc_dma_buffer;  // 内存基地址(DMA缓冲区)
  dma_init_struct.DMA_DIR = DMA_DIR_PeripheralSRC;  // 传输方向:外设到内存
  dma_init_struct.DMA_BufferSize = ADC_BUFFER_SIZE;  // 缓冲区大小
  dma_init_struct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;  // 禁止外设地址递增
  dma_init_struct.DMA_MemoryInc = DMA_MemoryInc_Enable;  // 使能内存地址递增
  dma_init_struct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;  // 外设数据大小:半字(16位)
  dma_init_struct.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;  // 内存数据大小:半字(16位)
  dma_init_struct.DMA_Mode = DMA_Mode_Circular;  // 循环模式
  dma_init_struct.DMA_Priority = DMA_Priority_High;  // 高优先级
  dma_init_struct.DMA_M2M = DMA_M2M_Disable;  // 禁止内存到内存传输
  DMA_Init(DMA1_Channel1, &dma_init_struct);

  // 使能DMA和ADC
  DMA_Cmd(DMA1_Channel1, ENABLE);  // 使能DMA通道1
  ADC_DMACmd(ADC1, ENABLE);  // 使能ADC的DMA请求

  // 校准并启动ADC
  ADC_Cmd(ADC1, ENABLE);  // 使能ADC1
  ADC_ResetCalibration(ADC1);  // 重置校准寄存器
  while (ADC_GetResetCalibrationStatus(ADC1))  // 等待重置校准完成
    ;
  ADC_StartCalibration(ADC1);  // 开始校准
  while (ADC_GetCalibrationStatus(ADC1))  // 等待校准完成
    ;

  ADC_SoftwareStartConvCmd(ADC1, ENABLE);  // 软件触发ADC转换
}

/**
 * @brief 获取特定通道的ADC平均值
 * @param channel_index ADC通道索引(0: 湿度, 1: 光照)
 * @param sample_count 用于平均的样本数量
 * @return 平均ADC值(12位)
 */
uint16_t adc_get_average_value(uint8_t channel_index, uint8_t sample_count) {
  uint32_t sum = 0;  // 样本总和
  uint8_t valid_samples = 0;  // 有效样本数

  // 从DMA缓冲区读取交替的样本(通道0在偶数索引,通道1在奇数索引)
  for (uint8_t i = 0; i < sample_count && i < ADC_BUFFER_SIZE / 2; i++) {
    sum += s_adc_dma_buffer[i * 2 + channel_index];  // 累加对应通道的样本
    valid_samples++;  // 计数有效样本
  }

  return (valid_samples > 0) ? (sum / valid_samples) : 0;  // 返回平均值(避免除零)
}

5.3 传感器数据读取

1.湿度传感器读取(humidity_read):

从ADC获取湿度通道的平均原始值。

将ADC值转换为电压(基于3.3V参考电压和12位ADC)。

通过传感器特定公式将电压转换为湿度值(0-100%)。

对结果进行范围限制,确保在有效区间内。

2.光照强度读取(light_read_intensity):

从 ADC 获取光照通道的平均原始值。

处理零值情况避免除零错误。

通过传感器公式将ADC值转换为光照强度(自定义单位)。

3.传感器处理与控制(process_sensors_and_control):

读取湿度和光照值并更新全局变量。

根据湿度阈值控制继电器:低于低阈值打开(浇水),高于高阈值关闭(停止浇水)。

打印当前状态信息(湿度、光照、阀门状态及阈值)。


#include "bsp_adc.h"
#include "do_sensor.h"
#include "sv.h"
#include < stdio.h >
extern float g_humidity_value;
extern float g_light_intensity;
extern uint8_t g_solenoid_valve_state;
extern int g_humidity_low_threshold;
extern int g_humidity_high_threshold;

/**
 * @brief 从传感器读取湿度值
 * @details 将ADC原始值转换为相对湿度(0~100%)
 * @return 湿度值(0.0~100.0)
 */
float humidity_read(void) {
  // 从湿度通道(索引0)读取ADC平均值
  uint16_t adc_raw_value = adc_get_average_value(0, ADC_BUFFER_SIZE / 2);

  // 将ADC值转换为电压(参考电压3.3V,12位ADC)
  float voltage = adc_raw_value * 3.3f / 4096.0f;

  // 将电压转换为湿度(传感器特定公式)
  float humidity = (3.3f - voltage) * 30.3f;

  // 将值限制在有效范围内(0~100%)
  if (humidity < 0.0f) {
    humidity = 0.0f;
  }
  if (humidity > 100.0f) {
    humidity = 100.0f;
  }

  return humidity;
}

/**
 * @brief 从传感器读取光照强度
 * @details 将ADC原始值转换为光照强度(任意单位)
 * @return 光照强度值
 */
float light_read_intensity(void) {
  // 从光照通道(索引1)读取ADC平均值
  uint16_t adc_raw_value = adc_get_average_value(1, ADC_BUFFER_SIZE / 2);

  // 避免除零错误
  if (adc_raw_value == 0) {
    adc_raw_value = 1;
  }

  // 将ADC值转换为光照强度(传感器特定公式)
  return 500000.0f / (float)adc_raw_value;
}

/**
 * @brief 读取传感器并控制电磁阀
 * @details 读取湿度和光照传感器,基于阈值控制阀门
 */
void process_sensors_and_control(void) {
  g_humidity_value = humidity_read();
  g_light_intensity = light_read_intensity();

  // 基于湿度阈值控制电磁阀
  if (g_humidity_value < g_humidity_low_threshold) {
    g_solenoid_valve_state = 1;
    sv_open(); // 打开阀门(开始浇水)
  } else if (g_humidity_value > g_humidity_high_threshold) {
    g_solenoid_valve_state = 0;
    sv_close(); // 关闭阀门(停止浇水)
  }

  // 打印当前状态
  printf("Humidity:%.1f Light:%.0f Valve:%s Low:%d High:%drn",
         g_humidity_value, g_light_intensity,
         g_solenoid_valve_state ? "ON" : "OFF", g_humidity_low_threshold,
         g_humidity_high_threshold);
}

5.4 继电器控制浇灌

1.硬件定义与初始化:

定义继电器控制引脚为 GPIOB的Pin10。

sv_init() 函数负责初始化GPIO:

使能GPIOB时钟。

配置引脚为开漏输出模式(GPIO_Mode_Out_OD),速度50MHz。

初始状态设置为高电平(关闭继电器,利用开漏模式的内部上拉)。

2.核心控制函数:

sv_close():通过设置引脚为高电平关闭继电器,停止浇水,并打印状态信息。

sv_open():通过清除引脚为低电平打开继电器,开始浇水,并打印状态信息。

#include "bsp_uart.h"
#include "sv.h"
#include "w55mh32_rcc.h"
#include < stdio.h >

/* 电磁阀控制引脚 */
#define SOLENOID_VALVE_PIN GPIO_Pin_10
#define SOLENOID_VALVE_PORT GPIOB

/**
 * @brief 初始化电磁阀(继电器)控制
 * @details 配置GPIO引脚为开漏模式用于继电器控制
 */
void sv_init(void) {
  GPIO_InitTypeDef gpio_init_struct;

  // 使能GPIOB时钟
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

  // 配置引脚为开漏输出(继电器控制)
  gpio_init_struct.GPIO_Mode = GPIO_Mode_Out_OD;
  gpio_init_struct.GPIO_Speed = GPIO_Speed_50MHz;
  gpio_init_struct.GPIO_Pin = SOLENOID_VALVE_PIN;
  GPIO_Init(SOLENOID_VALVE_PORT, &gpio_init_struct);

  // 初始状态:阀门关闭(引脚高电平,开漏模式配合内部上拉)
  GPIO_SetBits(SOLENOID_VALVE_PORT, SOLENOID_VALVE_PIN);
}

/**
 * @brief 关闭继电器(停止浇水)
 * @details 设置继电器控制引脚为高电平,使电磁阀失活
 */
void sv_close(void) {
  GPIO_SetBits(SOLENOID_VALVE_PORT, SOLENOID_VALVE_PIN);
  printf("Solenoid valve closed (high humidity).rn");
}

/**
 * @brief 打开继电器(开始浇水)
 * @details 清除继电器控制引脚为低电平,使电磁阀激活
 */
void sv_open(void) {
  GPIO_ResetBits(SOLENOID_VALVE_PORT, SOLENOID_VALVE_PIN);
  printf("Solenoid valve opened (low humidity).rn");
}
    

5.5 连接阿里云

1.功能定位:通过 MQTT 协议实现设备与阿里云 IoT 平台的双向通信,完成传感器数据上报与云端控制指令接收。

2.核心配置:包含阿里云MQTT 服务器地址、端口、认证信息(客户端ID、用户名、密码)及收发主题,采用QoS0等级通信。

3.关键流程:

初始化:解析域名、建立网络连接、配置MQTT客户端参数。

通信机制:通过状态机管理连接、订阅、消息收发及错误重连。

数据交互:将传感器数据(湿度、光照、阀门状态)打包为JSON上报;解析云端上下发的JSON指令,控制阀门门开关及更新湿度阈值。

4.与系统集成:关联传感器数据全局变量,调用继电器控制函数,实现本地设备状态与云端的同步。

需要注意:mqttconn s_mqtt_connection_params函数中参数要修改为4.1.2中创建设备的MQTT参数和订阅发布主题。

#include "MQTTClient.h"
#include "cJSON.h"
#include "delay.h"
#include "do_dns.h"
#include "do_mqtt.h"
#include "mqtt_interface.h"
#include "sv.h"
#include "wiz_interface.h"
#include "wizchip_conf.h"
#include < string.h >


#define MQTT_ETHERNET_MAX_SIZE (1024 * 2) // MQTT缓冲区最大大小

/* MQTT控制句柄 */
MQTTClient g_mqtt_client = {0};
Network g_mqtt_network = {0};
int g_mqtt_conn_status;
static uint8_t s_mqtt_run_status = CONN; // MQTT状态机状态

/* MQTT连接参数(替换为你的设备凭证) */
static mqttconn s_mqtt_connection_params = {
    .mqttHostUrl = "iot-06z009vm5y6jfwj.mqtt.iothub.aliyuncs.com",
    .server_ip =
        {
            0,
        },
    .port = 1883,
    .clientid = "k1zh33h3hte.IGAT|securemode=2,signmethod=hmacsha256,timestamp="
                "1752635274022|",
    .username = "IGAT&k1zh33h3hte",
    .passwd =
        "f04b7d14d10a981e0eb6248da38b52060ff443c3f4b825d01594dfaa7e5720c1",
    .pubtopic = "/sys/k1zh33h3hte/IGAT/thing/event/property/post",
    .subtopic = "/sys/k1zh33h3hte/IGAT/thing/service/property/set",
    .pubQoS = QOS0,
};

/* MQTT接收缓冲区(静态) */
static char s_mqtt_received_msg[512] = {0};
static uint8_t s_mqtt_receive_flag = 0; // 新MQTT消息标志

/* MQTT消息/配置结构 */
MQTTMessage g_mqtt_pub_msg = {.qos = QOS0, .retained = 0, .dup = 0, .id = 0};
MQTTPacket_willOptions g_mqtt_will = MQTTPacket_willOptions_initializer;
MQTTPacket_connectData g_mqtt_conn_data = MQTTPacket_connectData_initializer;

/* 全局传感器数据(与main.c共享) */
float g_humidity_value = 0.0f;      // 当前湿度
float g_light_intensity = 0.0f;     // 当前光照强度
uint8_t g_solenoid_valve_state = 0; // 电磁阀状态(1:开启, 0:关闭)

/* 湿度阈值(与main.c共享) */
extern int g_humidity_low_threshold;
extern int g_humidity_high_threshold;

/**
 * @brief 初始化MQTT客户端
 * @param sn 套接字号
 * @param send_buf MQTT发送数据缓冲区
 * @param recv_buf MQTT接收数据缓冲区
 */
void mqtt_init(uint8_t sn, uint8_t *send_buf, uint8_t *recv_buf) {
  wiz_NetInfo network_info = {0};
  wizchip_getnetinfo(&network_info);

  // 将MQTT服务器域名解析为IP地址
  if (do_dns(send_buf, (uint8_t *)s_mqtt_connection_params.mqttHostUrl,
             s_mqtt_connection_params.server_ip)) {
    while (1)
      ; // DNS解析失败时停机
  }

  // 初始化网络和MQTT客户端
  NewNetwork(&g_mqtt_network, sn);
  ConnectNetwork(&g_mqtt_network, s_mqtt_connection_params.server_ip,
                 s_mqtt_connection_params.port);
  MQTTClientInit(&g_mqtt_client, &g_mqtt_network, 1000, send_buf,
                 MQTT_ETHERNET_MAX_SIZE, recv_buf, MQTT_ETHERNET_MAX_SIZE);

  // 配置MQTT连接参数
  g_mqtt_conn_data.willFlag = 0;
  g_mqtt_conn_data.MQTTVersion = 4;
  g_mqtt_conn_data.clientID.cstring = s_mqtt_connection_params.clientid;
  g_mqtt_conn_data.username.cstring = s_mqtt_connection_params.username;
  g_mqtt_conn_data.password.cstring = s_mqtt_connection_params.passwd;
  g_mqtt_conn_data.keepAliveInterval = 30;
  g_mqtt_conn_data.cleansession = 1;
}

/**
 * @brief 解码MQTT JSON消息(设置阈值/阀门状态)
 * @param msg JSON消息负载
 */
void mqtt_json_decode(char *msg) {
  cJSON *root_json = cJSON_Parse(msg);
  if (!root_json) {
    printf("MQTT JSON parse failedrn");
    return;
  }

  // 从JSON中提取"params"对象
  cJSON *params_json = cJSON_GetObjectItem(root_json, "params");
  if (!params_json) {
    cJSON_Delete(root_json);
    return;
  }

  // 如果"Elect"字段存在,更新电磁阀状态
  cJSON *valve_json = cJSON_GetObjectItem(params_json, "Elect");
  if (valve_json) {
    g_solenoid_valve_state = valve_json->valueint;
    if (g_solenoid_valve_state) {
      sv_open();
    } else {
      sv_close();
    }
  }

  // 如果"Low"/"High"字段存在,更新湿度阈值
  cJSON *low_threshold_json = cJSON_GetObjectItem(params_json, "Low");
  cJSON *high_threshold_json = cJSON_GetObjectItem(params_json, "High");
  if (low_threshold_json) {
    g_humidity_low_threshold = low_threshold_json->valueint;
  }
  if (high_threshold_json) {
    g_humidity_high_threshold = high_threshold_json->valueint;
  }

  // 确保阈值范围有效(下限 <= 上限)
  if (g_humidity_low_threshold > g_humidity_high_threshold) {
    g_humidity_low_threshold = g_humidity_high_threshold - 1;
  }

  cJSON_Delete(root_json); // 释放JSON对象
}

/**
 * @brief 接收MQTT消息的回调函数
 * @param md 消息数据(主题和负载)
 */
void mqtt_message_arrived(MessageData *md) {
  char topic_name[64] = {0};
  char msg_payload[512] = {0};

  // 提取主题和负载
  sprintf(topic_name, "%.*s", (int)md->topicName->lenstring.len,
          md->topicName->lenstring.data);
  sprintf(msg_payload, "%.*s", (int)md->message->payloadlen,
          (char *)md->message->payload);
  printf("MQTT recv: %s, %srnrn", topic_name, msg_payload);

  // 存储消息并设置标志
  s_mqtt_receive_flag = 1;
  memset(s_mqtt_received_msg, 0, sizeof(s_mqtt_received_msg));
  memcpy(s_mqtt_received_msg, msg_payload, strlen(msg_payload));
}

/**
 * @brief 通过MQTT发布传感器数据
 * @details 以JSON格式发送湿度、光照、阀门状态和阈值
 */
void mqtt_post_properties(void) {
  char json_payload[256] = {0};

  // 将传感器数据格式化为JSON负载
  int payload_len =
      snprintf(json_payload, sizeof(json_payload),
               "{"id":"123","version":"1.0","params":{"
               ""Elect":%d,"
               ""Humidity":%.1f,"
               ""light":%.0f,"
               ""Low":%d,"
               ""High":%d"
               "},"method":"thing.event.property.post"}",
               g_solenoid_valve_state, g_humidity_value, g_light_intensity,
               g_humidity_low_threshold, g_humidity_high_threshold);

  // 发布消息
  g_mqtt_pub_msg.payload = json_payload;
  g_mqtt_pub_msg.payloadlen = payload_len;
  MQTTPublish(&g_mqtt_client, s_mqtt_connection_params.pubtopic,
              &g_mqtt_pub_msg);
  printf("MQTT published: %s, %srnrn", s_mqtt_connection_params.pubtopic,
         json_payload);
}

/**
 * @brief MQTT状态机(处理连接、订阅和消息)
 */
void do_mqtt(void) {
  uint8_t ret;
  switch (s_mqtt_run_status) {
  case CONN: // 连接到MQTT服务器
    ret = MQTTConnect(&g_mqtt_client, &g_mqtt_conn_data);
    printf("Connecting to MQTT server: %d.%d.%d.%d:%drn",
           s_mqtt_connection_params.server_ip[0],
           s_mqtt_connection_params.server_ip[1],
           s_mqtt_connection_params.server_ip[2],
           s_mqtt_connection_params.server_ip[3],
           s_mqtt_connection_params.port);
    printf("Connection %srnrn", ret == SUCCESSS ? "success" : "failed");
    s_mqtt_run_status = (ret == SUCCESSS) ? SUB : ERR;
    break;

  case SUB: // 订阅主题
    ret = MQTTSubscribe(&g_mqtt_client, s_mqtt_connection_params.subtopic,
                        s_mqtt_connection_params.pubQoS, mqtt_message_arrived);
    printf("Subscribing to %srn", s_mqtt_connection_params.subtopic);
    printf("Subscription %srnrn", ret == SUCCESSS ? "success" : "failed");
    s_mqtt_run_status = (ret == SUCCESSS) ? KEEPALIVE : ERR;
    break;

  case KEEPALIVE: // 维持连接并检查消息
    if (MQTTYield(&g_mqtt_client, 30) != SUCCESSS) {
      s_mqtt_run_status = ERR;
      break;
    }
    // 继续处理接收的消息
  case RECV: // 处理接收的消息
    if (s_mqtt_receive_flag) {
      s_mqtt_receive_flag = 0;
      mqtt_json_decode(s_mqtt_received_msg); // 解码并处理消息
    }
    break;

  case ERR: // 处理错误(重试)
    printf("MQTT error! Reconnecting...rn");
    delay_ms(1000);
    s_mqtt_run_status = CONN; // 重试连接
    break;

  default:
    break;
  }
}
    

5.6 网页控制

1.功能定位:提供Web服务接口,支持通过HTTP协议获取设备状态和控制参数。

2.核心功能:

解析HTTP 请求(支持 GET、POST方法)。

提供多个API端点:

根路径/:返回网页内容。

/api/sensor:以JSON格式返回传感器数据(湿度、光照、阀门状态、阈值)。

/api/threshold:接收POST请求更新湿度阈值。

3.数据交互:

读取全局变量获取传感器状态和阈值。

通过HTTP响应返回JSON格式数据。

解析POST请求中的JSON数据更新系统参数。

4.通信管理:

基于TCP状态机管理连接生命周期(建立、数据传输、关闭)。

处理不同HTTP状态码(200、204、400、404)。

#include "cJSON.h"
#include "loopback.h"
#include "socket.h"
#include "web_page.h"
#include "wizchip_conf.h"
#include < stdio.h >
#include < stdlib.h >
#include < string.h >


/* 使用web_page.h中定义的HTTP_RESPONSE_404 */
#define DATA_BUF_SIZE 1024
#define STR(x) #x // 用于行号的字符串化宏

/* 全局传感器数据(与main.c共享) */
extern float g_humidity_value;
extern float g_light_intensity;
extern uint8_t g_solenoid_valve_state;
extern int g_humidity_low_threshold;
extern int g_humidity_high_threshold;

/**
 * @brief HTTP请求行结构(方法、URI、版本)
 */
typedef struct {
  char method[16];  // 例如:"GET"
  char uri[256];    // 例如:"/api/sensor"
  char version[16]; // 例如:"HTTP/1.1"
} HttpReqLine;

/**
 * @brief 从原始数据解析HTTP请求行
 * @param request 原始HTTP请求数据
 * @param req_line 存储解析结果的输出结构
 * @return 成功返回0,失败返回-1
 */
static int http_parse_request_line(const char *request, HttpReqLine *req_line) {
  char buffer[1024];
  strncpy(buffer, request, sizeof(buffer));
  buffer[sizeof(buffer) - 1] = ''; // 确保字符串以空字符结尾

  // 查找第一行的结束符("rn"或"n")
  char *line_end = strstr(buffer, "rn");
  if (!line_end) {
    line_end = strstr(buffer, "n"); // 兼容Unix换行符
  }
  if (!line_end) {
    return -1; // 格式无效
  }
  *line_end = ''; // 截断到第一行

  // 使用空格分割为方法、URI和版本
  char *method = strtok(buffer, " ");
  char *uri = strtok(NULL, " ");
  char *version = strtok(NULL, " ");

  if (!method || !uri || !version) {
    return -1; // 请求行不完整
  }

  // 将解析的值复制到结构中
  strncpy(req_line->method, method, sizeof(req_line->method) - 1);
  strncpy(req_line->uri, uri, sizeof(req_line->uri) - 1);
  strncpy(req_line->version, version, sizeof(req_line->version) - 1);

  return 0;
}

/**
 * @brief 发送带有CORS头的HTTP响应
 * @param sn 套接字号
 * @param status HTTP状态码(例如:"200 OK")
 * @param content_type MIME类型(例如:"application/json")
 * @param body 响应体内容
 */
static void send_http_response(uint8_t sn, const char *status,
                               const char *content_type, const char *body) {
  char response[512];
  int body_len = strlen(body);

  // 构建带有CORS头的响应
  sprintf(response,
          "HTTP/1.1 %srn"
          "Content-Type: %srn"
          "Access-Control-Allow-Origin: *rn"
          "Access-Control-Allow-Methods: GET, POST, OPTIONSrn"
          "Access-Control-Allow-Headers: Content-Typern"
          "Connection: closern"
          "Content-Length: %drn"
          "rn"
          "%s",
          status, content_type, body_len, body);

  // 发送完整响应
  send(sn, (uint8_t *)response, strlen(response));
}

/**
 * @brief 带HTTP支持的TCP服务器回环测试
 * @param sn 套接字号
 * @param network_buf 网络数据缓冲区
 * @param port 监听端口
 * @return 成功返回1,失败返回错误码
 */
int32_t loopback_tcps(uint8_t sn, uint8_t *network_buf, uint16_t port) {
  int32_t ret;
  uint16_t recv_size = 0;
  uint16_t send_size = 0;

  switch (getSn_SR(sn)) {
  case SOCK_ESTABLISHED: // 连接已建立
    if (getSn_IR(sn) & Sn_IR_CON) {
      setSn_IR(sn, Sn_IR_CON); // 清除连接中断标志
    }

    // 检查接收数据
    if ((recv_size = getSn_RX_RSR(sn)) > 0) {
      if (recv_size > DATA_BUF_SIZE) {
        recv_size = DATA_BUF_SIZE; // 限制为缓冲区大小
      }

      // 读取接收的数据
      ret = recv(sn, network_buf, recv_size);
      if (ret <= 0) {
        return ret; // 处理错误
      }
      recv_size = (uint16_t)ret;
      network_buf[recv_size] = ''; // 添加空终止符

      // 解析HTTP请求行
      HttpReqLine req_line;
      if (http_parse_request_line((char *)network_buf, &req_line) == 0) {
        printf("HTTP Method: %s, URI: %sn", req_line.method, req_line.uri);

        // 处理OPTIONS请求(CORS预检)
        if (strcmp(req_line.method, "OPTIONS") == 0) {
          send_http_response(sn, "204 No Content", "text/plain", "");
          disconnect(sn);
          close(sn);
        }

        // 处理GET /(提供网页)
        else if (strcmp(req_line.method, "GET") == 0 &&
                 strcmp(req_line.uri, "/") == 0) {
          uint16_t content_len = strlen(index_page);
          send_size = 0;
          while (strlen(index_page) != send_size) {
            ret = send(sn, (uint8_t *)index_page + send_size,
                       strlen(index_page) - send_size);
            if (ret < 0) {
              close(sn);
              return ret;
            }
            send_size += ret;
          }
          disconnect(sn);
          close(sn);
        }

        // 处理GET /api/sensor(返回传感器数据)
        else if (strcmp(req_line.method, "GET") == 0 &&
                 strcmp(req_line.uri, "/api/sensor") == 0) {
          char sensor_json[128];
          sprintf(sensor_json,
                  "{"humi":%.1f,"light":%.0f,"sv":"%s","low":%d,"
                  ""high":%d}",
                  g_humidity_value, g_light_intensity,
                  g_solenoid_valve_state ? "ON" : "OFF",
                  g_humidity_low_threshold, g_humidity_high_threshold);

          send_http_response(sn, "200 OK", "application/json", sensor_json);
          disconnect(sn);
          close(sn);
        }

        // 处理POST /api/threshold(更新阈值)
        else if (strcmp(req_line.method, "POST") == 0 &&
                 strcmp(req_line.uri, "/api/threshold") == 0) {
          // 查找请求体(健壮解析)
          char *body_start = NULL;
          char *header_end = strstr((char *)network_buf, "rnrn");

          if (header_end) {
            body_start = header_end + 4; // 跳过"rnrn"
          } else {
            // 兼容Unix换行符
            header_end = strstr((char *)network_buf, "nn");
            if (header_end) {
              body_start = header_end + 2; // 跳过"nn"
            }
          }

          // 验证请求体存在
          if (!body_start || body_start >= (char *)network_buf + recv_size) {
            send_http_response(
                sn, "400 Bad Request", "application/json",
                "{"success":false, "error":"Missing request body"}");
            disconnect(sn);
            close(sn);
            return 1;
          }

          // 解析JSON请求体
          cJSON *root_json = cJSON_Parse(body_start);
          if (!root_json) {
            send_http_response(
                sn, "400 Bad Request", "application/json",
                "{"success":false, "error":"Invalid JSON format"}");
            disconnect(sn);
            close(sn);
            return 1;
          }

          // 提取并验证阈值
          cJSON *low_json = cJSON_GetObjectItem(root_json, "low");
          cJSON *high_json = cJSON_GetObjectItem(root_json, "high");

          if (cJSON_IsNumber(low_json) && cJSON_IsNumber(high_json)) {
            int new_low = low_json->valueint;
            int new_high = high_json->valueint;

            // 验证范围
            if (new_low < 0 || new_high > 100 || new_low >= new_high) {
              send_http_response(sn, "400 Bad Request", "application/json",
                                 "{"success":false, "error":"Invalid "
                                 "range (0-100, low < high)"}");
            } else {
              // 更新阈值
              g_humidity_low_threshold = new_low;
              g_humidity_high_threshold = new_high;
              printf("Thresholds updated: Low=%d, High=%dn", new_low,
                     new_high);

              send_http_response(sn, "200 OK", "application/json",
                                 "{"success":true}");
            }
          } else {
            send_http_response(sn, "400 Bad Request", "application/json",
                               "{"success":false, "error":"Missing 'low' "
                               "or 'high' parameters"}");
          }

          cJSON_Delete(root_json); // 释放JSON对象
          disconnect(sn);
          close(sn);
        }

        // 处理未知请求
        else {
          send_http_response(sn, "404 Not Found", "text/html",
                             "< html >
Page Not Found< /body >< /html >"); disconnect(sn); close(sn); } } else { printf("Failed to parse HTTP requestn"); send_http_response( sn, "400 Bad Request", "application/json", "{"success":false, "error":"Invalid request format"}"); disconnect(sn); close(sn); } } break; case SOCK_CLOSE_WAIT: // 关闭等待状态 if ((ret = disconnect(sn)) != SOCK_OK) { return ret; } break; case SOCK_INIT: // 套接字已初始化,开始监听 if ((ret = listen(sn)) != SOCK_OK) { return ret; } break; case SOCK_CLOSED: // 套接字已关闭,重新初始化 if ((ret = socket(sn, Sn_MR_TCP, port, 0x00)) != sn) { return ret; } break; default: break; } return 1; }

6 功能验证

程序烧录完毕,硬件连接完成如下图所示:

wKgZO2iVaIWARj4jAAQN6z0w7u0234.png

硬件连接完毕,上电通过串口助手打印如下信息:

wKgZPGiVaISAUsQYAAJf0cW25-A933.png

6.1 同步阿里云

通过串口每5S采集一次数据发送到云平台,当我们把光照传感器逐渐靠近光源,光照值越来越大。此时土壤湿度传感器未插入土壤,在空气中检测湿度小于30%RH,继电器打开,开始浇灌。

wKgZO2iVaISAEVPTAAN1jqcGqso148.pngwKgZO2iVaJOARsYvAFpZ7_vtiBQ973.png

当我们把土壤湿度传感器插入盆栽中,可以看到当湿度小于30%RH时自动开始灌溉,当湿度大于50%RH时停止灌溉。

wKgZO2iVaIeAe0LdAAg-_bDyPD4184.pngwKgZPGiVaJWALbVwAGow3Lx-be0266.png

当湿度小于30%RH时继电器打开,开始浇灌,当湿度大于50%RH时继电器关闭停止灌溉。

wKgZO2iVaImAG5jlAA_zCL5JI7A949.png

服务器下发指令控制继电器开关,进而实现对浇灌启停的控制。

wKgZPGiVaIqAJiZxAAzLYddel64222.png

6.2 网页控制

通过网页平台不仅能实时监测农田环境数据,更可远程灵活设定调控阈值,让田间管理实现精准化、智能化调控。

wKgZPGiVaIeAUB13AANEmCYUlgI206.png

7总结

该系统以W55MH32L-EVB为核心,通过MQTT连阿里云,采集土壤湿度、光照数据,实现水泵智能控制。支持自动与远程调控,5秒采集一次,同时支持网页修改湿度阈值,功能稳定,为智能灌溉提供有效方案。感谢大家的耐心阅读!如果您在搭建过程中遇到硬件接线、阿里云配置或代码调试问题,欢迎在评论区留言交流~ 觉得有帮助的话也请点赞收藏,您的支持是我分享的动力!

审核编辑 黄宇

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 单片机
    +关注

    关注

    6074

    文章

    45341

    浏览量

    663700
  • 阿里云
    +关注

    关注

    3

    文章

    1028

    浏览量

    45506
  • MQTT协议
    +关注

    关注

    0

    文章

    103

    浏览量

    6425
  • 智能农业
    +关注

    关注

    0

    文章

    134

    浏览量

    8792
收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    基于阿里MQTT物联网平台视频监控(下)

    1.项目介绍       本项目基于物联量平台远程的视频监控项目,通过MQTT协议实现两个设备间的数据上报与订阅。通过这个项目来演示,两个MQTT设备如何互相订阅,进行消息流转。在
    的头像 发表于 04-24 14:41 2405次阅读
    基于<b class='flag-5'>阿里</b><b class='flag-5'>云</b><b class='flag-5'>MQTT</b>物联网<b class='flag-5'>平台</b>视频<b class='flag-5'>监控</b>(下)

    esp8266连接阿里平台mqtt连接超时

    esp8266nodemcu在使用arduino.ide连接阿里平台的时候,wifi配置正常但连接不上mqtt,显示报错如下: Attempting
    发表于 10-26 21:39

    【NXP LPC54110试用申请】基于计算的农业智能检测及农产品溯源系统

    、平板电脑端、PC电脑端无缝对接。管理者可随时随地对种植园区进行远程监控。项目的独特之处:目前市场上大多溯源系统只能展示如品种、产地、生产企业、产品简介等简单的信息,基于计算的农业
    发表于 08-01 11:31

    基于阿里HiTSDB搭建工业物联网平台实践

    :https://promotion.aliyun.com/ntms/act/hitsdbdebute2018.html平台架构边缘计算:采集的工业数据上传到阿里的物联网套件,中间经过了MQ
    发表于 04-24 15:37

    基于onenet平台MQTT协议数据采集以及远程控制的个人总结资料

    基于onenet平台的环境监测采集以及相应远程控制的个人总结修改的代码资料。网络传输协议为MQTT
    发表于 04-01 12:33

    如何通过MQTT协议连接物联网阿里实现设备的远程IO监控开关量数字量模拟量状态的读取及控制

    本文介绍一种基于综科智控科技开发有限公司的物联网网关ZKB-1E1L实现的通过MQTT协议连接物联网阿里实现设备的远程IO监控开关量数字量模拟量状态的读取及
    发表于 09-19 11:59

    基于鸿蒙Hi3861V100 MQTT协议 对接阿里物联网平台

    更新啦!!!!!!!!!!基于鸿蒙HarmonyOS Hi3861V100 开发板通过MQTT协议 对接阿里IOT物联网平台同时支持APP端、IOT
    发表于 01-25 08:31

    stm32+W5500 与 阿里微消息队列 MQTT版本

    本帖最后由 北洋水师 于 2021-7-23 11:00 编辑 目的: STM32 + W5500 嵌入式以太网卡访问 阿里微消息队列 MQTT 服务器、实现基础的发布和订阅。开发工具
    发表于 07-23 10:55

    如何用阿里的Iot Studio制作web网页

    如何用阿里的Iot Studio制作web网页呢?并用产品自带的topic传输数据网页端呢?
    发表于 02-22 06:21

    基于OpenHarmony的阿里IoT服务实现

    用,广泛应用于物联网(IoT)。MQTT协议在卫星链路通信传感器、医疗设备、智能家居、及一些小型化设备中已广泛使用。阿里为国内主流的
    发表于 06-17 09:36

    【OpenHarmony开源开发者成长计划解决方案学生挑战赛】--基于OpenHarmony的智慧农业环境监控系统设计

    【项目名称】基于OpenHarmony的智慧农业环境监控系统设计【项目负责人】:张铭哲1、项目描述​环境监控和自动化控制
    发表于 09-02 21:20

    微信小程序使用MQTT远程控制单片机——阿里物联网平台

    微信小程序使用MQTT远程控制单片机——阿里物联网平台
    发表于 11-13 17:36 36次下载
    微信小程序使用<b class='flag-5'>MQTT</b>远程<b class='flag-5'>控制</b>单片机——<b class='flag-5'>阿里</b><b class='flag-5'>云</b>物联网<b class='flag-5'>平台</b>

    基于阿里MQTT物联网平台视频监控(上)

    本项目基于物联量平台远程的视频监控项目,通过MQTT协议实现两个设备间的数据上报与订阅。通过这个项目来演示,两个MQTT设备如何互相订阅,进行消息流转。在
    的头像 发表于 04-18 16:58 2368次阅读
    基于<b class='flag-5'>阿里</b><b class='flag-5'>云</b><b class='flag-5'>MQTT</b>物联网<b class='flag-5'>平台</b>视频<b class='flag-5'>监控</b>(上)

    MQTT接入阿里IoT平台使用说明

    MQTT接入阿里IoT平台使用说明
    发表于 03-06 17:37 4次下载

    如何轻松实现MQTT接入阿里IoT平台

    教你轻松实现使用MQTT协议接入阿里平台
    发表于 03-29 11:05 10次下载