2016-04-18 20:42:58 zhou6343178 阅读数 26923
  • 边缘计算 edgexfoundry 实战与源码剖析 之 app-...

    Linux 基金组织启动开源项目 EdgeX Foundry ,为物联网边缘计算开发一个标准化互操作框架。本课程详细剖析edgexfoundry golang 爱丁堡版本 app-function-sdk实现原理,与实战操作,将从core-data模块接到的event进行业务处理,如生成mqtt消息、发送restful到第三方、支持cbor格式生成图片等

    544 人正在学习 去看看 张忠辉

1、准备条件:
a)硬件:ATMEGA644PA 8位单片机 ENC28J60
b)下载MQTT c语言的包:
C/C++ MQTT Embedded clients
https://www.eclipse.org/paho/clients/c/embedded/
c)MQTT基础知识
http://mqtt.org/
http://download.csdn.net/detail/zhou6343178/9495445

2、移植MQTTPacket到项目里:
复制文件夹MQTTPacket到工程,复制MQTTPacket\samples里面的transport.c、transport.h到工程
这里写图片描述

3、修改已经transport.c里面的四个接口:

int transport_sendPacketBuffer(int sock, unsigned char* buf, int buflen);
int transport_getdata(unsigned char* buf, int count);
int transport_open(char* host, int port);
int transport_close(int sock);

看接口名称就知道,该四个接口实现的是TCP数据的收发,具体修改需要根据已经实现的TCP接口来定。(单片机上常用的TCP/IP协议栈主要有uip、LwIP)
注意:MQTT是tcp/ip的应用层,传输层需要自己实现,这四个接口分别对应TCP的连接、收、发和关闭。

4、实现订阅/发布,查看MQTTPacket\samples下的pub0sub1.c。
下面为参数的意义:
MQTTPacket_connectData data = MQTTPacket_connectData_initializer; //连接参数
data.clientID.cstring = “me2”; //客户ID,唯一
data.keepAliveInterval = 200; //保存存活时间200s
data.cleansession = 1;//重新连接后是否清除以前的信息 0表示不清除,1表示重连清除
data.username.cstring = “admin”;//如果服务器需要用户名密码,这里设置
data.password.cstring = “admin”;
Qos :
0 表示最多一次
1 表示至少一次
2 表示只有一次

MQTTDeserialize_publish(&dup,&qos,&retained,&msgid,&receivedTopic,&payload_in, &payloadlen_in, buf,buflen);

msgid:该包的ID,paketid,如果需要增加反馈(QoS为1和2需要反馈),需要获取该ID

发送反馈:

MQTTDeserialize_publish(&dup, &qos, &retained, &msgid,&receivedTopic,&payload_in, &payloadlen_in, buf, buflen);
int len = MQTTSerialize_puback(buf,buflen,msgid);
transport_sendPacketBuffer(ack_buf,len);

接收服务器的反馈:

if(PUBACK == MQTTPacket_read(buf,buflen,transport_getdata))
{
    unsigned char packettype = 0;
    unsigned char dup_ack = 0;
    unsigned short packetid = 0;

    MQTTDeserialize_ack(&packettype,&dup_ack,&packetid,buf,buflen);
}

比较发送的msgid和反馈获取的packetid,如果一致表示该包发送成功。
等等,具体看文档

5、服务器实现 mosquitto
请参考:http://blog.csdn.net/xukai871105/article/details/39252653
指令举例:
订阅 mosquitto_sub -h xxx.xxx.xxx.xxx -u admin -P admin -t substopic
-h 服务器域名
-u 用户名(没有可以省略)
-P密码(没有可以省略)
-t topic名称

发布:mosquitto_pub -h xxx.xxx.xxx.xxx -u admin -P admin -t substopic -m hello
-m 发布的内容

以上基本实现单片机与服务器的联调。如果需要手机控制单片机,可下载手机相关的MQTT包进行通讯。

=====更新(2018-03-09)======
很高兴这篇笔记能给有些人带来帮助,我把使用实例上传到 GitHub,其中有些对 MQTT 的改动,供大家参考。

2019-06-21 09:47:00 qq_34178998 阅读数 961
  • 边缘计算 edgexfoundry 实战与源码剖析 之 app-...

    Linux 基金组织启动开源项目 EdgeX Foundry ,为物联网边缘计算开发一个标准化互操作框架。本课程详细剖析edgexfoundry golang 爱丁堡版本 app-function-sdk实现原理,与实战操作,将从core-data模块接到的event进行业务处理,如生成mqtt消息、发送restful到第三方、支持cbor格式生成图片等

    544 人正在学习 去看看 张忠辉

    最近做了一个小项目,简单描述下项目结构,主要是java与单片机进行通信,为了实现通信可以采用中间件和http的方式,但是本人因为最近在学习ActiveMQ,所以更加偏向前者,最终确定了采用消息中间件的方式进行通信。

  首先完成的步骤分为这两步:

   1、单片机采用MQTT的通信协议将一个消息发送到一个消息中间件(本人采用ActiveMQ,也可以采用其他的消息中间件),这里需要注意下MQTT只能采用发布/订阅的模式进行消息传输。所以单边机将会发送一个带有消息体的主题到ActiveMQ中,

  2、JAVA部分则对该主题进行订阅,监听消息。

 而完成这两步,我现在有两种方案可供选择:

  1、单片机部分采用MQTT协议将主题消息发布到队列中,java部分直接整合ActiveMQ,用ActiveMQ对主题消息进行订阅。

   2、单片机部分采用MQTT协议将主题消息发布到队列中,java部分也采用MQTT协议进行处理,整合MQTT协议。

