全部产品
存储与CDN 数据库 安全 应用服务 数加·人工智能 数加·大数据基础服务 互联网中间件 视频服务 开发者工具 解决方案 物联网 钉钉智能硬件
阿里云物联网套件

利用设备影子实现灯泡偏好设置

更新时间:2017-10-12 15:15:26

需求描述:灯泡默认显示白色光,平时打开灯泡,显示黄色,遇到节假日,显示蓝色。

分析:由于灯泡没有存储能力,关灯之后不会存储自己上一次显示的状态,使用设备影子,能够实现这一功能。灯泡打开时,去读取影子信息,显示对应的状态,然后上报到云端,存储当前状态。之后每次打开,都去读取影子信息,显示期望改变成的状态或上一次显示的状态。

1、 控制台申请产品、设备。

2、 设备影子demo,填写相关信息

  1. /**
  2. * 设备key和secret信息
  3. */
  4. private static String deviceName = "********";
  5. private static String productKey = "******";
  6. private static String deviceSecret = "**************";

3、 灯泡开灯时,主动获取影子信息

  • 第一次开灯时,没有影子信息,灯泡显示默认状态,并上报信息到影子中
    1. JSONObject errorJsonObj = payloadJsonObj.getJSONObject("content");
    2. String errorCode = errorJsonObj.getString("errorcode");
    3. String errorMsg = errorJsonObj.getString("errormessage");
    4. //如果是影子没有内容,上报本次灯泡状态
    5. if (RESULT_CODE_NO_SHADOW.equals(errorCode)){
    6. System.out.println("影子是空的,显示白色,上报当前灯泡状态。");
    7. //更新设备影子
    8. Map<String, Object> attMap = new HashMap<String, Object>(128);
    9. attMap.put("color", "white");
    10. String shadowUpdateMsg = genUpdateShadowMsg(attMap);
    11. System.out.println("updateShadowMsg: " + shadowUpdateMsg);
    12. MqttMessage shadowMessage = new MqttMessage(shadowUpdateMsg.getBytes("UTF-8"));
    13. message.setQos(1);
    14. sampleClient.publish(shadowUpdateTopic, shadowMessage);
    15. }else {
    16. System.out.println("errorCode:" + errorCode);
    17. System.out.println("errorMsg:" + errorMsg);
    18. }
  • 之后开灯时,获取影子信息,如果有desired的color,按照color显示,同时将该颜色上报到影子,并清空desired信息;如果没有期望的颜色,获取上一次上报的color显示

    1. JSONObject shadowJsonObj = JSON.parseObject(message.toString());
    2. JSONObject payloadJsonObj = shadowJsonObj.getJSONObject("payload");
    3. shadowVersion = shadowJsonObj.getLong("version");
    4. LogUtil.print("shadowVersion:" + shadowVersion);
    5. //解析出desired和reported信息
    6. JSONObject stateJsonObj = payloadJsonObj.getJSONObject("state");
    7. String desiredString = stateJsonObj.getString("desired");
    8. String reportedString = stateJsonObj.getString("reported");
    9. LogUtil.print("desiredString:" + desiredString);
    10. if (desiredString != null) {
    11. //根据desired信息做业务处理
    12. String color = JSON.parseObject(desiredString).getString("color");
    13. System.out.println("要换颜色喽,灯泡显示颜色:" + color);
    14. //更新color信息到reported中
    15. if (color != null){
    16. String updateShadowReportdMsg = genUpdateShadowReportdMsg(reportedString, color);
    17. LogUtil.print("updateShadowReportdMsg:" + updateShadowReportdMsg);
    18. MqttMessage cleanShadowMqttMsg = new MqttMessage(updateShadowReportdMsg.getBytes("UTF-8"));
    19. message.setQos(1);
    20. sampleClient.publish(shadowUpdateTopic, cleanShadowMqttMsg);
    21. LogUtil.print("shadow reported msg update success");
    22. }
    23. //清空desired信息
    24. String cleanShadowMsg = genCleanShadowMsg(reportedString);
    25. LogUtil.print("cleanShadowMsg:" + cleanShadowMsg);
    26. MqttMessage cleanShadowMqttMsg = new MqttMessage(cleanShadowMsg.getBytes("UTF-8"));
    27. message.setQos(1);
    28. sampleClient.publish(shadowUpdateTopic, cleanShadowMqttMsg);
    29. LogUtil.print("send clean shadow msg done");
    30. }else {
    31. //没有desired信息 灯泡显示reported中color状态
    32. if (reportedString != null){
    33. System.out.println("开灯了,灯泡显示上一次颜色:" + JSON.parseObject(reportedString).getString("color"));
    34. }
    35. }

    4、 验证

  • 首次开灯(第一次运行)shadow1

  • 关灯后,服务端修改灯泡期望颜色,之后灯泡开灯

