MQTT.fx是一款基于Eclipse Paho,使用Java语言编写的MQTT客户端,支持Windows、Mac和Linux操作系统,可用于验证设备是否可与物联网平台正常连接,并通过Topic订阅和发布消息。本文以Windows系统下MQTT.fx为例,介绍模拟设备以MQTT协议接入物联网平台的步骤。

前提条件

已在物联网平台控制台创建产品和设备,并设备证书信息(ProductKey、DeviceName和DeviceSerect)。具体操作,请参见:

注意 MQTT.fx模拟的在线设备,仅支持非透传消息通信。如需实现透传信息通信,您可以使用真实设备或SDK进行测试。

使用MQTT.fx接入

  1. 访问MQTT.fx官网,下载并安装MQTT.fx Version 1.7.1 for Windows版本软件。
  2. 打开MQTT.fx软件,单击菜单栏中的Extras,选择Edit Connection Profiles
    编辑mqtt的基本信息
  3. Edit Connection Profiles页面,完成以下参数的设置。
    1. 设置基本信息。
      编辑mqtt.fx的基本信息
      参数 说明 示例
      Profile Name 输入您的自定义名称。 iot connection
      Profile Type MQTT服务器连接端口。选择MQTT Broker
      • 证书认证型端口:8883
      • 密钥认证型:1883
      MQTT Broker Profile Settings
      Broker Address 接入域名。

      接入域名格式:${YourProductKey}.iot-as-mqtt.${YourRegionId}.aliyuncs.com

      a1oGs******.iot-as-mqtt.cn-shanghai.aliyuncs.com
      • a1oGs******为本示例产品的productKey
      • cn-shanghai为本示例所在地域。
      Broker Port 设置为1883
      Client ID MQTT的协议字段。

      固定格式:${ClientID}|securemode=${Mode},signmethod=${SignMethod}|

      参数说明:

      • ${ClientId}:设备、App或Web等场景下的Client ID信息,可自定义,长度在64个字符以内。多数情况下为设备的ID信息,建议使用您设备的MAC地址或SN码。
      • ${Mode}:安全模式。
        • securemode=3:TCP直连模式,无需设置SSL/TLS信息。
        • securemode=2:TLS直连模式,需要设置SSL/TLS信息。
      • ${SignMethod}:算法类型,支持hmacmd5hmacsha1
      注意
      • MQTT.fx的Client ID和设备的${ClientId},切勿混淆。
      • 不要遗漏参数之间及最后的竖线(|)。
      • 设置参数时,请确保参数值中或参数值的前后均没有空格。
      • 输入Client ID信息后,请勿单击Generate

      本示例设备clientId12345,选择TLS直连模式和hmacsha1算法。

      12345|securemode=2,signmethod=hmacsha1|

      General

      本示例使用默认值。您也可以根据实际场景需求设置。
    2. 单击User Credentials,设置User NamePassword
      设置mqtt.fx的User Credentials
      参数 参数说明 示例
      User Name 由设备名DeviceName、and(&)和产品ProductKey组成。

      固定格式:${DeviceName}&${ProductKey}

      Light&a1oGs******
      • Light为设备的DeviceName。
      • a1oGs******为设备的ProductKey。
      Password 通过选择的加密方法,以设备的DeviceSecret为密钥,将参数和参数值拼接后,加密生成Password。
      注意
      • 如果您使用的MQTT.fx版本,在粘贴Password后不显示具体的字符串,只要光标已从输入框的前部移至了后部,则表示粘贴成功,请勿重复粘贴。
      • 请注意参数和参数值中字母的大小写。

      可通过以下任一方法生成Password。

      • 工具生成:

        单击下载Password生成小工具。解压文件后,双击sign.html文件,根据网页提示,配置参数,生成password。

        • productKeydeviceNamedeviceSecret:设备证书信息。可在控制台设备详情页查看。
        • timestamp:(可选)时间戳。
        • clientId:设备的ID信息,与MQTT.fx的Client ID${clientId}一致。
        • method:选择签名算法类型,与MQTT.fx的Client ID${SignMethod}一致。
      • 通过加密函数手动生成,本文提供hmacsha1的加密Demo,请参见附录:加密Demo

        其中:

        • ${productKey}${deviceName}${deviceSecret}:需替换为您的设备证书信息。
        • ${clientId}:替换为MQTT.fx的Client ID${clientId}
      • 工具生成示例:工具生成密码示例
      • 手动生成

        本示例没有选择可选参数timestamp。

        函数加密
    3. TLS直连模式(即securemode=2)下,单击SSL/TLS,选中Enable SSL/TLS,设置ProtocolTLSv1.2
      注意 TCP直连模式(即 securemode=3)下,无需设置SSL/TLS信息,直接进入下一步。
      设置mqtt.fx的SSL/TLS
  4. 设置完成后,单击右下角的OK
  5. 单击Connect
    右侧亮绿灯,表示连接成功。 连接mqtt.fx到物联网

    您可在物联网平台控制台,选择设备管理 > 设备,选择产品,查看该设备状态,预期设备为在线状态。

    在线