之后经过验证,还是采用了第二种方案,理由如下:采用方案1,单片机部分是采用MQTT协议进行发送,java部分接收消息,需要对消息进行处理,采用方案2可以直接接收消息内容。

若是采用方案1,则处理消息代码如下:

public void onMessage(Message message) {
	String responseXml = null;
	if (message instanceof BytesMessage) {
		ActiveMQBytesMessage bytesMessage = (ActiveMQBytesMessage) message;
		byte[] bys = null;
		try {
			bys = new byte[(int) bytesMessage.getBodyLength()];
			bytesMessage.readBytes(bys);
			responseXml = new String(bys);
			/*******此处为模拟数据******************/
			System.out.println("收到32单片机消息:" + responseXml);
		} catch (JMSException e) {
			e.printStackTrace();
		}
	} else {
		TextMessage bm = (TextMessage) message;
        try {
            responseXml = bm.getText();
            System.out.println("------------内容是:" + responseXml);
        } catch (JMSException e) {
            logger.error(e.getMessage(), e);
        }
	}

}

采用方案二:java部分采用SpringMVC+Spring+Mybatis框架(PS:网上SpringBoot+MQTT的资料很多)+MQTT

项目结构如下图所示

 考虑项目代码可能多,所以只贴关键性代码,首先将一个ssm框架搭建好后就可以开始配置MQTT了,

1、引入jar包

        <!-- mqtt开始 -->
		<dependency>
			<groupId>org.springframework.integration</groupId>
			<artifactId>spring-integration-core</artifactId>
			<version>4.1.0.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.integration</groupId>
			<artifactId>spring-integration-mqtt</artifactId>
			<version>4.1.0.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.eclipse.paho</groupId>
			<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
			<version>1.2.0</version>
		</dependency>
		<!-- mqtt结束 -->

2、配置spring-mqtt.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:int="http://www.springframework.org/schema/integration"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:int-mqtt="http://www.springframework.org/schema/integration/mqtt"
	xsi:schemaLocation="
        http://www.springframework.org/schema/integration 
        http://www.springframework.org/schema/integration/spring-integration-4.1.xsd
        http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans-3.1.xsd 
        http://www.springframework.org/schema/integration/mqtt 
        http://www.springframework.org/schema/integration/mqtt/spring-integration-mqtt-4.1.xsd
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context-3.1.xsd  ">

	<!-- 引入配置文件- -->
	<bean id="propertyConfigurer"
		class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
		<property name="location" value="classpath:jdbc.properties" />
	</bean>

	<bean id="clientFactory"
		class="org.springframework.integration.mqtt.core.DefaultMqttPahoClientFactory">
		<property name="userName" value="${mqtt.username}" />
		<property name="password" value="${mqtt.password}" />
		<property name="serverURIs">
			<array>
				<value>${mqtt.serverURI1}</value>
			</array>
		</property>
	</bean>
	<bean id="mqttHandler" class="org.springframework.integration.mqtt.outbound.MqttPahoMessageHandler">
		<constructor-arg name="clientId" value="${mqtt.clientId}"></constructor-arg>
		<constructor-arg name="clientFactory" ref="clientFactory"></constructor-arg>
		<property name="async" value="${mqtt.async}" />
		<property name="defaultQos" value="${mqtt.defaultQos}" />
		<property name="completionTimeout" value="${mqtt.completionTimeout}" />
	</bean>

	<!-- 消息适配器  -->
	<int-mqtt:message-driven-channel-adapter
		id="mqttInbound" client-id="CID_20190617" url="${mqtt.serverURI1}"
		topics="${mqtt.topic}" qos="${mqtt.defaultQos}" client-factory="clientFactory"  auto-startup="true"
		send-timeout="${mqtt.completionTimeout}" channel="startCase" />
		<int:channel id="startCase" />
		
	<!-- 消息处理类 -->
	<int:service-activator id="startCaseService"
    input-channel="startCase" ref="mqttCaseService" method="startCase" />
    <!--这里是指向消息监听到后对消息处理的类-->
    <bean id="mqttCaseService" class="com.zlw.comm.mqtt.MqttService" />
</beans>

3、jdbc.properties配置MQTT相关参数

#-----------------------------------mqtt配置----------------------------
#MQTT-登陆名
mqtt.username=admin
#MQTT-密码
mqtt.password=admin
#级别
mqtt.defaultQos=1
#MQTT-地址----配你的MQ的地址与端口
mqtt.serverURI1=tcp://192.168.1.168:1883
#超时时间
mqtt.completionTimeout=20000
#MQTT-监听的主题
mqtt.topic=RECEIVE_DATA
#MQTT-客户端名称
mqtt.clientId=webClientId
#\u662F\u5426\u6E05\u9664\u4F1A\u8BDD
mqtt.cleanSession=false
#\u662F\u5426\u5F02\u6B65\u53D1\u9001
mqtt.async=true
#\u5FC3\u8DF3
mqtt.keepAliveInterval=30

