之八-呕血制作-Lettuce IOT框架-移远BC35G+树莓派+华为OC+SpringBoot后台+微信小程序

上一篇主要讲的是PC端调试lettuce-Sea以及部署lettuce-Sea到树莓派并与华为OC平台进行联调而这一篇将要讲lettuce的对外服务lettuce-Air服务端java代码解析。

lettuce-Air服务端java代码讲解

想必上节课,大家已经在设备侧完成了对华为OC平台的集成,并且有了可观的效果。可以说已经胜利在望了。而这节课,我将给大家讲解如何接入华为OC平台的北向接口。并转换为对外服务的能力。

lettuce-Air的源代码
https://github.com/lipuqi/lettuce-Air

首先我们要有一个云服务器,开放8013端口。
其次云服务器上要安装JDK1.8的环境。

lettuce-Air服务端主要提供2种服务:

  1. 服务端后台对总体情况的监控。监控内容包括设备的当前状态和命令下发的当前状态。
    在这里插入图片描述
  2. 服务端后台对客户端lettuce-Land的服务提供,例如在客户端开关灯的操作,和客户端查询设备在线情况的操作。
    在这里插入图片描述
    lettuce-Air服务端主要使用的是spring boot的框架,因为便于大家演示操作,使用单例模式的Map作为缓存来存储数据,与华为OC平台采用API的方式对接。

接下来我就先讲解lettuce-Air服务端是与华为OC平台怎样对接的。
主要分3种接口:

  1. 订阅接口:这种接口是我们提供给平台的,用于平台向我们主动提供的数据。例如设备端的数据上报。
    在这里插入图片描述
  2. 主动访问式接口:这种接口是平台提供给我们的,用于我们主动触发相关操作。例如我们向设备发送一条命令。
    .
  3. 回调接口:这种接口是平台回调我们的接口,主要用于一些状态的返回等。例如我们刚才下发命令时,传给平台命令状态的回调地址。

所以一个完整的命令下发操作是这样的流程
在这里插入图片描述

首先我们先来看订阅平台的设备数据变化的通知。

在华为OC平台的控制板上我们选择订阅调试
在这里插入图片描述
然后大家可以看到,可以订阅很多种通知。我们这里只订阅了设备数据变化。
在订阅时需要填写服务器的接口。
在这里插入图片描述
这个添写的接口就是我们提供给平台的。

注意这里因为我们只做演示,采用的是HTTP的协议,如果想用HTTPS的,需要将证书信息设置到华为OC平台里(对接信息中设置)。

填写接口以后,平台会发送一条虚拟数据给接口自动检测接口是否通过。

下面我们来看看接口的入参
在这里插入图片描述
接口要使用POST方式

在这里插入图片描述
这是平台给我们的数据是怎样的结构

相关java代码片段
DeviceController.java

	/**
	 * 上报数据
	 * @param result
	 * @return
	 */
	@PostMapping(value = "/deviceDataChanged", produces = { "application/json;charset=UTF-8" })
	public GenericResponse deviceDataChanged(@RequestBody JSONObject result){
		try {
			deviceService.getDeviceData(result);
		} catch (CustomException ex) {
			throw ex;
		} catch (Exception e) {
			throw new BasicException(1000, e);
		}
		return ResponseFormat.retParam(200, "OK");
	}

DeviceServiceImpl.java

	@Override
	public void getDeviceData(JSONObject result) {
		
		BasicDevice device = null;
		
		//判断通知类型是否为上报数据类型
		if (result.containsKey("notifyType") && PushStatus.DEVICE_DATA_CHANGED.equals(result.getString("notifyType"))) {
			JSONObject service = result.getJSONObject("service");
			//判断是灯的服务还是设备的服务
			switch (service.getString("serviceId")) {
			case ServiceConstant.SwitchBulb:
				SwitchBulb_status switchBulb_status = new SwitchBulb_status();
				switchBulb_status.packaging(service);//解析消息内容
				
				//放到设备类中
				device = new Bulb();
				device.setStatus(switchBulb_status.getStatus());
				device.setUpdateTime();
				break;
			case ServiceConstant.OperationPi:
				OperationPi_status sperationPi_status = new OperationPi_status();
				sperationPi_status.packaging(service);//解析消息内容
				
				//放到设备类中
				device = new OperationPi();
				device.setStatus(sperationPi_status.getStatus());
				device.setUpdateTime();
				LOGGER.info("----------------------设备上报心跳----------------------");
				break;
			default:
				throw new CustomException(DeviceServiceImpl.class, "获取数据上报信息类型没有匹配");
			}
			
			//将设备参数更新到缓存中
			mapCache.put(device.getDeviceKey(), device);
		}
	}

