第14章 智能床位
第14章 智能床位
智能床位这个模块中有两个接口:
- 获取所有有智能设备的楼层
- 获取房间中的智能设备及数据
参考接口文档:智能监测-接口文档
智能床位的设备上报模拟数据
在iot-device-demo
模块中的PropertySample
类:
修改代码中的接入地址、设备id、设备密钥、物模型属性数据设置、物模型serviceId,即可上报数据。
package com.huaweicloud.sdk.iot.device.demo.device;import com.huaweicloud.sdk.iot.device.IoTDevice;
import com.huaweicloud.sdk.iot.device.client.requests.ServiceProperty;
import com.huaweicloud.sdk.iot.device.transport.ActionListener;import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;/*** 演示如何直接使用DeviceClient进行设备属性的上报和读写*/
public class PropertySample {private static final String IOT_ROOT_CA_RES_PATH = "ca.jks";private static final String IOT_ROOT_CA_TMP_PATH = "huaweicloud-iotda-tmp-" + IOT_ROOT_CA_RES_PATH;private static final Logger log = LogManager.getLogger(PropertySample.class);public static void main(String[] args) throws InterruptedException, IOException {// 加载iot平台的ca证书,进行服务端校验File tmpCAFile = new File(IOT_ROOT_CA_TMP_PATH);try (InputStream resource = CommandSample.class.getClassLoader().getResourceAsStream(IOT_ROOT_CA_RES_PATH)) {Files.copy(resource, tmpCAFile.toPath(), REPLACE_EXISTING);}// 创建设备并初始化. 用户请替换为自己的接入地址。-->watch01IoTDevice device = new IoTDevice(// 接入地址获取方式:登录华为云IoTDA控制台左侧导航栏“总览”页签,在选择的实例基本信息中,单击“接入信息”-->设备接入-->MQTT。选择8883端"ssl://230590aa95.st1.iotda-device.cn-east-3.myhuaweicloud.com:8883",// 设备id"67ab39f1bab900244b1e64a6_watch01", // 设备秘钥 设备创建成功后会自动生成"5f71b853642b8ccab767890a5b148f02", tmpCAFile);if (device.init() != 0) {return;}/*// 接收平台下发的属性读写 注释掉,用不到device.getClient().setPropertyListener(new PropertyListener() {// 处理写属性@Overridepublic void onPropertiesSet(String requestId, List<ServiceProperty> services) {// 遍历servicefor (ServiceProperty serviceProperty : services) {log.info("OnPropertiesSet, serviceId is {}", serviceProperty.getServiceId());// 遍历属性for (String name : serviceProperty.getProperties().keySet()) {log.info("property name is {}", name);log.info("set property value is {}", serviceProperty.getProperties().get(name));}}// 修改本地的属性值device.getClient().respondPropsSet(requestId, IotResult.SUCCESS);}*//*** 处理读属性。多数场景下,用户可以直接从平台读设备影子,此接口不用实现。* 但如果需要支持从设备实时读属性,则需要实现此接口。*//*@Overridepublic void onPropertiesGet(String requestId, String serviceId) {log.info("OnPropertiesGet, the serviceId is {}", serviceId);Map<String, Object> json = new HashMap<>();Random rand = new SecureRandom();json.put("alarm", 1);json.put("temperature", rand.nextFloat() * 100.0f);json.put("humidity", rand.nextFloat() * 100.0f);json.put("smokeConcentration", rand.nextFloat() * 100.0f);ServiceProperty serviceProperty = new ServiceProperty();serviceProperty.setProperties(json);serviceProperty.setServiceId("smokeDetector");device.getClient().respondPropsGet(requestId, Arrays.asList(serviceProperty));}});*/// 定时上报属性while (true) {Map<String, Object> json = new HashMap<>();Random rand = new SecureRandom();// 按照物模型设置属性,根据实际情况设置,下面是智能报警手表的物模型json.put("BodyTemp", 36);json.put("xueyang", rand.nextFloat()*100.0f);json.put("HeartRate", rand.nextFloat()*100.0f);json.put("BatteryPercentage", rand.nextFloat() * 100.0f);ServiceProperty serviceProperty = new ServiceProperty();serviceProperty.setProperties(json);serviceProperty.setServiceId("watch_services"); // serviceId要和物模型一致device.getClient().reportProperties(Arrays.asList(serviceProperty), new ActionListener() {@Overridepublic void onSuccess(Object context) {log.info("pubMessage success");}@Overridepublic void onFailure(Object context, Throwable var2) {log.error("reportProperties failed" + var2.toString());}});Thread.sleep(10000);}}
}
为智能烟雾报警器和睡眠监测带这两种设备上传模拟数据:
1)上报烟雾报警器模拟数据的示例代码:
/** Copyright (c) 2020-2023 Huawei Cloud Computing Technology Co., Ltd. All rights reserved.** Redistribution and use in source and binary forms, with or without modification,* are permitted provided that the following conditions are met:** 1. Redistributions of source code must retain the above copyright notice, this list of* conditions and the following disclaimer.** 2. Redistributions in binary form must reproduce the above copyright notice, this list* of conditions and the following disclaimer in the documentation and/or other materials* provided with the distribution.** 3. Neither the name of the copyright holder nor the names of its contributors may be used* to endorse or promote products derived from this software without specific prior written* permission.** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.*/package com.huaweicloud.sdk.iot.device.demo.device;import com.huaweicloud.sdk.iot.device.IoTDevice;
import com.huaweicloud.sdk.iot.device.client.requests.ServiceProperty;
import com.huaweicloud.sdk.iot.device.transport.ActionListener;
import lombok.extern.slf4j.Slf4j;import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.security.SecureRandom;
import java.util.*;import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;/*** 演示如何直接使用DeviceClient进行设备属性的上报和读写*/
@Slf4j
public class PropertySampleSomke {private static final String IOT_ROOT_CA_RES_PATH = "ca.jks";private static final String IOT_ROOT_CA_TMP_PATH = "huaweicloud-iotda-tmp-" + IOT_ROOT_CA_RES_PATH;public static void main(String[] args) throws InterruptedException, IOException {// 加载iot平台的ca证书,进行服务端校验File tmpCAFile = new File(IOT_ROOT_CA_TMP_PATH);try (InputStream resource = CommandSample.class.getClassLoader().getResourceAsStream(IOT_ROOT_CA_RES_PATH)) {Files.copy(resource, tmpCAFile.toPath(), REPLACE_EXISTING);}// 创建设备并初始化. 用户请替换为自己的接入地址。IoTDevice device = new IoTDevice("ssl://ba5420b08b.st1.iotda-device.cn-south-1.myhuaweicloud.com:8883","68baa83f61850a6b5712eba8_smoke02","6eef2d24cb41474fa84c73802a05911b", tmpCAFile);if (device.init() != 0) {return;}// 定时上报属性while (true) {Map<String, Object> json = new HashMap<>();Random rand = new SecureRandom();List<Integer> currentHumidityList = new ArrayList<>();Collections.addAll(currentHumidityList, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30);List<Integer> currentTemperatureList = new ArrayList<>();Collections.addAll(currentTemperatureList, 18, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 35);// 按照物模型设置属性json.put("CurrentHumidity", currentHumidityList.get((int) (Math.random() * currentHumidityList.size())));json.put("IndoorTemperature", currentTemperatureList.get((int) (Math.random() * currentTemperatureList.size())));json.put("SmokeSensorState", 0);ServiceProperty serviceProperty = new ServiceProperty();serviceProperty.setProperties(json);serviceProperty.setServiceId("alarm_services"); // serviceId要和物模型一致device.getClient().reportProperties(Arrays.asList(serviceProperty), new ActionListener() {@Overridepublic void onSuccess(Object context) {log.info("pubMessage success");}@Overridepublic void onFailure(Object context, Throwable var2) {log.error("reportProperties failed" + var2.toString());}});Thread.sleep(10000);}}
}
2)上报睡眠监测带模拟数据的示例代码:
/** Copyright (c) 2020-2023 Huawei Cloud Computing Technology Co., Ltd. All rights reserved.** Redistribution and use in source and binary forms, with or without modification,* are permitted provided that the following conditions are met:** 1. Redistributions of source code must retain the above copyright notice, this list of* conditions and the following disclaimer.** 2. Redistributions in binary form must reproduce the above copyright notice, this list* of conditions and the following disclaimer in the documentation and/or other materials* provided with the distribution.** 3. Neither the name of the copyright holder nor the names of its contributors may be used* to endorse or promote products derived from this software without specific prior written* permission.** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.*/package com.huaweicloud.sdk.iot.device.demo.device;import com.huaweicloud.sdk.iot.device.IoTDevice;
import com.huaweicloud.sdk.iot.device.client.requests.ServiceProperty;
import com.huaweicloud.sdk.iot.device.transport.ActionListener;
import lombok.extern.slf4j.Slf4j;import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.security.SecureRandom;
import java.util.*;import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;/*** 演示如何直接使用DeviceClient进行设备属性的上报和读写*/
@Slf4j
public class PropertySampleSleep {private static final String IOT_ROOT_CA_RES_PATH = "ca.jks";private static final String IOT_ROOT_CA_TMP_PATH = "huaweicloud-iotda-tmp-" + IOT_ROOT_CA_RES_PATH;public static void main(String[] args) throws InterruptedException, IOException {// 加载iot平台的ca证书,进行服务端校验File tmpCAFile = new File(IOT_ROOT_CA_TMP_PATH);try (InputStream resource = CommandSample.class.getClassLoader().getResourceAsStream(IOT_ROOT_CA_RES_PATH)) {Files.copy(resource, tmpCAFile.toPath(), REPLACE_EXISTING);}// 创建设备并初始化. 用户请替换为自己的接入地址。IoTDevice device = new IoTDevice("ssl://ba5420b08b.st1.iotda-device.cn-south-1.myhuaweicloud.com:8883","68baa97961850a6b5712ebc4_sleep02","b1e0a96a868f4f92963c6b69e75d52b1", tmpCAFile);if (device.init() != 0) {return;}// 定时上报属性while (true) {Map<String, Object> json = new HashMap<>();Random rand = new SecureRandom();List<Integer> bedExitCountList = new ArrayList<>();Collections.addAll(bedExitCountList, 1, 2, 3, 4, 5, 4, 3, 2, 1);List<Integer> sleepPhaseStateList = new ArrayList<>();Collections.addAll(sleepPhaseStateList, 0, 1, 2);List<Integer> heartRateList = new ArrayList<>();Collections.addAll(heartRateList, 66, 67, 68, 69, 98, 71, 72, 73, 110);List<Integer> respiratoryRateList = new ArrayList<>();Collections.addAll(respiratoryRateList, 87, 88, 77, 87, 90, 100, 76, 67, 87);// 按照物模型设置属性json.put("BedTime", new Date());json.put("BedExitCount", bedExitCountList.get((int) (Math.random() * bedExitCountList.size())));json.put("SleepPhaseState", sleepPhaseStateList.get((int) (Math.random() * sleepPhaseStateList.size())));json.put("HeartRate", heartRateList.get((int) (Math.random() * heartRateList.size())));json.put("RespiratoryRate", respiratoryRateList.get((int) (Math.random() * respiratoryRateList.size())));ServiceProperty serviceProperty = new ServiceProperty();serviceProperty.setProperties(json);serviceProperty.setServiceId("monitor_services"); // serviceId要和物模型一致device.getClient().reportProperties(Arrays.asList(serviceProperty), new ActionListener() {@Overridepublic void onSuccess(Object context) {log.info("pubMessage success");}@Overridepublic void onFailure(Object context, Throwable var2) {log.error("reportProperties failed" + var2.toString());}});Thread.sleep(10000);}}
}
获取所有有智能设备的楼层 - 接口开发
思路分析
目的是为了展示绑定了智能设备的楼层和楼层下的房间、床位信息以及智能设备最近一次上报的数据
比如下图中,只有3楼、4楼、5楼、1楼绑定了设备,那就只展示这些楼层
表关系如下图:
目前设备的绑定都是房间或者是床位
比如:烟雾报警绑定的位置是房间,睡眠监测带绑定的位置是床位
在设备(device)表中同时有三个字段确定绑定的位置
- location_type 位置类型 0:随身设备 1:固定设备
- physical_location_type 物理位置类型 0楼层 1房间 2床位
- binding_location 具体位置对应的id值(如果是房间,则是房间id,如果是床位,则对应床位id)
5张表关联查询(floor、room、bed、device【房间|床位】),对应的sql
select f.id, f.name, f.code
from floor fleft join room r on f.id = r.floor_idleft join bed b on r.id = b.room_idleft join device rd on rd.binding_location = r.id and rd.location_type = 1 and rd.physical_location_type = 1left join device bd on bd.binding_location = b.id and bd.location_type = 1 and bd.physical_location_type = 2
where (rd.id is not null or bd.id is not null)
group by f.id;
上述sql中的group by f.id 是为了去除重复数据
编码实现
Mapper层sql语句:
<select id="getAllFloorsWithDevice" resultType="com.zzyl.nursing.vo.FloorVo">select f.id, f.name, f.codefrom floor fleft join room r on f.id = r.floor_idleft join bed b on r.id = b.room_idleft join device rd on rd.binding_location = r.id and rd.location_type = 1 and rd.physical_location_type = 1left join device bd on bd.binding_location = b.id and bd.location_type = 1 and bd.physical_location_type = 2where (rd.id is not null or bd.id is not null)group by f.id
</select>
获取房间中的智能设备及数据 - 接口开发
思路分析
- 根据楼层ID查询房间或者是床位的设备数据(最新的一条数据)
在这个接口中返回的数据较多:
- 房间数据以及房间绑定的设备、设备上报的数据
- 床位数据以及床位入住的老人姓名、绑定的设备、设备上报的数据
实现方案有两种
- 方案一:通过楼层关联房间、床位、老人、设备表2次、设备数据表2次共7张表查询对应的数据(设备数据表数据较多,效率不高),不推荐这种方案
- 方案二:使用缓存,因为只需要查询最近一次的设备数据,可以把最近一次采集的数据存储到redis中,然后让房间或者床位进行匹配
-
-
按照这种实现思路,咱们需要改造上报数据的接收逻辑,将设备上报的数据保存到Redis中存储,将来需要展示设备最近一次上报的数据时只需从Redis中获取即可
-
编码实现
Service层代码:
/*** 根据楼层 id 获取房间中的智能设备及数据** @param floorId 楼层id* @return 结果*/
@Override
public List<RoomVo> getRoomsWithDeviceByFloorId(Long floorId) {// 跟据楼层 id 查询在楼层中的智能设备信息List<RoomVo> roomVos = roomMapper.getRoomsWithDeviceByFloorId(floorId);// 跟据 iotId 获取redis中的最新的一次智能设备数据信息roomVos.forEach(roomVo -> {// 遍历的是房间数据List<DeviceInfo> deviceVos = roomVo.getDeviceVos();// 房间设备所对应的设备上报的数据deviceVos.forEach(deviceInfo -> {String jsonStr = (String) redisTemplate.opsForHash().get(CacheConstants.IOT_DEVICE_LAST_DATA, deviceInfo.getIotId());if(StringUtils.isEmpty(jsonStr)) {return; // 跳出本次循环,并不是结束方法}deviceInfo.setDeviceDataVos(JSONUtil.toList(jsonStr, DeviceData.class));});// 遍历的是床位数据roomVo.getBedVoList().forEach(bedVo -> {// 获取床位对应的设备列表bedVo.getDeviceVos().forEach(deviceInfo -> {String jsonStr = (String) redisTemplate.opsForHash().get(CacheConstants.IOT_DEVICE_LAST_DATA, deviceInfo.getIotId());if(StringUtils.isEmpty(jsonStr)) {return; // 跳出本次循环,并不是结束方法}deviceInfo.setDeviceDataVos(JSONUtil.toList(jsonStr, DeviceData.class));});});});return roomVos;
}
Mapper层sql语句:
<resultMap id="roomVoDeviceByFloorIdResultMap" type="com.zzyl.nursing.vo.RoomVo"><id column="id" property="id"></id><result column="code" property="code"></result><collection property="bedVoList" ofType="com.zzyl.nursing.vo.BedVo"><id column="bid" property="id"/><result column="bed_number" property="bedNumber"></result><result column="bed_status" property="bedStatus"></result><result column="eid" property="elderId"></result><result column="ename" property="ename"></result><collection property="deviceVos" ofType="com.zzyl.nursing.vo.DeviceInfo"><id column="b_did" property="id"/><result column="b_iot_id" property="iotId"></result><result column="b_device_name" property="deviceName"></result><result column="b_product_key" property="productKey"></result><result column="b_product_name" property="productName"></result></collection></collection><collection property="deviceVos" ofType="com.zzyl.nursing.vo.DeviceInfo"><id column="did" property="id"/><result column="iot_id" property="iotId"></result><result column="device_name" property="deviceName"></result><result column="product_key" property="productKey"></result><result column="product_name" property="productName"></result></collection>
</resultMap>
<select id="getRoomsWithDeviceByFloorId" resultMap="roomVoDeviceByFloorIdResultMap">select r.id,r.code,b.id bid,b.bed_number,b.bed_status,e.id eid,e.name ename,d.id did,d.iot_id,d.device_name,d.product_key,d.product_name,dd.id b_did,dd.iot_id b_iot_id,dd.device_name b_device_name,dd.product_key b_product_key,dd.product_name b_product_namefrom room rleft join bed b on r.id = b.room_idleft join elder e on b.id = e.bed_idleft join device don d.binding_location = r.id and d.location_type = 1 and d.physical_location_type = 1left join device ddon dd.binding_location = b.id and dd.location_type = 1 and dd.physical_location_type = 2where r.floor_id = #{floorId}and (d.id is not null or dd.id is not null)
</select>