4、消息处理类MqttService.java

public class MqttService {
	
	
	public void startCase(String message){
		JSONObject jb = FastJsonUtils.toJsonObject(message);
		System.out.println("收到32单片机消息:" + message);
    }

}

之后启动项目就可以了,当单片机部分发送主题为RECEIVE_DATA的消息主题时候,java这边就能监听到并将消息输出,

启动项目后,这里为了方便进行调试,在java端写了一个接口,进行接口测试,接口控制层代码

@Controller
@RequestMapping("mqtt")
public class MqttController extends BaseController{
	@Resource
	private MqttPahoMessageHandler mqttHandler;
 
    /**
     * @Title: sendHello 
     * @Description: TOD
     * @author  : cxding  
     * @param mqttToSave
     * @return
     */
    @RequestMapping(value = "/hello",method = RequestMethod.POST)
    @ResponseBody
    public Result<String> sendHello(String text){
       /* JSONObject jb = FastJsonUtils.toJsonObject(text);*/
        // 构建消息
        Message<String> messages = MessageBuilder.withPayload(text)
                .setHeader(MqttHeaders.TOPIC, "RECEIVE_DATA")
                .build();
        // 发送消息
        mqttHandler.handleMessage(messages);
    	/*String jsonstr = FastJsonUtils.getBeanToJson(mqttToSave.getRgFlowerPot());
    	mqttGateway.sendToMqtt(jsonstr,mqttToSave.getKdTopic());*/
        return new Result<String>("OK",true);
    }
    
}

采用postman进行接口测试:

 

查看消息队列

有一条消息并且已经被消费了

查看控制台

 

控制台成功打印出消息

项目已经打包上传,打包地址:https://download.csdn.net/download/qq_34178998/11251538

 

 

 

 

 

 

 

 

2016-04-15 10:49:07 Omega_Alpha 阅读数 19833
  • 边缘计算 edgexfoundry 实战与源码剖析 之 app-...

    Linux 基金组织启动开源项目 EdgeX Foundry ,为物联网边缘计算开发一个标准化互操作框架。本课程详细剖析edgexfoundry golang 爱丁堡版本 app-function-sdk实现原理,与实战操作,将从core-data模块接到的event进行业务处理,如生成mqtt消息、发送restful到第三方、支持cbor格式生成图片等

    544 人正在学习 去看看 张忠辉

最近物联网是如此的热门,公司的老大都忍不住要弄个探索的研究开发了,但是我们公司是做App和网站的好吧,于是自己不幸被抓包了;

做事之前先百度一下关于物联网的概念——物联网是新一代信息技术的重要组成部分,也是“信息化”时代的重要发展阶段。其英文名称是:“Internet of things(IoT)”。顾名思义,物联网就是物物相连的互联网。这有两层意思:其一,物联网的核心和基础仍然是互联网,是在互联网基础上的延伸和扩展的网络;其二,其用户端延伸和扩展到了任何物品与物品之间,进行信息交换通信,也就是物物相息。物联网通过智能感知、识别技术与普适计算等通信感知技术,广泛应用于网络的融合中,也因此被称为继计算机、互联网之后世界信息产业发展的第三次浪潮。物联网是互联网的应用拓展,与其说物联网是网络,不如说物联网是业务和应用。因此,应用创新是物联网发展的核心,以用户体验为核心的创新2.0是物联网发展的灵魂。

由于是简单的实验,我就采用了stm32 板子,定时收集传感器数据,通过串口转wifi连接网络上传,同时关注serve端传过来的消息;其中我这边socket连接服务器是在串口转wifi模块中设置好服务器的ip和port,这就不用我在stm32中操心了;当然也可以在stm32 中同过串口设置 串口转wifi模块 进入AT模式来控制socket的连接和断开,所以移植过来的mqtt代码中可以去掉这一块了;——对了,差点忘记了,关于mqtt的demo很多,网上下了一大堆源码,从中找了一个embed 相关的subscribe的 C源码;

原本以为这是一件很简单的事情,源码有了,stm32 开发用了keil4,用C来编写毫无问题;不管三七二十一的把.h和.c文件全部copy过来,。。。。于是问题来了,人间的代码是在linux上跑的,我这个stm32单片机上没有系统,代码中的那些.h文件十有八九不能用,于是只能辛辛苦苦的根据main函数一步一步的把代码拷过来,还好,大部分源码都不用改动,要改的只有两个方面,一是涉及系统时间的,这方面我就用单片机的定时器计时,二是源码中的socket 的read和write我用stm32串口来get和send;