解析消息
SwitchBulb_status.java

	@Override
	public void packaging(JSONObject service) {
		try {
			if (service.containsKey("serviceId"))
				setServiceId(service.getString("serviceId"));
			if (service.containsKey("serviceType"))
				setServiceType(service.getString("serviceType"));
			if (service.containsKey("data")) {
				JSONObject serviceData = service.getJSONObject("data");
				if (serviceData.containsKey("status"))
					setStatus(serviceData.getInt("status"));
			}
		} catch (Exception e) {
			throw new CustomException(SwitchBulb_status.class, "SwitchBulb-status上传数据解析服务出现问题", e);
		}
	}

接下来我们来看一看命令下发的接口

首先我们要先了解一下平台命令下发有两种机制
在这里插入图片描述
其实之所以有2种机制,就是为了保证设备的低功耗。如果看过前面的对这个就会有更深的理解,有点类似于模组的PSM低功耗策略。
在这里插入图片描述
这里要注意,此接口采用的是HTTPS的方式,服务端调用此接口要使用HTTPS的方式调用。
这里lettuce-Air中已经集成华为API DEMO了HTTPS的调用工具,其中还需要华为的证书文件,这个证书文件我是外挂到程序外目录的,这里要注意一下。

在这里插入图片描述
Authorization就是需要提供一个凭证,这个凭证需要调用相关接口获取,这里我在lettuce-Air做好了一个获取凭证的工具。
callbackUrl就是我们服务端提供的对命令状态通知的接口。
expireTime就是前面说的两种命令下发的机制,0为立即下发,其他为缓存下发的超时时间。如果超过了这个时间,命令还没达到下发条件的话,就会废弃这条下发指令。
maxRetransmit如果失败就会重试的次数。
在这里插入图片描述
这个是指令的相关参数
在这里插入图片描述
在这里插入图片描述
这是响应给服务器的参数,
这里注意的是status
这个参数就是指令下发的状态。

相关java代码片段
AppController.java

	/**
	 * 发送一条指令
	 * @param method
	 * @param value
	 * @return
	 */
	@GetMapping("/sendCommand")
	public GenericResponse sendCommand(String method, Integer value){
		try {
			deviceService.sendCommand(method, value);
		} catch (CustomException ex) {
			throw ex;
		} catch (BasicException exc) {
			throw exc;
		} catch (Exception e) {
			throw new BasicException(1000, e);
		}
		return ResponseFormat.retParam(200, null);
	}

DeviceServiceImpl.java

	@Override
	public void sendCommand(String method, Integer value) throws Exception {
		if(StringUtils.isEmpty(method) || value == null){
			throw new BasicException(10002, new CustomException(DeviceServiceImpl.class, "下发命令参数为空"));
		}
		
/*		if(getOperationPiStatus() == 0){
			throw new BasicException(30001, new CustomException(DeviceServiceImpl.class, "设备已下线"));
		}*/
		
		JSONObject commandData = new JSONObject();
		JSONObject command = null;

		//根据响应的命令,封装不同的上报命令参数
		switch (method) {
		case ServiceConstant.ON_OFF:
			if(getBulbStatus() == value){
				return;
			}
			command = new SwitchBulb_ON_OFF(value).unpack();
			break;
		case ServiceConstant.QUERY_STATUS:
			command = new SwitchBulb_QUERY_STATUS(value).unpack();
			break;
		case ServiceConstant.QUIT_PYTHON:
			command = new OperationPi_QUIT_PYTHON(value).unpack();
			break;
		default:
			throw new BasicException(10001, new CustomException(DeviceServiceImpl.class, "下发命令类型没有匹配"));
		}
		
		//封装命令下发基础参数
		commandData.put("deviceId", huaweiIotProperties.getDeviceId());
		commandData.put("command", command);
		commandData.put("expireTime", 0);
		commandData.put("maxRetransmit", 3);
		commandData.put("callbackUrl", huaweiIotProperties.getCommandCallbackUrl());

		String result = huaweiIotApiUrl.getDeviceCommandsUrl(huaweiIotProperties.getAppID(), tokenUtil.getToken(),
				commandData);
		JSONObject resultJson = JSONObject.fromObject(result);
		
		//命令下发成功后,创建命令任务状态
		CommandTask commandTask = new CommandTask();
		commandTask.setCommandId(resultJson.getString("commandId"));
		commandTask.setMethod(method);
		commandTask.setStatus(resultJson.getString("status"));
		commandTask.setExpiresIn(System.currentTimeMillis() + (huaweiIotProperties.getCommandExecuteTime() + 1) * 1000);
		
		//创建任务状态系列
		mapCache.put(commandTask.getCommandId(), commandTask);
	}

封装参数的相关代码
SwitchBulb_ON_OFF.java

	@Override
	public JSONObject unpack() {
		JSONObject command = new JSONObject();
		command.put("serviceId", getServiceId());
		command.put("method", getMethod());
		
		JSONObject paras = new JSONObject();
		paras.put("toggleBulb", getToggleBulb());
		
		command.put("paras", paras);
		return command;
	}

