之前实现了使用Springboot+Netty基于天翼物联网平台CTWing(AIOT)终端TCP协议(透传模式)-设备终端(南向设备),模拟设备发送的数据给到了AIOT平台,那么在第三方应用也需要去订阅AIOT平台的数据,以及对设备进行下发指令(消息),订阅设备消息可以使用http,对整个产品进行设备消息订阅。
订阅方地址可以先用接口来接收json字符串,他的格式是json的(接收设备订阅的数据后,必须要有返回,否则AIOT平台会认为没有推送到位,还会推送直到触发禁制)
package boot.ctwing.tcp.app.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
/**
* 蚂蚁舞
*/
@Controller
@RequestMapping("/receive")
public class CtWingReceiveController {
private final Logger log = LoggerFactory.getLogger(this.getClass());
@PostMapping(value = "/tcp-transfer/data", produces = "application/json;charset=UTF-8")
@ResponseBody
public Map<String, Object> receiveTcpTransferController(@RequestBody String str) {
log.info(str);
Map<String, Object> mapResult = new HashMap<>();
mapResult.put("state", true);
return mapResult;
}
}
如果说不创建应用开发,只需要设备推送上来的数据,那么此刻就可以去启动模拟的设备,尝试发消息,北向应用也启动,这样的话就能订阅到从模拟设备发送过来的数据,下下行数据,需要先在应用开发-应用管理里面创建应用得到APP_KEY和APP_SECRET。
在这里我们能看到AIOT的接口文档和在线调试还有sdk下载,在这里我只需要下发指令的操作,那么就去找下发指令的文档信息,在线调试是最快调通下发的,我这里已经把它转换成程序了。
新建Springboot的maven项目,pom.xml文件导入依赖包(用到了swagger来测试下发数据)
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>boot.ctwing.tcp.app</groupId>
<artifactId>boot-example-ctwing-tcp-app-2.0.5</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>boot-example-ctwing-tcp-app-2.0.5</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.20</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.6</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>com.ctg.ag</groupId>
<artifactId>ctg-ag-sdk-core</artifactId>
<version>2.7.0-20221208.015855-5</version>
<scope>system</scope>
<systemPath>${project.basedir}/doc/lib/ctg-ag-sdk-core-2.7.0-20221208.015855-5.jar</systemPath>
</dependency>
<dependency>
<groupId>com.ctg.ag</groupId>
<artifactId>ag-sdk-biz-108549.tar.gz</artifactId>
<version>20221221.143437-SNAPSHOT</version>
<scope>system</scope>
<systemPath>${project.basedir}/doc/lib/ag-sdk-biz-108549.tar.gz-20221221.143437-SNAPSHOT.jar</systemPath>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>swagger-bootstrap-ui</artifactId>
<version>1.9.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>boot.ctwing.tcp.app.BootCtWingTcpApp</mainClass>
<includeSystemScope>true</includeSystemScope><!--外部进行打包-->
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
Springboot启动类,Netty启动
package boot.ctwing.tcp.app;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 蚂蚁舞
*/
@SpringBootApplication
public class BootCtWingTcpApp implements CommandLineRunner
{
public static void main( String[] args ) throws Exception {
SpringApplication.run(BootCtWingTcpApp.class, args);
System.out.println( "Hello World!" );
}
@Override
public void run(String... args) throws Exception {
}
}
SwaggerConfig配置
package boot.ctwing.tcp.app.config;
import com.google.common.base.Predicates;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
/**
* 蚂蚁舞
*/
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket createRestApi(){
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select()
.apis(RequestHandlerSelectors.any()).paths(PathSelectors.any())
.paths(Predicates.not(PathSelectors.regex("/error.*")))
.paths(PathSelectors.regex("/.*"))
.build().apiInfo(apiInfo());
}
private ApiInfo apiInfo(){
return new ApiInfoBuilder()
.title("天翼物联网CtWing北向接收订阅数据端")
.description("接收订阅数据端需要的下行测试接口")
.version("0.01")
.build();
}
/**
* http://localhost:8178/doc.html 地址和端口根据实际项目查看
*/
}
CtWingConstant
package boot.ctwing.tcp.app.config;
/**
* 蚂蚁舞
*/
public class CtWingConstant {
// 产品Id
public static final int PRODUCT_ID = 15506850;
// 应用开发-应用管理-具体应用的App Key
public static final String APP_KEY = "XXXXXXX";
// 应用开发-应用管理-具体应用的App Secret
public static final String APP_SECRET = "XXXXXXXX";
// 产品信息里的Master-APIkey
public static final String MASTER_KEY = "XXXXXXXXXXXXXXXXXXXX";
// 数据订阅通知
public static final String dataReport = "dataReport";
// 设备上线下线订阅通知
public static final String deviceOnlineOfflineReport = "deviceOnlineOfflineReport";
// 指令下发结果订阅
public static final String commandResponse = "commandResponse";
}
CtWingReceiveController
package boot.ctwing.tcp.app.controller;
import boot.ctwing.tcp.app.config.CtWingConstant;
import boot.ctwing.tcp.app.utils.CtWingUtils;
import com.alibaba.fastjson.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
/**
* 蚂蚁舞
*/
@Controller
@RequestMapping("/receive")
public class CtWingReceiveController {
private final Logger log = LoggerFactory.getLogger(this.getClass());
@PostMapping(value = "/tcp-transfer/data", produces = "application/json;charset=UTF-8")
@ResponseBody
public Map<String, Object> receiveTcpTransferController(@RequestBody String str) {
log.info(str);
JSONObject jsonObject = JSONObject.parseObject(str);
String messageType = jsonObject.getString("messageType");
switch (messageType){ // 这里只是例举了三种
case CtWingConstant.dataReport:
String APPdata = jsonObject.getJSONObject("payload").getString("APPdata");
byte[] decoded2 = Base64.getDecoder().decode(APPdata);
String hex2 = CtWingUtils.bytesToHexStr(decoded2);
System.out.println(CtWingUtils.hexStrToStr(hex2));
break;
case CtWingConstant.deviceOnlineOfflineReport:
String eventType = jsonObject.getString("eventType");
System.out.println(eventType);
break;
case CtWingConstant.commandResponse:
String taskId = jsonObject.getString("taskId");
//JSONObject jsonResult = jsonObject.getJSONObject("result");
System.out.println(taskId);
break;
default:
break;
}
Map<String, Object> mapResult = new HashMap<>();
mapResult.put("state", true);
return mapResult;
}
}
CtWingDownCmdController
package boot.ctwing.tcp.app.controller;
import boot.ctwing.tcp.app.config.CtWingConstant;
import boot.ctwing.tcp.app.utils.CtWingDownSendUtils_1;
import boot.ctwing.tcp.app.utils.CtWingDownSendUtils_2;
import boot.ctwing.tcp.app.utils.CtWingDownSendUtils_3;
import com.alibaba.fastjson.JSONObject;
import com.ctg.ag.sdk.biz.aep_device_command.CreateCommandResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
/**
* 蚂蚁舞 下行指令
*/
@Controller
@RequestMapping("/down")
public class CtWingDownCmdController {
private final Logger log = LoggerFactory.getLogger(this.getClass());
@PostMapping(value = "/cmd_send_1")
@ResponseBody
public String cmd_send_1(@RequestParam(name="imei",required = true) String imei, @RequestParam(name="payload",required = true) String payload) throws Exception {
JSONObject jsonContent = new JSONObject();
jsonContent.put("dataType", 1); // TCP和LWM2M协议透传的content内容, payload:指令内容,数据格式为十六进制时需要填十六进制字符串,
jsonContent.put("payload",payload); // dataType:数据类型:1字符串,2十六进制
JSONObject jsonObject = new JSONObject();
jsonObject.put("content", jsonContent); // 指令内容,必填,格式为Json
jsonObject.put("deviceId", CtWingConstant.PRODUCT_ID+imei); // 设备ID,(当指令级别为设备级时必填,为设备组级时则不填)
jsonObject.put("operator", "myw"); // 操作者,必填
jsonObject.put("productId", CtWingConstant.PRODUCT_ID); // 产品ID,必填
jsonObject.put("ttl", 0); // 设备指令缓存时长,选填。单位:秒,取值范围:0-864000,不携带则默认值:7200 如不需缓存请填0
jsonObject.put("level", 1); // 指令级别,1或2为设备级别,3为设备组级别,选填。不填默认设备级
CreateCommandResponse response = CtWingDownSendUtils_1.sendApi(jsonObject.toString());
log.info(response.toString());
return response.toString();
}
@PostMapping(value = "/cmd_send_2")
@ResponseBody
public String cmd_send_2(@RequestParam(name="imei",required = true) String imei, @RequestParam(name="payload",required = true) String payload) throws Exception {
JSONObject jsonContent = new JSONObject();
jsonContent.put("dataType", 1); // TCP和LWM2M协议透传的content内容, payload:指令内容,数据格式为十六进制时需要填十六进制字符串,
jsonContent.put("payload",payload); // dataType:数据类型:1字符串,2十六进制
JSONObject jsonObject = new JSONObject();
jsonObject.put("content", jsonContent); // 指令内容,必填,格式为Json
jsonObject.put("deviceId", CtWingConstant.PRODUCT_ID+imei); // 设备ID,(当指令级别为设备级时必填,为设备组级时则不填)
jsonObject.put("operator", "myw"); // 操作者,必填
jsonObject.put("productId", CtWingConstant.PRODUCT_ID); // 产品ID,必填
jsonObject.put("ttl", 0); // 设备指令缓存时长,选填。单位:秒,取值范围:0-864000,不携带则默认值:7200 如不需缓存请填0
jsonObject.put("level", 1); // 指令级别,1或2为设备级别,3为设备组级别,选填。不填默认设备级
String str = CtWingDownSendUtils_2.httpPostExample(jsonObject.toString());
log.info(str);
return str;
}
@PostMapping(value = "/cmd_send_3")
@ResponseBody
public String cmd_send_3(@RequestParam(name="imei",required = true) String imei, @RequestParam(name="payload",required = true) String payload) throws Exception {
JSONObject jsonContent = new JSONObject();
jsonContent.put("dataType", 1); // TCP和LWM2M协议透传的content内容, payload:指令内容,数据格式为十六进制时需要填十六进制字符串,
jsonContent.put("payload",payload); // dataType:数据类型:1字符串,2十六进制
JSONObject jsonObject = new JSONObject();
jsonObject.put("content", jsonContent); // 指令内容,必填,格式为Json
jsonObject.put("deviceId", CtWingConstant.PRODUCT_ID+imei); // 设备ID,(当指令级别为设备级时必填,为设备组级时则不填)
jsonObject.put("operator", "myw"); // 操作者,必填
jsonObject.put("productId", CtWingConstant.PRODUCT_ID); // 产品ID,必填
jsonObject.put("ttl", 0); // 设备指令缓存时长,选填。单位:秒,取值范围:0-864000,不携带则默认值:7200 如不需缓存请填0
jsonObject.put("level", 1); // 指令级别,1或2为设备级别,3为设备组级别,选填。不填默认设备级
String strResult = CtWingDownSendUtils_3.send_post(jsonObject.toString());
log.info(strResult);
return strResult;
}
@PostMapping(value = "/cmd_send_test")
@ResponseBody
public String cmd_send_test() throws Exception {
JSONObject jsonContent = new JSONObject();
jsonContent.put("dataType", 1); // TCP和LWM2M协议透传的content内容, payload:指令内容,数据格式为十六进制时需要填十六进制字符串,
jsonContent.put("payload","hello"); // dataType:数据类型:1字符串,2十六进制
JSONObject jsonObject = new JSONObject();
jsonObject.put("content", jsonContent); // 指令内容,必填,格式为Json
jsonObject.put("deviceId", CtWingConstant.PRODUCT_ID+"869401041201815"); // 设备ID,(当指令级别为设备级时必填,为设备组级时则不填)
jsonObject.put("operator", "myw"); // 操作者,必填
jsonObject.put("productId", CtWingConstant.PRODUCT_ID); // 产品ID,必填
jsonObject.put("ttl", 0); // 设备指令缓存时长,选填。单位:秒,取值范围:0-864000,不携带则默认值:7200 如不需缓存请填0
jsonObject.put("level", 1); // 指令级别,1或2为设备级别,3为设备组级别,选填。不填默认设备级
// CreateCommandResponse response = CtWingDownSendUtils_1.sendApi(jsonObject.toString());
// log.info(response.toString());
// return response.toString();
//
// String str = CtWingDownSendUtils_2.httpPostExample(jsonObject.toString());
// log.info(str);
// return str;
String strResult = CtWingDownSendUtils_3.send_post(jsonObject.toString());
log.info(strResult);
return strResult;
}
}
CtWingDownSendUtils_1
package boot.ctwing.tcp.app.utils;
import boot.ctwing.tcp.app.config.CtWingConstant;
import com.ctg.ag.sdk.biz.AepDeviceCommandClient;
import com.ctg.ag.sdk.biz.aep_device_command.CreateCommandRequest;
import com.ctg.ag.sdk.biz.aep_device_command.CreateCommandResponse;
/**
* 蚂蚁舞
*/
public class CtWingDownSendUtils_1 {
/**
* ctwing提供的sdk方式下发指令,方便快速,在后台创建应用后可获取sdk
*/
public static CreateCommandResponse sendApi(String jsonStr) throws Exception {
AepDeviceCommandClient client = AepDeviceCommandClient.newClient().appKey(CtWingConstant.APP_KEY).appSecret(CtWingConstant.APP_SECRET).build();
CreateCommandRequest request = new CreateCommandRequest();
// set your request params here
request.setParamMasterKey(CtWingConstant.MASTER_KEY); // single value
request.setBody(jsonStr.getBytes()); //具体格式
CreateCommandResponse commandResponse = client.CreateCommand(request);
client.shutdown();
return commandResponse;
}
}
CtWingDownSendUtils_2
package boot.ctwing.tcp.app.utils;
import boot.ctwing.tcp.app.config.CtWingConstant;
import org.apache.commons.codec.binary.Base64;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.*;
public class CtWingDownSendUtils_2 {
// 官方提供的example
public static String httpPostExample(String bodyString) throws Exception {
String secret = CtWingConstant.APP_SECRET;//密钥,到控制台->应用管理打开应用可以找到此值
String application = CtWingConstant.APP_KEY;//appKey,到控制台->应用管理打开应用可以找到此值
String version = "20190712225145";//api版本,到文档中心->使能平台API文档打开要调用的api可以找到版本值
String MasterKey = CtWingConstant.MASTER_KEY;//MasterKey,在产品中心打开对应的产品查看此值
// 下面以增加设备的API为例【具体信息请以使能平台的API文档为准】。
//请求BODY,到文档中心->使能平台API文档打开要调用的api中,在“请求BODY”中查看
//String bodyString = "{\"deviceName\":\"testDevice\",\"deviceSn\":\"\",\"imei\":123456789012345,\"operator\":\"admin\",\"productId\":\"9392\"}";
CloseableHttpClient httpClient = null;
HttpResponse response = null;
httpClient = HttpClientBuilder.create().build();
long offset = getTimeOffset();// 获取时间偏移量,方法见前面
// 构造请求的URL,具体参考文档中心->使能平台API文档中的请求地址和访问路径
URIBuilder uriBuilder = new URIBuilder();
uriBuilder.setScheme("https");
uriBuilder.setHost("ag-api.ctwing.cn/aep_device_command"); //请求地址
uriBuilder.setPath("/command"); //访问路径,可以在API文档中对应API中找到此访问路径
// 在请求的URL中添加参数,具体参考文档中心->API文档中请求参数说明
// (如果有MasterKey,将MasterKey加到head中,不加在此处)
//uriBuilder.addParameter("productId", "9392");//如果没有其他参数,此行不要
HttpPost httpPost = new HttpPost(uriBuilder.build());//构造post请求
long timestamp = System.currentTimeMillis() + offset;// 获取时间戳
Date date = new Date(timestamp);
SimpleDateFormat dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
String dataString = dateFormat.format(date);// 生成格式化的日期字符串
// head中添加公共参数
httpPost.addHeader("MasterKey", MasterKey);// MasterKey加在此处head中
httpPost.addHeader("application", application);
httpPost.addHeader("timestamp", "" + timestamp);
httpPost.addHeader("version", version);
httpPost.addHeader("Content-Type", "application/json; charset=UTF-8");
httpPost.addHeader("Date", dataString);
// 下列注释的head暂时未用到
// httpPost.addHeader("sdk", "GIT: a4fb7fca");
// httpPost.addHeader("Accept", "gzip,deflate");
// httpPost.addHeader("User-Agent", "Telecom API Gateway Java SDK");
// 构造签名需要的参数,如果参数中有MasterKey,则添加来参与签名计算,
// 其他参数根据实际API从URL中获取,如有其他参数,写法参考get示例
Map<String, String> param = new HashMap<String, String>();
param.put("MasterKey", MasterKey);
// 添加签名
httpPost.addHeader("signature", sign(param, timestamp, application, secret, bodyString.getBytes()));
//请求添加body部分
httpPost.setEntity(new StringEntity(bodyString,"utf-8"));
try {
// 发送请求
response = httpClient.execute(httpPost);
// 从response获取响应结果
String result = new String(EntityUtils.toByteArray(response.getEntity()));
httpClient.close();
return result;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
* @param param api 配置参数表
* @param timestamp UNIX格式时间戳
* @param application appKey,到应用管理打开应用可以找到此值
* @param secret 密钥,到应用管理打开应用可以找到此值
* @param body 请求body数据,如果是GET请求,此值写null
* @return 签名数据
*/
public static String sign(Map<String, String> param, long timestamp, String application, String secret, byte[] body) throws Exception {
// 连接系统参数
StringBuffer sb = new StringBuffer();
sb.append("application").append(":").append(application).append("\n");
sb.append("timestamp").append(":").append(timestamp).append("\n");
// 连接请求参数
if (param != null) {
TreeSet<String> keys = new TreeSet<String>(param.keySet());
for (String s : keys) {
String val = param.get(s);
sb.append(s).append(":").append(val == null ? "" : val).append("\n");
}
}
//body数据写入需要签名的字符流中
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write(sb.toString().getBytes("utf-8"));
if (body != null && body.length > 0) {
baos.write(body);
baos.write("\n".getBytes("utf-8"));
}
// 得到需要签名的字符串
String string = baos.toString("utf-8");
System.out.println("Sign string: " + string);
// hmac-sha1编码
byte[] bytes = null;
SecretKey secretKey = new SecretKeySpec(secret.getBytes("utf-8"), "HmacSha1");
Mac mac = Mac.getInstance(secretKey.getAlgorithm());
mac.init(secretKey);
bytes = mac.doFinal(string.getBytes("utf-8"));
// base64编码
// 得到需要提交的signature签名数据
return new String(Base64.encodeBase64(bytes));
}
public static long getTimeOffset() {
long offset = 0;
HttpResponse response = null;
//构造httpGet请求
CloseableHttpClient httpClient = HttpClientBuilder.create().build();
HttpGet httpTimeGet = new HttpGet("https://ag-api.ctwing.cn/echo");
try {
long start = System.currentTimeMillis();
response = httpClient.execute(httpTimeGet);
long end = System.currentTimeMillis();
//时间戳在返回的响应的head的x-ag-timestamp中
Header[] headers = response.getHeaders("x-ag-timestamp");
if (headers.length > 0) {
long serviceTime = Long.parseLong(headers[0].getValue());
offset = serviceTime - (start + end) / 2L;
}
httpClient.close();
} catch (IOException e) {
e.printStackTrace();
}
return offset;
}
}
CtWingDownSendUtils_3
package boot.ctwing.tcp.app.utils;
import boot.ctwing.tcp.app.config.CtWingConstant;
import cn.hutool.http.HttpRequest;
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* 蚂蚁舞
*/
public class CtWingDownSendUtils_3 {
// 使用hutool request
private static final String url_time = "https://ag-api.ctwing.cn/echo";
private static final String url_cmd = "http://ag-api.ctwing.cn/aep_device_command/command";
public static String send_post(String data) throws Exception {
long offset = getTimeOffset();// 获取时间偏移量
long timestamp = System.currentTimeMillis() + offset;// 获取时间戳
Date date = new Date(timestamp);
SimpleDateFormat dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
String dataString = dateFormat.format(date);// 生成格式化的日期字符串
Map<String, String> param = new HashMap<>();
param.put("MasterKey", CtWingConstant.MASTER_KEY);
String signature = sign(param, timestamp, CtWingConstant.APP_KEY, "if8hEBlPIs", data.getBytes());
//System.out.println(signature);
Map<String, String> headers = new HashMap<>();
headers.put("Content-Type", "application/json; charset=UTF-8");
headers.put("application", CtWingConstant.APP_KEY);
headers.put("MasterKey", CtWingConstant.MASTER_KEY);// MasterKey加在此处head中
headers.put("timestamp",timestamp+"");
headers.put("signature", signature);
headers.put("Date", dataString);
headers.put("version", "20190712225145"); //版本 需要根据实际来确定
return HttpRequest.post(url_cmd).headerMap(headers, true).body(data).timeout(6000).execute().body();
}
public static String sign(Map<String, String> param, long timestamp, String application, String secret, byte[] body) throws Exception {
// 连接系统参数
StringBuffer sb = new StringBuffer();
sb.append("application").append(":").append(application).append("\n");
sb.append("timestamp").append(":").append(timestamp).append("\n");
// 连接请求参数
if (param != null) {
TreeSet<String> keys = new TreeSet<String>(param.keySet());
for (String s : keys) {
String val = param.get(s);
sb.append(s).append(":").append(val == null ? "" : val).append("\n");
}
}
//body数据写入需要签名的字符流中
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write(sb.toString().getBytes(StandardCharsets.UTF_8));
if (body != null && body.length > 0) {
baos.write(body);
baos.write("\n".getBytes(StandardCharsets.UTF_8));
}
// 得到需要签名的字符串
String string = baos.toString("utf-8");
// hmac-sha1编码
byte[] bytes = null;
SecretKey secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSha1");
Mac mac = Mac.getInstance(secretKey.getAlgorithm());
mac.init(secretKey);
bytes = mac.doFinal(string.getBytes(StandardCharsets.UTF_8));
// base64编码
// 得到需要提交的signature签名数据
return new String(Base64.encodeBase64(bytes));
}
public static long getTimeOffset() {
long offset = 0;
long start = System.currentTimeMillis();
cn.hutool.http.HttpResponse res = HttpRequest.get(url_time).execute();
long end = System.currentTimeMillis();
//时间戳在返回的响应的head的x-ag-timestamp中
String headers = res.header("x-ag-timestamp");
if (headers.length() > 0) {
long serviceTime = Long.parseLong(headers);
offset = serviceTime - (start + end) / 2L;
}
return offset;
}
}
CtWingUtils
package boot.ctwing.tcp.app.utils;
import java.nio.charset.StandardCharsets;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 蚂蚁舞
*/
public class CtWingUtils {
private static final Logger log = LoggerFactory.getLogger(CtWingUtils.class);
private static final char[] HEXES = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
public static String bytesToHexStr(byte[] bytes) {
if (bytes == null || bytes.length == 0) {
return null;
}
StringBuilder hex = new StringBuilder(bytes.length * 2);
for (byte b : bytes) {
hex.append(HEXES[(b >> 4) & 0x0F]);
hex.append(HEXES[b & 0x0F]);
}
return hex.toString().toUpperCase();
}
public static byte[] hexStrToBytes(String hex) {
if (hex == null || hex.length() == 0) {
return null;
}
char[] hexChars = hex.toCharArray();
byte[] bytes = new byte[hexChars.length / 2]; // 如果 hex 中的字符不是偶数个, 则忽略最后一个
for (int i = 0; i < bytes.length; i++) {
bytes[i] = (byte) Integer.parseInt("" + hexChars[i * 2] + hexChars[i * 2 + 1], 16);
}
return bytes;
}
public static String strToHexStr(String str) {
StringBuilder sb = new StringBuilder();
byte[] bs = str.getBytes();
int bit;
for (int i = 0; i < bs.length; i++) {
bit = (bs[i] & 0x0f0) >> 4;
sb.append(HEXES[bit]);
bit = bs[i] & 0x0f;
sb.append(HEXES[bit]);
}
return sb.toString().trim();
}
public static String hexStrToStr(String hexStr) {
//能被16整除,肯定可以被2整除
byte[] array = new byte[hexStr.length() / 2];
try {
for (int i = 0; i < array.length; i++) {
array[i] = (byte) (0xff & Integer.parseInt(hexStr.substring(i * 2, i * 2 + 2), 16));
}
hexStr = new String(array, StandardCharsets.UTF_8);
} catch (Exception e) {
e.printStackTrace();
return "";
}
return hexStr;
}
}
代码目录结构
├─boot-example-ctwing-tcp-app-2.0.5
│ │ pom.xml
│ │
│ ├─doc
│ │ │ 108549_sdk.tar.gz
│ │ │ Readme.md
│ │ │
│ │ ├─demo
│ │ │ AepCommandModbusDemo.java
│ │ │ AepDeviceCommandDemo.java
│ │ │ AepDeviceCommandLwmProfileDemo.java
│ │ │ AepDeviceControlDemo.java
│ │ │ AepDeviceEventDemo.java
│ │ │ AepDeviceGroupManagementDemo.java
│ │ │ AepDeviceManagementDemo.java
│ │ │ AepDeviceModelDemo.java
│ │ │ AepDeviceStatusDemo.java
│ │ │ AepEdgeGatewayDemo.java
│ │ │ AepFirmwareManagementDemo.java
│ │ │ AepModbusDeviceManagementDemo.java
│ │ │ AepMqSubDemo.java
│ │ │ AepNbDeviceManagementDemo.java
│ │ │ AepProductManagementDemo.java
│ │ │ AepPublicProductManagementDemo.java
│ │ │ AepRuleEngineDemo.java
│ │ │ AepSoftwareManagementDemo.java
│ │ │ AepSoftwareUpgradeManagementDemo.java
│ │ │ AepStandardManagementDemo.java
│ │ │ AepSubscribeNorthDemo.java
│ │ │ AepUpgradeManagementDemo.java
│ │ │ TenantAppStatisticsDemo.java
│ │ │ TenantDeviceStatisticsDemo.java
│ │ │ UsrDemo.java
│ │ │
│ │ ├─doc
│ │ │ ApiDocument_AEPNB设备管理_aep_nb_device_management.md
│ │ │ ApiDocument_AEP事件上报_aep_device_event.md
│ │ │ ApiDocument_AEP产品管理_aep_product_management.md
│ │ │ ApiDocument_AEP公共产品管理_aep_public_product_management.md
│ │ │ ApiDocument_AEP分组管理_aep_device_group_management.md
│ │ │ ApiDocument_AEP升级包管理_aep_software_management.md
│ │ │ ApiDocument_AEP固件管理_aep_firmware_management.md
│ │ │ ApiDocument_AEP固件远程升级管理_aep_upgrade_management.md
│ │ │ ApiDocument_AEP指令下发_aep_device_command.md
│ │ │ ApiDocument_AEP指令下发_lwm2m有profile_aep_device_command_lwm_profile.md
│ │ │ ApiDocument_AEP指令下发_modbus_aep_command_modbus.md
│ │ │ ApiDocument_AEP数据查询_aep_device_status.md
│ │ │ ApiDocument_AEP标准物模型管理_aep_standard_management.md
│ │ │ ApiDocument_AEP物模型管理_aep_device_model.md
│ │ │ ApiDocument_AEP规则引擎_aep_rule_engine.md
│ │ │ ApiDocument_AEP订阅管理_aep_subscribe_north.md
│ │ │ ApiDocument_AEP设备管理_aep_device_management.md
│ │ │ ApiDocument_AEP设备管理_modbus_aep_modbus_device_management.md
│ │ │ ApiDocument_AEP软件升级管理_aep_software_upgrade_management.md
│ │ │ ApiDocument_AEP边缘网关接入_aep_edge_gateway.md
│ │ │ ApiDocument_AEP远程控制_aep_device_control.md
│ │ │ ApiDocument_MQ订阅推送管理_aep_mq_sub.md
│ │ │ ApiDocument_User_usr.md
│ │ │ ApiDocument_感知终端数据分析_tenant_device_statistics.md
│ │ │ ApiDocument_物联网应用数据分析_tenant_app_statistics.md
│ │ │
│ │ ├─lib
│ │ │ ag-sdk-biz-108549.tar.gz-20221221.143437-SNAPSHOT.jar
│ │ │ ag-sdk-biz-108549.tar.gz-20221221.143437-SNAPSHOT.pom.xml
│ │ │ ctg-ag-sdk-core-2.7.0-20221208.015855-5.jar
│ │ │ ctg-ag-sdk-core-2.7.0-20221208.015855-5.pom.xml
│ │ │
│ │ └─src
│ │ ag-sdk-biz-108549.tar.gz-20221221.143437-source.jar
│ │
│ └─src
│ ├─main
│ │ ├─java
│ │ │ └─boot
│ │ │ └─ctwing
│ │ │ └─tcp
│ │ │ └─app
│ │ │ │ BootCtWingTcpApp.java
│ │ │ │
│ │ │ ├─config
│ │ │ │ CtWingConstant.java
│ │ │ │ SwaggerConfig.java
│ │ │ │
│ │ │ ├─controller
│ │ │ │ CtWingDownCmdController.java
│ │ │ │ CtWingReceiveController.java
│ │ │ │
│ │ │ └─utils
│ │ │ CtWingDownSendUtils_1.java
│ │ │ CtWingDownSendUtils_2.java
│ │ │ CtWingDownSendUtils_3.java
│ │ │ CtWingUtils.java
│ │ │
│ │ └─resources
│ │ application.properties
│ │ logback-spring.xml
│ │
│ └─test
│ └─java
│ └─boot
│ └─ctwing
│ └─tcp
│ └─app
│ BootCtWingTcpAppTest.java
│ Test.java
│
订阅需要部署在云服务器上或者其他方式,启动项目后,在启动模拟设备端的应用,操作上行下行交互,可以看到很不错的结果
在这里我用了三种方式视线指令的下发
第一种 AIOT提供的SDK 超好用
第二种 AIOT在官方文档提供的Demo 代码较多,方便理解,也可用
第三种 使用Hutool工具的请求来实现,实际也是参考AIOT官方提供demo实现的
再看看AIOT平台的下发指令日志
订阅数据的json数据格式,这里记录一下
上下线消息通知
{"timestamp":1671885117746,"tenantId":"XXXXXX","protocol":"tcp","productId":"15506850","messageType":"deviceOnlineOfflineReport","ipv4Address":"39.144.17.238","iccid":"undefined","eventType":1,"deviceId":"15506850869401041201815"}
{"timestamp":1671886478951,"tenantId":"XXXXXX","protocol":"tcp","productId":"15506850","messageType":"deviceOnlineOfflineReport","eventType":0,"deviceId":"15506850869401041201815"}
数据上报通知
{"upPacketSN":-1,"upDataSN":-1,"topic":"ad","timestamp":1671885539011,"tenantId":"XXXXXX","serviceId":"","protocol":"tcp","productId":"15506850","payload":{"APPdata":"6JqC6JqB6Iie"},"messageType":"dataReport","deviceType":"","deviceId":"15506850869401041201815","assocAssetId":"","IMSI":"","IMEI":""}
{"upPacketSN":-1,"upDataSN":-1,"topic":"ad","timestamp":1671885567941,"tenantId":"XXXXXX","serviceId":"","protocol":"tcp","productId":"15506850","payload":{"APPdata":"bXl5aHR3MTIzNDU0NjU0Njc0ZXF3amRjcW93ZWljcW93aXhjbmRjeA=="},"messageType":"dataReport","deviceType":"","deviceId":"15506850869401041201815","assocAssetId":"","IMSI":"","IMEI":""}
指令下发后通知
{"timestamp":1671932803885,"tenantId":"XXXXXX","taskId":2,"result":{"resultDetail":"","resultCode":"SENT"},"protocol":"tcp","productId":"15506850","messageType":"commandResponse","deviceId":"15506850869401041201815"}