大框架已经搭好,于是一步步配置好,进入循环中不断subscribe和publish就可以了;由于我下的源码是只有subscribe的,我无法知道人家是如何实现同时接受和发送的,但我想linux上多半是开个线程就可以了;由于我用的是stm32,没法开多线程,于是初学单片机的我傻眼了,我就简单粗暴的再while循环中先subscribe在publish,很明显,这绝对是错误的,运行起来服务器那里一发消息过来,我这边就很容易挂掉;于是我在想,linux的多线程在底层是怎么实现的呢?是不是和以前学到的所谓时间片的分配有关,那我岂不是没那个能力来实现了?好吧,这个问题看俩很深奥,留给以后的我给解决吧;作为初学者,我就采用了建立一个buffer,串口中断接收数据放到buffer中,循环中判断buffer有数据的话就去处理,没有的话就去publish;这样一个模型就畅快的建立起来了;

2019-08-27 19:35:57 qq_41879767 阅读数 113
  • 边缘计算 edgexfoundry 实战与源码剖析 之 app-...

    Linux 基金组织启动开源项目 EdgeX Foundry ,为物联网边缘计算开发一个标准化互操作框架。本课程详细剖析edgexfoundry golang 爱丁堡版本 app-function-sdk实现原理,与实战操作,将从core-data模块接到的event进行业务处理,如生成mqtt消息、发送restful到第三方、支持cbor格式生成图片等

    544 人正在学习 去看看 张忠辉
  基于MQTT通信协议 实现单片机与腾讯云IOT|阿里云IOT|中国移动ONENET的对接。
在实际调试时选择STM32F103C8T6与ESP826601S的开发板,对接过程是一个比较艰难的过程。编译、烧录、下载
至单片机后通过串口调试助手能够实现配置连接WIFI加入网络,和远端服务器建立TCP联系,但是云端设备始终处于
未激活状态,查找了很多做IOT通信方面的例程,很多例程都是基于云的SDK实现的,对于第一次调试玩IOT的小白而言
着实有点不大友好。后来查找设备未激活的缘由,主要认为是本机地址没能够和远程服务器实现匹配,然后就通过网络
调试助手,将MQTT协议报文发送过去。
  ONENET更新的幅度有点大,导致我竟然找不到它的加入产品与设备在哪个点击栏。我根据MQTT协议的配置方式和
 腾讯云主机地址、官方文档、加密解密方式发现挣扎了很久还是没能够得到对方主机20 02 00 00的CONNACK报文回复。
 那我最后辗转反侧又回到了阿里云的IOT,真的得感谢马云爸爸,总算是成功实现了设备激活,那主要就是分享一下和
 阿里云的故事了。

 1.首先要登录阿里云,可以使用支付宝登录,进行个人实名验证。然后找到物联网通信,阿里在物联网通信这方面的开发文档
 说实话其实蛮丰富的,其SDK环境也为各种语言的开发提供了可能,还提供了设备与云中间的开发平台便利操作。
 
 2.进入物联网通信控制平台,选择产品创建,在产品创建中完善相关信息,可以选择JSON数据格式,我本人选择的还是
 比较Low级别的自定义透传的二进制,选择设备节点类型。这个页面完成之后会出现产品密钥PRODUCTKEY,这个密钥可
 以复制到建立的记事本中,后面的一些配置会用到。还有一点,在这个创建产品的时候你需要注意到左上角你的远程
 主机地址是在哪,一般情况下默认设置为华东上海地区,地址不同可能导致你最后需要建立连接的域名也是不同的。
 以前阿里云和腾讯云的远程主机对接是不同的,现在好像两家都是一样的,配置时前面是:产品ID + “.”  + 服务器地址
 类似于这样:PRODUCTID.iot-as-mqtt.cn-shanghai.aliyuncs.com   PRODUCTID是你创建产品时会生成的,一般格式
 可能就是这样a16lzjHgAYy
 
 3.产品一栏中有产品名称与设备管理,点击设备管理,添加设备,添加设备页面需要填写对应归属的产品和设备名称,
 设备名称要用中文,一般情况下如果是建立三元关系组(APP IOT 单片机)的话,那可以命名为###kfb ###app,现在的
 要求长度是至少六个字符,我在设置的时候用的szykfb  szyapp分别加入两个设备,设备建立完成后会出现如下提醒信息
 那分别是产品密钥和设备名称、设备密钥
 {
  "ProductKey": "a16lzjHgAYy",
  "DeviceName": "szykfb",
 "DeviceSecret": "PKxVLqDI5bOnvbUXw299vG5nDOTjosbj"
 }
 那剩下APP设备的加入也是这样,那接下来主要就是配置相应的加密解密信息了。
 
 4.阿里云MQTT协议需要首先建立connect报文,建立三元关系组的connect报文。
 客户端ID :  *|securemode=3,signmethod=hmacsha1|             *设备名称   注意替换
 用户名   :  *&#                 *设备名称 #ProductKey  注意替换   
 密码    用设备密钥对clientId*deviceName*productKey#进行hmacsha1加密后的结果      *设备名称 #ProductKey  注意替换

 这里提供一个网站:http://encode.chahuo.com/ 用来实现获取密码  加密hmacsha1
 那么把上述三元关系组存储在记事本中,我们的记事本中现在有以下信息:
 
 开发板:
 PRODUCTKET :a16lzjHgAYy
 客户端ID : szykfb|securemode=3,signmethod=hmacsha1|
 用户名:szykfb&a16lzjHgAYy
 密码:c6109a56f9992082a159383cd59ff4894e618285
 主机地址:a16lzjHgAYy.iot-as-mqtt.cn-shanghai.aliyuncs.com
 5. 接下来就是要了解MQTT协议的 CONNECT报文了,CONNECT报文主要有固定报头和可变报头组成,没有有效负载。
 那我这里就以程序为例附上报文的讲解吧,具体的协议内容还是结合MQTT协议的手册去看吧。当然最好先用网络调试
 助手TCP CLIENT模式 ,远程主机选择PRODUCTID.iot-as-mqtt.cn-shanghai.aliyuncs.com,端口号1883,对上述三元关系组
 进行16进制转换建立CONNECT报文,具体程序如下,CONNECT报文固定报头为:00 ?? 4D 51 54 54 04 C2 00 78
 我这里C2是建立在不保留当前会话与不发布遗嘱消息的前提下的,最后两位KEEP ALIVE 78表示120s的对话时间,也就是
 说两条报文发送时间不能超过这个时间,否则的话设备就会自动离线状态了。那也可以根据自己想要的时间进行修改。??主要是计算??后面发送的字节长度,最后要转换为十六进制才行。
 后面就是分别将客户端ID 用户名 密码进行ASCII转16进制,并且计算长度,主要就是00+长度+内容组合起来,千万要细心
 不然你可能会抓狂到为什么还没激活呢?为什么还没激活呢?!!那我这个例子调出来的connect报文就是:
 10 72 00 04 4D 51 54 54 04 C2 00 78 00 28 73 7A 79 61 70 70 7C 73 65 63 75 72 65 6D 6F 64 65 3D 33 2C 73 69 67 6E 6D 65 74 68 6F 64 3D 68 6D 61 63 73 68 61 31 7C 00 12 73 7A 79 61 70 70 26 61 31 36 6C 7A 6A 48 67 41 59 79 00 28 30 32 66 34 66 62 39 30 34 39 66 61 35 34 34 33 63 39 64 31 33 65 33 63 65 63 30 35 63 34 64 64 33 31 66 36 33 62 34 31
 然后点击发送,你会接收到20 02 00 00,其实你会发现 就算你某个字节出了问题发送给阿里云iot他也会回复你,
 不过回复的是20 02 00 04,可是腾讯云就不一样,他就是不搭理你,可能马化腾爸爸比较高冷吧。那这就很有意思了。