调用API的相关代码
HuaweiIotApiUrl.java

	/**
	 * 执行命令
	 * @param appID
	 * @param token
	 * @param paramCreateDeviceCommand
	 * @return
	 * @throws Exception
	 */
	public String getDeviceCommandsUrl(String appID, String token, JSONObject paramCreateDeviceCommand) throws Exception {
        HttpsUtil httpsUtil = new HttpsUtil();
        httpsUtil.initSSLConfigForTwoWay();
        
        Map<String, String> header = new HashMap<>();
        header.put("app_key", appID);
        header.put("Authorization", "Bearer" + " " + token);
        
        HttpResponse responseCreateDeviceCommand = httpsUtil.doPostJson(deviceCommandsUrl, header, paramCreateDeviceCommand.toString());
		
		return httpsUtil.getHttpResponseBody(responseCreateDeviceCommand);
	}

还有回调命令状态的接口

在这里插入图片描述
在这里插入图片描述
resultCode使用的就是前面下发命令状态的相关常量。
这里注意的是如果有响应参数,那么当resultCodeSUCCESSFUL状态时,会将解析后的json消息放在resultDetail中。

相关java代码片段
DeviceController.java

	/**
	 * 返回命令下发状态
	 * @param result
	 * @return
	 */
	@PostMapping(value = "/commandStatus", produces = { "application/json;charset=UTF-8" })
	public GenericResponse commandStatus(@RequestBody JSONObject result){
		try {
			deviceService.getCommandStatus(result);
		} catch (CustomException ex) {
			throw ex;
		} catch (Exception e) {
			throw new BasicException(1000, e);
		}
		return ResponseFormat.retParam(200, "OK");
	}

DeviceServiceImpl.java

	@Override
	public void getCommandStatus(JSONObject result) {
		
		if (result.containsKey("commandId")) {
			
			//根据消息标识获取任务
			CommandTask commandTask = (CommandTask) mapCache.get(result.getString("commandId"));
			if (commandTask == null) {
				throw new CustomException(DeviceServiceImpl.class, "获取命令状态时的命令标识不在序列中");
			}
			
			//解析数据
			JSONObject commandResult = result.getJSONObject("result");
			String resultCode = commandResult.getString("resultCode");
			commandTask.setStatus(resultCode);
			
			//如果任务状态为成功
			if("SUCCESSFUL".equals(resultCode)){
				//解析响应内容
				getCommandRsp(commandTask.getMethod(), commandResult.getJSONObject("resultDetail").getInt("result"));
			}
			
			//更新任务状态
			mapCache.put(commandTask.getCommandId(), commandTask);
		}
	}

	/**
	 * 解析响应内容
	 * @param method
	 * @param value
	 */
	private void getCommandRsp(String method, Integer value) {
		BasicDevice device = null;
		
		if (method != null && value != null) {
			//判断命令,将响应后的内容更新相应设备状态
			switch (method) {
			case ServiceConstant.ON_OFF:
				device = new Bulb();
				device.setStatus(value);
				device.setUpdateTime();
				break;
			case ServiceConstant.QUERY_STATUS:
				device = new Bulb();
				device.setStatus(value);
				device.setUpdateTime();
				break;
			case ServiceConstant.QUIT_PYTHON:
				device = new OperationPi();
				device.setStatus(value);
				device.setUpdateTime();
				break;
			default:
				throw new CustomException(DeviceServiceImpl.class, "获取命令响应类型没有匹配");
			}
			
			//更新设备状态
			mapCache.put(device.getDeviceKey(), device);
		}
	}

其他的还有给lettuce-Land提供的接口

	/**
	 * 获取灯的当前状态
	 * @return
	 */
	@GetMapping(value = "/getBulbStatus", produces = { "application/json;charset=UTF-8" })
	public GenericResponse getBulbStatus(){
		return ResponseFormat.retParam(200, deviceService.getBulbStatus());
	}
	
	/**
	 * 获取设备当前状态
	 * @return
	 */
	@GetMapping(value = "/getOperationPiStatus", produces = { "application/json;charset=UTF-8" })
	public GenericResponse getOperationPiStatus(){
		return ResponseFormat.retParam(200, deviceService.getOperationPiStatus());
	}

和给监控提供的接口

	/**
	 * 获取设备列表
	 * @return
	 */
	@GetMapping(value = "/getDeviceList", produces = { "application/json;charset=UTF-8" })
	public GenericResponse getDeviceList(){
		return ResponseFormat.retParam(200, mapCache.getDeviceList());
	}
	
	/**
	 * 获取任务列表
	 * @return
	 */
	@GetMapping(value = "/getTaskList", produces = { "application/json;charset=UTF-8" })
	public GenericResponse getTaskList(){
		return ResponseFormat.retParam(200, mapCache.getTaskList());
	}

监控是每5秒刷新一次。

lettuce-Air服务端java代码讲解就到这里了,其他代码部分可以自己专研一下。下一章我们将java代码部署到服务器上,再把前面的流程串起来联合调试。
欢迎加入我们的QQ群一起讨论IOT的问题。

在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值