下文通过测试自定义Topic的上下行通信,验证MQTT.fx与物联网平台连接是否成功。若测试与本示例结果不符,表示通信连接失败,您需根据日志信息,进行修正。

下行通信测试

  1. 物联网平台控制台的产品详情页面,单击Topic类列表 > 自定义Topic,找到一个具有订阅权限的自定义Topic。
    本示例使用Topic: /a1oGs4X***/${deviceName}/user/get,您需替换 ${deviceName}为设备名称 Light

    更多信息,请参见自定义Topic

  2. 在MQTT.fx上单击Subscribe,在Subscribe文本框中,输入上一步的Topic,再单击Subscribe

    订阅成功后,该Topic将显示在列表中。

    自定义Topic显示在mqtt.fx订阅列表中
  3. 返回物联网平台,进入该设备的设备详情页面,在Topic列表页签下,单击已订阅Topic对应的发布消息
    在mqtt.fx订阅的Topic下发布消息
  4. 输入消息内容,单击确认
    在mqtt.fx订阅的Topic下输入要发布的消息
  5. 回到MQTT.fx上,查看接收到的消息。
    mqtt.fx接收到消息
  6. 回到物联网平台,在设备详情页面,单击日志服务页签的前往查看,在日志服务页面,查看云到设备消息
    日志信息

上行通信测试

  1. 物联网平台控制台的产品详情页面,单击Topic类列表 > 自定义Topic,找到一个具有发布权限的自定义Topic。
    本示例使用Topic: /a1oGs4X***/${deviceName}/user/update/error,您需替换 ${deviceName}为设备名称 Light

    更多信息,请参见自定义Topic

  2. 在MQTT.fx上,单击Publish,在Publish文本框中,输入上一步的Topic。在文本编辑页面,输入要发送的消息内容,然后单击Publish
    在mqtt.fx发布消息
  3. 回到物联网平台,在设备详情页面,单击日志服务页签的前往查看,在日志服务页面,查看设备到云消息
    查看mqtt.fx发布到物联网平台的消息

查看日志

在MQTT.fx上,单击Log查看操作日志和错误提示日志。

查看mqtt.fx的log

附录:加密Demo

package com.aliyun.iot.util;

import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

public class SignDemo {

    public static String sign(Map<String, String> params, String deviceSecret, String signMethod) {
        //将参数Key按字典顺序排序
        String[] sortedKeys = params.keySet().toArray(new String[] {});
        Arrays.sort(sortedKeys);

        //生成规范化请求字符串
        StringBuilder canonicalizedQueryString = new StringBuilder();
        for (String key : sortedKeys) {
            if ("sign".equalsIgnoreCase(key)) {
                continue;
            }
            canonicalizedQueryString.append(key).append(params.get(key));
        }

        try {
            String key = deviceSecret;
            return encryptHMAC(signMethod, canonicalizedQueryString.toString(), key);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static String encryptHMAC(String signMethod, String content, String key) throws Exception {
        SecretKey secretKey = new SecretKeySpec(key.getBytes("utf-8"), signMethod);
        Mac mac = Mac.getInstance(secretKey.getAlgorithm());
        mac.init(secretKey);
        byte[] data = mac.doFinal(content.getBytes("utf-8"));
        return bytesToHexString(data);
    }

    public static final String bytesToHexString(byte[] bArray) {

        StringBuffer sb = new StringBuffer(bArray.length);
        String sTemp;
        for (int i = 0; i < bArray.length; i++) {
            sTemp = Integer.toHexString(0xFF & bArray[i]);
            if (sTemp.length() < 2) {
                sb.append(0);
            }
            sb.append(sTemp.toUpperCase());
        }
        return sb.toString();
    }

    public static void main(String args[]) {
        
        Map<String, String> params = new HashMap<String, String>();
        params.put("productKey", "${productKey}");
        params.put("deviceName", "${deviceName}");
        params.put("clientId", "${clientId}");
        //时间戳,可选参数。传入时,删除下面两行代码前的正斜线
        //String t = System.currentTimeMillis() + "";
        //params.put("timestamp", t);

        String sign = SignDemo.sign(params, "${deviceSecret}", "hmacsha1");
        System.out.print(sign);
    }

}