//首先需要发送CONNECT报文 第二次CONNECT报文被处理为违规
void MQTT_ConnectPack(void)
{
	Fixed_len = 2;
	Variable _len = 10;
	//可变报头包含协议名 协议级别 连接标志 保持连接
	/*		协议名称
			0x00
			0x04
			M 0x4D
			Q 0x51
			T 0x54
			T 0x54
			协议级别  不支持的协议级别服务器发送返回码0x01的CONNACK报文断开连接
			0x04
			连接标志:Username flag + Passward flag +Will Retain +Will Qos+Will flag +Clean Sessision +Reserved
			reserved = 0 
			clean session = 0     基于当前会话
			will flag = 1  发送遗嘱消息 will flag =0 不能发送遗嘱消息 
			will qos  = 0x01 | 0x02 | 0x00
			will retain = 0 遗嘱标志为1时服务端将遗嘱消息当做非保留消息发布 will retain = 1当做保留消息发布
			username flag = 0  有效载荷不能包含用户名字段
			passward flag = 0 有效载荷不能包含密码字段
			连接标志设为11000010 0xC2
			用户名与密码标志为1 有效载荷后面加CLIENTID +遗嘱主题+遗嘱消息+用户名+密码  由于willflag=0所以不需要考虑遗嘱
			
	*/
	Payload_len = 2 + ClientID_len + 2 + Username_len + 2 + Passward_len;
	//有效载荷长度 客户端标识符 遗嘱主题 遗嘱消息 用户名 密码
	
	temp_buffer[0] = 0x10; //固定报头第一位
	temp_buffer[1] = Variable_len + Payload_len;//固定报头第二位
	//可变报文
	temp_buffer[2] = 0x00; //协议名称长度MSB
	temp_buffer[3] = 0x04;//协议名称长度LSB
	temp_buffer[4] = 0x4D;//M
	temp_buffer[5] = 0x51;//Q
	temp_buffer[6] = 0x54;//T
	temp_buffer[7] = 0x54;//T
	temp_buffer[8] = 0x04;//协议级别
	temp_buffer[9] = 0xC2; //不保留会话状态与发布遗嘱消息
	temp_buffer[10] = 0x00;//保持连接MSB
	temp_buffer[11] = 0x78;//保持连接LSB,取120s为两个报文之间发送的时间室间隔
	//有效载荷
	temp_buffer[12] = ClientID / 256;//计算客户端ID的高字节
	temp_buffer[13] = ClientID % 256;//计算客户端ID的低字节
	memcpy(&temp_buffer[14],ClientID,ClientID_len);//存储客户端ID
	temp_buffer[14+ClientID_len] = Username / 256;//计算用户名的高字节
	temp_buffer[15+ClientID_len] = Username % 256;//计算用户名的低字节
	memcpy(&temp_buffer[16+ClientID_len],Username,Username_len);//存储用户名信息
	temp_buffer[16+ClientID_len+Username_len] = Passward / 256;//存储密码的高字节
	temp_buffer[17+ClientID_len+Username_len] = Passward % 256;//存储密码的低字节
	memcpy(&temp_buffer[18+ClientID_len],Passward,Passward_len);//存储密码信息
	
	TxDataBuf_Deal(temp_buff, Fixed_len + Variable_len + Payload_len); //加入发送数据缓冲区
	
}
 	6.那我们为什么会接受到20 02 00 00 与20 02 00 04两种结果呢?
 	来看看CONNACK报文,固定报头是20 02,可变报头长度为2表示2个字节,可变报头第一个字节在于当前会话位。
 	如果服务端收到清理会话(CleanSession)标志为 1 的连接,除了将 CONNACK 报文中的返回码设置为 之外,
 	还必须将 CONNACK 报文中的当前会话设置(Session Present)标志为 0 [MQTT-3.2.2-1]。
	如果服务端收到一个 CleanSession 为 0 的连接,当前会话标志的值取决于服务端是否已经保存了 ClientI对应客户端的会话状态。
	如果服务端已经保存了会话状态,它必须将 CONNACK 报文中的当前会话标志设
	置为 1 [MQTT-3.2.2-2]。如果服务端没有已保存的会话状态,它必须将 CONNACK报文中的当前会话设为 0。
	还需要将 CONNACK 报文中的返回码设置为 0 [MQTT-3.2.2-3]。当前会话标志使服务端和客户端在是否有已存储的会话状态上保持一致。
	一旦完成了会话的初始化设置,已经保存会话状态的客户端将期望服务端维持它存储的会话状态。如果客
	户端从服务端收到的当前的值与预期的不同,客户端可以选择继续这个会话或者断开连接。客户端可以丢
	弃客户端和服务端之间的会话状态,方法是,断开连接,将清理会话标志设置为 1,再次连接,然后再次
	断开连接。如果服务端发送了一个包含非零返回码的 CONNACK 报文,它必须将当前会话标志设置为 0 [MQTT-3.2.2-4]。
 	第二个字节中00表示已经接受,01表示不支持协议版本,02表示服务端不可用,04表示用户名或密码错误,那
 	这个时候就要重新检查哪个地方出现问题了,粗心真是要命的。05表示客户端未授权。
 	
 	7.接下来就是处理订阅和发布消息 PUBLISH和SUBSCRIBE的问题了。SUBSCIRBE报文也是由固定报头和可变报头组成对的,
 	固定报头0x82 + 剩余长度,剩余长度基本都是可变长度+有效载荷,可变长度为2,主要是报文标识符的MSB和LSB,
 	这个感觉没有特别关键的地方,手册上距离0x0A也就是设置为10。有效载荷主要是主题过滤器和Qos等级字段组合,如果没有
 	就会违反MQTT协议了。前两个字节也就是主题的长度,然后再是主题和服务质量要求 可以设置为Qo0,Qo1,Qo2,然后放到
 	发送缓冲区就行,那我们用网络调试助手调试的时候也是按照这种方式构建二进制串就行。具体操作方式同6中处理方式一样,
 	按照协议要求转换即可。
 	比如:/a16lzjHgAYy/szykfb/user/s_data 的订阅,转换为16进制写成SUBSCRIBE报文为:82 24 00 0A 00 1F 2F 61 31 36 6C 7A 6A 48 67 41 59 79 2F 73 7A 79 6B 66 62 2F 75 73 65 72 2F 73 5F 64 61 74 61 00 (Qos0)
	82 24 00 0A 00 1F 2F 61 31 36 6C 7A 6A 48 67 41 59 79 2F 73 7A 79 6B 66 62 2F 75 73 65 72 2F 73 5F 64 61 74 61 01 (Qos1)
	发送报文后可以受到服务器的SUBACK报文,固定报头为90+剩余长度(后面几个字节的长度)+可变报头(报文标识符,同SUBSCRIBE中一样)+有效载荷(0x00|0x01|0x02|0x80) 0X80表示失败,前面几个表示Qos等级
	我在实际网络助手调试的时候也遇到了接收到0x80这种情况,检查之后发现我在订阅消息的时候connect错了对象,牛头不对马嘴不就是这个意思嘞?千万不要把张三戴到李四头上。
	那么程序主要就是如下了,对着协议应该是比较容易理解的。