shadow2

shadow3

  • 关灯,再开

shadow4

如果要想改变灯泡的显示颜色,只需修改影子信息即可,这样,没有存储能力的灯泡,就通过设备影子实现了偏好设置。

完整demo代码

  1. /**设备端查询影子method*/
  2. private static final String SHADOW_METHOD_REPLY = "reply";
  3. /**服务端下发method*/
  4. private static final String SHADOW_METHOD_CONTROL = "control";
  5. private static final String AUTH_RESULT_SUCCESS_KEY = "code";
  6. private static final String RESULT_CODE_SUCCESS = "200";
  7. private static final String RESULT_CODE_NO_SHADOW = "407";
  8. private static final String RESULT_STATUS_SUCCESS = "success";
  9. /**
  10. * 认证服务器地址 每个区域不一样
  11. */
  12. private static String authUrl = "https://iot-auth.cn-shanghai.aliyuncs.com/auth/devicename";
  13. /**
  14. * 设备key和secret信息
  15. */
  16. private static String deviceName = "***";
  17. private static String productKey = "***";
  18. private static String deviceSecret = "***";
  19. /**
  20. * 设备影子topic
  21. */
  22. private static String shadowAckTopic = "/shadow/get/" + productKey + "/" + deviceName;
  23. private static String shadowUpdateTopic = "/shadow/update/" + productKey + "/" + deviceName;
  24. /**
  25. * 影子版本号
  26. */
  27. private static long shadowVersion = 0;
  28. /**
  29. * 根据属性key-value 生成shadow json格式数据
  30. *
  31. * @param attributeMap
  32. * @return
  33. */
  34. private static String genUpdateShadowMsg(Map<String, Object> attributeMap) {
  35. Set<String> attSet = attributeMap.keySet();
  36. Map<String, Object> attMap = new LinkedHashMap<String, Object>();
  37. for (String attKey : attSet) {
  38. attMap.put(attKey, attributeMap.get(attKey));
  39. }
  40. Map<String, Object> reportedMap = new LinkedHashMap<String, Object>();
  41. reportedMap.put("reported", attMap);
  42. Map<String, Object> shadowJsonMap = new LinkedHashMap<String, Object>();
  43. shadowJsonMap.put("method", "update");
  44. shadowJsonMap.put("state", reportedMap);
  45. //shadow version自增
  46. shadowVersion++;
  47. shadowJsonMap.put("version", shadowVersion);
  48. return JSON.toJSONString(shadowJsonMap);
  49. }
  50. /**
  51. * 生成clean shadow json数据
  52. *
  53. * @param reportMsg
  54. * @return
  55. */
  56. private static String genCleanShadowMsg(String reportMsg) {
  57. Map<String, Object> stateMap = new LinkedHashMap<String, Object>();
  58. if (reportMsg == null || reportMsg.length() == 0) {
  59. stateMap.put("reported", "null");
  60. } else {
  61. JSONObject reportJsonObj = JSON.parseObject(reportMsg);
  62. Set<String> attSet = reportJsonObj.keySet();
  63. Map<String, Object> attMap = new LinkedHashMap<String, Object>();
  64. for (String attKey : attSet) {
  65. attMap.put(attKey, reportJsonObj.getString(attKey));
  66. }
  67. stateMap.put("reported", attMap);
  68. }
  69. stateMap.put("desired", "null");
  70. Map<String, Object> cleanShadowMap = new LinkedHashMap<String, Object>();
  71. cleanShadowMap.put("method", "update");
  72. cleanShadowMap.put("state", stateMap);
  73. shadowVersion++;
  74. cleanShadowMap.put("version", shadowVersion);
  75. return JSON.toJSONString(cleanShadowMap);
  76. }
  77. /**
  78. * 更新影子reported json数据 ,将color同步更新到reported中
  79. *
  80. * @param reportMsg
  81. * @param color
  82. * @return
  83. */
  84. private static String genUpdateShadowReportdMsg(String reportMsg, String color) {
  85. Map<String, Object> stateMap = new LinkedHashMap<String, Object>();
  86. Map<String, Object> attMap = new LinkedHashMap<String, Object>();
  87. if (reportMsg != null){
  88. JSONObject reportJsonObj = JSON.parseObject(reportMsg);
  89. Set<String> attSet = reportJsonObj.keySet();
  90. for (String attKey : attSet) {
  91. attMap.put(attKey, reportJsonObj.getString(attKey));
  92. }
  93. }
  94. attMap.put("color", color);
  95. stateMap.put("reported", attMap);
  96. Map<String, Object> cleanShadowMap = new LinkedHashMap<String, Object>();
  97. cleanShadowMap.put("method", "update");
  98. cleanShadowMap.put("state", stateMap);
  99. shadowVersion++;
  100. cleanShadowMap.put("version", shadowVersion);
  101. return JSON.toJSONString(cleanShadowMap);
  102. }
  103. /**
  104. * 解析出desired信息
  105. *
  106. * @param message
  107. * @param sampleClient
  108. * @throws Exception
  109. */
  110. private static void parseDesiredMsg(MqttMessage message, MqttClient sampleClient) throws Exception {
  111. JSONObject shadowJsonObj = JSON.parseObject(message.toString());
  112. JSONObject payloadJsonObj = shadowJsonObj.getJSONObject("payload");
  113. shadowVersion = shadowJsonObj.getLong("version");
  114. LogUtil.print("shadowVersion:" + shadowVersion);
  115. //解析出desired
  116. JSONObject stateJsonObj = payloadJsonObj.getJSONObject("state");
  117. String desiredString = stateJsonObj.getString("desired");
  118. String reportedString = stateJsonObj.getString("reported");
  119. LogUtil.print("desiredString:" + desiredString);
  120. //清空shadow信息
  121. if (desiredString != null) {
  122. //TODO 根据desired信息做业务处理
  123. String color = JSON.parseObject(desiredString).getString("color");
  124. System.out.println("要换颜色喽,灯泡显示颜色:" + color);
  125. //更新color信息到reported中
  126. if (color != null){
  127. String updateShadowReportdMsg = genUpdateShadowReportdMsg(reportedString, color);
  128. LogUtil.print("updateShadowReportdMsg:" + updateShadowReportdMsg);
  129. MqttMessage cleanShadowMqttMsg = new MqttMessage(updateShadowReportdMsg.getBytes("UTF-8"));
  130. message.setQos(1);
  131. sampleClient.publish(shadowUpdateTopic, cleanShadowMqttMsg);
  132. LogUtil.print("shadow reported msg update success");
  133. }
  134. //清空desired信息
  135. String cleanShadowMsg = genCleanShadowMsg(reportedString);
  136. LogUtil.print("cleanShadowMsg:" + cleanShadowMsg);
  137. MqttMessage cleanShadowMqttMsg = new MqttMessage(cleanShadowMsg.getBytes("UTF-8"));
  138. message.setQos(1);
  139. sampleClient.publish(shadowUpdateTopic, cleanShadowMqttMsg);
  140. LogUtil.print("send clean shadow msg done");
  141. }else {
  142. //没有desired信息 灯泡显示reported状态
  143. if (reportedString != null){
  144. System.out.println("开灯了,灯泡显示上一次颜色:" + JSON.parseObject(reportedString).getString("color"));
  145. }
  146. }
  147. }
  148. public static void main(String... strings) throws Exception {
  149. /* 客户端设备 自己的一个标记 */
  150. String clientId = productKey + "&" + deviceName;
  151. Map<String, String> params = new HashMap<String, String>(16);
  152. /** 这个是对应用户在控制台注册的 设备productkey */
  153. params.put("productKey", productKey);
  154. /** 这个是对应用户在控制台注册的 设备name */
  155. params.put("deviceName", deviceName);
  156. params.put("timestamp", "" + System.currentTimeMillis());
  157. params.put("clientId", clientId);
  158. //签名
  159. params.put("sign", SignUtil.sign(params, deviceSecret, "hmacMD5"));
  160. //请求资源 mqtt
  161. params.put("resources", "mqtt");
  162. String result = AbstractAliyunWebUtils.doPost(authUrl, params, 5000, 5000);
  163. LogUtil.print("result=[" + result + "]");
  164. JSONObject mapResult;
  165. try {
  166. mapResult = JSON.parseObject(result);
  167. } catch (Exception e) {
  168. System.out.println("https auth result is invalid json fmt");
  169. return;
  170. }
  171. if (RESULT_CODE_SUCCESS.equals(mapResult.getString(AUTH_RESULT_SUCCESS_KEY))) {
  172. LogUtil.print("认证成功!" + mapResult.get("data"));
  173. LogUtil.print("data=[" + mapResult + "]");
  174. } else {
  175. System.err.println("认证失败!");
  176. throw new RuntimeException(
  177. "认证失败:" + mapResult.get("code") + "," + mapResult.get("message"));
  178. }
  179. JSONObject data = (JSONObject)mapResult.get("data");
  180. //sign TODO 服务器返回的sign签名 防止域名劫持验证
  181. //mqtt服务器 TODO
  182. String targetServer = "ssl://"
  183. + data.getJSONObject("resources").getJSONObject("mqtt")
  184. .getString("host")
  185. + ":" + data.getJSONObject("resources").getJSONObject("mqtt")
  186. .getString("port");
  187. String token = data.getString("iotToken");
  188. String iotId = data.getString("iotId");
  189. //客户端ID格式:
  190. /* 设备端自定义的标记,字符范围[0-9][a-z][A-Z] */
  191. String mqttClientId = clientId;
  192. /* 认证后得到的云端iotId */
  193. String mqttUsername = iotId;
  194. /* 认证后得到的token 有效期7天 */
  195. String mqttPassword = token;
  196. System.err.println("mqttclientId=" + mqttClientId);
  197. connectMqtt(targetServer, mqttClientId, mqttUsername, mqttPassword);
  198. }
  199. private static void connectMqtt(String url, String clientId, String mqttUsername,
  200. String mqttPassword) throws Exception {
  201. MemoryPersistence persistence = new MemoryPersistence();
  202. SSLSocketFactory socketFactory = createSSLSocket();
  203. final MqttClient sampleClient = new MqttClient(url, clientId, persistence);
  204. MqttConnectOptions connOpts = new MqttConnectOptions();
  205. /* MQTT 3.1.1 */
  206. connOpts.setMqttVersion(4);
  207. connOpts.setSocketFactory(socketFactory);
  208. //设置是否自动重连
  209. connOpts.setAutomaticReconnect(true);
  210. //如果是true 那么清理所有离线消息,即qos1 或者 2的所有未接收内容
  211. connOpts.setCleanSession(false);
  212. connOpts.setUserName(mqttUsername);
  213. connOpts.setPassword(mqttPassword.toCharArray());
  214. connOpts.setKeepAliveInterval(65);
  215. LogUtil.print(clientId + "进行连接, 目的地: " + url);
  216. //sampleClient.setManualAcks(true);//不要自动回执ack
  217. sampleClient.connect(connOpts);
  218. sampleClient.setCallback(new MqttCallback() {
  219. @Override
  220. public void connectionLost(Throwable cause) {
  221. LogUtil.print("连接失败,原因:" + cause);
  222. cause.printStackTrace();
  223. }
  224. @Override
  225. public void messageArrived(String topic, MqttMessage message) throws Exception {
  226. LogUtil.print("接收到消息,来至Topic [" + topic + "] , 内容是:["
  227. + new String(message.getPayload(), "UTF-8") + "], ");
  228. }
  229. @Override
  230. public void deliveryComplete(IMqttDeliveryToken token) {
  231. //如果是qos 0消息 token.resp是没有回复的
  232. LogUtil.print("消息发送成功! " + ((token == null || token.getResponse() == null) ? "null"
  233. : token.getResponse().getKey()));
  234. }
  235. });
  236. LogUtil.print("连接成功:---");
  237. //订阅shadow topic
  238. sampleClient.subscribe(shadowAckTopic, new IMqttMessageListener() {
  239. @Override
  240. public void messageArrived(String topic, MqttMessage message) throws Exception {
  241. LogUtil.print("收到消息:" + message + ",topic=" + topic);
  242. JSONObject shadowJsonObj = JSON.parseObject(message.toString());
  243. String shadowMethod = shadowJsonObj.getString("method");
  244. JSONObject payloadJsonObj = shadowJsonObj.getJSONObject("payload");
  245. /* method是reply,解析成功还是失败*/
  246. if (SHADOW_METHOD_REPLY.equals(shadowMethod)) {
  247. String status = payloadJsonObj.getString("status");
  248. String stateInfo = payloadJsonObj.getString("state");
  249. if (RESULT_STATUS_SUCCESS.equals(status)) {
  250. if (stateInfo == null) {
  251. System.out.println("update shadow success");
  252. } else {
  253. //解析出desired信息
  254. parseDesiredMsg(message, sampleClient);
  255. }
  256. } else {
  257. JSONObject errorJsonObj = payloadJsonObj.getJSONObject("content");
  258. String errorCode = errorJsonObj.getString("errorcode");
  259. String errorMsg = errorJsonObj.getString("errormessage");
  260. //如果是影子没有内容,上报本次灯泡状态
  261. if (RESULT_CODE_NO_SHADOW.equals(errorCode)){
  262. System.out.println("影子是空的,显示白色,上报当前灯泡状态!");
  263. //更新设备影子
  264. Map<String, Object> attMap = new HashMap<String, Object>(128);
  265. attMap.put("color", "white");
  266. String shadowUpdateMsg = genUpdateShadowMsg(attMap);
  267. System.out.println("updateShadowMsg: " + shadowUpdateMsg);
  268. MqttMessage shadowMessage = new MqttMessage(shadowUpdateMsg.getBytes("UTF-8"));
  269. message.setQos(1);
  270. sampleClient.publish(shadowUpdateTopic, shadowMessage);
  271. }else {
  272. System.out.println("errorCode:" + errorCode);
  273. System.out.println("errorMsg:" + errorMsg);
  274. }
  275. }
  276. }
  277. /* method是control,解析出desired和version信息 */
  278. else if (SHADOW_METHOD_CONTROL.equals(shadowMethod)) {
  279. parseDesiredMsg(message, sampleClient);
  280. }
  281. }
  282. });
  283. //获取影子内容,解析出version信息
  284. String getShadowInfo = "{\"method\": \"get\"}";
  285. MqttMessage shadowMessage = new MqttMessage(getShadowInfo.getBytes("UTF-8"));
  286. shadowMessage.setQos(1);
  287. sampleClient.publish(shadowUpdateTopic, shadowMessage);
  288. //等待获取到版本号
  289. Thread.sleep(1000);
  290. }
  291. private static SSLSocketFactory createSSLSocket() throws Exception {
  292. SSLContext context = SSLContext.getInstance("TLSV1.2");
  293. context.init(null, new TrustManager[] {new AliyunIotX509TrustManager()}, null);
  294. SSLSocketFactory socketFactory = context.getSocketFactory();
  295. return socketFactory;
  296. }
本文导读目录