//订阅报文,制定Qos等级
void MQTT_Subscribe(char *topic_name, int QoS)
{
	Fixed_len = 2;
	Variable_len = 2;
	Payload_len = 2 + strlen(topic_name) + 1; //计算有效负荷长度 = 2字节(topic_name长度)+ topic_name字符串的长度 + 1字节服务等级
	temp_buffer[0] = 0x82;//固定报头首个字节
	temp_buffer[1] = Variable_len + Paylode_len;//存储剩余长度
	temp_buffer[2] = 00;//报文标识符MSB
	temp_buffer[3] = 0A;//报文标识符LSB  标识符为10时
	temp_buffer[4] = strlen(topic_name) / 256;//主题长度MSB
	temp_buffer[5] = strlen(topic_name) % 256;//主题长度LSB
	memcpy(&temp_buffer[6],topic_name,strlen(topic_name));//拷贝主题名称
	temp_buffer[6+strlen(topic_name)] = Qos;//订阅等级
	TxDataBuf_Deal(temp_buff, Fixed_len + Variable_len + Payload_len);  //加入发送数据缓冲区
	
}
 8.有了订阅当然还要有发布,那发布就是靠PUBLISH来实现了。发布消息的固定报头相比前面几个有点繁琐,
 可以确定的是第一个字节的高位0011也就是3,后面就要根据重发标志DUP、QOS等级和保留位来进行确定了。
 那这里可以分析一下QOS不同等级的关系,QOS0的话最多只能发一次,
 发了一次后服务端到底有没有收到也就不管了,QOS1的话至少发一次,发送之后呢服务端会有对应PUBACK报文返回,
 QOS2的话仅仅发一次,发了一次后也会有相应的报文数据反馈。那我们这里认为DUP取0,选择订阅QOS0消息,
 因为我要的实时数据,你发送一次就够了我肯定不要上一秒的数据,肯定是越新越好。那我第一个字节这样分析的话
 就确定为0x30了。第二个字节就是老朋友剩余长度,然后依次放入可变长度,具体地在代码中可能看得比较详细了。
/*
QoS0:最多分发一次的分发协议 Qos0 送到或没送到   发送者必须发送Qos=0,DUP=0的PUBLISH报文
QoS1:至少分发一次需要PUBACK确认 必须包含报文标识符 QoS=1 DUP = 0
QoS2:仅发送一次 QoS = 2 DUP = 0 从接受者处接收到对应的PUBREC报文,接到PUBREC后必须发送一个PUBREL且包含与原始PUBLISH报文相同的报文标识符
*/
//发布消息 
void MQTT_PublishQs0(char *topic, char *data, int data_len)
{
	Fixed_len = 2;                             
	Variable_len = 2 + strlen(topic);          //可变报头长度:2字节(topic长度)+ topic字符串的长度
	Payload_len = data_len;                    //有效负荷长度:就是data_len
	//固定报头主要有两个字节,第一个字节 MQTT控制报文类型0011 +重发标志DUP+Qos+保留标志Retain
	//根据分析QOS分析 DUP = 0 QOS = 00 RETAIN = 0  第一个字节为0030
	temp_buffer[0] = 0x30;//固定报头首个字节
	temp_buffer[1] = Variable_len + Paylode_len;
	temp_buffer[2] = strlen(topic) / 256; //放主题高字节
	temp_buffer[3] = strlen(topic) %256;  //主题低字节
	memcpy(&temp_buffer[4],topic,strlen(topic));//拷贝主题
	memcpy(&temp_buffer[4+strlen(topic)],data,data_len);//拷贝数据
	
	TxDataBuf_Deal(temp_buff, Fixed_len + Variable_len + Payload_len);  //加入发送数据缓冲区
}

那么还是来举个栗子,/a16lzjHgAYy/szykfb/user/p_data 转16进制,数据为123 转十六进制后为31 32 33,
最后可以得到对应十六进制数据串为 30 24 00 1F 2F 61 31 36 6C 7A 6A 48 67 41 59 79 2F 73 7A 79 6B 66 62 2F 75 73 65 72 2F 70 5F 64 61 74 61 31 32 33。打开两个网络调试助手,都接阿里云上海地址,分别发送APP KFB对应的CONNECT报文和SUBSCIRBE报文,最后在KFB端发送上述数据可以在APP处接收到同样的数据,这一步也需要在云端规则引擎处加入SQL语言配置。

开发板转发消息到APP
同样的也可以按照上述完成APP转发到开发板消息的配置,这样就实现了相互接收。
到这里为止基本山 单片机 iot APP三者的对接就完成了。
后续故事继续等待探索,嗝。排版有点烂,也比较懒emmmm。

2019-04-26 14:53:54 songisgood 阅读数 283
  • 边缘计算 edgexfoundry 实战与源码剖析 之 app-...

    Linux 基金组织启动开源项目 EdgeX Foundry ,为物联网边缘计算开发一个标准化互操作框架。本课程详细剖析edgexfoundry golang 爱丁堡版本 app-function-sdk实现原理,与实战操作,将从core-data模块接到的event进行业务处理,如生成mqtt消息、发送restful到第三方、支持cbor格式生成图片等

    544 人正在学习 去看看 张忠辉

    物联网这个词已经火了一阵子(觉得炒作的成分大一些),现在到像是过气的网红,今天咱就说说基于TCP的MQTT到底是个啥,当初听到MQTT觉得很高大上的样子,各种专业名词,发布订阅代理甩一脸,虽然有了吹嘘的资本,但是一直没有理解MQTT到底是什么,单片机开发人员都喜欢刨根问底,MQTT在程序中存在的最终形式是什么?作为嵌入式开发人员该怎样理解。

一、从嵌入式角度看MQTT

       从很久之前就开始关注MQTT了,大部分资料都是站在服务器开发,前端开发的角度,或者是单纯的分析协议本身,这两种解释对嵌入式开发着很不友好。比如网站服务器开发人员,他们只需要调用相关的软件包或者库,看看对应的API就可以使用了,比如下面node开发的服务端和客服端。

var server = new mosca.Server(settings);

server.on('clientConnected', function(client) {
    console.log('client connected', client.id);
});    //mqtt服务器
var mqtt = require('mqtt');
var client = mqtt.connect('mqtt://192.168.1.136:8000');//创建客户端

What?这样也可以,对应嵌入式开发来说是不可想象的,底层的驱懂全部封装起来,使用十分简单。

而我为了在stm32上实现一个mqtt客户端写了这么多。

所以说不同的开发角度,导致对这个问题解释出现了偏差,这个解释权我觉得掌握在了开发服务器这些人手里了,嵌入式工程师只会上网看别人写的博客,自己又不写,写的人大部分都是做服务器开发的,所以才会很难理解。

看到这是不是觉得说了一堆废话,赶紧上代码,做嵌入式的要服务器代码,做服务器的要嵌入式的代码。可惜了,我只能说说我的见解,有没有代码看以后的心情了,毕竟理解了MQTT本质,用单片机开发出MQTT客户端还是很容易。做嵌入式应该都知道modbus协议吧,我觉得MQTT就相当于在modbus,这种通信协议可以在TCP或者UDP上跑就是了,举个栗子。

MQTT和modbus不严谨对比
MQTT modbus
SOCKET 串口
TCP/UDP  

SOCKET透传,和串口透传,透传就是所发即所收,我想能看懂上面的人应该也是玩烂了这两种通信方式。

下面我们做个试验,说明一下MQTT和SOCKET的关系。

下面这串16进制的报文是我通过串口助手从单片机发出去的数据中抓取的MQTTCONNECT – 连接服务端报文。具体啥意思感兴趣的可以

对照一下MQTT-3.1.1协议,3章 MQTT控制报文,这里不解释了,在贴上对应的单片机程序。

 

10 2E 00 04 4D 51 54 54 04 EE 00 14 00 09 77 68 6F 5F 69 73 5F 6D 65 00 
0A 49 5F 61 6D 5F 64 65 61 74 68 00 07 68 65 6C 70 20 6D 65 00 00 00 00 
					myMqttConData.clientID.cstring = "who_is_me";
					myMqttConData.keepAliveInterval = 20;
					myMqttConData.cleansession = 1;
					myMqttConData.username.cstring = "";
					myMqttConData.password.cstring = "";
					
					myMqttConData.willFlag = 1;
					myMqttConData.will.topicName.cstring = "I_am_death";
					myMqttConData.will.qos = 1;
					myMqttConData.will.retained = 1;
					myMqttConData.will.message.cstring = "help me";

实验开始之前先找个公共的MQTT服务器,这里推荐一个国内的通信猫服务器http://tongxinmao.com/App/Detail/id/126

如果想自己搭建一个服务器推荐EMQ3条命令即可运行一个服务器非常简单 https://developer.emqx.io/docs/emq/v3/cn/install.html#linux 

开始实验:

1、开启一个TCP客户端正确填写IP和端口号点击连接

显示连接成功。随便发送点东西测试一下

 

发了几次发现TCP断开连接了 ,想想为什么断开连接?

现在的服务器是MQTT服务器,通信格式必须要按MQTT格式发送,为啥断开,因为MQTT服务器检测到通信格式不对,就是“”“暗号”对不上了,跑路了。就相当于你往modbus客户端上随便发送写数据,同样也不会叼你一个道理。

下面我们按照正确的格式发送一个连接报文。

奇迹发生了,服务器给我应对了,激动的泪流满面!!!

对照一下通信协议第一个字节是报文的类型,2表示的服务器收到你的请求了,剩下的信息啥意思感兴趣请对照协议。

 

总结:

对于做嵌入式开发的人员可以将SOCKET抽象成单片机的串口,而MQTT相当于是串口上的modbus协议,不管使用的是何种方式上网(wifi GPRS 3G 4G)首先要调通TCP或者UDP能实现透传,然后将要透传的内容按照MQTT的报文打包,将打包后的数据包(数组)通过TCP/UDP透传出去就好了,接收也是同样的道理。

使用MQTT的意义

对于嵌入式开发人员,可能会有个疑惑,使用MQTT咋就节约资源了呢,我一直就是这样用发送的16进制包啊,这个问题其实是对比对象的问题,MQTT是相对于其他的网络协议比如HTTP 这类,你如果和modbus、CAN等协议相比,MQTT同样也很繁琐。其实使用MQTT是减轻了开发服务器人员的负担,直接调用API,就OK了,在一个就是MQTT协议比较严谨,出现问题的机会小一些。

可不可以不使用MQTT

答案是当然可以了,如果你的团队愿意,可以在socket透传上使用modbus,只要你愿意使用啥都没有问题,完全可以自己定义一个比MQTT更简洁的通信协议,当然这样会苦了,开发服务器的小伙伴。但是使用MQTT坑嵌入式开发的,单片机维护这个网络连接也是需要动些脑筋的。最后祝看到的小伙伴,互相甩锅,和气生财!     手动滑稽.jpg

 

 

 

 

MQTT实现

阅读数 336

没有更多推荐了,返回首页